Compare commits
5 Commits
origin-202
...
pre-0.2.2-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75bf38fa71 | ||
|
|
92cc574e15 | ||
|
|
546093360f | ||
|
|
d94b7a3e81 | ||
|
|
e7470bdfac |
53
.github/workflows/cmake.yml
vendored
Normal file
53
.github/workflows/cmake.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: CMake
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'test/**'
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'test/**'
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||||
|
BUILD_TYPE: Debug
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
|
||||||
|
# You can convert this to a matrix build if you need cross-platform coverage.
|
||||||
|
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- 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
|
||||||
|
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
# Build your program with the given configuration
|
||||||
|
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{github.workspace}}/build
|
||||||
|
# Execute tests defined by the CMake configuration.
|
||||||
|
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
|
||||||
|
run: ${{github.workspace}}/build/bin/yaze_test
|
||||||
45
.github/workflows/doxy.yml
vendored
Normal file
45
.github/workflows/doxy.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Doxygen Action
|
||||||
|
|
||||||
|
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||||
|
# events but only for the master branch
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job called "build"
|
||||||
|
build:
|
||||||
|
# The type of runner that the job will run on
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Delete the html directory if it exists
|
||||||
|
- name: Delete html directory
|
||||||
|
run: rm -rf html
|
||||||
|
|
||||||
|
# Installs graphviz for DOT graphs
|
||||||
|
- name: Install graphviz
|
||||||
|
run: sudo apt-get install graphviz
|
||||||
|
|
||||||
|
- name: Doxygen Action
|
||||||
|
uses: mattnotmitt/doxygen-action@v1.1.0
|
||||||
|
with:
|
||||||
|
# Path to Doxyfile
|
||||||
|
doxyfile-path: "./Doxyfile" # default is ./Doxyfile
|
||||||
|
# Working directory
|
||||||
|
working-directory: "." # default is .
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# Default Doxyfile build documentation to html directory.
|
||||||
|
# Change the directory if changes in Doxyfile
|
||||||
|
publish_dir: ./html
|
||||||
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
.vscode/
|
||||||
|
src/lib/SDL2
|
||||||
|
src/lib/cmake
|
||||||
|
src/lib/GL
|
||||||
|
src/lib/abseil-cpp
|
||||||
|
src/lib/libGLEW.2.2.0.dylib
|
||||||
|
src/lib/libGLEW.2.2.dylib
|
||||||
|
src/lib/libGLEW.a
|
||||||
|
src/lib/libGLEW.dylib
|
||||||
|
src/lib/libSDL2_test.a
|
||||||
|
src/lib/libSDL2-2.0.0.dylib
|
||||||
|
src/lib/libSDL2.a
|
||||||
|
src/lib/libSDL2.dylib
|
||||||
|
src/lib/libSDL2main.a
|
||||||
|
checks.json
|
||||||
|
assets/lib/libasar.dll
|
||||||
|
cmake/yaze.plist.in
|
||||||
25
.gitmodules
vendored
25
.gitmodules
vendored
@@ -1,3 +1,24 @@
|
|||||||
[submodule "src/Library/imgui"]
|
[submodule "src/lib/imgui"]
|
||||||
path = src/Library/imgui
|
path = src/lib/imgui
|
||||||
url = https://github.com/ocornut/imgui.git
|
url = https://github.com/ocornut/imgui.git
|
||||||
|
[submodule "src/lib/ImGuiFileDialog"]
|
||||||
|
path = src/lib/ImGuiFileDialog
|
||||||
|
url = https://github.com/aiekick/ImGuiFileDialog.git
|
||||||
|
[submodule "src/lib/ImGuiColorTextEdit"]
|
||||||
|
path = src/lib/ImGuiColorTextEdit
|
||||||
|
url = https://github.com/BalazsJako/ImGuiColorTextEdit.git
|
||||||
|
[submodule "assets/asm/alttp-hacker-workspace"]
|
||||||
|
path = assets/asm/alttp-hacker-workspace
|
||||||
|
url = https://github.com/scawful/alttp-hacker-workspace.git
|
||||||
|
[submodule "src/lib/abseil-cpp"]
|
||||||
|
path = src/lib/abseil-cpp
|
||||||
|
url = https://github.com/abseil/abseil-cpp.git
|
||||||
|
[submodule "src/lib/SDL"]
|
||||||
|
path = src/lib/SDL
|
||||||
|
url = https://github.com/libsdl-org/SDL.git
|
||||||
|
[submodule "src/lib/asar"]
|
||||||
|
path = src/lib/asar
|
||||||
|
url = https://github.com/RPGHacker/asar.git
|
||||||
|
[submodule "src/lib/imgui_test_engine"]
|
||||||
|
path = src/lib/imgui_test_engine
|
||||||
|
url = https://github.com/ocornut/imgui_test_engine.git
|
||||||
|
|||||||
34
CMakeLists.txt
Normal file
34
CMakeLists.txt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# CMake Specifications
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
# Yet Another Zelda3 Editor
|
||||||
|
# by scawful
|
||||||
|
project(yaze VERSION 0.10)
|
||||||
|
|
||||||
|
# C++ Standard Specifications
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
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_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
|
||||||
|
set(BUILD_SHARED_LIBS OFF)
|
||||||
|
set(CMAKE_FIND_FRAMEWORK LAST)
|
||||||
|
|
||||||
|
# Abseil Standard Specifications
|
||||||
|
include(cmake/absl.cmake)
|
||||||
|
|
||||||
|
# Video Libraries
|
||||||
|
find_package(PNG REQUIRED)
|
||||||
|
include(cmake/sdl2.cmake)
|
||||||
|
|
||||||
|
# Asar
|
||||||
|
add_subdirectory(src/lib/asar/src)
|
||||||
|
include(cmake/asar.cmake)
|
||||||
|
|
||||||
|
# ImGui
|
||||||
|
include(cmake/imgui.cmake)
|
||||||
|
|
||||||
|
# Project Files
|
||||||
|
add_subdirectory(src)
|
||||||
15
LICENSE
Normal file
15
LICENSE
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Copyright (C) 2022 Justin Scofield
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
53
README.md
Normal file
53
README.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Yet Another Zelda3 Editor
|
||||||
|
|
||||||
|
- Platform: Windows, macOS, GNU/Linux
|
||||||
|
- Dependencies: SDL2, ImGui
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo.
|
||||||
|
|
||||||
|
Takes heavy inspiration from ALTTP community efforts such as [Hyrule Magic](https://www.romhacking.net/utilities/200/) and [ZScream](https://github.com/Zarby89/ZScreamDungeon)
|
||||||
|
|
||||||
|
Building and installation
|
||||||
|
-------------------------
|
||||||
|
[CMake](http://www.cmake.org "CMake") is required to build yaze
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Screenshots
|
||||||
|
--------
|
||||||
|

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

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

|
||||||
|
|
||||||
|
|
||||||
1
assets/asm/alttp-hacker-workspace
Submodule
1
assets/asm/alttp-hacker-workspace
Submodule
Submodule assets/asm/alttp-hacker-workspace added at 2520fb70c3
32
assets/asm/mosaic_change.asm
Normal file
32
assets/asm/mosaic_change.asm
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
org <HOOK>
|
||||||
|
JML AreaCheck
|
||||||
|
|
||||||
|
org <EXPANDED_SPACE>
|
||||||
|
|
||||||
|
AreaCheck:
|
||||||
|
PHB : PHK : PLB
|
||||||
|
|
||||||
|
TAX
|
||||||
|
LDA .pool, X
|
||||||
|
|
||||||
|
BEQ .noMosaic1
|
||||||
|
PLB
|
||||||
|
JML $02AAE5
|
||||||
|
|
||||||
|
.noMosaic1
|
||||||
|
|
||||||
|
LDX $8A
|
||||||
|
LDA .pool, X
|
||||||
|
|
||||||
|
BEQ .noMosaic2
|
||||||
|
PLB
|
||||||
|
JML $02AAE5
|
||||||
|
|
||||||
|
.noMosaic2
|
||||||
|
|
||||||
|
PLB
|
||||||
|
JML $02AAF4
|
||||||
|
|
||||||
|
NOP
|
||||||
|
.pool
|
||||||
160
assets/asm/template_song.asm
Normal file
160
assets/asm/template_song.asm
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
;
|
||||||
|
; Credit to Zarby89
|
||||||
|
;
|
||||||
|
lorom
|
||||||
|
|
||||||
|
!End = $00
|
||||||
|
!Rest = $C9
|
||||||
|
!Tie = $C8
|
||||||
|
|
||||||
|
macro SetChannelVolume(v)
|
||||||
|
db $ED, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro SetMasterVolume(v)
|
||||||
|
db $E5, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro SetTempo(v)
|
||||||
|
db $E7, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro SetInstrument(v)
|
||||||
|
db $E0, <v>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
macro CallSubroutine(addr, repeat)
|
||||||
|
db $EF
|
||||||
|
dw <addr>
|
||||||
|
db <repeat>
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
;1/4 = $48
|
||||||
|
;1/4 double = $6C
|
||||||
|
;1/4 triplet = $30
|
||||||
|
;1/8 = $24
|
||||||
|
;1/8 double = $36
|
||||||
|
;1/8 triplet = $18
|
||||||
|
;1/16 = $12
|
||||||
|
;1/16 double = $1B
|
||||||
|
;1/32 = $09
|
||||||
|
; To make a whole note you tie 4 1/4 so something like
|
||||||
|
;%SetDuration(48)
|
||||||
|
;db !C4, !Tie, !Tie, !Tie ; will play a whole note (1/1)
|
||||||
|
;db !C4, !Tie ; will play a half note (1/2)
|
||||||
|
|
||||||
|
macro SetDuration(v)
|
||||||
|
db <v>, $7F
|
||||||
|
endmacro
|
||||||
|
|
||||||
|
|
||||||
|
!C1 = $80
|
||||||
|
!C1s = $81
|
||||||
|
!D1 = $82
|
||||||
|
!D1s = $83
|
||||||
|
!E1 = $84
|
||||||
|
!F1 = $85
|
||||||
|
!F1s = $86
|
||||||
|
!G1 = $87
|
||||||
|
!G1s = $88
|
||||||
|
!A1 = $89
|
||||||
|
!A1s = $8A
|
||||||
|
!B1 = $8B
|
||||||
|
|
||||||
|
|
||||||
|
!C2 = $8C
|
||||||
|
!C2s = $8D
|
||||||
|
!D2 = $8E
|
||||||
|
!D2s = $8F
|
||||||
|
!E2 = $90
|
||||||
|
!F2 = $91
|
||||||
|
!F2s = $92
|
||||||
|
!G2 = $93
|
||||||
|
!G2s = $94
|
||||||
|
!A2 = $95
|
||||||
|
!A2s = $96
|
||||||
|
!B2 = $97
|
||||||
|
|
||||||
|
|
||||||
|
!C3 = $98
|
||||||
|
!C3s = $99
|
||||||
|
!D3 = $9A
|
||||||
|
!D3s = $9B
|
||||||
|
!E3 = $9C
|
||||||
|
!F3 = $9D
|
||||||
|
!F3s = $9E
|
||||||
|
!G3 = $9F
|
||||||
|
!G3s = $A0
|
||||||
|
!A3 = $A1
|
||||||
|
!A3s = $A2
|
||||||
|
!B3 = $A3
|
||||||
|
|
||||||
|
!C4 = $A4
|
||||||
|
!C4s = $A5
|
||||||
|
!D4 = $A6
|
||||||
|
!D4s = $A7
|
||||||
|
!E4 = $A8
|
||||||
|
!F4 = $A9
|
||||||
|
!F4s = $AA
|
||||||
|
!G4 = $AB
|
||||||
|
!G4s = $AC
|
||||||
|
!A4 = $AD
|
||||||
|
!A4s = $AE
|
||||||
|
!B4 = $AF
|
||||||
|
|
||||||
|
!C5 = $B0
|
||||||
|
!C5s = $B1
|
||||||
|
!D5 = $B2
|
||||||
|
!D5s = $B3
|
||||||
|
!E5 = $B4
|
||||||
|
!F5 = $B5
|
||||||
|
!F5s = $B6
|
||||||
|
!G5 = $B7
|
||||||
|
!G5s = $B8
|
||||||
|
!A5 = $B9
|
||||||
|
!A5s = $BA
|
||||||
|
!B5 = $BB
|
||||||
|
|
||||||
|
!C6 = $BC
|
||||||
|
!C6s = $BD
|
||||||
|
!D6 = $BE
|
||||||
|
!D6s = $BF
|
||||||
|
!E6 = $C0
|
||||||
|
!F6 = $C1
|
||||||
|
!F6s = $C2
|
||||||
|
!G6 = $C3
|
||||||
|
!G6s = $C4
|
||||||
|
!A6 = $C5
|
||||||
|
!A6s = $C6
|
||||||
|
!B6 = $C7
|
||||||
|
|
||||||
|
org $1A9FF8 ; Hyrule Castle (Song Header information)
|
||||||
|
Sections:
|
||||||
|
!ARAMAddr = $D0FF
|
||||||
|
!StartingAddr = Sections
|
||||||
|
dw !ARAMAddr+$0A
|
||||||
|
dw !ARAMAddr+$0A
|
||||||
|
dw $00FF
|
||||||
|
dw !ARAMAddr
|
||||||
|
dw $0000
|
||||||
|
|
||||||
|
Channels:
|
||||||
|
!ARAMC = !ARAMAddr-Sections
|
||||||
|
dw Channel0+!ARAMC
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
dw $0000
|
||||||
|
|
||||||
|
|
||||||
|
Channel0:
|
||||||
|
SetMasterVolume($80)
|
||||||
|
SetTempo($40)
|
||||||
|
SetInstrument($17)
|
||||||
|
|
||||||
|
db !Rest, !Rest, !Rest
|
||||||
|
|
||||||
|
db !End
|
||||||
BIN
assets/font/Cousine-Regular.ttf
Normal file
BIN
assets/font/Cousine-Regular.ttf
Normal file
Binary file not shown.
BIN
assets/font/DroidSans.ttf
Normal file
BIN
assets/font/DroidSans.ttf
Normal file
Binary file not shown.
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/font/Karla-Regular.ttf
Normal file
BIN
assets/font/Karla-Regular.ttf
Normal file
Binary file not shown.
BIN
assets/font/MaterialIcons-Regular.ttf
Normal file
BIN
assets/font/MaterialIcons-Regular.ttf
Normal file
Binary file not shown.
BIN
assets/font/NotoSansJP.ttf
Normal file
BIN
assets/font/NotoSansJP.ttf
Normal file
Binary file not shown.
BIN
assets/font/Roboto-Medium.ttf
Normal file
BIN
assets/font/Roboto-Medium.ttf
Normal file
Binary file not shown.
25
assets/layouts/overworld.zeml
Normal file
25
assets/layouts/overworld.zeml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
BeginTabBar title="##OwEditorTabBar" {
|
||||||
|
BeginTabItem title="Map Editor" {
|
||||||
|
Function id="owToolset",
|
||||||
|
|
||||||
|
Table id="##owEditTable" count="2" flags="Resizable|Reorderable|Hideable|BordersOuter|BordersV" {
|
||||||
|
TableSetupColumn title="Canvas" flags="WidthStretch",
|
||||||
|
TableSetupColumn title="Tile Selector" flags="WidthFixed" width="256",
|
||||||
|
TableHeadersRow
|
||||||
|
TableNextRow,
|
||||||
|
TableNextColumn,
|
||||||
|
Function id="OverworldCanvas",
|
||||||
|
TableNextColumn,
|
||||||
|
Function id="OverworldTileSelector",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BeginTabItem title="Tile16 Editor" {
|
||||||
|
Function id="OwTile16Editor"
|
||||||
|
}
|
||||||
|
BeginTabItem title "Graphics Group Editor" {
|
||||||
|
Function id="OwGfxGroupEditor"
|
||||||
|
}
|
||||||
|
BeginTabItem title="Usage Statistics" {
|
||||||
|
Function id="OwUsageStats"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
cmake/absl.cmake
Normal file
20
cmake/absl.cmake
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
find_package(absl)
|
||||||
|
set(ABSL_PROPAGATE_CXX_STD ON)
|
||||||
|
set(ABSL_CXX_STANDARD 17)
|
||||||
|
set(ABSL_USE_GOOGLETEST_HEAD ON)
|
||||||
|
set(ABSL_ENABLE_INSTALL ON)
|
||||||
|
set(
|
||||||
|
ABSL_TARGETS
|
||||||
|
absl::strings
|
||||||
|
absl::flags
|
||||||
|
absl::status
|
||||||
|
absl::statusor
|
||||||
|
absl::examine_stack
|
||||||
|
absl::stacktrace
|
||||||
|
absl::base
|
||||||
|
absl::config
|
||||||
|
absl::core_headers
|
||||||
|
absl::raw_logging_internal
|
||||||
|
absl::failure_signal_handler
|
||||||
|
absl::flat_hash_map
|
||||||
|
)
|
||||||
33
cmake/asar.cmake
Normal file
33
cmake/asar.cmake
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES)
|
||||||
|
target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR})
|
||||||
|
set(ASAR_GEN_EXE OFF)
|
||||||
|
set(ASAR_GEN_DLL ON)
|
||||||
|
set(ASAR_GEN_LIB ON)
|
||||||
|
set(ASAR_GEN_EXE_TEST OFF)
|
||||||
|
set(ASAR_GEN_DLL_TEST OFF)
|
||||||
|
|
||||||
|
set(ASAR_STATIC_SRC
|
||||||
|
"../src/lib/asar/src/asar/interface-lib.cpp"
|
||||||
|
"../src/lib/asar/src/asar/addr2line.cpp"
|
||||||
|
"../src/lib/asar/src/asar/arch-65816.cpp"
|
||||||
|
"../src/lib/asar/src/asar/arch-spc700.cpp"
|
||||||
|
"../src/lib/asar/src/asar/arch-superfx.cpp"
|
||||||
|
"../src/lib/asar/src/asar/assembleblock.cpp"
|
||||||
|
"../src/lib/asar/src/asar/crc32.cpp"
|
||||||
|
"../src/lib/asar/src/asar/libcon.cpp"
|
||||||
|
"../src/lib/asar/src/asar/libsmw.cpp"
|
||||||
|
"../src/lib/asar/src/asar/libstr.cpp"
|
||||||
|
"../src/lib/asar/src/asar/macro.cpp"
|
||||||
|
"../src/lib/asar/src/asar/main.cpp"
|
||||||
|
"../src/lib/asar/src/asar/asar_math.cpp"
|
||||||
|
"../src/lib/asar/src/asar/virtualfile.cpp"
|
||||||
|
"../src/lib/asar/src/asar/warnings.cpp"
|
||||||
|
"../src/lib/asar/src/asar/errors.cpp"
|
||||||
|
"../src/lib/asar/src/asar/platform/file-helpers.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32 OR MINGW)
|
||||||
|
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/windows/file-helpers-win32.cpp")
|
||||||
|
else()
|
||||||
|
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/linux/file-helpers-linux.cpp")
|
||||||
|
endif()
|
||||||
43
cmake/imgui.cmake
Normal file
43
cmake/imgui.cmake
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# gui libraries ---------------------------------------------------------------
|
||||||
|
set(IMGUI_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui)
|
||||||
|
file(GLOB IMGUI_SOURCES ${IMGUI_PATH}/*.cpp)
|
||||||
|
add_library("ImGui" STATIC ${IMGUI_SOURCES})
|
||||||
|
target_include_directories("ImGui" PUBLIC ${IMGUI_PATH})
|
||||||
|
target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR})
|
||||||
|
target_compile_definitions(ImGui PUBLIC
|
||||||
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
|
set(IMGUI_FILE_DLG_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiFileDialog)
|
||||||
|
file(GLOB IMGUI_FILE_DLG_SOURCES ${IMGUI_FILE_DLG_PATH}/*.cpp)
|
||||||
|
add_library("ImGuiFileDialog" STATIC ${IMGUI_FILE_DLG_SOURCES})
|
||||||
|
target_include_directories(ImGuiFileDialog PUBLIC ${IMGUI_PATH})
|
||||||
|
target_compile_definitions(ImGuiFileDialog PUBLIC
|
||||||
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
|
set(IMGUI_COLOR_TEXT_EDIT_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiColorTextEdit)
|
||||||
|
file(GLOB IMGUI_COLOR_TEXT_EDIT_SOURCES ${IMGUI_COLOR_TEXT_EDIT_PATH}/*.cpp)
|
||||||
|
add_library("ImGuiColorTextEdit" STATIC ${IMGUI_COLOR_TEXT_EDIT_SOURCES})
|
||||||
|
target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
|
||||||
|
target_compile_definitions(ImGuiColorTextEdit PUBLIC
|
||||||
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
|
set(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_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
|
||||||
|
)
|
||||||
6
cmake/sdl2.cmake
Normal file
6
cmake/sdl2.cmake
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# SDL2
|
||||||
|
if (UNIX)
|
||||||
|
add_subdirectory(src/lib/SDL)
|
||||||
|
else()
|
||||||
|
find_package(SDL2)
|
||||||
|
endif()
|
||||||
27
docs/build-instructions.md
Normal file
27
docs/build-instructions.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Build Instructions
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
For VSCode users, use the following CMake extensions with MinGW-w64
|
||||||
|
|
||||||
|
https://marketplace.visualstudio.com/items?itemName=twxs.cmake
|
||||||
|
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
|
||||||
|
|
||||||
|
https://www.msys2.org/
|
||||||
|
|
||||||
|
Add to environment variables `C:\msys64\mingw64\bin`
|
||||||
|
|
||||||
|
Install the following packages using `pacman -S <package-name>`
|
||||||
|
|
||||||
|
`mingw-w64-x86_64-gcc`
|
||||||
|
`mingw-w64-x86_64-gcc-libs`
|
||||||
|
`mingw-w64-x86_64-cmake`
|
||||||
|
`mingw-w64-x86_64-glew`
|
||||||
|
`mingw-w64-x86_64-lib-png`
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
|
||||||
|
- Clang 15.0.1 x86_64-apple-darrwin22.5.0
|
||||||
|
- SDL2 Source v2.26.5
|
||||||
|
- Removed snes_spc
|
||||||
|
- Removed asar_static
|
||||||
66
docs/compression.md
Normal file
66
docs/compression.md
Normal 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
23
docs/getting-started.md
Normal 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
140
docs/infrastructure.md
Normal 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.
|
||||||
112
src/CMakeLists.txt
Normal file
112
src/CMakeLists.txt
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
set(
|
||||||
|
YAZE_APP_CORE_SRC
|
||||||
|
app/core/common.cc
|
||||||
|
app/core/controller.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
|
||||||
|
app/gfx/snes_color.cc
|
||||||
|
app/gfx/tilesheet.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
set(
|
||||||
|
YAZE_GUI_SRC
|
||||||
|
app/gui/asset_browser.cc
|
||||||
|
app/gui/canvas.cc
|
||||||
|
app/gui/input.cc
|
||||||
|
app/gui/style.cc
|
||||||
|
app/gui/color.cc
|
||||||
|
app/gui/zeml.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
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 ws2_32)
|
||||||
|
add_definitions(-DSDL_MAIN_HANDLED)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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")
|
||||||
|
target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MACOS)
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/yaze.ico)
|
||||||
|
set_target_properties(yaze
|
||||||
|
PROPERTIES
|
||||||
|
BUNDLE True
|
||||||
|
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
|
||||||
|
)
|
||||||
|
elseif(UNIX)
|
||||||
|
set_target_properties(yaze
|
||||||
|
PROPERTIES
|
||||||
|
BUNDLE True
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set_target_properties(yaze
|
||||||
|
PROPERTIES
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||||
|
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(test)
|
||||||
Submodule src/Library/imgui deleted from 0b1bcfcc20
40
src/app/CMakeLists.txt
Normal file
40
src/app/CMakeLists.txt
Normal 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()
|
||||||
187
src/app/core/common.cc
Normal file
187
src/app/core/common.cc
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#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 {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hextodec has been imported from SNESDisasm to parse hex numbers
|
||||||
|
int HexToDec(char *input, int length) {
|
||||||
|
int result = 0;
|
||||||
|
int value;
|
||||||
|
int ceiling = length - 1;
|
||||||
|
int power16 = 16;
|
||||||
|
|
||||||
|
int j = ceiling;
|
||||||
|
|
||||||
|
for (; j >= 0; j--) {
|
||||||
|
if (input[j] >= 'A' && input[j] <= 'F') {
|
||||||
|
value = input[j] - 'F';
|
||||||
|
value += 15;
|
||||||
|
} else {
|
||||||
|
value = input[j] - '9';
|
||||||
|
value += 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j == ceiling) {
|
||||||
|
result += value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += (value * power16);
|
||||||
|
power16 *= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StringReplace(std::string &str, const std::string &from,
|
||||||
|
const std::string &to) {
|
||||||
|
size_t start = str.find(from);
|
||||||
|
if (start == std::string::npos) return false;
|
||||||
|
|
||||||
|
str.replace(start, from.length(), to);
|
||||||
|
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
|
||||||
234
src/app/core/common.h
Normal file
234
src/app/core/common.h
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
#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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
|
||||||
|
#endif
|
||||||
852
src/app/core/constants.h
Normal file
852
src/app/core/constants.h
Normal file
@@ -0,0 +1,852 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_CONSTANTS_H
|
||||||
|
#define YAZE_APP_CORE_CONSTANTS_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
|
||||||
|
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
|
||||||
|
#define END_TAB_BAR() \
|
||||||
|
ImGui::EndTabBar(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TAB_ITEM(w) if (ImGui::BeginTabItem(w)) {
|
||||||
|
#define END_TAB_ITEM() \
|
||||||
|
ImGui::EndTabItem(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MENU_ITEM(w) if (ImGui::MenuItem(w))
|
||||||
|
#define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v))
|
||||||
|
|
||||||
|
#define BUTTON_COLUMN(w) \
|
||||||
|
ImGui::TableNextColumn(); \
|
||||||
|
ImGui::Button(w);
|
||||||
|
|
||||||
|
#define TEXT_COLUMN(w) \
|
||||||
|
ImGui::TableNextColumn(); \
|
||||||
|
ImGui::Text(w);
|
||||||
|
|
||||||
|
#define BEGIN_TABLE(l, n, f) if (ImGui::BeginTable(l, n, f, ImVec2(0, 0))) {
|
||||||
|
#define SETUP_COLUMN(l) ImGui::TableSetupColumn(l);
|
||||||
|
|
||||||
|
#define TABLE_HEADERS() \
|
||||||
|
ImGui::TableHeadersRow(); \
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
|
#define NEXT_COLUMN() ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
#define END_TABLE() \
|
||||||
|
ImGui::EndTable(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define HOVER_HINT(string) \
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip(string);
|
||||||
|
|
||||||
|
#define PRINT_IF_ERROR(expression) \
|
||||||
|
{ \
|
||||||
|
auto error = expression; \
|
||||||
|
if (!error.ok()) { \
|
||||||
|
std::cout << error.ToString() << std::endl; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EXIT_IF_ERROR(expression) \
|
||||||
|
{ \
|
||||||
|
auto error = expression; \
|
||||||
|
if (!error.ok()) { \
|
||||||
|
std::cout << error.ToString() << std::endl; \
|
||||||
|
return EXIT_FAILURE; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define RETURN_VOID_IF_ERROR(expression) \
|
||||||
|
{ \
|
||||||
|
auto error = expression; \
|
||||||
|
if (!error.ok()) { \
|
||||||
|
std::cout << error.ToString() << std::endl; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define RETURN_IF_ERROR(expression) \
|
||||||
|
{ \
|
||||||
|
auto error = expression; \
|
||||||
|
if (!error.ok()) { \
|
||||||
|
return error; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSIGN_OR_RETURN(type_variable_name, expression) \
|
||||||
|
ASSIGN_OR_RETURN_IMPL(APPEND_NUMBER(error_or_value, __LINE__), \
|
||||||
|
type_variable_name, expression)
|
||||||
|
|
||||||
|
#define ASSIGN_OR_RETURN_IMPL(error_or_value, type_variable_name, expression) \
|
||||||
|
auto error_or_value = expression; \
|
||||||
|
if (!error_or_value.ok()) { \
|
||||||
|
return error_or_value.status(); \
|
||||||
|
} \
|
||||||
|
type_variable_name = std::move(*error_or_value);
|
||||||
|
|
||||||
|
#define ASSIGN_OR_LOG_ERROR(type_variable_name, expression) \
|
||||||
|
ASSIGN_OR_LOG_ERROR_IMPL(APPEND_NUMBER(error_or_value, __LINE__), \
|
||||||
|
type_variable_name, expression)
|
||||||
|
|
||||||
|
#define ASSIGN_OR_LOG_ERROR_IMPL(error_or_value, type_variable_name, \
|
||||||
|
expression) \
|
||||||
|
auto error_or_value = expression; \
|
||||||
|
if (!error_or_value.ok()) { \
|
||||||
|
std::cout << error_or_value.status().ToString() << std::endl; \
|
||||||
|
} \
|
||||||
|
type_variable_name = std::move(*error_or_value);
|
||||||
|
|
||||||
|
#define APPEND_NUMBER(expression, number) \
|
||||||
|
APPEND_NUMBER_INNER(expression, number)
|
||||||
|
|
||||||
|
#define APPEND_NUMBER_INNER(expression, number) expression##number
|
||||||
|
|
||||||
|
#define TEXT_WITH_SEPARATOR(text) \
|
||||||
|
ImGui::Text(text); \
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
#define TABLE_BORDERS_RESIZABLE \
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable
|
||||||
|
|
||||||
|
#define CLEAR_AND_RETURN_STATUS(status) \
|
||||||
|
if (!status.ok()) { \
|
||||||
|
auto temp = status; \
|
||||||
|
status = absl::OkStatus(); \
|
||||||
|
return temp; \
|
||||||
|
}
|
||||||
|
|
||||||
|
using ushort = unsigned short;
|
||||||
|
using uint = unsigned int;
|
||||||
|
using uchar = unsigned char;
|
||||||
|
using Bytes = std::vector<uint8_t>;
|
||||||
|
|
||||||
|
using OWBlockset = std::vector<std::vector<uint16_t>>;
|
||||||
|
struct OWMapTiles {
|
||||||
|
OWBlockset light_world; // 64 maps
|
||||||
|
OWBlockset dark_world; // 64 maps
|
||||||
|
OWBlockset special_world; // 32 maps
|
||||||
|
};
|
||||||
|
using OWMapTiles = struct OWMapTiles;
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
constexpr uint32_t kRedPen = 0xFF0000FF;
|
||||||
|
constexpr float kYazeVersion = 0.2;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Magic numbers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Bit set for object priority
|
||||||
|
constexpr ushort TilePriorityBit = 0x2000;
|
||||||
|
|
||||||
|
/// Bit set for object hflip
|
||||||
|
constexpr ushort TileHFlipBit = 0x4000;
|
||||||
|
|
||||||
|
/// Bit set for object vflip
|
||||||
|
constexpr ushort TileVFlipBit = 0x8000;
|
||||||
|
|
||||||
|
/// Bits used for tile name
|
||||||
|
constexpr ushort TileNameMask = 0x03FF;
|
||||||
|
|
||||||
|
constexpr int Uncompressed3BPPSize = 0x0600;
|
||||||
|
constexpr int UncompressedSheetSize = 0x0800;
|
||||||
|
|
||||||
|
constexpr int NumberOfRooms = 296;
|
||||||
|
|
||||||
|
constexpr int NumberOfColors = 3143;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Game Graphics
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
constexpr int tile_address = 0x1B52; // JP = Same
|
||||||
|
constexpr int tile_address_floor = 0x1B5A; // JP = Same
|
||||||
|
constexpr int subtype1_tiles = 0x8000; // JP = Same
|
||||||
|
constexpr int subtype2_tiles = 0x83F0; // JP = Same
|
||||||
|
constexpr int subtype3_tiles = 0x84F0; // JP = Same
|
||||||
|
constexpr int gfx_animated_pointer = 0x10275; // JP 0x10624 //long pointer
|
||||||
|
|
||||||
|
constexpr int hud_palettes = 0xDD660;
|
||||||
|
constexpr int maxGfx = 0xC3FB5;
|
||||||
|
|
||||||
|
constexpr int kTilesheetWidth = 128;
|
||||||
|
constexpr int kTilesheetHeight = 32;
|
||||||
|
constexpr int kTilesheetDepth = 8;
|
||||||
|
|
||||||
|
// TEXT EDITOR RELATED CONSTANTS
|
||||||
|
constexpr int gfx_font = 0x70000; // 2bpp format
|
||||||
|
constexpr int text_data = 0xE0000;
|
||||||
|
constexpr int text_data2 = 0x75F40;
|
||||||
|
constexpr int pointers_dictionaries = 0x74703;
|
||||||
|
constexpr int characters_width = 0x74ADF;
|
||||||
|
|
||||||
|
constexpr int entrance_gfx_group = 0x5D97;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Gravestones related variables
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
constexpr int GravesYTilePos = 0x49968; // short (0x0F entries)
|
||||||
|
constexpr int GravesXTilePos = 0x49986; // short (0x0F entries)
|
||||||
|
constexpr int GravesTilemapPos = 0x499A4; // short (0x0F entries)
|
||||||
|
constexpr int GravesGFX = 0x499C2; // short (0x0F entries)
|
||||||
|
|
||||||
|
constexpr int GravesXPos = 0x4994A; // short (0x0F entries)
|
||||||
|
constexpr int GravesYLine = 0x4993A; // short (0x08 entries)
|
||||||
|
constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
|
||||||
|
|
||||||
|
constexpr int GraveLinkSpecialHole = 0x46DD9; // short
|
||||||
|
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Names
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static const std::string RoomEffect[] = {"Nothing",
|
||||||
|
"Nothing",
|
||||||
|
"Moving Floor",
|
||||||
|
"Moving Water",
|
||||||
|
"Trinexx Shell",
|
||||||
|
"Red Flashes",
|
||||||
|
"Light Torch to See Floor",
|
||||||
|
"Ganon's Darkness"};
|
||||||
|
|
||||||
|
static const std::string RoomTag[] = {"Nothing",
|
||||||
|
|
||||||
|
"NW Kill Enemy to Open",
|
||||||
|
"NE Kill Enemy to Open",
|
||||||
|
"SW Kill Enemy to Open",
|
||||||
|
"SE Kill Enemy to Open",
|
||||||
|
"W Kill Enemy to Open",
|
||||||
|
"E Kill Enemy to Open",
|
||||||
|
"N Kill Enemy to Open",
|
||||||
|
"S Kill Enemy to Open",
|
||||||
|
"Clear Quadrant to Open",
|
||||||
|
"Clear Full Tile to Open",
|
||||||
|
|
||||||
|
"NW Push Block to Open",
|
||||||
|
"NE Push Block to Open",
|
||||||
|
"SW Push Block to Open",
|
||||||
|
"SE Push Block to Open",
|
||||||
|
"W Push Block to Open",
|
||||||
|
"E Push Block to Open",
|
||||||
|
"N Push Block to Open",
|
||||||
|
"S Push Block to Open",
|
||||||
|
"Push Block to Open",
|
||||||
|
"Pull Lever to Open",
|
||||||
|
"Collect Prize to Open",
|
||||||
|
|
||||||
|
"Hold Switch Open Door",
|
||||||
|
"Toggle Switch to Open Door",
|
||||||
|
"Turn off Water",
|
||||||
|
"Turn on Water",
|
||||||
|
"Water Gate",
|
||||||
|
"Water Twin",
|
||||||
|
"Moving Wall Right",
|
||||||
|
"Moving Wall Left",
|
||||||
|
"Crash",
|
||||||
|
"Crash",
|
||||||
|
"Push Switch Exploding Wall",
|
||||||
|
"Holes 0",
|
||||||
|
"Open Chest (Holes 0)",
|
||||||
|
"Holes 1",
|
||||||
|
"Holes 2",
|
||||||
|
"Defeat Boss for Dungeon Prize",
|
||||||
|
|
||||||
|
"SE Kill Enemy to Push Block",
|
||||||
|
"Trigger Switch Chest",
|
||||||
|
"Pull Lever Exploding Wall",
|
||||||
|
"NW Kill Enemy for Chest",
|
||||||
|
"NE Kill Enemy for Chest",
|
||||||
|
"SW Kill Enemy for Chest",
|
||||||
|
"SE Kill Enemy for Chest",
|
||||||
|
"W Kill Enemy for Chest",
|
||||||
|
"E Kill Enemy for Chest",
|
||||||
|
"N Kill Enemy for Chest",
|
||||||
|
"S Kill Enemy for Chest",
|
||||||
|
"Clear Quadrant for Chest",
|
||||||
|
"Clear Full Tile for Chest",
|
||||||
|
|
||||||
|
"Light Torches to Open",
|
||||||
|
"Holes 3",
|
||||||
|
"Holes 4",
|
||||||
|
"Holes 5",
|
||||||
|
"Holes 6",
|
||||||
|
"Agahnim Room",
|
||||||
|
"Holes 7",
|
||||||
|
"Holes 8",
|
||||||
|
"Open Chest for Holes 8",
|
||||||
|
"Push Block for Chest",
|
||||||
|
"Clear Room for Triforce Door",
|
||||||
|
"Light Torches for Chest",
|
||||||
|
"Kill Boss Again"};
|
||||||
|
|
||||||
|
static const std::string SecretItemNames[] = {
|
||||||
|
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
|
||||||
|
"Bomb", "Heart ", "Blue Rupee",
|
||||||
|
|
||||||
|
"Key", "Arrow", "Bomb", "Heart", "Magic",
|
||||||
|
"Full Magic", "Cucco", "Green Soldier", "Bush Stal", "Blue Soldier",
|
||||||
|
|
||||||
|
"Landmine", "Heart", "Fairy", "Heart",
|
||||||
|
"Nothing ", // 22
|
||||||
|
|
||||||
|
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
|
||||||
|
|
||||||
|
static const std::string TileTypeNames[] = {
|
||||||
|
"$00 Nothing (standard floor)",
|
||||||
|
"$01 Collision",
|
||||||
|
"$02 Collision",
|
||||||
|
"$03 Collision",
|
||||||
|
"$04 Collision",
|
||||||
|
"$05 Nothing (unused?)",
|
||||||
|
"$06 Nothing (unused?)",
|
||||||
|
"$07 Nothing (unused?)",
|
||||||
|
"$08 Deep water",
|
||||||
|
"$09 Shallow water",
|
||||||
|
"$0A Unknown? Possibly unused",
|
||||||
|
"$0B Collision (different in Overworld and unknown)",
|
||||||
|
"$0C Overlay mask",
|
||||||
|
"$0D Spike floor",
|
||||||
|
"$0E GT ice",
|
||||||
|
"$0F Ice palace ice",
|
||||||
|
"$10 Slope ◤",
|
||||||
|
"$11 Slope ◥",
|
||||||
|
"$12 Slope ◣",
|
||||||
|
"$13 Slope ◢",
|
||||||
|
"$14 Nothing (unused?)",
|
||||||
|
"$15 Nothing (unused?)",
|
||||||
|
"$16 Nothing (unused?)",
|
||||||
|
"$17 Nothing (unused?)",
|
||||||
|
"$18 Slope ◤",
|
||||||
|
"$19 Slope ◥",
|
||||||
|
"$1A Slope ◣",
|
||||||
|
"$1B Slope ◢",
|
||||||
|
"$1C Layer 2 overlay",
|
||||||
|
"$1D North single-layer auto stairs",
|
||||||
|
"$1E North layer-swap auto stairs",
|
||||||
|
"$1F North layer-swap auto stairs",
|
||||||
|
"$20 Pit",
|
||||||
|
"$21 Nothing (unused?)",
|
||||||
|
"$22 Manual stairs",
|
||||||
|
"$23 Pot switch",
|
||||||
|
"$24 Pressure switch",
|
||||||
|
"$25 Nothing (unused but referenced by somaria blocks)",
|
||||||
|
"$26 Collision (near stairs?)",
|
||||||
|
"$27 Brazier/Fence/Statue/Block/General hookable things",
|
||||||
|
"$28 North ledge",
|
||||||
|
"$29 South ledge",
|
||||||
|
"$2A East ledge",
|
||||||
|
"$2B West ledge",
|
||||||
|
"$2C ◤ ledge",
|
||||||
|
"$2D ◣ ledge",
|
||||||
|
"$2E ◥ ledge",
|
||||||
|
"$2F ◢ ledge",
|
||||||
|
"$30 Straight inter-room stairs south/up 0",
|
||||||
|
"$31 Straight inter-room stairs south/up 1",
|
||||||
|
"$32 Straight inter-room stairs south/up 2",
|
||||||
|
"$33 Straight inter-room stairs south/up 3",
|
||||||
|
"$34 Straight inter-room stairs north/down 0",
|
||||||
|
"$35 Straight inter-room stairs north/down 1",
|
||||||
|
"$36 Straight inter-room stairs north/down 2",
|
||||||
|
"$37 Straight inter-room stairs north/down 3",
|
||||||
|
"$38 Straight inter-room stairs north/down edge",
|
||||||
|
"$39 Straight inter-room stairs south/up edge",
|
||||||
|
"$3A Star tile (inactive on load)",
|
||||||
|
"$3B Star tile (active on load)",
|
||||||
|
"$3C Nothing (unused?)",
|
||||||
|
"$3D South single-layer auto stairs",
|
||||||
|
"$3E South layer-swap auto stairs",
|
||||||
|
"$3F South layer-swap auto stairs",
|
||||||
|
"$40 Thick grass",
|
||||||
|
"$41 Nothing (unused?)",
|
||||||
|
"$42 Gravestone / Tower of hera ledge shadows??",
|
||||||
|
"$43 Skull Woods entrance/Hera columns???",
|
||||||
|
"$44 Spike",
|
||||||
|
"$45 Nothing (unused?)",
|
||||||
|
"$46 Desert Tablet",
|
||||||
|
"$47 Nothing (unused?)",
|
||||||
|
"$48 Diggable ground",
|
||||||
|
"$49 Nothing (unused?)",
|
||||||
|
"$4A Diggable ground",
|
||||||
|
"$4B Warp tile",
|
||||||
|
"$4C Nothing (unused?) | Something unknown in overworld",
|
||||||
|
"$4D Nothing (unused?) | Something unknown in overworld",
|
||||||
|
"$4E Square corners in EP overworld",
|
||||||
|
"$4F Square corners in EP overworld",
|
||||||
|
"$50 Green bush",
|
||||||
|
"$51 Dark bush",
|
||||||
|
"$52 Gray rock",
|
||||||
|
"$53 Black rock",
|
||||||
|
"$54 Hint tile/Sign",
|
||||||
|
"$55 Big gray rock",
|
||||||
|
"$56 Big black rock",
|
||||||
|
"$57 Bonk rocks",
|
||||||
|
"$58 Chest 0",
|
||||||
|
"$59 Chest 1",
|
||||||
|
"$5A Chest 2",
|
||||||
|
"$5B Chest 3",
|
||||||
|
"$5C Chest 4",
|
||||||
|
"$5D Chest 5",
|
||||||
|
"$5E Spiral stairs",
|
||||||
|
"$5F Spiral stairs",
|
||||||
|
"$60 Rupee tile",
|
||||||
|
"$61 Nothing (unused?)",
|
||||||
|
"$62 Bombable floor",
|
||||||
|
"$63 Minigame chest",
|
||||||
|
"$64 Nothing (unused?)",
|
||||||
|
"$65 Nothing (unused?)",
|
||||||
|
"$66 Crystal peg down",
|
||||||
|
"$67 Crystal peg up",
|
||||||
|
"$68 Upwards conveyor",
|
||||||
|
"$69 Downwards conveyor",
|
||||||
|
"$6A Leftwards conveyor",
|
||||||
|
"$6B Rightwards conveyor",
|
||||||
|
"$6C North vines",
|
||||||
|
"$6D South vines",
|
||||||
|
"$6E West vines",
|
||||||
|
"$6F East vines",
|
||||||
|
"$70 Pot/Hammer peg/Push block 00",
|
||||||
|
"$71 Pot/Hammer peg/Push block 01",
|
||||||
|
"$72 Pot/Hammer peg/Push block 02",
|
||||||
|
"$73 Pot/Hammer peg/Push block 03",
|
||||||
|
"$74 Pot/Hammer peg/Push block 04",
|
||||||
|
"$75 Pot/Hammer peg/Push block 05",
|
||||||
|
"$76 Pot/Hammer peg/Push block 06",
|
||||||
|
"$77 Pot/Hammer peg/Push block 07",
|
||||||
|
"$78 Pot/Hammer peg/Push block 08",
|
||||||
|
"$79 Pot/Hammer peg/Push block 09",
|
||||||
|
"$7A Pot/Hammer peg/Push block 0A",
|
||||||
|
"$7B Pot/Hammer peg/Push block 0B",
|
||||||
|
"$7C Pot/Hammer peg/Push block 0C",
|
||||||
|
"$7D Pot/Hammer peg/Push block 0D",
|
||||||
|
"$7E Pot/Hammer peg/Push block 0E",
|
||||||
|
"$7F Pot/Hammer peg/Push block 0F",
|
||||||
|
"$80 North/South door",
|
||||||
|
"$81 East/West door",
|
||||||
|
"$82 North/South shutter door",
|
||||||
|
"$83 East/West shutter door",
|
||||||
|
"$84 North/South layer 2 door",
|
||||||
|
"$85 East/West layer 2 door",
|
||||||
|
"$86 North/South layer 2 shutter door",
|
||||||
|
"$87 East/West layer 2 shutter door",
|
||||||
|
"$88 Some type of door (?)",
|
||||||
|
"$89 East/West transport door",
|
||||||
|
"$8A Some type of door (?)",
|
||||||
|
"$8B Some type of door (?)",
|
||||||
|
"$8C Some type of door (?)",
|
||||||
|
"$8D Some type of door (?)",
|
||||||
|
"$8E Entrance door",
|
||||||
|
"$8F Entrance door",
|
||||||
|
"$90 Layer toggle shutter door (?)",
|
||||||
|
"$91 Layer toggle shutter door (?)",
|
||||||
|
"$92 Layer toggle shutter door (?)",
|
||||||
|
"$93 Layer toggle shutter door (?)",
|
||||||
|
"$94 Layer toggle shutter door (?)",
|
||||||
|
"$95 Layer toggle shutter door (?)",
|
||||||
|
"$96 Layer toggle shutter door (?)",
|
||||||
|
"$97 Layer toggle shutter door (?)",
|
||||||
|
"$98 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$99 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9A Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9B Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9C Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9D Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9E Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$9F Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$A0 North/South Dungeon swap door",
|
||||||
|
"$A1 Dungeon toggle door (?)",
|
||||||
|
"$A2 Dungeon toggle door (?)",
|
||||||
|
"$A3 Dungeon toggle door (?)",
|
||||||
|
"$A4 Dungeon toggle door (?)",
|
||||||
|
"$A5 Dungeon toggle door (?)",
|
||||||
|
"$A6 Nothing (unused?)",
|
||||||
|
"$A7 Nothing (unused?)",
|
||||||
|
"$A8 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$A9 Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AA Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AB Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AC Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AD Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AE Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$AF Layer+Dungeon toggle shutter door (?)",
|
||||||
|
"$B0 Somaria ─",
|
||||||
|
"$B1 Somaria │",
|
||||||
|
"$B2 Somaria ┌",
|
||||||
|
"$B3 Somaria └",
|
||||||
|
"$B4 Somaria ┐",
|
||||||
|
"$B5 Somaria ┘",
|
||||||
|
"$B6 Somaria ⍰ 1 way",
|
||||||
|
"$B7 Somaria ┬",
|
||||||
|
"$B8 Somaria ┴",
|
||||||
|
"$B9 Somaria ├",
|
||||||
|
"$BA Somaria ┤",
|
||||||
|
"$BB Somaria ┼",
|
||||||
|
"$BC Somaria ⍰ 2 way",
|
||||||
|
"$BD Somaria ┼ crossover",
|
||||||
|
"$BE Pipe entrance",
|
||||||
|
"$BF Nothing (unused?)",
|
||||||
|
"$C0 Torch 00",
|
||||||
|
"$C1 Torch 01",
|
||||||
|
"$C2 Torch 02",
|
||||||
|
"$C3 Torch 03",
|
||||||
|
"$C4 Torch 04",
|
||||||
|
"$C5 Torch 05",
|
||||||
|
"$C6 Torch 06",
|
||||||
|
"$C7 Torch 07",
|
||||||
|
"$C8 Torch 08",
|
||||||
|
"$C9 Torch 09",
|
||||||
|
"$CA Torch 0A",
|
||||||
|
"$CB Torch 0B",
|
||||||
|
"$CC Torch 0C",
|
||||||
|
"$CD Torch 0D",
|
||||||
|
"$CE Torch 0E",
|
||||||
|
"$CF Torch 0F",
|
||||||
|
"$D0 Nothing (unused?)",
|
||||||
|
"$D1 Nothing (unused?)",
|
||||||
|
"$D2 Nothing (unused?)",
|
||||||
|
"$D3 Nothing (unused?)",
|
||||||
|
"$D4 Nothing (unused?)",
|
||||||
|
"$D5 Nothing (unused?)",
|
||||||
|
"$D6 Nothing (unused?)",
|
||||||
|
"$D7 Nothing (unused?)",
|
||||||
|
"$D8 Nothing (unused?)",
|
||||||
|
"$D9 Nothing (unused?)",
|
||||||
|
"$DA Nothing (unused?)",
|
||||||
|
"$DB Nothing (unused?)",
|
||||||
|
"$DC Nothing (unused?)",
|
||||||
|
"$DD Nothing (unused?)",
|
||||||
|
"$DE Nothing (unused?)",
|
||||||
|
"$DF Nothing (unused?)",
|
||||||
|
"$E0 Nothing (unused?)",
|
||||||
|
"$E1 Nothing (unused?)",
|
||||||
|
"$E2 Nothing (unused?)",
|
||||||
|
"$E3 Nothing (unused?)",
|
||||||
|
"$E4 Nothing (unused?)",
|
||||||
|
"$E5 Nothing (unused?)",
|
||||||
|
"$E6 Nothing (unused?)",
|
||||||
|
"$E7 Nothing (unused?)",
|
||||||
|
"$E8 Nothing (unused?)",
|
||||||
|
"$E9 Nothing (unused?)",
|
||||||
|
"$EA Nothing (unused?)",
|
||||||
|
"$EB Nothing (unused?)",
|
||||||
|
"$EC Nothing (unused?)",
|
||||||
|
"$ED Nothing (unused?)",
|
||||||
|
"$EE Nothing (unused?)",
|
||||||
|
"$EF Nothing (unused?)",
|
||||||
|
"$F0 Door 0 bottom",
|
||||||
|
"$F1 Door 1 bottom",
|
||||||
|
"$F2 Door 2 bottom",
|
||||||
|
"$F3 Door 3 bottom",
|
||||||
|
"$F4 Door X bottom? (unused?)",
|
||||||
|
"$F5 Door X bottom? (unused?)",
|
||||||
|
"$F6 Door X bottom? (unused?)",
|
||||||
|
"$F7 Door X bottom? (unused?)",
|
||||||
|
"$F8 Door 0 top",
|
||||||
|
"$F9 Door 1 top",
|
||||||
|
"$FA Door 2 top",
|
||||||
|
"$FB Door 3 top",
|
||||||
|
"$FC Door X top? (unused?)",
|
||||||
|
"$FD Door X top? (unused?)",
|
||||||
|
"$FE Door X top? (unused?)",
|
||||||
|
"$FF Door X top? (unused?)"};
|
||||||
|
|
||||||
|
static const std::string kSpriteDefaultNames[]{
|
||||||
|
"00 Raven",
|
||||||
|
"01 Vulture",
|
||||||
|
"02 Flying Stalfos Head",
|
||||||
|
"03 No Pointer (Empty",
|
||||||
|
"04 Pull Switch (good",
|
||||||
|
"05 Pull Switch (unused",
|
||||||
|
"06 Pull Switch (bad",
|
||||||
|
"07 Pull Switch (unused",
|
||||||
|
"08 Octorock (one way",
|
||||||
|
"09 Moldorm (Boss",
|
||||||
|
"0A Octorock (four way",
|
||||||
|
"0B Chicken",
|
||||||
|
"0C Octorock (?",
|
||||||
|
"0D Buzzblock",
|
||||||
|
"0E Snapdragon",
|
||||||
|
"0F Octoballoon",
|
||||||
|
"10 Octoballon Hatchlings",
|
||||||
|
"11 Hinox",
|
||||||
|
"12 Moblin",
|
||||||
|
"13 Mini Helmasaure",
|
||||||
|
"14 Gargoyle's Domain Gate",
|
||||||
|
"15 Antifairy",
|
||||||
|
"16 Sahasrahla / Aginah",
|
||||||
|
"17 Bush Hoarder",
|
||||||
|
"18 Mini Moldorm",
|
||||||
|
"19 Poe",
|
||||||
|
"1A Dwarves",
|
||||||
|
"1B Arrow in wall",
|
||||||
|
"1C Statue",
|
||||||
|
"1D Weathervane",
|
||||||
|
"1E Crystal Switch",
|
||||||
|
"1F Bug-Catching Kid",
|
||||||
|
"20 Sluggula",
|
||||||
|
"21 Push Switch",
|
||||||
|
"22 Ropa",
|
||||||
|
"23 Red Bari",
|
||||||
|
"24 Blue Bari",
|
||||||
|
"25 Talking Tree",
|
||||||
|
"26 Hardhat Beetle",
|
||||||
|
"27 Deadrock",
|
||||||
|
"28 Storytellers",
|
||||||
|
"29 Blind Hideout attendant",
|
||||||
|
"2A Sweeping Lady",
|
||||||
|
"2B Storytellers",
|
||||||
|
"2C Lumberjacks",
|
||||||
|
"2D Telepathic Stones",
|
||||||
|
"2E Multipurpose Sprite",
|
||||||
|
"2F Race Npc",
|
||||||
|
"30 Person?",
|
||||||
|
"31 Fortune Teller",
|
||||||
|
"32 Angry Brothers",
|
||||||
|
"33 Pull for items",
|
||||||
|
"34 Scared Girl",
|
||||||
|
"35 Innkeeper",
|
||||||
|
"36 Witch",
|
||||||
|
"37 Waterfall",
|
||||||
|
"38 Arrow Target",
|
||||||
|
"39 Average Middle",
|
||||||
|
"3A Half Magic Bat",
|
||||||
|
"3B Dash Item",
|
||||||
|
"3C Village Kid",
|
||||||
|
"3D Signs? Chicken lady also showed up / Scared ladies outside houses.",
|
||||||
|
"3E Rock Hoarder",
|
||||||
|
"3F Tutorial Soldier",
|
||||||
|
"40 Lightning Lock",
|
||||||
|
"41 Blue Sword Soldier / Used by guards to detect player",
|
||||||
|
"42 Green Sword Soldier",
|
||||||
|
"43 Red Spear Soldier",
|
||||||
|
"44 Assault Sword Soldier",
|
||||||
|
"45 Green Spear Soldier",
|
||||||
|
"46 Blue Archer",
|
||||||
|
"47 Green Archer",
|
||||||
|
"48 Red Javelin Soldier",
|
||||||
|
"49 Red Javelin Soldier 2",
|
||||||
|
"4A Red Bomb Soldiers",
|
||||||
|
"4B Green Soldier Recruits",
|
||||||
|
"4C Geldman",
|
||||||
|
"4D Rabbit",
|
||||||
|
"4E Popo",
|
||||||
|
"4F Popo 2",
|
||||||
|
"50 Cannon Balls",
|
||||||
|
"51 Armos",
|
||||||
|
"52 Giant Zora",
|
||||||
|
"53 Armos Knights (Boss",
|
||||||
|
"54 Lanmolas (Boss",
|
||||||
|
"55 Fireball Zora",
|
||||||
|
"56 Walking Zora",
|
||||||
|
"57 Desert Palace Barriers",
|
||||||
|
"58 Crab",
|
||||||
|
"59 Bird",
|
||||||
|
"5A Squirrel",
|
||||||
|
"5B Spark (Left to Right",
|
||||||
|
"5C Spark (Right to Left",
|
||||||
|
"5D Roller (vertical moving",
|
||||||
|
"5E Roller (vertical moving",
|
||||||
|
"5F Roller",
|
||||||
|
"60 Roller (horizontal moving",
|
||||||
|
"61 Beamos",
|
||||||
|
"62 Master Sword",
|
||||||
|
"63 Devalant (Non",
|
||||||
|
"64 Devalant (Shooter",
|
||||||
|
"65 Shooting Gallery Proprietor",
|
||||||
|
"66 Moving Cannon Ball Shooters (Right",
|
||||||
|
"67 Moving Cannon Ball Shooters (Left",
|
||||||
|
"68 Moving Cannon Ball Shooters (Down",
|
||||||
|
"69 Moving Cannon Ball Shooters (Up",
|
||||||
|
"6A Ball N' Chain Trooper",
|
||||||
|
"6B Cannon Soldier",
|
||||||
|
"6C Mirror Portal",
|
||||||
|
"6D Rat",
|
||||||
|
"6E Rope",
|
||||||
|
"6F Keese",
|
||||||
|
"70 Helmasaur King Fireball",
|
||||||
|
"71 Leever",
|
||||||
|
"72 Activator for the ponds (where you throw in items",
|
||||||
|
"73 Uncle / Priest",
|
||||||
|
"74 Running Man",
|
||||||
|
"75 Bottle Salesman",
|
||||||
|
"76 Princess Zelda",
|
||||||
|
"77 Antifairy (Alternate",
|
||||||
|
"78 Village Elder",
|
||||||
|
"79 Bee",
|
||||||
|
"7A Agahnim",
|
||||||
|
"7B Agahnim Energy Ball",
|
||||||
|
"7C Hyu",
|
||||||
|
"7D Big Spike Trap",
|
||||||
|
"7E Guruguru Bar (Clockwise",
|
||||||
|
"7F Guruguru Bar (Counter Clockwise",
|
||||||
|
"80 Winder",
|
||||||
|
"81 Water Tektite",
|
||||||
|
"82 Antifairy Circle",
|
||||||
|
"83 Green Eyegore",
|
||||||
|
"84 Red Eyegore",
|
||||||
|
"85 Yellow Stalfos",
|
||||||
|
"86 Kodongos",
|
||||||
|
"87 Flames",
|
||||||
|
"88 Mothula (Boss",
|
||||||
|
"89 Mothula's Beam",
|
||||||
|
"8A Spike Trap",
|
||||||
|
"8B Gibdo",
|
||||||
|
"8C Arrghus (Boss",
|
||||||
|
"8D Arrghus spawn",
|
||||||
|
"8E Terrorpin",
|
||||||
|
"8F Slime",
|
||||||
|
"90 Wallmaster",
|
||||||
|
"91 Stalfos Knight",
|
||||||
|
"92 Helmasaur King",
|
||||||
|
"93 Bumper",
|
||||||
|
"94 Swimmers",
|
||||||
|
"95 Eye Laser (Right",
|
||||||
|
"96 Eye Laser (Left",
|
||||||
|
"97 Eye Laser (Down",
|
||||||
|
"98 Eye Laser (Up",
|
||||||
|
"99 Pengator",
|
||||||
|
"9A Kyameron",
|
||||||
|
"9B Wizzrobe",
|
||||||
|
"9C Tadpoles",
|
||||||
|
"9D Tadpoles",
|
||||||
|
"9E Ostrich (Haunted Grove",
|
||||||
|
"9F Flute",
|
||||||
|
"A0 Birds (Haunted Grove",
|
||||||
|
"A1 Freezor",
|
||||||
|
"A2 Kholdstare (Boss",
|
||||||
|
"A3 Kholdstare's Shell",
|
||||||
|
"A4 Falling Ice",
|
||||||
|
"A5 Zazak Fireball",
|
||||||
|
"A6 Red Zazak",
|
||||||
|
"A7 Stalfos",
|
||||||
|
"A8 Bomber Flying Creatures from Darkworld",
|
||||||
|
"A9 Bomber Flying Creatures from Darkworld",
|
||||||
|
"AA Pikit",
|
||||||
|
"AB Maiden",
|
||||||
|
"AC Apple",
|
||||||
|
"AD Lost Old Man",
|
||||||
|
"AE Down Pipe",
|
||||||
|
"AF Up Pipe",
|
||||||
|
"B0 Right Pip",
|
||||||
|
"B1 Left Pipe",
|
||||||
|
"B2 Good bee again?",
|
||||||
|
"B3 Hylian Inscription",
|
||||||
|
"B4 Thief?s chest (not the one that follows you",
|
||||||
|
"B5 Bomb Salesman",
|
||||||
|
"B6 Kiki",
|
||||||
|
"B7 Maiden following you in Blind Dungeon",
|
||||||
|
"B8 Monologue Testing Sprite",
|
||||||
|
"B9 Feuding Friends on Death Mountain",
|
||||||
|
"BA Whirlpool",
|
||||||
|
"BB Salesman / chestgame guy / 300 rupee giver guy / Chest game thief",
|
||||||
|
"BC Drunk in the inn",
|
||||||
|
"BD Vitreous (Large Eyeball",
|
||||||
|
"BE Vitreous (Small Eyeball",
|
||||||
|
"BF Vitreous' Lightning",
|
||||||
|
"C0 Monster in Lake of Ill Omen / Quake Medallion",
|
||||||
|
"C1 Agahnim teleporting Zelda to dark world",
|
||||||
|
"C2 Boulders",
|
||||||
|
"C3 Gibo",
|
||||||
|
"C4 Thief",
|
||||||
|
"C5 Medusa",
|
||||||
|
"C6 Four Way Fireball Spitters (spit when you use your sword",
|
||||||
|
"C7 Hokku",
|
||||||
|
"C8 Big Fairy who heals you",
|
||||||
|
"C9 Tektite",
|
||||||
|
"CA Chain Chomp",
|
||||||
|
"CB Trinexx",
|
||||||
|
"CC Another part of trinexx",
|
||||||
|
"CD Yet another part of trinexx",
|
||||||
|
"CE Blind The Thief (Boss)",
|
||||||
|
"CF Swamola",
|
||||||
|
"D0 Lynel",
|
||||||
|
"D1 Bunny Beam",
|
||||||
|
"D2 Flopping fish",
|
||||||
|
"D3 Stal",
|
||||||
|
"D4 Landmine",
|
||||||
|
"D5 Digging Game Proprietor",
|
||||||
|
"D6 Ganon",
|
||||||
|
"D7 Copy of Ganon",
|
||||||
|
"D8 Heart",
|
||||||
|
"D9 Green Rupee",
|
||||||
|
"DA Blue Rupee",
|
||||||
|
"DB Red Rupee",
|
||||||
|
"DC Bomb Refill (1)",
|
||||||
|
"DD Bomb Refill (4)",
|
||||||
|
"DE Bomb Refill (8)",
|
||||||
|
"DF Small Magic Refill",
|
||||||
|
"E0 Full Magic Refill",
|
||||||
|
"E1 Arrow Refill (5)",
|
||||||
|
"E2 Arrow Refill (10)",
|
||||||
|
"E3 Fairy",
|
||||||
|
"E4 Key",
|
||||||
|
"E5 Big Key",
|
||||||
|
"E6 Shield",
|
||||||
|
"E7 Mushroom",
|
||||||
|
"E8 Fake Master Sword",
|
||||||
|
"E9 Magic Shop dude / His items",
|
||||||
|
"EA Heart Container",
|
||||||
|
"EB Heart Piece",
|
||||||
|
"EC Bushes",
|
||||||
|
"ED Cane Of Somaria Platform",
|
||||||
|
"EE Mantle",
|
||||||
|
"EF Cane of Somaria Platform (Unused)",
|
||||||
|
"F0 Cane of Somaria Platform (Unused)",
|
||||||
|
"F1 Cane of Somaria Platform (Unused)",
|
||||||
|
"F2 Medallion Tablet",
|
||||||
|
"F3",
|
||||||
|
"F4 Falling Rocks",
|
||||||
|
"F5",
|
||||||
|
"F6",
|
||||||
|
"F7",
|
||||||
|
"F8",
|
||||||
|
"F9",
|
||||||
|
"FA",
|
||||||
|
"FB",
|
||||||
|
"FC",
|
||||||
|
"FD",
|
||||||
|
"FE",
|
||||||
|
"FF",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::string overlordnames[] = {
|
||||||
|
"Overlord_SpritePositionTarget",
|
||||||
|
"Overlord_AllDirectionMetalBallFactory",
|
||||||
|
"Overlord_CascadeMetalBallFactory",
|
||||||
|
"Overlord_StalfosFactory",
|
||||||
|
"Overlord_StalfosTrap",
|
||||||
|
"Overlord_SnakeTrap",
|
||||||
|
"Overlord_MovingFloor",
|
||||||
|
"Overlord_ZolFactory",
|
||||||
|
"Overlord_WallMasterFactory",
|
||||||
|
"Overlord_CrumbleTilePath 1",
|
||||||
|
"Overlord_CrumbleTilePath 2",
|
||||||
|
"Overlord_CrumbleTilePath 3",
|
||||||
|
"Overlord_CrumbleTilePath 4",
|
||||||
|
"Overlord_CrumbleTilePath 5",
|
||||||
|
"Overlord_CrumbleTilePath 6",
|
||||||
|
"Overlord_PirogusuFactory 1",
|
||||||
|
"Overlord_PirogusuFactory 2",
|
||||||
|
"Overlord_PirogusuFactory 3",
|
||||||
|
"Overlord_PirogusuFactory 4",
|
||||||
|
"Overlord_FlyingTileFactory",
|
||||||
|
"Overlord_WizzrobeFactory",
|
||||||
|
"Overlord_ZoroFactory",
|
||||||
|
"Overlord_StalfosTrapTriggerWindow",
|
||||||
|
"Overlord_RedStalfosTrap",
|
||||||
|
"Overlord_ArmosCoordinator",
|
||||||
|
"Overlord_BombTrap",
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
583
src/app/core/controller.cc
Normal file
583
src/app/core/controller.cc
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
#include "controller.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/imgui_internal.h"
|
||||||
|
|
||||||
|
#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 "app/gui/icons.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
||||||
|
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||||
|
|
||||||
|
using ::ImVec2;
|
||||||
|
using ImGui::Begin;
|
||||||
|
using ImGui::End;
|
||||||
|
using ImGui::GetIO;
|
||||||
|
using ImGui::NewFrame;
|
||||||
|
using ImGui::SetNextWindowPos;
|
||||||
|
using ImGui::SetNextWindowSize;
|
||||||
|
|
||||||
|
void NewMasterFrame() {
|
||||||
|
const ImGuiIO &io = GetIO();
|
||||||
|
ImGui_ImplSDLRenderer2_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
NewFrame();
|
||||||
|
SetNextWindowPos(gui::kZeroPos);
|
||||||
|
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||||
|
SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||||
|
|
||||||
|
if (!Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||||
|
End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeKeymap() {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.KeyMap[ImGuiKey_LeftSuper] = SDL_GetScancodeFromKey(SDLK_LGUI);
|
||||||
|
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
|
||||||
|
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
|
||||||
|
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
|
||||||
|
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
|
||||||
|
io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN);
|
||||||
|
io.KeyMap[ImGuiKey_LeftArrow] = SDL_GetScancodeFromKey(SDLK_LEFT);
|
||||||
|
io.KeyMap[ImGuiKey_RightArrow] = SDL_GetScancodeFromKey(SDLK_RIGHT);
|
||||||
|
io.KeyMap[ImGuiKey_Delete] = SDL_GetScancodeFromKey(SDLK_DELETE);
|
||||||
|
io.KeyMap[ImGuiKey_Escape] = SDL_GetScancodeFromKey(SDLK_ESCAPE);
|
||||||
|
io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
|
||||||
|
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
|
||||||
|
io.KeyMap[ImGuiKey_PageUp] = SDL_GetScancodeFromKey(SDLK_PAGEUP);
|
||||||
|
io.KeyMap[ImGuiKey_PageDown] = SDL_GetScancodeFromKey(SDLK_PAGEDOWN);
|
||||||
|
io.KeyMap[ImGuiKey_Home] = SDL_GetScancodeFromKey(SDLK_HOME);
|
||||||
|
io.KeyMap[ImGuiKey_Space] = SDL_GetScancodeFromKey(SDLK_SPACE);
|
||||||
|
io.KeyMap[ImGuiKey_1] = SDL_GetScancodeFromKey(SDLK_1);
|
||||||
|
io.KeyMap[ImGuiKey_2] = SDL_GetScancodeFromKey(SDLK_2);
|
||||||
|
io.KeyMap[ImGuiKey_3] = SDL_GetScancodeFromKey(SDLK_3);
|
||||||
|
io.KeyMap[ImGuiKey_4] = SDL_GetScancodeFromKey(SDLK_4);
|
||||||
|
io.KeyMap[ImGuiKey_5] = SDL_GetScancodeFromKey(SDLK_5);
|
||||||
|
io.KeyMap[ImGuiKey_6] = SDL_GetScancodeFromKey(SDLK_6);
|
||||||
|
io.KeyMap[ImGuiKey_7] = SDL_GetScancodeFromKey(SDLK_7);
|
||||||
|
io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8);
|
||||||
|
io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9);
|
||||||
|
io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0);
|
||||||
|
io.KeyMap[ImGuiKey_A] = SDL_GetScancodeFromKey(SDLK_a);
|
||||||
|
io.KeyMap[ImGuiKey_B] = SDL_GetScancodeFromKey(SDLK_b);
|
||||||
|
io.KeyMap[ImGuiKey_C] = SDL_GetScancodeFromKey(SDLK_c);
|
||||||
|
io.KeyMap[ImGuiKey_D] = SDL_GetScancodeFromKey(SDLK_d);
|
||||||
|
io.KeyMap[ImGuiKey_E] = SDL_GetScancodeFromKey(SDLK_e);
|
||||||
|
io.KeyMap[ImGuiKey_F] = SDL_GetScancodeFromKey(SDLK_f);
|
||||||
|
io.KeyMap[ImGuiKey_G] = SDL_GetScancodeFromKey(SDLK_g);
|
||||||
|
io.KeyMap[ImGuiKey_H] = SDL_GetScancodeFromKey(SDLK_h);
|
||||||
|
io.KeyMap[ImGuiKey_I] = SDL_GetScancodeFromKey(SDLK_i);
|
||||||
|
io.KeyMap[ImGuiKey_J] = SDL_GetScancodeFromKey(SDLK_j);
|
||||||
|
io.KeyMap[ImGuiKey_K] = SDL_GetScancodeFromKey(SDLK_k);
|
||||||
|
io.KeyMap[ImGuiKey_L] = SDL_GetScancodeFromKey(SDLK_l);
|
||||||
|
io.KeyMap[ImGuiKey_M] = SDL_GetScancodeFromKey(SDLK_m);
|
||||||
|
io.KeyMap[ImGuiKey_N] = SDL_GetScancodeFromKey(SDLK_n);
|
||||||
|
io.KeyMap[ImGuiKey_O] = SDL_GetScancodeFromKey(SDLK_o);
|
||||||
|
io.KeyMap[ImGuiKey_P] = SDL_GetScancodeFromKey(SDLK_p);
|
||||||
|
io.KeyMap[ImGuiKey_Q] = SDL_GetScancodeFromKey(SDLK_q);
|
||||||
|
io.KeyMap[ImGuiKey_R] = SDL_GetScancodeFromKey(SDLK_r);
|
||||||
|
io.KeyMap[ImGuiKey_S] = SDL_GetScancodeFromKey(SDLK_s);
|
||||||
|
io.KeyMap[ImGuiKey_T] = SDL_GetScancodeFromKey(SDLK_t);
|
||||||
|
io.KeyMap[ImGuiKey_U] = SDL_GetScancodeFromKey(SDLK_u);
|
||||||
|
io.KeyMap[ImGuiKey_V] = SDL_GetScancodeFromKey(SDLK_v);
|
||||||
|
io.KeyMap[ImGuiKey_W] = SDL_GetScancodeFromKey(SDLK_w);
|
||||||
|
io.KeyMap[ImGuiKey_X] = SDL_GetScancodeFromKey(SDLK_x);
|
||||||
|
io.KeyMap[ImGuiKey_Y] = SDL_GetScancodeFromKey(SDLK_y);
|
||||||
|
io.KeyMap[ImGuiKey_Z] = SDL_GetScancodeFromKey(SDLK_z);
|
||||||
|
io.KeyMap[ImGuiKey_F1] = SDL_GetScancodeFromKey(SDLK_F1);
|
||||||
|
io.KeyMap[ImGuiKey_F2] = SDL_GetScancodeFromKey(SDLK_F2);
|
||||||
|
io.KeyMap[ImGuiKey_F3] = SDL_GetScancodeFromKey(SDLK_F3);
|
||||||
|
io.KeyMap[ImGuiKey_F4] = SDL_GetScancodeFromKey(SDLK_F4);
|
||||||
|
io.KeyMap[ImGuiKey_F5] = SDL_GetScancodeFromKey(SDLK_F5);
|
||||||
|
io.KeyMap[ImGuiKey_F6] = SDL_GetScancodeFromKey(SDLK_F6);
|
||||||
|
io.KeyMap[ImGuiKey_F7] = SDL_GetScancodeFromKey(SDLK_F7);
|
||||||
|
io.KeyMap[ImGuiKey_F8] = SDL_GetScancodeFromKey(SDLK_F8);
|
||||||
|
io.KeyMap[ImGuiKey_F9] = SDL_GetScancodeFromKey(SDLK_F9);
|
||||||
|
io.KeyMap[ImGuiKey_F10] = SDL_GetScancodeFromKey(SDLK_F10);
|
||||||
|
io.KeyMap[ImGuiKey_F11] = SDL_GetScancodeFromKey(SDLK_F11);
|
||||||
|
io.KeyMap[ImGuiKey_F12] = SDL_GetScancodeFromKey(SDLK_F12);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
|
||||||
|
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_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, editor::MasterEditor &editor) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
int key = event.key.keysym.scancode;
|
||||||
|
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
|
||||||
|
io.KeysDown[key] = (event.type == SDL_KEYDOWN);
|
||||||
|
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
|
||||||
|
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
||||||
|
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
||||||
|
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.DisplaySize.x = static_cast<float>(event.window.data1);
|
||||||
|
io.DisplaySize.y = static_cast<float>(event.window.data2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleMouseMovement(int &wheel) {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
int mouseX;
|
||||||
|
int mouseY;
|
||||||
|
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
|
||||||
|
|
||||||
|
io.DeltaTime = 1.0f / 60.0f;
|
||||||
|
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
||||||
|
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
|
||||||
|
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
|
||||||
|
io.MouseDown[2] = buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE);
|
||||||
|
io.MouseWheel = static_cast<float>(wheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
absl::Status Controller::OnEntry(std::string filename) {
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
platform_ = Platform::kiOS;
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS */
|
||||||
|
platform_ = Platform::kiOS;
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
platform_ = Platform::kMacOS;
|
||||||
|
#endif
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
platform_ = Platform::kWindows;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
platform_ = Platform::kLinux;
|
||||||
|
#else
|
||||||
|
platform_ = Platform::kUnknown;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(CreateSDL_Window())
|
||||||
|
RETURN_IF_ERROR(CreateRenderer())
|
||||||
|
RETURN_IF_ERROR(CreateGuiContext())
|
||||||
|
if (flags()->kLoadAudioDevice) {
|
||||||
|
RETURN_IF_ERROR(LoadAudioDevice())
|
||||||
|
master_editor_.emulator().set_audio_buffer(audio_buffer_);
|
||||||
|
master_editor_.emulator().set_audio_device_id(audio_device_);
|
||||||
|
}
|
||||||
|
InitializeKeymap();
|
||||||
|
master_editor_.SetupScreen(renderer_, filename);
|
||||||
|
active_ = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::OnInput() {
|
||||||
|
int wheel = 0;
|
||||||
|
SDL_Event event;
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
HandleKeyDown(event, master_editor_);
|
||||||
|
break;
|
||||||
|
case SDL_KEYUP:
|
||||||
|
HandleKeyUp(event, master_editor_);
|
||||||
|
break;
|
||||||
|
case SDL_TEXTINPUT:
|
||||||
|
io.AddInputCharactersUTF8(event.text.text);
|
||||||
|
break;
|
||||||
|
case SDL_MOUSEWHEEL:
|
||||||
|
wheel = event.wheel.y;
|
||||||
|
break;
|
||||||
|
case SDL_WINDOWEVENT:
|
||||||
|
switch (event.window.event) {
|
||||||
|
case SDL_WINDOWEVENT_CLOSE:
|
||||||
|
CloseWindow();
|
||||||
|
break;
|
||||||
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
|
ChangeWindowSizeEvent(event);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleMouseMovement(wheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Controller::OnLoad() {
|
||||||
|
if (master_editor_.quit()) {
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
NewMasterFrame();
|
||||||
|
RETURN_IF_ERROR(master_editor_.Update());
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::DoRender() const {
|
||||||
|
ImGui::Render();
|
||||||
|
SDL_RenderClear(renderer_.get());
|
||||||
|
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get());
|
||||||
|
SDL_RenderPresent(renderer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
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::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
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Controller::CreateRenderer() {
|
||||||
|
renderer_ = std::unique_ptr<SDL_Renderer, sdl_deleter>(
|
||||||
|
SDL_CreateRenderer(window_.get(), -1,
|
||||||
|
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
|
||||||
|
sdl_deleter());
|
||||||
|
if (renderer_ == nullptr) {
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
|
||||||
|
} else {
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
||||||
|
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Controller::CreateGuiContext() {
|
||||||
|
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_ImplSDLRenderer2_Init(renderer_.get());
|
||||||
|
|
||||||
|
if (flags()->kLoadSystemFonts) {
|
||||||
|
LoadSystemFonts();
|
||||||
|
} else {
|
||||||
|
RETURN_IF_ERROR(LoadFontFamilies());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Japanese font configuration
|
||||||
|
ImFontConfig japanese_font_config;
|
||||||
|
japanese_font_config.MergeMode = true;
|
||||||
|
icons_config.GlyphOffset.y = 5.0f;
|
||||||
|
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||||
|
icons_config.PixelSnapH = true;
|
||||||
|
|
||||||
|
// List of fonts to be loaded
|
||||||
|
std::vector<const char *> font_paths = {KARLA_REGULAR, ROBOTO_MEDIUM,
|
||||||
|
COUSINE_REGULAR, IBM_PLEX_JP};
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
85
src/app/core/controller.h
Normal file
85
src/app/core/controller.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_CONTROLLER_H
|
||||||
|
#define YAZE_APP_CORE_CONTROLLER_H
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||||
|
#include "imgui/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 "app/editor/utils/editor.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv);
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Main controller for the application.
|
||||||
|
*
|
||||||
|
* This class is responsible for managing the main window and the
|
||||||
|
* main editor. It is the main entry point for the application.
|
||||||
|
*/
|
||||||
|
class Controller : public ExperimentFlags {
|
||||||
|
public:
|
||||||
|
bool IsActive() const { return active_; }
|
||||||
|
absl::Status OnEntry(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 {
|
||||||
|
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); }
|
||||||
|
};
|
||||||
|
|
||||||
|
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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_CONTROLLER_H
|
||||||
141
src/app/core/labeling.cc
Normal file
141
src/app/core/labeling.cc
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#include "app/core/labeling.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
bool ResourceLabelManager::LoadLabels(const std::string& filename) {
|
||||||
|
std::ifstream file(filename);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
// Create the file if it does not exist
|
||||||
|
std::ofstream create_file(filename);
|
||||||
|
if (!create_file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
create_file.close();
|
||||||
|
file.open(filename);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filename_ = filename;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string type, key, value;
|
||||||
|
if (std::getline(iss, type, ',') && std::getline(iss, key, ',') &&
|
||||||
|
std::getline(iss, value)) {
|
||||||
|
labels_[type][key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
labels_loaded_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceLabelManager::SaveLabels() {
|
||||||
|
if (!labels_loaded_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::ofstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& type_pair : labels_) {
|
||||||
|
for (const auto& label_pair : type_pair.second) {
|
||||||
|
file << type_pair.first << "," << label_pair.first << ","
|
||||||
|
<< label_pair.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceLabelManager::DisplayLabels(bool* p_open) {
|
||||||
|
if (!labels_loaded_) {
|
||||||
|
ImGui::Text("No labels loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Begin("Resource Labels", p_open)) {
|
||||||
|
for (const auto& type_pair : labels_) {
|
||||||
|
if (ImGui::TreeNode(type_pair.first.c_str())) {
|
||||||
|
for (const auto& label_pair : type_pair.second) {
|
||||||
|
std::string label_id = type_pair.first + "_" + label_pair.first;
|
||||||
|
ImGui::Text("%s: %s", label_pair.first.c_str(),
|
||||||
|
label_pair.second.c_str());
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Update Labels")) {
|
||||||
|
if (SaveLabels()) {
|
||||||
|
ImGui::Text("Labels updated successfully!");
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Failed to update labels.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceLabelManager::EditLabel(const std::string& type,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& newValue) {
|
||||||
|
labels_[type][key] = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceLabelManager::SelectableLabelWithNameEdit(
|
||||||
|
bool selected, const std::string& type, const std::string& key,
|
||||||
|
const std::string& defaultValue) {
|
||||||
|
std::string label = CreateOrGetLabel(type, key, defaultValue);
|
||||||
|
ImGui::Selectable(label.c_str(), selected,
|
||||||
|
ImGuiSelectableFlags_AllowDoubleClick);
|
||||||
|
std::string label_id = type + "_" + key;
|
||||||
|
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup(label_id.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginPopupContextItem(label_id.c_str())) {
|
||||||
|
std::string* new_label = &labels_[type][key];
|
||||||
|
if (ImGui::InputText("##Label", new_label,
|
||||||
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||||
|
labels_[type][key] = *new_label;
|
||||||
|
}
|
||||||
|
if (ImGui::Button(ICON_MD_CLOSE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ResourceLabelManager::CreateOrGetLabel(
|
||||||
|
const std::string& type, const std::string& key,
|
||||||
|
const std::string& defaultValue) {
|
||||||
|
if (labels_.find(type) == labels_.end()) {
|
||||||
|
labels_[type] = std::unordered_map<std::string, std::string>();
|
||||||
|
}
|
||||||
|
if (labels_[type].find(key) == labels_[type].end()) {
|
||||||
|
labels_[type][key] = defaultValue;
|
||||||
|
}
|
||||||
|
return labels_[type][key];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
53
src/app/core/labeling.h
Normal file
53
src/app/core/labeling.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_LABELING_H_
|
||||||
|
#define YAZE_APP_CORE_LABELING_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
// Default types
|
||||||
|
static constexpr absl::string_view kDefaultTypes[] = {
|
||||||
|
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
|
||||||
|
|
||||||
|
struct ResourceLabelManager {
|
||||||
|
bool LoadLabels(const std::string& filename);
|
||||||
|
bool SaveLabels();
|
||||||
|
void DisplayLabels(bool* p_open);
|
||||||
|
void EditLabel(const std::string& type, const std::string& key,
|
||||||
|
const std::string& newValue);
|
||||||
|
void SelectableLabelWithNameEdit(bool selected, const std::string& type,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& defaultValue);
|
||||||
|
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||||
|
const std::string& defaultValue);
|
||||||
|
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||||
|
const absl::string_view& defaultValue);
|
||||||
|
|
||||||
|
bool labels_loaded_ = false;
|
||||||
|
std::string filename_;
|
||||||
|
struct ResourceType {
|
||||||
|
std::string key_name;
|
||||||
|
std::string display_description;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
|
||||||
|
labels_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_LABELING_H_
|
||||||
18
src/app/core/platform/app_delegate.h
Normal file
18
src/app/core/platform/app_delegate.h
Normal 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
|
||||||
243
src/app/core/platform/app_delegate.mm
Normal file
243
src/app/core/platform/app_delegate.mm
Normal 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
|
||||||
8
src/app/core/platform/clipboard.cc
Normal file
8
src/app/core/platform/clipboard.cc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include "app/core/platform/clipboard.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
|
||||||
|
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
|
||||||
|
int& height) {}
|
||||||
27
src/app/core/platform/clipboard.h
Normal file
27
src/app/core/platform/clipboard.h
Normal 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
|
||||||
45
src/app/core/platform/clipboard.mm
Normal file
45
src/app/core/platform/clipboard.mm
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "clipboard.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef TARGET_OS_MAC
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
void 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
|
||||||
96
src/app/core/platform/file_dialog.h
Normal file
96
src/app/core/platform/file_dialog.h
Normal 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
|
||||||
116
src/app/core/platform/file_dialog.mm
Normal file
116
src/app/core/platform/file_dialog.mm
Normal 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__
|
||||||
6
src/app/core/platform/file_path.h
Normal file
6
src/app/core/platform/file_path.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
|
|
||||||
|
std::string GetBundleResourcePath();
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
26
src/app/core/platform/file_path.mm
Normal file
26
src/app/core/platform/file_path.mm
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include <Foundation/Foundation.h>
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
std::string GetBundleResourcePath() {}
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
std::string GetBundleResourcePath() {
|
||||||
|
NSBundle* bundle = [NSBundle mainBundle];
|
||||||
|
NSString* resourceDirectoryPath = [bundle bundlePath];
|
||||||
|
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
|
||||||
|
return [path UTF8String];
|
||||||
|
}
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
std::string GetBundleResourcePath() {
|
||||||
|
NSBundle* bundle = [NSBundle mainBundle];
|
||||||
|
NSString* resourceDirectoryPath = [bundle bundlePath];
|
||||||
|
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
|
||||||
|
return [path UTF8String];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
81
src/app/core/platform/font_loader.cc
Normal file
81
src/app/core/platform/font_loader.cc
Normal 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
|
||||||
26
src/app/core/platform/font_loader.h
Normal file
26
src/app/core/platform/font_loader.h
Normal 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
|
||||||
74
src/app/core/platform/font_loader.mm
Normal file
74
src/app/core/platform/font_loader.mm
Normal 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
138
src/app/core/project.h
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PROJECT_H
|
||||||
|
#define YAZE_APP_CORE_PROJECT_H
|
||||||
|
|
||||||
|
#include "absl/strings/match.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
constexpr absl::string_view kProjectFileExtension = ".yaze";
|
||||||
|
constexpr absl::string_view kProjectFileFilter =
|
||||||
|
"Yaze Project Files (*.yaze)\0*.yaze\0";
|
||||||
|
constexpr absl::string_view kPreviousRomFilenameDelimiter =
|
||||||
|
"PreviousRomFilename";
|
||||||
|
constexpr absl::string_view kEndOfProjectFile = "EndOfProjectFile";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct Project
|
||||||
|
* @brief Represents a project in the application.
|
||||||
|
*
|
||||||
|
* A project is a collection of files and resources that are used in the
|
||||||
|
* creation of a Zelda3 hack that can be saved and loaded. This makes it so the
|
||||||
|
* user can have different rom file names for a single project and keep track of
|
||||||
|
* backups.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct Project : public core::ExperimentFlags {
|
||||||
|
/**
|
||||||
|
* @brief Creates a new project.
|
||||||
|
*
|
||||||
|
* @param project_name The name of the project.
|
||||||
|
* @param project_path The path to the project.
|
||||||
|
* @return An absl::Status indicating the success or failure of the project
|
||||||
|
* creation.
|
||||||
|
*/
|
||||||
|
absl::Status Create(const std::string &project_name) {
|
||||||
|
name = project_name;
|
||||||
|
project_opened_ = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Open(const std::string &project_path) {
|
||||||
|
filepath = project_path;
|
||||||
|
name = project_path.substr(project_path.find_last_of("/") + 1);
|
||||||
|
|
||||||
|
std::ifstream in(project_path);
|
||||||
|
|
||||||
|
if (!in.good()) {
|
||||||
|
return absl::InternalError("Could not open project file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
std::getline(in, name);
|
||||||
|
std::getline(in, filepath);
|
||||||
|
std::getline(in, rom_filename_);
|
||||||
|
std::getline(in, code_folder_);
|
||||||
|
std::getline(in, labels_filename_);
|
||||||
|
|
||||||
|
while (std::getline(in, line)) {
|
||||||
|
if (line == kEndOfProjectFile) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absl::StrContains(line, kPreviousRomFilenameDelimiter)) {
|
||||||
|
previous_rom_filenames_.push_back(
|
||||||
|
line.substr(line.find(kPreviousRomFilenameDelimiter) +
|
||||||
|
kPreviousRomFilenameDelimiter.size() + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Save() {
|
||||||
|
RETURN_IF_ERROR(CheckForEmptyFields());
|
||||||
|
|
||||||
|
std::ofstream out(filepath + "/" + name + ".yaze");
|
||||||
|
if (!out.good()) {
|
||||||
|
return absl::InternalError("Could not open project file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
out << name << std::endl;
|
||||||
|
out << filepath << std::endl;
|
||||||
|
out << rom_filename_ << std::endl;
|
||||||
|
out << code_folder_ << std::endl;
|
||||||
|
out << labels_filename_ << std::endl;
|
||||||
|
|
||||||
|
for (const auto &filename : previous_rom_filenames_) {
|
||||||
|
out << kPreviousRomFilenameDelimiter << " " << filename << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << kEndOfProjectFile << std::endl;
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status CheckForEmptyFields() {
|
||||||
|
if (name.empty() || filepath.empty() || rom_filename_.empty() ||
|
||||||
|
code_folder_.empty() || labels_filename_.empty()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Project fields cannot be empty. Please load a rom file, set your "
|
||||||
|
"code folder, and set your labels file. See HELP for more details.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status SerializeExperimentFlags() {
|
||||||
|
auto flags = mutable_flags();
|
||||||
|
// TODO: Serialize flags
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool project_opened_ = false;
|
||||||
|
std::string name;
|
||||||
|
std::string filepath;
|
||||||
|
std::string rom_filename_ = "";
|
||||||
|
std::string code_folder_ = "";
|
||||||
|
std::string labels_filename_ = "";
|
||||||
|
std::vector<std::string> previous_rom_filenames_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PROJECT_H
|
||||||
19
src/app/core/testable.h
Normal file
19
src/app/core/testable.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_TESTABLE_H
|
||||||
|
#define YAZE_APP_CORE_TESTABLE_H
|
||||||
|
|
||||||
|
#include <imgui_test_engine/imgui_te_context.h>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
class GuiTestable {
|
||||||
|
public:
|
||||||
|
virtual void RegisterTests(ImGuiTestEngine* e) = 0;
|
||||||
|
|
||||||
|
ImGuiTestEngine* test_engine;
|
||||||
|
};
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_TESTABLE_H
|
||||||
21
src/app/editor/CMakeLists.txt
Normal file
21
src/app/editor/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
set(
|
||||||
|
YAZE_APP_EDITOR_SRC
|
||||||
|
app/editor/dungeon/dungeon_editor.cc
|
||||||
|
app/editor/master_editor.cc
|
||||||
|
app/editor/master_editor_test.cc
|
||||||
|
app/editor/settings_editor.cc
|
||||||
|
app/editor/overworld_editor.cc
|
||||||
|
app/editor/sprite/sprite_editor.cc
|
||||||
|
app/editor/music/music_editor.cc
|
||||||
|
app/editor/message/message_editor.cc
|
||||||
|
app/editor/message/message_editor_test.cc
|
||||||
|
app/editor/code/assembly_editor.cc
|
||||||
|
app/editor/graphics/screen_editor.cc
|
||||||
|
app/editor/graphics/graphics_editor.cc
|
||||||
|
app/editor/graphics/palette_editor.cc
|
||||||
|
app/editor/graphics/tile16_editor.cc
|
||||||
|
app/editor/graphics/gfx_group_editor.cc
|
||||||
|
app/editor/utils/gfx_context.cc
|
||||||
|
app/editor/overworld/refresh.cc
|
||||||
|
app/editor/overworld/entity.cc
|
||||||
|
)
|
||||||
367
src/app/editor/code/assembly_editor.cc
Normal file
367
src/app/editor/code/assembly_editor.cc
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
#include "assembly_editor.h"
|
||||||
|
|
||||||
|
#include "ImGuiColorTextEdit/TextEditor.h"
|
||||||
|
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "core/constants.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<std::string> RemoveIgnoredFiles(
|
||||||
|
const std::vector<std::string>& files,
|
||||||
|
const std::vector<std::string>& ignored_files) {
|
||||||
|
std::vector<std::string> filtered_files;
|
||||||
|
for (const auto& file : files) {
|
||||||
|
// Remove subdirectory files
|
||||||
|
if (file.find('/') != std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Make sure the file has an extension
|
||||||
|
if (file.find('.') == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::find(ignored_files.begin(), ignored_files.end(), file) ==
|
||||||
|
ignored_files.end()) {
|
||||||
|
filtered_files.push_back(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered_files;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::FolderItem LoadFolder(const std::string& folder) {
|
||||||
|
// Check if .gitignore exists in the folder
|
||||||
|
std::ifstream gitignore(folder + "/.gitignore");
|
||||||
|
std::vector<std::string> ignored_files;
|
||||||
|
if (gitignore.good()) {
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(gitignore, line)) {
|
||||||
|
if (line[0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line[0] == '!') {
|
||||||
|
// Ignore the file
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ignored_files.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core::FolderItem current_folder;
|
||||||
|
current_folder.name = folder;
|
||||||
|
auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
|
||||||
|
current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
|
||||||
|
|
||||||
|
for (const auto& folder :
|
||||||
|
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
|
||||||
|
core::FolderItem folder_item;
|
||||||
|
folder_item.name = folder;
|
||||||
|
std::string full_folder = current_folder.name + "/" + folder;
|
||||||
|
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
|
||||||
|
for (const auto& files : folder_files) {
|
||||||
|
// Remove subdirectory files
|
||||||
|
if (files.find('/') != std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Make sure the file has an extension
|
||||||
|
if (files.find('.') == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::find(ignored_files.begin(), ignored_files.end(), files) !=
|
||||||
|
ignored_files.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
folder_item.files.push_back(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& subdir :
|
||||||
|
FileDialogWrapper::GetSubdirectoriesInFolder(full_folder)) {
|
||||||
|
core::FolderItem subfolder_item;
|
||||||
|
subfolder_item.name = subdir;
|
||||||
|
subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir);
|
||||||
|
folder_item.subfolders.push_back(subfolder_item);
|
||||||
|
}
|
||||||
|
current_folder.subfolders.push_back(folder_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void AssemblyEditor::OpenFolder(const std::string& folder_path) {
|
||||||
|
current_folder_ = LoadFolder(folder_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::Update(bool& is_loaded) {
|
||||||
|
ImGui::Begin("Assembly Editor", &is_loaded);
|
||||||
|
if (ImGui::BeginMenuBar()) {
|
||||||
|
DrawFileMenu();
|
||||||
|
DrawEditMenu();
|
||||||
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor");
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::InlineUpdate() {
|
||||||
|
ChangeActiveFile("assets/asm/template_song.asm");
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor", ImVec2(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::UpdateCodeView() {
|
||||||
|
ImGui::BeginTable("##table_view", 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_Resizable);
|
||||||
|
|
||||||
|
// Table headers
|
||||||
|
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 256.0f);
|
||||||
|
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Table data
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (current_folder_.name != "") {
|
||||||
|
DrawCurrentFolder();
|
||||||
|
} else {
|
||||||
|
if (ImGui::Button("Open Folder")) {
|
||||||
|
current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawCurrentFolder() {
|
||||||
|
if (ImGui::BeginChild("##current_folder", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
if (ImGui::BeginTable("##file_table", 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_Sortable)) {
|
||||||
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
|
||||||
|
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto& file : current_folder_.files) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Selectable(file.c_str())) {
|
||||||
|
ChangeActiveFile(absl::StrCat(current_folder_.name, "/", file));
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("File");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& subfolder : current_folder_.subfolders) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::TreeNode(subfolder.name.c_str())) {
|
||||||
|
for (const auto& file : subfolder.files) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Selectable(file.c_str())) {
|
||||||
|
ChangeActiveFile(absl::StrCat(current_folder_.name, "/",
|
||||||
|
subfolder.name, "/", file));
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("File");
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
} else {
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawFileTabView() {
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("AssemblyFileTabBar", ImGuiTabBarFlags_None)) {
|
||||||
|
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_None)) {
|
||||||
|
if (std::find(active_files_.begin(), active_files_.end(),
|
||||||
|
current_file_id_) != active_files_.end()) {
|
||||||
|
// Room is already open
|
||||||
|
next_tab_id++;
|
||||||
|
}
|
||||||
|
active_files_.push_back(next_tab_id++); // Add new tab
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit our regular tabs
|
||||||
|
for (int n = 0; n < active_files_.Size;) {
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(files_[active_files_[n]].data(), &open,
|
||||||
|
ImGuiTabItemFlags_None)) {
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
{
|
||||||
|
std::ifstream t(current_file_);
|
||||||
|
if (t.good()) {
|
||||||
|
std::string str((std::istreambuf_iterator<char>(t)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
text_editor_.SetText(str);
|
||||||
|
} else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Error opening file: %s\n", current_file_.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
open_files_[active_files_[n]].Render("##asm_editor");
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
active_files_.erase(active_files_.Data + n);
|
||||||
|
else
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawFileMenu() {
|
||||||
|
if (ImGui::BeginMenu("File")) {
|
||||||
|
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||||
|
ImGuiFileDialog::Instance()->OpenDialog(
|
||||||
|
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Save", "Ctrl+S")) {
|
||||||
|
// TODO: Implement this
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
|
||||||
|
if (ImGuiFileDialog::Instance()->IsOk()) {
|
||||||
|
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
|
||||||
|
}
|
||||||
|
ImGuiFileDialog::Instance()->Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawEditMenu() {
|
||||||
|
if (ImGui::BeginMenu("Edit")) {
|
||||||
|
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
|
||||||
|
text_editor_.Undo();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
|
||||||
|
text_editor_.Redo();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
|
||||||
|
text_editor_.Cut();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
|
||||||
|
text_editor_.Copy();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
|
||||||
|
text_editor_.Paste();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Find", "Ctrl+F")) {
|
||||||
|
// TODO: Implement this.
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::SetEditorText() {
|
||||||
|
if (!file_is_loaded_) {
|
||||||
|
std::ifstream t(current_file_);
|
||||||
|
if (t.good()) {
|
||||||
|
std::string str((std::istreambuf_iterator<char>(t)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
text_editor_.SetText(str);
|
||||||
|
} else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n",
|
||||||
|
current_file_.c_str());
|
||||||
|
}
|
||||||
|
file_is_loaded_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Cut() {
|
||||||
|
text_editor_.Cut();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Copy() {
|
||||||
|
text_editor_.Copy();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Paste() {
|
||||||
|
text_editor_.Paste();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Undo() {
|
||||||
|
text_editor_.Undo();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Redo() {
|
||||||
|
text_editor_.Redo();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Update() { return absl::OkStatus(); }
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
79
src/app/editor/code/assembly_editor.h
Normal file
79
src/app/editor/code/assembly_editor.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
||||||
|
|
||||||
|
#include "ImGuiColorTextEdit/TextEditor.h"
|
||||||
|
#include "ImGuiFileDialog/ImGuiFileDialog.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <istream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class AssemblyEditor
|
||||||
|
* @brief Text editor for modifying assembly code.
|
||||||
|
*/
|
||||||
|
class AssemblyEditor : public Editor {
|
||||||
|
public:
|
||||||
|
AssemblyEditor() {
|
||||||
|
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
|
||||||
|
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
||||||
|
text_editor_.SetShowWhitespaces(false);
|
||||||
|
type_ = EditorType::kAssembly;
|
||||||
|
}
|
||||||
|
void ChangeActiveFile(const std::string_view &filename) {
|
||||||
|
current_file_ = filename;
|
||||||
|
file_is_loaded_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update(bool &is_loaded);
|
||||||
|
void InlineUpdate();
|
||||||
|
|
||||||
|
void UpdateCodeView();
|
||||||
|
|
||||||
|
absl::Status Cut() override;
|
||||||
|
absl::Status Copy() override;
|
||||||
|
absl::Status Paste() override;
|
||||||
|
|
||||||
|
absl::Status Undo() override;
|
||||||
|
absl::Status Redo() override;
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
void OpenFolder(const std::string &folder_path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawFileMenu();
|
||||||
|
void DrawEditMenu();
|
||||||
|
|
||||||
|
void SetEditorText();
|
||||||
|
|
||||||
|
void DrawCurrentFolder();
|
||||||
|
|
||||||
|
void DrawFileTabView();
|
||||||
|
|
||||||
|
bool file_is_loaded_ = false;
|
||||||
|
|
||||||
|
std::vector<std::string> files_;
|
||||||
|
std::vector<TextEditor> open_files_;
|
||||||
|
ImVector<int> active_files_;
|
||||||
|
int current_file_id_ = 0;
|
||||||
|
|
||||||
|
std::string current_file_;
|
||||||
|
core::FolderItem current_folder_;
|
||||||
|
TextEditor text_editor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
88
src/app/editor/code/memory_editor.h
Normal file
88
src/app/editor/code/memory_editor.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
#include "imgui_memory_editor.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/core/project.h"
|
||||||
|
#include "app/editor/code/assembly_editor.h"
|
||||||
|
#include "app/editor/code/memory_editor.h"
|
||||||
|
#include "app/editor/dungeon/dungeon_editor.h"
|
||||||
|
#include "app/editor/graphics/graphics_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/graphics/screen_editor.h"
|
||||||
|
#include "app/editor/music/music_editor.h"
|
||||||
|
#include "app/editor/overworld_editor.h"
|
||||||
|
#include "app/editor/sprite/sprite_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/editor/utils/gfx_context.h"
|
||||||
|
#include "app/editor/utils/recent_files.h"
|
||||||
|
#include "app/emu/emulator.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
struct MemoryEditorWithDiffChecker : public SharedRom {
|
||||||
|
void Update(bool &show_memory_editor) {
|
||||||
|
static MemoryEditor mem_edit;
|
||||||
|
static MemoryEditor comp_edit;
|
||||||
|
static bool show_compare_rom = false;
|
||||||
|
static Rom comparison_rom;
|
||||||
|
ImGui::Begin("Hex Editor", &show_memory_editor);
|
||||||
|
if (ImGui::Button("Compare Rom")) {
|
||||||
|
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name));
|
||||||
|
show_compare_rom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t convert_address = 0;
|
||||||
|
gui::InputHex("SNES to PC", (int *)&convert_address, 6, 200.f);
|
||||||
|
SameLine();
|
||||||
|
Text("%x", core::SnesToPc(convert_address));
|
||||||
|
|
||||||
|
// mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
|
||||||
|
BEGIN_TABLE("Memory Comparison", 2, ImGuiTableFlags_Resizable);
|
||||||
|
SETUP_COLUMN("Source")
|
||||||
|
SETUP_COLUMN("Dest")
|
||||||
|
|
||||||
|
NEXT_COLUMN()
|
||||||
|
Text("%s", rom()->filename().data());
|
||||||
|
mem_edit.DrawContents((void *)&(*rom()), rom()->size());
|
||||||
|
|
||||||
|
NEXT_COLUMN()
|
||||||
|
if (show_compare_rom) {
|
||||||
|
comp_edit.SetComparisonData((void *)&(*rom()));
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
ImGui::BeginChild("Comparison ROM");
|
||||||
|
Text("%s", comparison_rom.filename().data());
|
||||||
|
comp_edit.DrawContents((void *)&(comparison_rom), comparison_rom.size());
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::EndGroup();
|
||||||
|
}
|
||||||
|
END_TABLE()
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
830
src/app/editor/dungeon/dungeon_editor.cc
Normal file
830
src/app/editor/dungeon/dungeon_editor.cc
Normal file
@@ -0,0 +1,830 @@
|
|||||||
|
#include "dungeon_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/labeling.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/dungeon/object_names.h"
|
||||||
|
#include "app/zelda3/dungeon/room_names.h"
|
||||||
|
#include "zelda3/dungeon/room.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::RadioButton;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kDungeonObjectTableFlags =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||||
|
ImGuiTableFlags_BordersV;
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::Update() {
|
||||||
|
if (!is_loaded_ && rom()->is_loaded()) {
|
||||||
|
RETURN_IF_ERROR(Initialize());
|
||||||
|
is_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refresh_graphics_) {
|
||||||
|
RETURN_IF_ERROR(RefreshGraphics());
|
||||||
|
refresh_graphics_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TAB_BAR("##DungeonEditorTabBar")
|
||||||
|
TAB_ITEM("Room Editor")
|
||||||
|
status_ = UpdateDungeonRoomView();
|
||||||
|
END_TAB_ITEM()
|
||||||
|
TAB_ITEM("Usage Statistics")
|
||||||
|
if (is_loaded_) {
|
||||||
|
static bool calc_stats = false;
|
||||||
|
if (!calc_stats) {
|
||||||
|
CalculateUsageStats();
|
||||||
|
calc_stats = true;
|
||||||
|
}
|
||||||
|
DrawUsageStats();
|
||||||
|
}
|
||||||
|
END_TAB_ITEM()
|
||||||
|
END_TAB_BAR()
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::Initialize() {
|
||||||
|
auto dungeon_man_pal_group = rom()->palette_group().dungeon_main;
|
||||||
|
for (int i = 0; i < 0x100 + 40; i++) {
|
||||||
|
rooms_.emplace_back(zelda3::dungeon::Room(i));
|
||||||
|
rooms_[i].LoadHeader();
|
||||||
|
rooms_[i].LoadRoomFromROM();
|
||||||
|
if (flags()->kDrawDungeonRoomGraphics) {
|
||||||
|
rooms_[i].LoadRoomGraphics();
|
||||||
|
}
|
||||||
|
|
||||||
|
room_size_pointers_.push_back(rooms_[i].room_size_ptr());
|
||||||
|
if (rooms_[i].room_size_ptr() != 0x0A8000) {
|
||||||
|
room_size_addresses_[i] = rooms_[i].room_size_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dungeon_palette_ptr = rom()->paletteset_ids[rooms_[i].palette][0];
|
||||||
|
ASSIGN_OR_RETURN(auto palette_id,
|
||||||
|
rom()->ReadWord(0xDEC4B + dungeon_palette_ptr));
|
||||||
|
int p_id = palette_id / 180;
|
||||||
|
auto color = dungeon_man_pal_group[p_id][3];
|
||||||
|
room_palette_[rooms_[i].palette] = color.rgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadDungeonRoomSize();
|
||||||
|
// LoadRoomEntrances
|
||||||
|
for (int i = 0; i < 0x07; ++i) {
|
||||||
|
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x85; ++i) {
|
||||||
|
entrances_.emplace_back(zelda3::dungeon::RoomEntrance(*rom(), i, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the palette group and palette for the dungeon
|
||||||
|
full_palette_ = dungeon_man_pal_group[current_palette_group_id_];
|
||||||
|
ASSIGN_OR_RETURN(current_palette_group_,
|
||||||
|
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
|
||||||
|
|
||||||
|
graphics_bin_ = *rom()->mutable_bitmap_manager();
|
||||||
|
// Create a vector of pointers to the current block bitmaps
|
||||||
|
for (int block : rooms_[current_room_id_].blocks()) {
|
||||||
|
room_gfx_sheets_.emplace_back(&graphics_bin_[block]);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::RefreshGraphics() {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int block = rooms_[current_room_id_].blocks()[i];
|
||||||
|
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
|
||||||
|
current_palette_group_[current_palette_id_], 0));
|
||||||
|
rom()->UpdateBitmap(&graphics_bin_[block], true);
|
||||||
|
}
|
||||||
|
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
|
||||||
|
for (int i = 9; i < 16; i++) {
|
||||||
|
int block = rooms_[current_room_id_].blocks()[i];
|
||||||
|
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
|
||||||
|
sprites_aux1_pal_group[current_palette_id_], 0));
|
||||||
|
rom()->UpdateBitmap(&graphics_bin_[block], true);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::LoadDungeonRoomSize() {
|
||||||
|
std::map<int, std::vector<int>> rooms_by_bank;
|
||||||
|
for (const auto& room : room_size_addresses_) {
|
||||||
|
int bank = room.second >> 16;
|
||||||
|
rooms_by_bank[bank].push_back(room.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process and calculate room sizes within each bank
|
||||||
|
for (auto& bank_rooms : rooms_by_bank) {
|
||||||
|
// Sort the rooms within this bank
|
||||||
|
std::sort(bank_rooms.second.begin(), bank_rooms.second.end());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < bank_rooms.second.size(); ++i) {
|
||||||
|
int room_ptr = bank_rooms.second[i];
|
||||||
|
|
||||||
|
// Identify the room ID for the current room pointer
|
||||||
|
int room_id =
|
||||||
|
std::find_if(room_size_addresses_.begin(), room_size_addresses_.end(),
|
||||||
|
[room_ptr](const auto& entry) {
|
||||||
|
return entry.second == room_ptr;
|
||||||
|
})
|
||||||
|
->first;
|
||||||
|
|
||||||
|
if (room_ptr != 0x0A8000) {
|
||||||
|
if (i < bank_rooms.second.size() - 1) {
|
||||||
|
// Calculate size as difference between current room and next room
|
||||||
|
// in the same bank
|
||||||
|
rooms_[room_id].set_room_size(bank_rooms.second[i + 1] - room_ptr);
|
||||||
|
} else {
|
||||||
|
// Calculate size for the last room in this bank
|
||||||
|
int bank_end_address = (bank_rooms.first << 16) | 0xFFFF;
|
||||||
|
rooms_[room_id].set_room_size(bank_end_address - room_ptr + 1);
|
||||||
|
}
|
||||||
|
total_room_size_ += rooms_[room_id].room_size();
|
||||||
|
} else {
|
||||||
|
// Room with address 0x0A8000
|
||||||
|
rooms_[room_id].set_room_size(0x00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::UpdateDungeonRoomView() {
|
||||||
|
DrawToolset();
|
||||||
|
|
||||||
|
if (palette_showing_) {
|
||||||
|
ImGui::Begin("Palette Editor", &palette_showing_, 0);
|
||||||
|
auto dungeon_main_pal_group = rom()->palette_group().dungeon_main;
|
||||||
|
current_palette_ = dungeon_main_pal_group[current_palette_group_id_];
|
||||||
|
gui::SelectablePalettePipeline(current_palette_id_, refresh_graphics_,
|
||||||
|
current_palette_);
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Room Selector");
|
||||||
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
ImGui::GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Object Selector");
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
TAB_BAR("##DungeonRoomTabBar");
|
||||||
|
TAB_ITEM("Rooms");
|
||||||
|
DrawRoomSelector();
|
||||||
|
END_TAB_ITEM();
|
||||||
|
TAB_ITEM("Entrances");
|
||||||
|
DrawEntranceSelector();
|
||||||
|
END_TAB_ITEM();
|
||||||
|
END_TAB_BAR();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawDungeonTabView();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawTileSelector();
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawToolset() {
|
||||||
|
if (BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("#undoTool");
|
||||||
|
TableSetupColumn("#redoTool");
|
||||||
|
TableSetupColumn("#separator");
|
||||||
|
TableSetupColumn("#anyTool");
|
||||||
|
|
||||||
|
TableSetupColumn("#bg1Tool");
|
||||||
|
TableSetupColumn("#bg2Tool");
|
||||||
|
TableSetupColumn("#bg3Tool");
|
||||||
|
TableSetupColumn("#separator");
|
||||||
|
TableSetupColumn("#spriteTool");
|
||||||
|
TableSetupColumn("#itemTool");
|
||||||
|
TableSetupColumn("#doorTool");
|
||||||
|
TableSetupColumn("#blockTool");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_UNDO)) {
|
||||||
|
PRINT_IF_ERROR(Undo());
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_REDO)) {
|
||||||
|
PRINT_IF_ERROR(Redo());
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
Text(ICON_MD_MORE_VERT);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_NONE, background_type_ == kBackgroundAny)) {
|
||||||
|
background_type_ = kBackgroundAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_1, background_type_ == kBackground1)) {
|
||||||
|
background_type_ = kBackground1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_2, background_type_ == kBackground2)) {
|
||||||
|
background_type_ = kBackground2;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_3, background_type_ == kBackground3)) {
|
||||||
|
background_type_ = kBackground3;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
Text(ICON_MD_MORE_VERT);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
|
||||||
|
placement_type_ = kSprite;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Sprites");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
|
||||||
|
placement_type_ = kItem;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Items");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
|
||||||
|
placement_type_ = kDoor;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Doors");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) {
|
||||||
|
placement_type_ = kBlock;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Blocks");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_PALETTE)) {
|
||||||
|
palette_showing_ = !palette_showing_;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawRoomSelector() {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
gui::InputHexWord("Room ID", ¤t_room_id_);
|
||||||
|
gui::InputHex("Palette ID", ¤t_palette_id_);
|
||||||
|
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||||
|
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
int i = 0;
|
||||||
|
for (const auto each_room_name : zelda3::dungeon::kRoomNames) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_room_id_ == i, "Dungeon Room Names",
|
||||||
|
core::UppercaseHexByte(i), zelda3::dungeon::kRoomNames[i].data());
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
// TODO: Jump to tab if room is already open
|
||||||
|
current_room_id_ = i;
|
||||||
|
if (!active_rooms_.contains(i)) {
|
||||||
|
active_rooms_.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using ImGui::Separator;
|
||||||
|
|
||||||
|
void DungeonEditor::DrawEntranceSelector() {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
auto current_entrance = entrances_[current_entrance_id_];
|
||||||
|
gui::InputHexWord("Entrance ID", ¤t_entrance.entrance_id_);
|
||||||
|
gui::InputHexWord("Room ID", ¤t_entrance.room_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon ID", ¤t_entrance.dungeon_id_, 50.f, true);
|
||||||
|
gui::InputHexByte("Blockset", ¤t_entrance.blockset_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Music", ¤t_entrance.music_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("Floor", ¤t_entrance.floor_);
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexWord("Player X ", ¤t_entrance.x_position_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Player Y ", ¤t_entrance.y_position_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Camera X", ¤t_entrance.camera_trigger_x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Camera Y", ¤t_entrance.camera_trigger_y_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Scroll X ", ¤t_entrance.camera_x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Scroll Y ", ¤t_entrance.camera_y_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Exit", ¤t_entrance.exit_, 50.f, true);
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
Text("Camera Boundaries");
|
||||||
|
Separator();
|
||||||
|
Text("\t\t\t\t\tNorth East South West");
|
||||||
|
gui::InputHexByte("Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f,
|
||||||
|
true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qe_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qs_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qw_, 50.f, true);
|
||||||
|
|
||||||
|
gui::InputHexByte("Full room", ¤t_entrance.camera_boundary_fn_, 50.f,
|
||||||
|
true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fe_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fs_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fw_, 50.f, true);
|
||||||
|
|
||||||
|
if (BeginChild("EntranceSelector", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
for (int i = 0; i < 0x85 + 7; i++) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_entrance_id_ == i, "Dungeon Entrance Names",
|
||||||
|
core::UppercaseHexByte(i),
|
||||||
|
zelda3::dungeon::kEntranceNames[i].data());
|
||||||
|
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_entrance_id_ = i;
|
||||||
|
if (!active_rooms_.contains(i)) {
|
||||||
|
active_rooms_.push_back(entrances_[i].room_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawDungeonTabView() {
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
|
if (BeginTabBar("MyTabBar", kDungeonTabBarFlags)) {
|
||||||
|
if (ImGui::TabItemButton(ICON_MD_ADD, kDungeonTabFlags)) {
|
||||||
|
if (std::find(active_rooms_.begin(), active_rooms_.end(),
|
||||||
|
current_room_id_) != active_rooms_.end()) {
|
||||||
|
// Room is already open
|
||||||
|
next_tab_id++;
|
||||||
|
}
|
||||||
|
active_rooms_.push_back(next_tab_id++); // Add new tab
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit our regular tabs
|
||||||
|
for (int n = 0; n < active_rooms_.Size;) {
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
if (active_rooms_[n] > sizeof(zelda3::dungeon::kRoomNames) / 4) {
|
||||||
|
active_rooms_.erase(active_rooms_.Data + n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem(zelda3::dungeon::kRoomNames[active_rooms_[n]].data(),
|
||||||
|
&open, ImGuiTabItemFlags_None)) {
|
||||||
|
DrawDungeonCanvas(active_rooms_[n]);
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
active_rooms_.erase(active_rooms_.Data + n);
|
||||||
|
else
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
gui::InputHexByte("Layout", &rooms_[room_id].layout);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Blockset", &rooms_[room_id].blockset);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Palette", &rooms_[room_id].palette);
|
||||||
|
|
||||||
|
gui::InputHexByte("Floor1", &rooms_[room_id].floor1);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Floor2", &rooms_[room_id].floor2);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexWord("Message ID", &rooms_[room_id].message_id_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
canvas_.DrawBackground(ImVec2(0x200, 0x200));
|
||||||
|
canvas_.DrawContextMenu();
|
||||||
|
if (is_loaded_) {
|
||||||
|
canvas_.DrawBitmap(rooms_[room_id].layer1(), 0, 0);
|
||||||
|
}
|
||||||
|
canvas_.DrawGrid();
|
||||||
|
canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawRoomGraphics() {
|
||||||
|
const auto height = 0x40;
|
||||||
|
const int num_sheets = 0x10;
|
||||||
|
room_gfx_canvas_.DrawBackground(ImVec2(0x100 + 1, num_sheets * height + 1));
|
||||||
|
room_gfx_canvas_.DrawContextMenu();
|
||||||
|
room_gfx_canvas_.DrawTileSelector(32);
|
||||||
|
if (is_loaded_) {
|
||||||
|
auto blocks = rooms_[current_room_id_].blocks();
|
||||||
|
int current_block = 0;
|
||||||
|
for (int block : blocks) {
|
||||||
|
int offset = height * (current_block + 1);
|
||||||
|
int top_left_y = room_gfx_canvas_.zero_point().y + 2;
|
||||||
|
if (current_block >= 1) {
|
||||||
|
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
|
||||||
|
}
|
||||||
|
room_gfx_canvas_.draw_list()->AddImage(
|
||||||
|
(void*)graphics_bin_[block].texture(),
|
||||||
|
ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y),
|
||||||
|
ImVec2(room_gfx_canvas_.zero_point().x + 0x100,
|
||||||
|
room_gfx_canvas_.zero_point().y + offset));
|
||||||
|
current_block += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
room_gfx_canvas_.DrawGrid(32.0f);
|
||||||
|
room_gfx_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawTileSelector() {
|
||||||
|
if (BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||||
|
if (BeginTabItem("Room Graphics")) {
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3);
|
||||||
|
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
DrawRoomGraphics();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Object Renderer")) {
|
||||||
|
DrawObjectRenderer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawObjectRenderer() {
|
||||||
|
if (BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
ImGui::GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Canvas");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
|
||||||
|
|
||||||
|
int selected_object = 0;
|
||||||
|
int i = 0;
|
||||||
|
for (const auto object_name : zelda3::dungeon::Type1RoomObjectNames) {
|
||||||
|
if (ImGui::Selectable(object_name.data(), selected_object == i)) {
|
||||||
|
selected_object = i;
|
||||||
|
current_object_ = i;
|
||||||
|
object_renderer_.LoadObject(i,
|
||||||
|
rooms_[current_room_id_].mutable_blocks());
|
||||||
|
rom()->RenderBitmap(object_renderer_.bitmap());
|
||||||
|
object_loaded_ = true;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
// Right side of the table - Canvas
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), true);
|
||||||
|
|
||||||
|
object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||||
|
object_canvas_.DrawContextMenu();
|
||||||
|
object_canvas_.DrawTileSelector(32);
|
||||||
|
if (object_loaded_) {
|
||||||
|
object_canvas_.DrawBitmap(*object_renderer_.bitmap(), 0, 0);
|
||||||
|
}
|
||||||
|
object_canvas_.DrawGrid(32.0f);
|
||||||
|
object_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
EndChild();
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object_loaded_) {
|
||||||
|
ImGui::Begin("Memory Viewer", &object_loaded_, 0);
|
||||||
|
static MemoryEditor mem_edit;
|
||||||
|
mem_edit.DrawContents((void*)object_renderer_.mutable_memory(),
|
||||||
|
object_renderer_.mutable_memory()->size());
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void DungeonEditor::CalculateUsageStats() {
|
||||||
|
for (const auto& room : rooms_) {
|
||||||
|
if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) {
|
||||||
|
blockset_usage_[room.blockset] = 1;
|
||||||
|
} else {
|
||||||
|
blockset_usage_[room.blockset] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spriteset_usage_.find(room.spriteset) == spriteset_usage_.end()) {
|
||||||
|
spriteset_usage_[room.spriteset] = 1;
|
||||||
|
} else {
|
||||||
|
spriteset_usage_[room.spriteset] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (palette_usage_.find(room.palette) == palette_usage_.end()) {
|
||||||
|
palette_usage_[room.palette] = 1;
|
||||||
|
} else {
|
||||||
|
palette_usage_[room.palette] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::RenderSetUsage(
|
||||||
|
const absl::flat_hash_map<uint16_t, int>& usage_map, uint16_t& selected_set,
|
||||||
|
int spriteset_offset) {
|
||||||
|
// Sort the usage map by set number
|
||||||
|
std::vector<std::pair<uint16_t, int>> sorted_usage(usage_map.begin(),
|
||||||
|
usage_map.end());
|
||||||
|
std::sort(sorted_usage.begin(), sorted_usage.end(),
|
||||||
|
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||||
|
|
||||||
|
for (const auto& [set, count] : sorted_usage) {
|
||||||
|
std::string display_str;
|
||||||
|
if (spriteset_offset != 0x00) {
|
||||||
|
display_str = absl::StrFormat("%#02x, %#02x: %d", set,
|
||||||
|
(set + spriteset_offset), count);
|
||||||
|
} else {
|
||||||
|
display_str =
|
||||||
|
absl::StrFormat("%#02x: %d", (set + spriteset_offset), count);
|
||||||
|
}
|
||||||
|
if (ImGui::Selectable(display_str.c_str(), selected_set == set)) {
|
||||||
|
selected_set = set; // Update the selected set when clicked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Calculate the unused sets in a usage map
|
||||||
|
// Range for blocksets 0-0x24
|
||||||
|
// Range for spritesets 0-0x8F
|
||||||
|
// Range for palettes 0-0x47
|
||||||
|
template <typename T>
|
||||||
|
void RenderUnusedSets(const absl::flat_hash_map<T, int>& usage_map, int max_set,
|
||||||
|
int spriteset_offset = 0x00) {
|
||||||
|
std::vector<int> unused_sets;
|
||||||
|
for (int i = 0; i < max_set; i++) {
|
||||||
|
if (usage_map.find(i) == usage_map.end()) {
|
||||||
|
unused_sets.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& set : unused_sets) {
|
||||||
|
if (spriteset_offset != 0x00) {
|
||||||
|
Text("%#02x, %#02x", set, (set + spriteset_offset));
|
||||||
|
} else {
|
||||||
|
Text("%#02x", set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void DungeonEditor::DrawUsageStats() {
|
||||||
|
if (Button("Refresh")) {
|
||||||
|
selected_blockset_ = 0xFFFF;
|
||||||
|
selected_spriteset_ = 0xFFFF;
|
||||||
|
selected_palette_ = 0xFFFF;
|
||||||
|
spriteset_usage_.clear();
|
||||||
|
blockset_usage_.clear();
|
||||||
|
palette_usage_.clear();
|
||||||
|
CalculateUsageStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
if (BeginTable("DungeonUsageStatsTable", 8,
|
||||||
|
kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImGui::GetContentRegionAvail())) {
|
||||||
|
TableSetupColumn("Blockset Usage");
|
||||||
|
TableSetupColumn("Unused Blockset");
|
||||||
|
TableSetupColumn("Palette Usage");
|
||||||
|
TableSetupColumn("Unused Palette");
|
||||||
|
TableSetupColumn("Spriteset Usage");
|
||||||
|
TableSetupColumn("Unused Spriteset");
|
||||||
|
TableSetupColumn("Usage Grid");
|
||||||
|
TableSetupColumn("Group Preview");
|
||||||
|
TableHeadersRow();
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderSetUsage(blockset_usage_, selected_blockset_);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderUnusedSets(blockset_usage_, 0x25);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("PaletteUsageScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderSetUsage(palette_usage_, selected_palette_);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderUnusedSets(palette_usage_, 0x48);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
|
||||||
|
BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderSetUsage(spriteset_usage_, selected_spriteset_, 0x40);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderUnusedSets(spriteset_usage_, 0x90, 0x40);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UsageGrid", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
Text("%s", absl::StrFormat("Total size of all rooms: %d hex format: %#06x",
|
||||||
|
total_room_size_, total_room_size_)
|
||||||
|
.c_str());
|
||||||
|
DrawUsageGrid();
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (selected_blockset_ < 0x25) {
|
||||||
|
gfx_group_editor_.SetSelectedBlockset(selected_blockset_);
|
||||||
|
gfx_group_editor_.DrawBlocksetViewer(true);
|
||||||
|
} else if (selected_spriteset_ < 0x90) {
|
||||||
|
gfx_group_editor_.SetSelectedSpriteset(selected_spriteset_ + 0x40);
|
||||||
|
gfx_group_editor_.DrawSpritesetViewer(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawUsageGrid() {
|
||||||
|
int totalSquares = 296;
|
||||||
|
int squaresWide = 16;
|
||||||
|
int squaresTall = (totalSquares + squaresWide - 1) /
|
||||||
|
squaresWide; // Ceiling of totalSquares/squaresWide
|
||||||
|
|
||||||
|
for (int row = 0; row < squaresTall; ++row) {
|
||||||
|
ImGui::NewLine();
|
||||||
|
|
||||||
|
for (int col = 0; col < squaresWide; ++col) {
|
||||||
|
// Check if we have reached 295 squares
|
||||||
|
if (row * squaresWide + col >= totalSquares) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Determine if this square should be highlighted
|
||||||
|
const auto& room = rooms_[row * squaresWide + col];
|
||||||
|
|
||||||
|
// Create a button or selectable for each square
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
ImVec4 color = room_palette_[room.palette];
|
||||||
|
color.x = color.x / 255;
|
||||||
|
color.y = color.y / 255;
|
||||||
|
color.z = color.z / 255;
|
||||||
|
color.w = 1.0f;
|
||||||
|
if (rooms_[row * squaresWide + col].room_size() > 0xFFFF) {
|
||||||
|
color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
|
||||||
|
}
|
||||||
|
if (rooms_[row * squaresWide + col].room_size() == 0) {
|
||||||
|
color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
|
||||||
|
}
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, color);
|
||||||
|
// Make the button text darker
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
|
||||||
|
bool highlight = room.blockset == selected_blockset_ ||
|
||||||
|
room.spriteset == selected_spriteset_ ||
|
||||||
|
room.palette == selected_palette_;
|
||||||
|
|
||||||
|
// Set highlight color if needed
|
||||||
|
if (highlight) {
|
||||||
|
ImGui::PushStyleColor(
|
||||||
|
ImGuiCol_Button,
|
||||||
|
ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color
|
||||||
|
}
|
||||||
|
if (Button(absl::StrFormat("%#x",
|
||||||
|
rooms_[row * squaresWide + col].room_size())
|
||||||
|
.c_str(),
|
||||||
|
ImVec2(55, 30))) {
|
||||||
|
// Switch over to the room editor tab
|
||||||
|
// and add a room tab by the ID of the square
|
||||||
|
// that was clicked
|
||||||
|
}
|
||||||
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup(
|
||||||
|
absl::StrFormat("RoomContextMenu%d", row * squaresWide + col)
|
||||||
|
.c_str());
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
// Reset style if it was highlighted
|
||||||
|
if (highlight) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the square is hovered
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
// Display a tooltip with all the room properties
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
Text("Room ID: %d", row * squaresWide + col);
|
||||||
|
Text("Blockset: %#02x", room.blockset);
|
||||||
|
Text("Spriteset: %#02x", room.spriteset);
|
||||||
|
Text("Palette: %#02x", room.palette);
|
||||||
|
Text("Floor1: %#02x", room.floor1);
|
||||||
|
Text("Floor2: %#02x", room.floor2);
|
||||||
|
Text("Message ID: %#04x", room.message_id_);
|
||||||
|
Text("Size: %#016llx", room.room_size());
|
||||||
|
Text("Size Pointer: %#016llx", room.room_size_ptr());
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep squares in the same line
|
||||||
|
SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
153
src/app/editor/dungeon/dungeon_editor.h
Normal file
153
src/app/editor/dungeon/dungeon_editor.h
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/labeling.h"
|
||||||
|
#include "app/editor/graphics/gfx_group_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "zelda3/dungeon/room.h"
|
||||||
|
#include "zelda3/dungeon/room_entrance.h"
|
||||||
|
#include "zelda3/dungeon/room_object.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
constexpr ImGuiTabItemFlags kDungeonTabFlags =
|
||||||
|
ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip;
|
||||||
|
|
||||||
|
constexpr ImGuiTabBarFlags kDungeonTabBarFlags =
|
||||||
|
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||||
|
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||||
|
ImGuiTabBarFlags_TabListPopupButton;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kDungeonTableFlags =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||||
|
ImGuiTableFlags_BordersV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DungeonEditor class for editing dungeons.
|
||||||
|
*
|
||||||
|
* This class is currently a work in progress and is used for editing dungeons.
|
||||||
|
* It provides various functions for updating, cutting, copying, pasting,
|
||||||
|
* undoing, and redoing. It also includes methods for drawing the toolset, room
|
||||||
|
* selector, entrance selector, dungeon tab view, dungeon canvas, room graphics,
|
||||||
|
* tile selector, and object renderer. Additionally, it handles loading room
|
||||||
|
* entrances, calculating usage statistics, and rendering set usage.
|
||||||
|
*/
|
||||||
|
class DungeonEditor : public Editor,
|
||||||
|
public SharedRom,
|
||||||
|
public core::ExperimentFlags {
|
||||||
|
public:
|
||||||
|
DungeonEditor() { type_ = EditorType::kDungeon; }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
|
||||||
|
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
|
||||||
|
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
|
||||||
|
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
|
||||||
|
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
void add_room(int i) { active_rooms_.push_back(i); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
absl::Status Initialize();
|
||||||
|
absl::Status RefreshGraphics();
|
||||||
|
|
||||||
|
void LoadDungeonRoomSize();
|
||||||
|
|
||||||
|
absl::Status UpdateDungeonRoomView();
|
||||||
|
|
||||||
|
void DrawToolset();
|
||||||
|
void DrawRoomSelector();
|
||||||
|
void DrawEntranceSelector();
|
||||||
|
|
||||||
|
void DrawDungeonTabView();
|
||||||
|
void DrawDungeonCanvas(int room_id);
|
||||||
|
|
||||||
|
void DrawRoomGraphics();
|
||||||
|
void DrawTileSelector();
|
||||||
|
void DrawObjectRenderer();
|
||||||
|
|
||||||
|
void CalculateUsageStats();
|
||||||
|
void DrawUsageStats();
|
||||||
|
void DrawUsageGrid();
|
||||||
|
void RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
|
||||||
|
uint16_t& selected_set, int spriteset_offset = 0x00);
|
||||||
|
|
||||||
|
enum BackgroundType {
|
||||||
|
kNoBackground,
|
||||||
|
kBackground1,
|
||||||
|
kBackground2,
|
||||||
|
kBackground3,
|
||||||
|
kBackgroundAny,
|
||||||
|
};
|
||||||
|
enum PlacementType { kNoType, kSprite, kItem, kDoor, kBlock };
|
||||||
|
|
||||||
|
int background_type_ = kNoBackground;
|
||||||
|
int placement_type_ = kNoType;
|
||||||
|
int current_object_ = 0;
|
||||||
|
|
||||||
|
bool is_loaded_ = false;
|
||||||
|
bool object_loaded_ = false;
|
||||||
|
bool palette_showing_ = false;
|
||||||
|
bool refresh_graphics_ = false;
|
||||||
|
bool show_object_render_ = false;
|
||||||
|
|
||||||
|
uint16_t current_entrance_id_ = 0;
|
||||||
|
uint16_t current_room_id_ = 0;
|
||||||
|
uint64_t current_palette_id_ = 0;
|
||||||
|
uint64_t current_palette_group_id_ = 0;
|
||||||
|
|
||||||
|
ImVector<int> active_rooms_;
|
||||||
|
|
||||||
|
GfxGroupEditor gfx_group_editor_;
|
||||||
|
PaletteEditor palette_editor_;
|
||||||
|
gfx::SnesPalette current_palette_;
|
||||||
|
gfx::SnesPalette full_palette_;
|
||||||
|
gfx::PaletteGroup current_palette_group_;
|
||||||
|
|
||||||
|
gui::Canvas canvas_;
|
||||||
|
gui::Canvas room_gfx_canvas_;
|
||||||
|
gui::Canvas object_canvas_;
|
||||||
|
|
||||||
|
gfx::Bitmap room_gfx_bmp_;
|
||||||
|
gfx::BitmapManager graphics_bin_;
|
||||||
|
|
||||||
|
std::vector<gfx::Bitmap*> room_gfx_sheets_;
|
||||||
|
std::vector<zelda3::dungeon::Room> rooms_;
|
||||||
|
std::vector<zelda3::dungeon::RoomEntrance> entrances_;
|
||||||
|
std::vector<gfx::BitmapManager> room_graphics_;
|
||||||
|
zelda3::dungeon::DungeonObjectRenderer object_renderer_;
|
||||||
|
|
||||||
|
absl::flat_hash_map<uint16_t, int> spriteset_usage_;
|
||||||
|
absl::flat_hash_map<uint16_t, int> blockset_usage_;
|
||||||
|
absl::flat_hash_map<uint16_t, int> palette_usage_;
|
||||||
|
|
||||||
|
std::vector<int64_t> room_size_pointers_;
|
||||||
|
|
||||||
|
uint16_t selected_blockset_ = 0xFFFF; // 0xFFFF indicates no selection
|
||||||
|
uint16_t selected_spriteset_ = 0xFFFF;
|
||||||
|
uint16_t selected_palette_ = 0xFFFF;
|
||||||
|
|
||||||
|
uint64_t total_room_size_ = 0;
|
||||||
|
|
||||||
|
std::unordered_map<int, int> room_size_addresses_;
|
||||||
|
std::unordered_map<int, ImVec4> room_palette_;
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
311
src/app/editor/graphics/gfx_group_editor.cc
Normal file
311
src/app/editor/graphics/gfx_group_editor.cc
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
#include "gfx_group_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginGroup;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::ColorButton;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndGroup;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::GetContentRegionAvail;
|
||||||
|
using ImGui::GetStyle;
|
||||||
|
using ImGui::IsItemClicked;
|
||||||
|
using ImGui::PopID;
|
||||||
|
using ImGui::PushID;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Selectable;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::SetNextItemWidth;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
using gfx::kPaletteGroupNames;
|
||||||
|
using gfx::PaletteCategory;
|
||||||
|
|
||||||
|
absl::Status GfxGroupEditor::Update() {
|
||||||
|
if (BeginTabBar("GfxGroupEditor")) {
|
||||||
|
if (BeginTabItem("Main")) {
|
||||||
|
gui::InputHexByte("Selected Blockset", &selected_blockset_,
|
||||||
|
(uint8_t)0x24);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "blockset", "0x" + std::to_string(selected_blockset_),
|
||||||
|
"Blockset " + std::to_string(selected_blockset_));
|
||||||
|
DrawBlocksetViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Rooms")) {
|
||||||
|
gui::InputHexByte("Selected Blockset", &selected_roomset_, (uint8_t)81);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "roomset", "0x" + std::to_string(selected_roomset_),
|
||||||
|
"Roomset " + std::to_string(selected_roomset_));
|
||||||
|
DrawRoomsetViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Sprites")) {
|
||||||
|
gui::InputHexByte("Selected Spriteset", &selected_spriteset_,
|
||||||
|
(uint8_t)143);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "spriteset", "0x" + std::to_string(selected_spriteset_),
|
||||||
|
"Spriteset " + std::to_string(selected_spriteset_));
|
||||||
|
Text("Values");
|
||||||
|
DrawSpritesetViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Palettes")) {
|
||||||
|
DrawPaletteViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
|
||||||
|
if (BeginTable("##BlocksetTable", sheet_only ? 1 : 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
SetNextItemWidth(100.f);
|
||||||
|
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
|
||||||
|
&rom()->main_blockset_ids[selected_blockset_][i]);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
|
||||||
|
auto sheet = rom()->bitmap_manager()[sheet_id];
|
||||||
|
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||||
|
0x20, true, false, 22);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawRoomsetViewer() {
|
||||||
|
Text("Values - Overwrites 4 of main blockset");
|
||||||
|
if (BeginTable("##Roomstable", 3,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("List", ImGuiTableColumnFlags_WidthFixed, 100);
|
||||||
|
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginChild("##RoomsetList");
|
||||||
|
for (int i = 0; i < 0x51; i++) {
|
||||||
|
BeginGroup();
|
||||||
|
std::string roomset_label = absl::StrFormat("0x%02X", i);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "roomset", roomset_label, "Roomset " + roomset_label);
|
||||||
|
if (IsItemClicked()) {
|
||||||
|
selected_roomset_ = i;
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SetNextItemWidth(100.f);
|
||||||
|
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
|
||||||
|
&rom()->room_blockset_ids[selected_roomset_][i]);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
|
||||||
|
auto sheet = rom()->bitmap_manager()[sheet_id];
|
||||||
|
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||||
|
0x20, true, false, 23);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
|
||||||
|
if (BeginTable("##SpritesTable", sheet_only ? 1 : 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
}
|
||||||
|
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SetNextItemWidth(100.f);
|
||||||
|
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
|
||||||
|
&rom()->spriteset_ids[selected_spriteset_][i]);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
|
||||||
|
auto sheet = rom()->bitmap_manager()[115 + sheet_id];
|
||||||
|
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||||
|
0x20, true, false, 24);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
|
||||||
|
if (palette.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int n = 0; n < palette.size(); n++) {
|
||||||
|
PushID(n);
|
||||||
|
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
|
||||||
|
// Small icon of the color in the palette
|
||||||
|
if (gui::SnesColorButton(absl::StrCat("Palette", n), palette[n],
|
||||||
|
ImGuiColorEditFlags_NoAlpha |
|
||||||
|
ImGuiColorEditFlags_NoPicker |
|
||||||
|
ImGuiColorEditFlags_NoTooltip)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawPaletteViewer() {
|
||||||
|
gui::InputHexByte("Selected Paletteset", &selected_paletteset_);
|
||||||
|
if (selected_paletteset_ >= 71) {
|
||||||
|
selected_paletteset_ = 71;
|
||||||
|
}
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "paletteset", "0x" + std::to_string(selected_paletteset_),
|
||||||
|
"Paletteset " + std::to_string(selected_paletteset_));
|
||||||
|
|
||||||
|
uint8_t &dungeon_main_palette_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][0];
|
||||||
|
uint8_t &dungeon_spr_pal_1_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][1];
|
||||||
|
uint8_t &dungeon_spr_pal_2_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][2];
|
||||||
|
uint8_t &dungeon_spr_pal_3_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][3];
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Main", &dungeon_main_palette_val);
|
||||||
|
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kDungeons].data(),
|
||||||
|
std::to_string(dungeon_main_palette_val), "Unnamed dungeon palette");
|
||||||
|
auto &palette = *rom()->mutable_palette_group()->dungeon_main.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][0]);
|
||||||
|
DrawPaletteFromPaletteGroup(palette);
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Spr Pal 1", &dungeon_spr_pal_1_val);
|
||||||
|
auto &spr_aux_pal1 =
|
||||||
|
*rom()->mutable_palette_group()->sprites_aux1.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][1]);
|
||||||
|
DrawPaletteFromPaletteGroup(spr_aux_pal1);
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kSpritesAux1].data(),
|
||||||
|
std::to_string(dungeon_spr_pal_1_val), "Dungeon Spr Pal 1");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Spr Pal 2", &dungeon_spr_pal_2_val);
|
||||||
|
auto &spr_aux_pal2 =
|
||||||
|
*rom()->mutable_palette_group()->sprites_aux2.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][2]);
|
||||||
|
DrawPaletteFromPaletteGroup(spr_aux_pal2);
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kSpritesAux2].data(),
|
||||||
|
std::to_string(dungeon_spr_pal_2_val), "Dungeon Spr Pal 2");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Spr Pal 3", &dungeon_spr_pal_3_val);
|
||||||
|
auto &spr_aux_pal3 =
|
||||||
|
*rom()->mutable_palette_group()->sprites_aux3.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][3]);
|
||||||
|
DrawPaletteFromPaletteGroup(spr_aux_pal3);
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kSpritesAux3].data(),
|
||||||
|
std::to_string(dungeon_spr_pal_3_val), "Dungeon Spr Pal 3");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
73
src/app/editor/graphics/gfx_group_editor.h
Normal file
73
src/app/editor/graphics/gfx_group_editor.h
Normal 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
|
||||||
844
src/app/editor/graphics/graphics_editor.cc
Normal file
844
src/app/editor/graphics/graphics_editor.cc
Normal 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_, ¤t_bitmap,
|
||||||
|
current_color_);
|
||||||
|
rom()->UpdateBitmap(¤t_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", ¤t_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", ¤t_palette_index_);
|
||||||
|
ImGui::Combo("Palette", ¤t_palette_, kPaletteGroupAddressesKeys,
|
||||||
|
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col_file_palette_.size() != 0) {
|
||||||
|
gui::SelectablePalettePipeline(current_palette_index_, refresh_graphics_,
|
||||||
|
col_file_palette_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawObjImport() {
|
||||||
|
gui::TextWithSeparators("OBJ Import");
|
||||||
|
|
||||||
|
InputText("##ObjFile", obj_file_path_, 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", ¤t_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
|
||||||
205
src/app/editor/graphics/graphics_editor.h
Normal file
205
src/app/editor/graphics/graphics_editor.h
Normal 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
|
||||||
469
src/app/editor/graphics/palette_editor.cc
Normal file
469
src/app/editor/graphics/palette_editor.cc
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
#include "palette_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::AcceptDragDropPayload;
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginDragDropTarget;
|
||||||
|
using ImGui::BeginGroup;
|
||||||
|
using ImGui::BeginPopup;
|
||||||
|
using ImGui::BeginPopupContextItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::ColorButton;
|
||||||
|
using ImGui::ColorPicker4;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndDragDropTarget;
|
||||||
|
using ImGui::EndGroup;
|
||||||
|
using ImGui::EndPopup;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::GetContentRegionAvail;
|
||||||
|
using ImGui::GetStyle;
|
||||||
|
using ImGui::OpenPopup;
|
||||||
|
using ImGui::PopID;
|
||||||
|
using ImGui::PushID;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Selectable;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::SetClipboardText;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetColumnIndex;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
using ImGui::TreeNode;
|
||||||
|
using ImGui::TreePop;
|
||||||
|
|
||||||
|
using namespace gfx;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kPaletteTableFlags =
|
||||||
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
|
||||||
|
|
||||||
|
constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
|
||||||
|
|
||||||
|
constexpr ImGuiColorEditFlags kPalButtonFlags2 = ImGuiColorEditFlags_NoAlpha |
|
||||||
|
ImGuiColorEditFlags_NoPicker |
|
||||||
|
ImGuiColorEditFlags_NoTooltip;
|
||||||
|
|
||||||
|
constexpr ImGuiColorEditFlags kColorPopupFlags =
|
||||||
|
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
#ifdef IMGUI_USE_STB_SPRINTF
|
||||||
|
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
|
||||||
|
#else
|
||||||
|
int w = vsnprintf(buf, buf_size, fmt, args);
|
||||||
|
#endif
|
||||||
|
va_end(args);
|
||||||
|
if (buf == nullptr) return w;
|
||||||
|
if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
|
||||||
|
buf[w] = 0;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float color_saturate(float f) {
|
||||||
|
return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define F32_TO_INT8_SAT(_VAL) \
|
||||||
|
((int)(color_saturate(_VAL) * 255.0f + \
|
||||||
|
0.5f)) // Saturated, always output 0..255
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::Update() {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
// Initialize the labels
|
||||||
|
for (int i = 0; i < kNumPalettes; i++) {
|
||||||
|
rom()->resource_label()->CreateOrGetLabel(
|
||||||
|
"Palette Group Name", std::to_string(i),
|
||||||
|
std::string(kPaletteGroupNames[i]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return absl::NotFoundError("ROM not open, no palettes to display");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTable("paletteEditorTable", 2, kPaletteTableFlags, ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Palette Groups", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Palette Sets and Metadata",
|
||||||
|
ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
TableNextColumn();
|
||||||
|
DrawModifiedColors();
|
||||||
|
|
||||||
|
DrawCustomPalette();
|
||||||
|
Separator();
|
||||||
|
gui::SnesColorEdit4("Current Color Picker", ¤t_color_,
|
||||||
|
ImGuiColorEditFlags_NoAlpha);
|
||||||
|
Separator();
|
||||||
|
DisplayCategoryTable();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gfx_group_editor_.DrawPaletteViewer();
|
||||||
|
Separator();
|
||||||
|
static bool in_use = false;
|
||||||
|
ImGui::Checkbox("Palette in use? ", &in_use);
|
||||||
|
Separator();
|
||||||
|
static std::string palette_notes = "Notes about the palette";
|
||||||
|
ImGui::InputTextMultiline("Notes", palette_notes.data(), 1024,
|
||||||
|
ImVec2(-1, ImGui::GetTextLineHeight() * 4),
|
||||||
|
ImGuiInputTextFlags_AllowTabInput);
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_AND_RETURN_STATUS(status_)
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DrawCustomPalette() {
|
||||||
|
if (BeginChild("ColorPalette", ImVec2(0, 40), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
|
for (int i = 0; i < custom_palette_.size(); i++) {
|
||||||
|
PushID(i);
|
||||||
|
SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
gui::SnesColorEdit4("##customPalette", &custom_palette_[i],
|
||||||
|
ImGuiColorEditFlags_NoInputs);
|
||||||
|
// Accept a drag drop target which adds a color to the custom_palette_
|
||||||
|
if (BeginDragDropTarget()) {
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
|
||||||
|
ImVec4 color = ImVec4(0, 0, 0, 1.0f);
|
||||||
|
memcpy((float*)&color, payload->Data, sizeof(float));
|
||||||
|
custom_palette_.push_back(SnesColor(color));
|
||||||
|
}
|
||||||
|
EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (ImGui::Button("Add Color")) {
|
||||||
|
custom_palette_.push_back(SnesColor(0x7FFF));
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (ImGui::Button("Export to Clipboard")) {
|
||||||
|
std::string clipboard;
|
||||||
|
for (const auto& color : custom_palette_) {
|
||||||
|
clipboard += absl::StrFormat("$%04X,", color.snes());
|
||||||
|
}
|
||||||
|
SetClipboardText(clipboard.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DisplayCategoryTable() {
|
||||||
|
if (BeginTable("Category Table", 8,
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_SizingStretchSame |
|
||||||
|
ImGuiTableFlags_Hideable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Weapons and Gear");
|
||||||
|
TableSetupColumn("Overworld and Area Colors");
|
||||||
|
TableSetupColumn("Global Sprites");
|
||||||
|
TableSetupColumn("Sprites Aux1");
|
||||||
|
TableSetupColumn("Sprites Aux2");
|
||||||
|
TableSetupColumn("Sprites Aux3");
|
||||||
|
TableSetupColumn("Maps and Items");
|
||||||
|
TableSetupColumn("Dungeons");
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableSetColumnIndex(0);
|
||||||
|
if (TreeNode("Sword")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSword);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Shield")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kShield);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Clothes")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kClothes, true);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableSetColumnIndex(1);
|
||||||
|
gui::BeginChildWithScrollbar("##WorldPaletteScrollRegion");
|
||||||
|
if (TreeNode("World Colors")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kWorldColors);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Area Colors")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kAreaColors);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableSetColumnIndex(2);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kGlobalSprites, true);
|
||||||
|
|
||||||
|
TableSetColumnIndex(3);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux1);
|
||||||
|
|
||||||
|
TableSetColumnIndex(4);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux2);
|
||||||
|
|
||||||
|
TableSetColumnIndex(5);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux3);
|
||||||
|
|
||||||
|
TableSetColumnIndex(6);
|
||||||
|
gui::BeginChildWithScrollbar("##MapPaletteScrollRegion");
|
||||||
|
if (TreeNode("World Map")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kWorldMap, true);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Dungeon Map")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kDungeonMap);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Triforce")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kTriforce);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Crystal")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kCrystal);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableSetColumnIndex(7);
|
||||||
|
gui::BeginChildWithScrollbar("##DungeonPaletteScrollRegion");
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kDungeons, true);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
|
||||||
|
if (!rom()->is_loaded()) {
|
||||||
|
return absl::NotFoundError("ROM not open, no palettes to display");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto palette_group_name = kPaletteGroupNames[category];
|
||||||
|
gfx::PaletteGroup* palette_group =
|
||||||
|
rom()->mutable_palette_group()->get_group(palette_group_name.data());
|
||||||
|
const auto size = palette_group->size();
|
||||||
|
|
||||||
|
static bool edit_color = false;
|
||||||
|
for (int j = 0; j < size; j++) {
|
||||||
|
gfx::SnesPalette* palette = palette_group->mutable_palette(j);
|
||||||
|
auto pal_size = palette->size();
|
||||||
|
|
||||||
|
for (int n = 0; n < pal_size; n++) {
|
||||||
|
PushID(n);
|
||||||
|
if (!right_side) {
|
||||||
|
if ((n % 7) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
} else {
|
||||||
|
if ((n % 15) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto popup_id =
|
||||||
|
absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
|
||||||
|
|
||||||
|
// Small icon of the color in the palette
|
||||||
|
if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
|
||||||
|
kPalNoAlpha)) {
|
||||||
|
ASSIGN_OR_RETURN(current_color_, palette->GetColor(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginPopupContextItem(popup_id.c_str())) {
|
||||||
|
RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
|
||||||
|
}
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, palette_group_name.data(), /*key=*/std::to_string(j),
|
||||||
|
"Unnamed Palette");
|
||||||
|
if (right_side) Separator();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DrawModifiedColors() {
|
||||||
|
if (BeginChild("ModifiedColors", ImVec2(0, 100), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
|
for (int i = 0; i < history_.size(); i++) {
|
||||||
|
PushID(i);
|
||||||
|
gui::SnesColorEdit4("Original ", &history_.GetOriginalColor(i),
|
||||||
|
ImGuiColorEditFlags_NoInputs);
|
||||||
|
SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
gui::SnesColorEdit4("Modified ", &history_.GetModifiedColor(i),
|
||||||
|
ImGuiColorEditFlags_NoInputs);
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
|
||||||
|
int j, int n) {
|
||||||
|
auto col = gfx::ToFloatArray(palette[n]);
|
||||||
|
auto original_color = palette[n];
|
||||||
|
if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
|
||||||
|
history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
|
||||||
|
/*palette_index=*/j, /*color_index=*/n,
|
||||||
|
original_color, palette[n]);
|
||||||
|
palette[n].set_modified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
|
||||||
|
if (BeginPopup("Copy")) {
|
||||||
|
int cr = F32_TO_INT8_SAT(col[0]);
|
||||||
|
int cg = F32_TO_INT8_SAT(col[1]);
|
||||||
|
int cb = F32_TO_INT8_SAT(col[2]);
|
||||||
|
char buf[64];
|
||||||
|
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
|
||||||
|
col[1], col[2]);
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
// SNES Format
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
|
||||||
|
ConvertRGBtoSNES(ImVec4(col[0], col[1], col[2], 1.0f)));
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndPopup();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
|
||||||
|
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||||
|
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
|
||||||
|
ImGuiColorEditFlags_NoDragDrop |
|
||||||
|
ImGuiColorEditFlags_NoOptions;
|
||||||
|
|
||||||
|
// Generate a default palette. The palette will persist and can be edited.
|
||||||
|
static bool init = false;
|
||||||
|
if (loaded && !init) {
|
||||||
|
status_ = InitializeSavedPalette(palette);
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImVec4 backup_color;
|
||||||
|
bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
|
||||||
|
SameLine(0, GetStyle().ItemInnerSpacing.x);
|
||||||
|
open_popup |= Button("Palette");
|
||||||
|
if (open_popup) {
|
||||||
|
OpenPopup("mypicker");
|
||||||
|
backup_color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginPopup("mypicker")) {
|
||||||
|
TEXT_WITH_SEPARATOR("Current Overworld Palette");
|
||||||
|
ColorPicker4("##picker", (float*)&color,
|
||||||
|
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
||||||
|
ImGuiColorEditFlags_NoSmallPreview);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
BeginGroup(); // Lock X position
|
||||||
|
Text("Current ==>");
|
||||||
|
SameLine();
|
||||||
|
Text("Previous");
|
||||||
|
|
||||||
|
if (Button("Update Map Palette")) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorButton(
|
||||||
|
"##current", color,
|
||||||
|
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||||
|
ImVec2(60, 40));
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (ColorButton(
|
||||||
|
"##previous", backup_color,
|
||||||
|
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||||
|
ImVec2(60, 40)))
|
||||||
|
color = backup_color;
|
||||||
|
|
||||||
|
// List of Colors in Overworld Palette
|
||||||
|
Separator();
|
||||||
|
Text("Palette");
|
||||||
|
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
|
||||||
|
PushID(n);
|
||||||
|
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
|
||||||
|
if (ColorButton("##palette", saved_palette_[n], kPalButtonFlags2,
|
||||||
|
ImVec2(20, 20)))
|
||||||
|
color = ImVec4(saved_palette_[n].x, saved_palette_[n].y,
|
||||||
|
saved_palette_[n].z, color.w); // Preserve alpha!
|
||||||
|
|
||||||
|
if (BeginDragDropTarget()) {
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||||
|
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||||
|
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
|
||||||
|
EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette,
|
||||||
|
int index) {
|
||||||
|
if (index >= palette.size()) {
|
||||||
|
return absl::InvalidArgumentError("Index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current color
|
||||||
|
ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
|
||||||
|
auto currentColor = color.rgb();
|
||||||
|
if (ColorPicker4("Color Picker", (float*)&palette[index])) {
|
||||||
|
// The color was modified, update it in the palette
|
||||||
|
palette(index, currentColor);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::ResetColorToOriginal(
|
||||||
|
gfx::SnesPalette& palette, int index,
|
||||||
|
const gfx::SnesPalette& originalPalette) {
|
||||||
|
if (index >= palette.size() || index >= originalPalette.size()) {
|
||||||
|
return absl::InvalidArgumentError("Index out of bounds");
|
||||||
|
}
|
||||||
|
ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
|
||||||
|
auto originalColor = color.rgb();
|
||||||
|
palette(index, originalColor);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
140
src/app/editor/graphics/palette_editor.h
Normal file
140
src/app/editor/graphics/palette_editor.h
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/graphics/gfx_group_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
namespace palette_internal {
|
||||||
|
struct PaletteChange {
|
||||||
|
std::string group_name;
|
||||||
|
size_t palette_index;
|
||||||
|
size_t color_index;
|
||||||
|
gfx::SnesColor original_color;
|
||||||
|
gfx::SnesColor new_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PaletteEditorHistory {
|
||||||
|
public:
|
||||||
|
// Record a change in the palette editor
|
||||||
|
void RecordChange(const std::string& groupName, size_t paletteIndex,
|
||||||
|
size_t colorIndex, const gfx::SnesColor& originalColor,
|
||||||
|
const gfx::SnesColor& newColor) {
|
||||||
|
// Check size and remove the oldest if necessary
|
||||||
|
if (recentChanges.size() >= maxHistorySize) {
|
||||||
|
recentChanges.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the new change
|
||||||
|
recentChanges.push_back(
|
||||||
|
{groupName, paletteIndex, colorIndex, originalColor, newColor});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get recent changes for display in the palette editor
|
||||||
|
const std::deque<PaletteChange>& GetRecentChanges() const {
|
||||||
|
return recentChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the original color
|
||||||
|
gfx::SnesColor RestoreOriginalColor(const std::string& groupName,
|
||||||
|
size_t paletteIndex,
|
||||||
|
size_t colorIndex) const {
|
||||||
|
for (const auto& change : recentChanges) {
|
||||||
|
if (change.group_name == groupName &&
|
||||||
|
change.palette_index == paletteIndex &&
|
||||||
|
change.color_index == colorIndex) {
|
||||||
|
return change.original_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle error or return default (this is just an example,
|
||||||
|
// handle as appropriate for your application)
|
||||||
|
return gfx::SnesColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size() const { return recentChanges.size(); }
|
||||||
|
|
||||||
|
gfx::SnesColor& GetModifiedColor(size_t index) {
|
||||||
|
return recentChanges[index].new_color;
|
||||||
|
}
|
||||||
|
gfx::SnesColor& GetOriginalColor(size_t index) {
|
||||||
|
return recentChanges[index].original_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<PaletteChange> recentChanges;
|
||||||
|
static const size_t maxHistorySize = 50; // or any other number you deem fit
|
||||||
|
};
|
||||||
|
} // namespace palette_internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PaletteEditor
|
||||||
|
* @brief Allows the user to view and edit in game palettes.
|
||||||
|
*/
|
||||||
|
class PaletteEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
PaletteEditor() {
|
||||||
|
type_ = EditorType::kPalette;
|
||||||
|
custom_palette_.push_back(gfx::SnesColor(0x7FFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
absl::Status Cut() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Copy() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Paste() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Undo() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Redo() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Find() override { return absl::OkStatus(); }
|
||||||
|
|
||||||
|
void DisplayCategoryTable();
|
||||||
|
|
||||||
|
absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index);
|
||||||
|
absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index,
|
||||||
|
const gfx::SnesPalette& originalPalette);
|
||||||
|
void DisplayPalette(gfx::SnesPalette& palette, bool loaded);
|
||||||
|
absl::Status DrawPaletteGroup(int category, bool right_side = false);
|
||||||
|
|
||||||
|
void DrawCustomPalette();
|
||||||
|
|
||||||
|
void DrawModifiedColors();
|
||||||
|
|
||||||
|
private:
|
||||||
|
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
|
||||||
|
absl::Status InitializeSavedPalette(const gfx::SnesPalette& palette) {
|
||||||
|
for (int n = 0; n < palette.size(); n++) {
|
||||||
|
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
|
||||||
|
saved_palette_[n].x = color.rgb().x / 255;
|
||||||
|
saved_palette_[n].y = color.rgb().y / 255;
|
||||||
|
saved_palette_[n].z = color.rgb().z / 255;
|
||||||
|
saved_palette_[n].w = 255; // Alpha
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
gfx::SnesColor current_color_;
|
||||||
|
|
||||||
|
GfxGroupEditor gfx_group_editor_;
|
||||||
|
|
||||||
|
std::vector<gfx::SnesColor> custom_palette_;
|
||||||
|
|
||||||
|
ImVec4 saved_palette_[256] = {};
|
||||||
|
|
||||||
|
palette_internal::PaletteEditorHistory history_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
437
src/app/editor/graphics/screen_editor.cc
Normal file
437
src/app/editor/graphics/screen_editor.cc
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
#include "screen_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::Update() {
|
||||||
|
TAB_BAR("##TabBar")
|
||||||
|
TAB_ITEM("Dungeon Maps")
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
DrawDungeonMapsEditor();
|
||||||
|
}
|
||||||
|
END_TAB_ITEM()
|
||||||
|
DrawInventoryMenuEditor();
|
||||||
|
DrawOverworldMapEditor();
|
||||||
|
DrawTitleScreenEditor();
|
||||||
|
DrawNamingScreenEditor();
|
||||||
|
END_TAB_BAR()
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawInventoryMenuEditor() {
|
||||||
|
TAB_ITEM("Inventory Menu")
|
||||||
|
|
||||||
|
static bool create = false;
|
||||||
|
if (!create && rom()->is_loaded()) {
|
||||||
|
status_ = inventory_.Create();
|
||||||
|
palette_ = inventory_.Palette();
|
||||||
|
create = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawInventoryToolset();
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
|
||||||
|
ImGui::TableSetupColumn("Canvas");
|
||||||
|
ImGui::TableSetupColumn("Tiles");
|
||||||
|
ImGui::TableSetupColumn("Palette");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
screen_canvas_.DrawBackground();
|
||||||
|
screen_canvas_.DrawContextMenu();
|
||||||
|
screen_canvas_.DrawBitmap(inventory_.Bitmap(), 2, create);
|
||||||
|
screen_canvas_.DrawGrid(32.0f);
|
||||||
|
screen_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
|
||||||
|
tilesheet_canvas_.DrawContextMenu();
|
||||||
|
tilesheet_canvas_.DrawBitmap(inventory_.Tilesheet(), 2, create);
|
||||||
|
tilesheet_canvas_.DrawGrid(16.0f);
|
||||||
|
tilesheet_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
status_ = gui::DisplayPalette(palette_, create);
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawInventoryToolset() {
|
||||||
|
if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("#drawTool");
|
||||||
|
ImGui::TableSetupColumn("#sep1");
|
||||||
|
ImGui::TableSetupColumn("#zoomOut");
|
||||||
|
ImGui::TableSetupColumn("#zoomIN");
|
||||||
|
ImGui::TableSetupColumn("#sep2");
|
||||||
|
ImGui::TableSetupColumn("#bg2Tool");
|
||||||
|
ImGui::TableSetupColumn("#bg3Tool");
|
||||||
|
ImGui::TableSetupColumn("#itemTool");
|
||||||
|
|
||||||
|
BUTTON_COLUMN(ICON_MD_UNDO)
|
||||||
|
BUTTON_COLUMN(ICON_MD_REDO)
|
||||||
|
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_ZOOM_OUT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_ZOOM_IN)
|
||||||
|
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_DRAW)
|
||||||
|
BUTTON_COLUMN(ICON_MD_BUILD)
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::LoadDungeonMaps() {
|
||||||
|
std::vector<std::array<uint8_t, 25>> current_floor_rooms_d;
|
||||||
|
std::vector<std::array<uint8_t, 25>> current_floor_gfx_d;
|
||||||
|
int total_floors_d;
|
||||||
|
uint8_t nbr_floor_d;
|
||||||
|
uint8_t nbr_basement_d;
|
||||||
|
|
||||||
|
for (int d = 0; d < 14; d++) {
|
||||||
|
current_floor_rooms_d.clear();
|
||||||
|
current_floor_gfx_d.clear();
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
int ptr,
|
||||||
|
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
int ptrGFX,
|
||||||
|
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
|
||||||
|
ptr |= 0x0A0000; // Add bank to the short ptr
|
||||||
|
ptrGFX |= 0x0A0000; // Add bank to the short ptr
|
||||||
|
int pcPtr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
|
||||||
|
int pcPtrGFX =
|
||||||
|
core::SnesToPc(ptrGFX); // Contains data for the next 25 rooms
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
ushort bossRoomD,
|
||||||
|
rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2)));
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
nbr_basement_d,
|
||||||
|
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
|
||||||
|
nbr_basement_d &= 0x0F;
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
nbr_floor_d,
|
||||||
|
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
|
||||||
|
nbr_floor_d &= 0xF0;
|
||||||
|
nbr_floor_d = nbr_floor_d >> 4;
|
||||||
|
|
||||||
|
total_floors_d = nbr_basement_d + nbr_floor_d;
|
||||||
|
|
||||||
|
dungeon_map_labels_.emplace_back();
|
||||||
|
|
||||||
|
// for each floor in the dungeon
|
||||||
|
for (int i = 0; i < total_floors_d; i++) {
|
||||||
|
dungeon_map_labels_[d].emplace_back();
|
||||||
|
|
||||||
|
std::array<uint8_t, 25> rdata;
|
||||||
|
std::array<uint8_t, 25> gdata;
|
||||||
|
|
||||||
|
// for each room on the floor
|
||||||
|
for (int j = 0; j < 25; j++) {
|
||||||
|
// rdata[j] = 0x0F;
|
||||||
|
gdata[j] = 0xFF;
|
||||||
|
rdata[j] = rom()->data()[pcPtr + j + (i * 25)]; // Set the rooms
|
||||||
|
|
||||||
|
if (rdata[j] == 0x0F) {
|
||||||
|
gdata[j] = 0xFF;
|
||||||
|
} else {
|
||||||
|
gdata[j] = rom()->data()[pcPtrGFX++];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label = core::UppercaseHexByte(rdata[j]);
|
||||||
|
dungeon_map_labels_[d][i][j] = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_floor_gfx_d.push_back(gdata); // Add new floor gfx data
|
||||||
|
current_floor_rooms_d.push_back(rdata); // Add new floor data
|
||||||
|
}
|
||||||
|
|
||||||
|
dungeon_maps_.emplace_back(bossRoomD, nbr_floor_d, nbr_basement_d,
|
||||||
|
current_floor_rooms_d, current_floor_gfx_d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::SaveDungeonMaps() {
|
||||||
|
for (int d = 0; d < 14; d++) {
|
||||||
|
int ptr = zelda3::screen::kDungeonMapRoomsPtr + (d * 2);
|
||||||
|
int ptrGFX = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
|
||||||
|
int pcPtr = core::SnesToPc(ptr);
|
||||||
|
int pcPtrGFX = core::SnesToPc(ptrGFX);
|
||||||
|
|
||||||
|
const int nbr_floors = dungeon_maps_[d].nbr_of_floor;
|
||||||
|
const int nbr_basements = dungeon_maps_[d].nbr_of_basement;
|
||||||
|
for (int i = 0; i < nbr_floors + nbr_basements; i++) {
|
||||||
|
for (int j = 0; j < 25; j++) {
|
||||||
|
// rom()->data()[pcPtr + j + (i * 25)] =
|
||||||
|
// dungeon_maps_[d].floor_rooms[i][j];
|
||||||
|
// rom()->data()[pcPtrGFX++] = dungeon_maps_[d].floor_gfx[i][j];
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(rom()->WriteByte(ptr + j + (i * 25),
|
||||||
|
dungeon_maps_[d].floor_rooms[i][j]));
|
||||||
|
RETURN_IF_ERROR(rom()->WriteByte(ptrGFX + j + (i * 25),
|
||||||
|
dungeon_maps_[d].floor_gfx[i][j]));
|
||||||
|
pcPtrGFX++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::LoadDungeonMapTile16() {
|
||||||
|
tile16_sheet_.Init(256, 192, gfx::TileType::Tile16);
|
||||||
|
|
||||||
|
for (int i = 0; i < 186; i++) {
|
||||||
|
int addr = zelda3::screen::kDungeonMapTile16;
|
||||||
|
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
|
||||||
|
addr = zelda3::screen::kDungeonMapTile16Expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto tl, rom()->ReadWord(addr + (i * 8)));
|
||||||
|
gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto tr, rom()->ReadWord(addr + 2 + (i * 8)));
|
||||||
|
gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto bl, rom()->ReadWord(addr + 4 + (i * 8)));
|
||||||
|
gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8)));
|
||||||
|
gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
|
||||||
|
|
||||||
|
tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4);
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette(
|
||||||
|
*rom()->mutable_dungeon_palette(3)));
|
||||||
|
rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
|
||||||
|
|
||||||
|
for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
|
||||||
|
if (tile16_individual_.count(i) == 0) {
|
||||||
|
auto tile = tile16_sheet_.GetTile16(i);
|
||||||
|
tile16_individual_[i] = tile;
|
||||||
|
rom()->RenderBitmap(&tile16_individual_[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawDungeonMapsTabs() {
|
||||||
|
auto current_dungeon = dungeon_maps_[selected_dungeon];
|
||||||
|
if (ImGui::BeginTabBar("##DungeonMapTabs")) {
|
||||||
|
auto nbr_floors =
|
||||||
|
current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
|
||||||
|
for (int i = 0; i < nbr_floors; i++) {
|
||||||
|
std::string tab_name = absl::StrFormat("Floor %d", i + 1);
|
||||||
|
if (i >= current_dungeon.nbr_of_floor) {
|
||||||
|
tab_name = absl::StrFormat("Basement %d",
|
||||||
|
i - current_dungeon.nbr_of_floor + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(tab_name.c_str())) {
|
||||||
|
floor_number = i;
|
||||||
|
// screen_canvas_.LoadCustomLabels(dungeon_map_labels_[selected_dungeon]);
|
||||||
|
// screen_canvas_.set_current_labels(floor_number);
|
||||||
|
screen_canvas_.DrawBackground(ImVec2(325, 325));
|
||||||
|
screen_canvas_.DrawTileSelector(64.f);
|
||||||
|
|
||||||
|
auto boss_room = current_dungeon.boss_room;
|
||||||
|
for (int j = 0; j < 25; j++) {
|
||||||
|
if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
|
||||||
|
int tile16_id = current_dungeon.floor_rooms[floor_number][j];
|
||||||
|
int tile_x = (tile16_id % 16) * 16;
|
||||||
|
int tile_y = (tile16_id / 16) * 16;
|
||||||
|
int posX = ((j % 5) * 32);
|
||||||
|
int posY = ((j / 5) * 32);
|
||||||
|
|
||||||
|
if (tile16_individual_.count(tile16_id) == 0) {
|
||||||
|
auto tile = tile16_sheet_.GetTile16(tile16_id);
|
||||||
|
std::cout << "Tile16: " << tile16_id << std::endl;
|
||||||
|
rom()->RenderBitmap(&tile);
|
||||||
|
tile16_individual_[tile16_id] = tile;
|
||||||
|
}
|
||||||
|
screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2),
|
||||||
|
(posY * 2), 4.0f);
|
||||||
|
|
||||||
|
if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
|
||||||
|
screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64,
|
||||||
|
64, core::kRedPen);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label =
|
||||||
|
dungeon_map_labels_[selected_dungeon][floor_number][j];
|
||||||
|
screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screen_canvas_.DrawGrid(64.f, 5);
|
||||||
|
screen_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
if (!screen_canvas_.points().empty()) {
|
||||||
|
int x = screen_canvas_.points().front().x / 64;
|
||||||
|
int y = screen_canvas_.points().front().y / 64;
|
||||||
|
selected_room = x + (y * 5);
|
||||||
|
}
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
gui::InputHexByte(
|
||||||
|
"Selected Room",
|
||||||
|
¤t_dungeon.floor_rooms[floor_number].at(selected_room));
|
||||||
|
|
||||||
|
gui::InputHexWord("Boss Room", ¤t_dungeon.boss_room);
|
||||||
|
|
||||||
|
if (ImGui::Button("Copy Floor", ImVec2(100, 0))) {
|
||||||
|
copy_button_pressed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Paste Floor", ImVec2(100, 0))) {
|
||||||
|
paste_button_pressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawDungeonMapsEditor() {
|
||||||
|
if (!dungeon_maps_loaded_) {
|
||||||
|
if (LoadDungeonMaps().ok()) {
|
||||||
|
if (LoadDungeonMapTile16().ok()) {
|
||||||
|
auto bitmap_manager = rom()->mutable_bitmap_manager();
|
||||||
|
sheets_.emplace(0, *bitmap_manager->mutable_bitmap(212));
|
||||||
|
sheets_.emplace(1, *bitmap_manager->mutable_bitmap(213));
|
||||||
|
sheets_.emplace(2, *bitmap_manager->mutable_bitmap(214));
|
||||||
|
sheets_.emplace(3, *bitmap_manager->mutable_bitmap(215));
|
||||||
|
dungeon_maps_loaded_ = true;
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Failed to load dungeon map tile16");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Failed to load dungeon maps");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> dungeon_names = {
|
||||||
|
"Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
|
||||||
|
"Desert Palace", "Tower of Hera", "Agahnim's Tower",
|
||||||
|
"Palace of Darkness", "Swamp Palace", "Skull Woods",
|
||||||
|
"Thieves' Town", "Ice Palace", "Misery Mire",
|
||||||
|
"Turtle Rock", "Ganon's Tower"};
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("DungeonMapsTable", 4, ImGuiTableFlags_Resizable)) {
|
||||||
|
ImGui::TableSetupColumn("Dungeon");
|
||||||
|
ImGui::TableSetupColumn("Map");
|
||||||
|
ImGui::TableSetupColumn("Rooms Gfx");
|
||||||
|
ImGui::TableSetupColumn("Tiles Gfx");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Dungeon column
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
for (int i = 0; i < dungeon_names.size(); i++) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
|
||||||
|
dungeon_names[i]);
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
selected_dungeon = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map column
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawDungeonMapsTabs();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
|
||||||
|
tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
|
||||||
|
tilesheet_canvas_.DrawContextMenu();
|
||||||
|
tilesheet_canvas_.DrawTileSelector(32.f);
|
||||||
|
tilesheet_canvas_.DrawBitmap(*tile16_sheet_.bitmap(), 2, true);
|
||||||
|
tilesheet_canvas_.DrawGrid(32.f);
|
||||||
|
tilesheet_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
if (!tilesheet_canvas_.points().empty()) {
|
||||||
|
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
|
||||||
|
(tilesheet_canvas_.points().front().y / 32) * 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
tilemap_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
|
||||||
|
tilemap_canvas_.DrawContextMenu();
|
||||||
|
tilemap_canvas_.DrawBitmapTable(sheets_);
|
||||||
|
tilemap_canvas_.DrawGrid();
|
||||||
|
tilemap_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawTitleScreenEditor() {
|
||||||
|
TAB_ITEM("Title Screen")
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
void ScreenEditor::DrawNamingScreenEditor() {
|
||||||
|
TAB_ITEM("Naming Screen")
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
void ScreenEditor::DrawOverworldMapEditor() {
|
||||||
|
TAB_ITEM("Overworld Map")
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawToolset() {
|
||||||
|
static bool show_bg1 = true;
|
||||||
|
static bool show_bg2 = true;
|
||||||
|
static bool show_bg3 = true;
|
||||||
|
|
||||||
|
static bool drawing_bg1 = true;
|
||||||
|
static bool drawing_bg2 = false;
|
||||||
|
static bool drawing_bg3 = false;
|
||||||
|
|
||||||
|
ImGui::Checkbox("Show BG1", &show_bg1);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Show BG2", &show_bg2);
|
||||||
|
|
||||||
|
ImGui::Checkbox("Draw BG1", &drawing_bg1);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Draw BG2", &drawing_bg2);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Draw BG3", &drawing_bg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
106
src/app/editor/graphics/screen_editor.h
Normal file
106
src/app/editor/graphics/screen_editor.h
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/screen/dungeon_map.h"
|
||||||
|
#include "app/zelda3/screen/inventory.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ScreenEditor class allows the user to edit a variety of screens in
|
||||||
|
* the game or create a custom menu.
|
||||||
|
*
|
||||||
|
* This class is currently a work in progress (WIP) and provides functionality
|
||||||
|
* for updating the screens, saving dungeon maps, drawing different types of
|
||||||
|
* screens, loading dungeon maps, and managing various properties related to the
|
||||||
|
* editor.
|
||||||
|
*
|
||||||
|
* The screens that can be edited include the title screen, naming screen,
|
||||||
|
* overworld map, inventory menu, and more.
|
||||||
|
*
|
||||||
|
* The class inherits from the SharedRom class.
|
||||||
|
*/
|
||||||
|
class ScreenEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
ScreenEditor() {
|
||||||
|
screen_canvas_.SetCanvasSize(ImVec2(512, 512));
|
||||||
|
type_ = EditorType::kScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
|
||||||
|
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
|
||||||
|
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
|
||||||
|
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
|
||||||
|
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
absl::Status SaveDungeonMaps();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawTitleScreenEditor();
|
||||||
|
void DrawNamingScreenEditor();
|
||||||
|
void DrawOverworldMapEditor();
|
||||||
|
|
||||||
|
void DrawInventoryMenuEditor();
|
||||||
|
void DrawToolset();
|
||||||
|
void DrawInventoryToolset();
|
||||||
|
|
||||||
|
absl::Status LoadDungeonMaps();
|
||||||
|
absl::Status LoadDungeonMapTile16();
|
||||||
|
void DrawDungeonMapsTabs();
|
||||||
|
void DrawDungeonMapsEditor();
|
||||||
|
|
||||||
|
std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
|
||||||
|
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
|
||||||
|
|
||||||
|
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
|
||||||
|
|
||||||
|
bool dungeon_maps_loaded_ = false;
|
||||||
|
|
||||||
|
int selected_tile16_ = 0;
|
||||||
|
int selected_dungeon = 0;
|
||||||
|
uint8_t selected_room = 0;
|
||||||
|
uint8_t boss_room = 0;
|
||||||
|
int floor_number = 1;
|
||||||
|
|
||||||
|
bool copy_button_pressed = false;
|
||||||
|
bool paste_button_pressed = false;
|
||||||
|
|
||||||
|
Bytes all_gfx_;
|
||||||
|
zelda3::screen::Inventory inventory_;
|
||||||
|
gfx::SnesPalette palette_;
|
||||||
|
gui::Canvas screen_canvas_;
|
||||||
|
gui::Canvas tilesheet_canvas_;
|
||||||
|
gui::Canvas tilemap_canvas_;
|
||||||
|
|
||||||
|
gfx::BitmapTable sheets_;
|
||||||
|
|
||||||
|
gfx::Tilesheet tile16_sheet_;
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
400
src/app/editor/graphics/tile16_editor.cc
Normal file
400
src/app/editor/graphics/tile16_editor.cc
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
#include "tile16_editor.h"
|
||||||
|
|
||||||
|
#include "ImGuiFileDialog/ImGuiFileDialog.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginMenu;
|
||||||
|
using ImGui::BeginMenuBar;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::Combo;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndMenu;
|
||||||
|
using ImGui::EndMenuBar;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::GetContentRegionAvail;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::InitBlockset(
|
||||||
|
gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp,
|
||||||
|
const std::vector<gfx::Bitmap>& tile16_individual,
|
||||||
|
uint8_t all_tiles_types[0x200]) {
|
||||||
|
all_tiles_types_ = all_tiles_types;
|
||||||
|
tile16_blockset_bmp_ = tile16_blockset_bmp;
|
||||||
|
tile16_individual_ = tile16_individual;
|
||||||
|
current_gfx_bmp_ = current_gfx_bmp;
|
||||||
|
tile8_gfx_data_ = current_gfx_bmp_.vector();
|
||||||
|
RETURN_IF_ERROR(LoadTile8());
|
||||||
|
ImVector<std::string> tile16_names;
|
||||||
|
for (int i = 0; i < 0x200; ++i) {
|
||||||
|
std::string str = core::UppercaseHexByte(all_tiles_types_[i]);
|
||||||
|
tile16_names.push_back(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
|
||||||
|
*tile8_source_canvas_.custom_labels_enabled() = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::Update() {
|
||||||
|
if (!map_blockset_loaded_) {
|
||||||
|
return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(DrawMenu());
|
||||||
|
if (BeginTabBar("Tile16 Editor Tabs")) {
|
||||||
|
RETURN_IF_ERROR(DrawTile16Editor());
|
||||||
|
RETURN_IF_ERROR(UpdateTile16Transfer());
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::DrawMenu() {
|
||||||
|
if (BeginMenuBar()) {
|
||||||
|
if (BeginMenu("View")) {
|
||||||
|
Checkbox("Show Collision Types",
|
||||||
|
tile8_source_canvas_.custom_labels_enabled());
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::DrawTile16Editor() {
|
||||||
|
if (BeginTabItem("Tile16 Editing")) {
|
||||||
|
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
TableNextColumn();
|
||||||
|
RETURN_IF_ERROR(UpdateBlockset());
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
RETURN_IF_ERROR(UpdateTile16Edit());
|
||||||
|
RETURN_IF_ERROR(DrawTileEditControls());
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateBlockset() {
|
||||||
|
gui::BeginPadding(2);
|
||||||
|
gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
|
||||||
|
blockset_canvas_.DrawBackground();
|
||||||
|
gui::EndPadding();
|
||||||
|
{
|
||||||
|
blockset_canvas_.DrawContextMenu();
|
||||||
|
blockset_canvas_.DrawTileSelector(32);
|
||||||
|
blockset_canvas_.DrawBitmap(*tile16_blockset_bmp_, 0, map_blockset_loaded_);
|
||||||
|
blockset_canvas_.DrawGrid();
|
||||||
|
blockset_canvas_.DrawOverlay();
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blockset_canvas_.points().empty()) {
|
||||||
|
notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos();
|
||||||
|
notify_tile16.apply_changes();
|
||||||
|
|
||||||
|
if (notify_tile16.modified()) {
|
||||||
|
current_tile16_ = notify_tile16.get();
|
||||||
|
current_tile16_bmp_ = &tile16_individual_[notify_tile16];
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
RETURN_IF_ERROR(current_tile16_bmp_->ApplyPalette(
|
||||||
|
ow_main_pal_group[current_palette_]));
|
||||||
|
rom()->RenderBitmap(current_tile16_bmp_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
|
||||||
|
constexpr int tile8_size = 8;
|
||||||
|
constexpr int tile16_size = 16;
|
||||||
|
|
||||||
|
// Calculate the tile index for x and y based on the click_position
|
||||||
|
// Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
|
||||||
|
int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
|
||||||
|
int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
|
||||||
|
std::cout << "Tile Index X: " << tile_index_x << std::endl;
|
||||||
|
std::cout << "Tile Index Y: " << tile_index_y << std::endl;
|
||||||
|
|
||||||
|
// Calculate the pixel start position within the Tile16
|
||||||
|
ImVec2 start_position;
|
||||||
|
start_position.x = ((tile_index_x) / 4) * 0x40;
|
||||||
|
start_position.y = ((tile_index_y) / 4) * 0x40;
|
||||||
|
std::cout << "Start Position X: " << start_position.x << std::endl;
|
||||||
|
std::cout << "Start Position Y: " << start_position.y << std::endl;
|
||||||
|
|
||||||
|
// Draw the Tile8 to the correct position within the Tile16
|
||||||
|
for (int y = 0; y < tile8_size; ++y) {
|
||||||
|
for (int x = 0; x < tile8_size; ++x) {
|
||||||
|
int pixel_index =
|
||||||
|
(start_position.y + y) * tile16_size + ((start_position.x) + x);
|
||||||
|
int gfx_pixel_index = y * tile8_size + x;
|
||||||
|
current_tile16_bmp_->WriteToPixel(
|
||||||
|
pixel_index,
|
||||||
|
current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateTile16Edit() {
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
|
||||||
|
if (BeginChild("Tile8 Selector", ImVec2(GetContentRegionAvail().x, 0x175),
|
||||||
|
true)) {
|
||||||
|
tile8_source_canvas_.DrawBackground();
|
||||||
|
tile8_source_canvas_.DrawContextMenu(¤t_gfx_bmp_);
|
||||||
|
if (tile8_source_canvas_.DrawTileSelector(32)) {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
|
||||||
|
ow_main_pal_group[0], current_palette_));
|
||||||
|
rom()->UpdateBitmap(¤t_gfx_individual_[current_tile8_]);
|
||||||
|
}
|
||||||
|
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
|
||||||
|
tile8_source_canvas_.DrawGrid();
|
||||||
|
tile8_source_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
// The user selected a tile8
|
||||||
|
if (!tile8_source_canvas_.points().empty()) {
|
||||||
|
uint16_t x = tile8_source_canvas_.points().front().x / 16;
|
||||||
|
uint16_t y = tile8_source_canvas_.points().front().y / 16;
|
||||||
|
|
||||||
|
current_tile8_ = x + (y * 8);
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
|
||||||
|
ow_main_pal_group[0], current_palette_));
|
||||||
|
rom()->UpdateBitmap(¤t_gfx_individual_[current_tile8_]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginChild("Tile16 Editor Options",
|
||||||
|
ImVec2(GetContentRegionAvail().x, 0x50), true)) {
|
||||||
|
tile16_edit_canvas_.DrawBackground();
|
||||||
|
tile16_edit_canvas_.DrawContextMenu(current_tile16_bmp_);
|
||||||
|
tile16_edit_canvas_.DrawBitmap(*current_tile16_bmp_, 0, 0, 4.0f);
|
||||||
|
if (!tile8_source_canvas_.points().empty()) {
|
||||||
|
if (tile16_edit_canvas_.DrawTilePainter(
|
||||||
|
current_gfx_individual_[current_tile8_], 16, 2.0f)) {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
DrawToCurrentTile16(tile16_edit_canvas_.drawn_tile_position()));
|
||||||
|
rom()->UpdateBitmap(current_tile16_bmp_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tile16_edit_canvas_.DrawGrid();
|
||||||
|
tile16_edit_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::DrawTileEditControls() {
|
||||||
|
Separator();
|
||||||
|
Text("Tile16 ID: %d", current_tile16_);
|
||||||
|
Text("Tile8 ID: %d", current_tile8_);
|
||||||
|
Text("Options:");
|
||||||
|
gui::InputHexByte("Palette", ¬ify_palette.mutable_get());
|
||||||
|
notify_palette.apply_changes();
|
||||||
|
if (notify_palette.modified()) {
|
||||||
|
auto palette = palettesets_[current_palette_].main;
|
||||||
|
auto value = notify_palette.get();
|
||||||
|
if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
|
||||||
|
palette = palettesets_[current_palette_].aux1;
|
||||||
|
value -= 0x04;
|
||||||
|
} else if (notify_palette.get() > 0x06) {
|
||||||
|
palette = palettesets_[current_palette_].aux2;
|
||||||
|
value -= 0x06;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > 0x00) {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_tile16_bmp_->ApplyPaletteWithTransparent(palette, value));
|
||||||
|
rom()->UpdateBitmap(¤t_gfx_bmp_);
|
||||||
|
rom()->UpdateBitmap(current_tile16_bmp_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkbox("X Flip", &x_flip);
|
||||||
|
Checkbox("Y Flip", &y_flip);
|
||||||
|
Checkbox("Priority Tile", &priority_tile);
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::LoadTile8() {
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
|
||||||
|
current_gfx_individual_.reserve(1024);
|
||||||
|
|
||||||
|
for (int index = 0; index < 1024; index++) {
|
||||||
|
std::vector<uint8_t> tile_data(0x40, 0x00);
|
||||||
|
|
||||||
|
// Copy the pixel data for the current tile into the vector
|
||||||
|
for (int ty = 0; ty < 8; ty++) {
|
||||||
|
for (int tx = 0; tx < 8; tx++) {
|
||||||
|
// Current Gfx Data is 16 sheets of 8x8 tiles ordered 16 wide by 4 tall
|
||||||
|
|
||||||
|
// Calculate the position in the tile data vector
|
||||||
|
int position = tx + (ty * 0x08);
|
||||||
|
|
||||||
|
// Calculate the position in the current gfx data
|
||||||
|
int num_columns = current_gfx_bmp_.width() / 8;
|
||||||
|
int num_rows = current_gfx_bmp_.height() / 8;
|
||||||
|
int x = (index % num_columns) * 8 + tx;
|
||||||
|
int y = (index / num_columns) * 8 + ty;
|
||||||
|
int gfx_position = x + (y * 0x100);
|
||||||
|
|
||||||
|
// Get the pixel value from the current gfx data
|
||||||
|
uint8_t value = current_gfx_bmp_.data()[gfx_position];
|
||||||
|
|
||||||
|
if (value & 0x80) {
|
||||||
|
value -= 0x88;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_data[position] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_gfx_individual_.emplace_back();
|
||||||
|
current_gfx_individual_[index].Create(0x08, 0x08, 0x08, tile_data);
|
||||||
|
RETURN_IF_ERROR(current_gfx_individual_[index].ApplyPaletteWithTransparent(
|
||||||
|
ow_main_pal_group[0], current_palette_));
|
||||||
|
rom()->RenderBitmap(¤t_gfx_individual_[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
map_blockset_loaded_ = true;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tile16 Transfer
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateTile16Transfer() {
|
||||||
|
if (BeginTabItem("Tile16 Transfer")) {
|
||||||
|
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
GetContentRegionAvail().x / 2);
|
||||||
|
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
GetContentRegionAvail().x / 2);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
RETURN_IF_ERROR(UpdateBlockset());
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
RETURN_IF_ERROR(UpdateTransferTileCanvas());
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
|
||||||
|
// Create a button for loading another ROM
|
||||||
|
if (Button("Load ROM")) {
|
||||||
|
ImGuiFileDialog::Instance()->OpenDialog(
|
||||||
|
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", ".");
|
||||||
|
}
|
||||||
|
gui::FileDialogPipeline(
|
||||||
|
"ChooseTransferFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||||
|
std::string filePathName =
|
||||||
|
ImGuiFileDialog::Instance()->GetFilePathName();
|
||||||
|
transfer_status_ = transfer_rom_.LoadFromFile(filePathName);
|
||||||
|
transfer_started_ = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Implement tile16 transfer
|
||||||
|
if (transfer_started_ && !transfer_blockset_loaded_) {
|
||||||
|
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
|
||||||
|
graphics_bin_ = transfer_rom_.graphics_bin();
|
||||||
|
|
||||||
|
// Load the Link to the Past overworld.
|
||||||
|
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
|
||||||
|
transfer_overworld_.set_current_map(0);
|
||||||
|
palette_ = transfer_overworld_.AreaPalette();
|
||||||
|
|
||||||
|
// Create the tile16 blockset image
|
||||||
|
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
|
||||||
|
0x80, 0x2000, 0x80, transfer_overworld_.Tile16Blockset(),
|
||||||
|
transfer_blockset_bmp_, palette_));
|
||||||
|
transfer_blockset_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a canvas for holding the tiles which will be exported
|
||||||
|
gui::BitmapCanvasPipeline(transfer_canvas_, transfer_blockset_bmp_, 0x100,
|
||||||
|
(8192 * 2), 0x20, transfer_blockset_loaded_, true,
|
||||||
|
3);
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::SetCurrentTile(int id) {
|
||||||
|
current_tile16_ = id;
|
||||||
|
current_tile16_bmp_ = &tile16_individual_[id];
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_tile16_bmp_->ApplyPalette(ow_main_pal_group[current_palette_]));
|
||||||
|
rom()->RenderBitmap(current_tile16_bmp_);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
118
src/app/editor/graphics/tile16_editor.h
Normal file
118
src/app/editor/graphics/tile16_editor.h
Normal 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
|
||||||
842
src/app/editor/master_editor.cc
Normal file
842
src/app/editor/master_editor.cc
Normal file
@@ -0,0 +1,842 @@
|
|||||||
|
#include "master_editor.h"
|
||||||
|
|
||||||
|
#include "ImGuiColorTextEdit/TextEditor.h"
|
||||||
|
#include "ImGuiFileDialog/ImGuiFileDialog.h"
|
||||||
|
#include "abseil-cpp/absl/strings/match.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
#include "imgui_internal.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/editor/code/assembly_editor.h"
|
||||||
|
#include "app/editor/dungeon/dungeon_editor.h"
|
||||||
|
#include "app/editor/graphics/graphics_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/graphics/screen_editor.h"
|
||||||
|
#include "app/editor/music/music_editor.h"
|
||||||
|
#include "app/editor/overworld_editor.h"
|
||||||
|
#include "app/editor/sprite/sprite_editor.h"
|
||||||
|
#include "app/editor/utils/flags.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 namespace ImGui;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool BeginCentered(const char *name) {
|
||||||
|
ImGuiIO const &io = GetIO();
|
||||||
|
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||||
|
SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||||
|
ImGuiWindowFlags flags =
|
||||||
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
|
||||||
|
return Begin(name, nullptr, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEditorActive(Editor* editor, std::vector<Editor*>& active_editors) {
|
||||||
|
return std::find(active_editors.begin(), active_editors.end(), editor) !=
|
||||||
|
active_editors.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void MasterEditor::SetupScreen(std::shared_ptr<SDL_Renderer> renderer,
|
||||||
|
std::string filename) {
|
||||||
|
sdl_renderer_ = renderer;
|
||||||
|
rom()->SetupRenderer(renderer);
|
||||||
|
if (!filename.empty()) {
|
||||||
|
PRINT_IF_ERROR(rom()->LoadFromFile(filename));
|
||||||
|
}
|
||||||
|
overworld_editor_.InitializeZeml();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MasterEditor::Update() {
|
||||||
|
ManageKeyboardShortcuts();
|
||||||
|
|
||||||
|
DrawYazeMenu();
|
||||||
|
DrawFileDialog();
|
||||||
|
DrawStatusPopup();
|
||||||
|
DrawAboutPopup();
|
||||||
|
DrawInfoPopup();
|
||||||
|
|
||||||
|
if (rom()->is_loaded() && !rom_assets_loaded_) {
|
||||||
|
// Load all of the graphics data from the game.
|
||||||
|
RETURN_IF_ERROR(rom()->LoadAllGraphicsData())
|
||||||
|
// Initialize overworld graphics, maps, and palettes
|
||||||
|
RETURN_IF_ERROR(overworld_editor_.LoadGraphics());
|
||||||
|
rom_assets_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ManageActiveEditors();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::ManageActiveEditors() {
|
||||||
|
// Show popup pane to select an editor to add
|
||||||
|
static bool show_add_editor = false;
|
||||||
|
if (show_add_editor) OpenPopup("AddEditor");
|
||||||
|
|
||||||
|
if (BeginPopup("AddEditor", ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
if (MenuItem("Overworld", nullptr, false,
|
||||||
|
!IsEditorActive(&overworld_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&overworld_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Dungeon", nullptr, false,
|
||||||
|
!IsEditorActive(&dungeon_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&dungeon_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Graphics", nullptr, false,
|
||||||
|
!IsEditorActive(&graphics_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&graphics_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Music", nullptr, false,
|
||||||
|
!IsEditorActive(&music_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&music_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Palette", nullptr, false,
|
||||||
|
!IsEditorActive(&palette_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&palette_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Screen", nullptr, false,
|
||||||
|
!IsEditorActive(&screen_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&screen_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Sprite", nullptr, false,
|
||||||
|
!IsEditorActive(&sprite_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&sprite_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Code", nullptr, false,
|
||||||
|
!IsEditorActive(&assembly_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&assembly_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Message", nullptr, false,
|
||||||
|
!IsEditorActive(&message_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&message_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Settings", nullptr, false,
|
||||||
|
!IsEditorActive(&settings_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&settings_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsPopupOpen("AddEditor")) {
|
||||||
|
show_add_editor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabBar("##TabBar", ImGuiTabBarFlags_Reorderable |
|
||||||
|
ImGuiTabBarFlags_AutoSelectNewTabs)) {
|
||||||
|
for (auto editor : active_editors_) {
|
||||||
|
bool open = true;
|
||||||
|
switch (editor->type()) {
|
||||||
|
case EditorType::kOverworld:
|
||||||
|
if (overworld_editor_.jump_to_tab() == -1) {
|
||||||
|
if (BeginTabItem("Overworld", &open)) {
|
||||||
|
current_editor_ = &overworld_editor_;
|
||||||
|
status_ = overworld_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kDungeon:
|
||||||
|
if (BeginTabItem("Dungeon", &open)) {
|
||||||
|
current_editor_ = &dungeon_editor_;
|
||||||
|
status_ = dungeon_editor_.Update();
|
||||||
|
if (overworld_editor_.jump_to_tab() != -1) {
|
||||||
|
dungeon_editor_.add_room(overworld_editor_.jump_to_tab());
|
||||||
|
overworld_editor_.jump_to_tab_ = -1;
|
||||||
|
}
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kGraphics:
|
||||||
|
if (BeginTabItem("Graphics", &open)) {
|
||||||
|
current_editor_ = &graphics_editor_;
|
||||||
|
status_ = graphics_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kMusic:
|
||||||
|
if (BeginTabItem("Music", &open)) {
|
||||||
|
current_editor_ = &music_editor_;
|
||||||
|
|
||||||
|
status_ = music_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kPalette:
|
||||||
|
if (BeginTabItem("Palette", &open)) {
|
||||||
|
current_editor_ = &palette_editor_;
|
||||||
|
status_ = palette_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kScreen:
|
||||||
|
if (BeginTabItem("Screen", &open)) {
|
||||||
|
current_editor_ = &screen_editor_;
|
||||||
|
status_ = screen_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kSprite:
|
||||||
|
if (BeginTabItem("Sprite", &open)) {
|
||||||
|
current_editor_ = &sprite_editor_;
|
||||||
|
status_ = sprite_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kAssembly:
|
||||||
|
if (BeginTabItem("Code", &open)) {
|
||||||
|
current_editor_ = &assembly_editor_;
|
||||||
|
assembly_editor_.UpdateCodeView();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kSettings:
|
||||||
|
if (BeginTabItem("Settings", &open)) {
|
||||||
|
current_editor_ = &settings_editor_;
|
||||||
|
status_ = settings_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kMessage:
|
||||||
|
if (BeginTabItem("Message", &open)) {
|
||||||
|
current_editor_ = &message_editor_;
|
||||||
|
status_ = message_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!open) {
|
||||||
|
active_editors_.erase(
|
||||||
|
std::remove(active_editors_.begin(), active_editors_.end(), editor),
|
||||||
|
active_editors_.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing)) {
|
||||||
|
show_add_editor = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::ManageKeyboardShortcuts() {
|
||||||
|
bool ctrl_or_super = (GetIO().KeyCtrl || GetIO().KeySuper);
|
||||||
|
|
||||||
|
// If CMD + R is pressed, reload the top result of recent files
|
||||||
|
if (IsKeyDown(ImGuiKey_R) && ctrl_or_super) {
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
if (!manager.GetRecentFiles().empty()) {
|
||||||
|
auto front = manager.GetRecentFiles().front();
|
||||||
|
std::cout << "Reloading: " << front << std::endl;
|
||||||
|
OpenRomOrProject(front);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_F1)) {
|
||||||
|
about_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If CMD + Q is pressed, quit the application
|
||||||
|
if (IsKeyDown(ImGuiKey_Q) && ctrl_or_super) {
|
||||||
|
quit_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If CMD + O is pressed, open a file dialog
|
||||||
|
if (IsKeyDown(ImGuiKey_O) && ctrl_or_super) {
|
||||||
|
LoadRom();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If CMD + S is pressed, save the current ROM
|
||||||
|
if (IsKeyDown(ImGuiKey_S) && ctrl_or_super) {
|
||||||
|
SaveRom();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_X) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Cut();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_C) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_V) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Paste();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_Z) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_Y) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Redo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_F) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Find();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawFileDialog() {
|
||||||
|
gui::FileDialogPipeline("ChooseFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||||
|
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||||
|
status_ = rom()->LoadFromFile(filePathName);
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
|
||||||
|
// Load existing recent files
|
||||||
|
manager.Load();
|
||||||
|
|
||||||
|
// Add a new file
|
||||||
|
manager.AddFile(filePathName);
|
||||||
|
|
||||||
|
// Save the updated list
|
||||||
|
manager.Save();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawStatusPopup() {
|
||||||
|
if (!status_.ok()) {
|
||||||
|
show_status_ = true;
|
||||||
|
prev_status_ = status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_status_ && (BeginCentered("StatusWindow"))) {
|
||||||
|
Text("%s", ICON_MD_ERROR);
|
||||||
|
Text("%s", prev_status_.ToString().c_str());
|
||||||
|
Spacing();
|
||||||
|
NextColumn();
|
||||||
|
Columns(1);
|
||||||
|
Separator();
|
||||||
|
NewLine();
|
||||||
|
SameLine(128);
|
||||||
|
if (Button("OK", gui::kDefaultModalSize) ||
|
||||||
|
IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) {
|
||||||
|
show_status_ = false;
|
||||||
|
status_ = absl::OkStatus();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
|
||||||
|
SetClipboardText(prev_status_.ToString().c_str());
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawAboutPopup() {
|
||||||
|
if (about_) OpenPopup("About");
|
||||||
|
if (BeginPopupModal("About", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Yet Another Zelda3 Editor - v%.2f", core::kYazeVersion);
|
||||||
|
Text("Written by: scawful");
|
||||||
|
Spacing();
|
||||||
|
Text("Special Thanks: Zarby89, JaredBrian");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
about_ = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawInfoPopup() {
|
||||||
|
if (rom_info_) OpenPopup("ROM Information");
|
||||||
|
if (BeginPopupModal("ROM Information", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Title: %s", rom()->title());
|
||||||
|
Text("ROM Size: %ld", rom()->size());
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize) ||
|
||||||
|
IsKeyPressed(GetKeyIndex(ImGuiKey_Space))) {
|
||||||
|
rom_info_ = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawYazeMenu() {
|
||||||
|
static bool show_display_settings = false;
|
||||||
|
static bool show_command_line_interface = false;
|
||||||
|
|
||||||
|
if (BeginMenuBar()) {
|
||||||
|
DrawFileMenu();
|
||||||
|
DrawEditMenu();
|
||||||
|
DrawViewMenu();
|
||||||
|
DrawTestMenu();
|
||||||
|
DrawProjectMenu();
|
||||||
|
DrawHelpMenu();
|
||||||
|
|
||||||
|
SameLine(GetWindowWidth() - GetStyle().ItemSpacing.x -
|
||||||
|
CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 150);
|
||||||
|
// Modify the style of the button to have no background color
|
||||||
|
PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||||
|
if (Button(ICON_MD_DISPLAY_SETTINGS)) {
|
||||||
|
show_display_settings = !show_display_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Button(ICON_MD_TERMINAL)) {
|
||||||
|
show_command_line_interface = !show_command_line_interface;
|
||||||
|
}
|
||||||
|
PopStyleColor();
|
||||||
|
|
||||||
|
Text("%s", absl::StrCat("yaze v", core::kYazeVersion).c_str());
|
||||||
|
|
||||||
|
EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_display_settings) {
|
||||||
|
Begin("Display Settings", &show_display_settings, ImGuiWindowFlags_None);
|
||||||
|
gui::DrawDisplaySettings();
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_command_line_interface) {
|
||||||
|
Begin("Command Line Interface", &show_command_line_interface,
|
||||||
|
ImGuiWindowFlags_None);
|
||||||
|
Text("Enter a command:");
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::OpenRomOrProject(const std::string& filename) {
|
||||||
|
if (absl::StrContains(filename, ".yaze")) {
|
||||||
|
status_ = current_project_.Open(filename);
|
||||||
|
if (status_.ok()) {
|
||||||
|
status_ = OpenProject();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status_ = rom()->LoadFromFile(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawFileMenu() {
|
||||||
|
static bool save_as_menu = false;
|
||||||
|
static bool new_project_menu = false;
|
||||||
|
|
||||||
|
if (BeginMenu("File")) {
|
||||||
|
if (MenuItem("Open", "Ctrl+O")) {
|
||||||
|
LoadRom();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Open Recent")) {
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
if (manager.GetRecentFiles().empty()) {
|
||||||
|
MenuItem("No Recent Files", nullptr, false, false);
|
||||||
|
} else {
|
||||||
|
for (const auto& filePath : manager.GetRecentFiles()) {
|
||||||
|
if (MenuItem(filePath.c_str())) {
|
||||||
|
OpenRomOrProject(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
MENU_ITEM2("Save", "Ctrl+S") {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
SaveRom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MENU_ITEM("Save As..") { save_as_menu = true; }
|
||||||
|
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
MENU_ITEM("Reload") { status_ = rom()->Reload(); }
|
||||||
|
MENU_ITEM("Close") {
|
||||||
|
status_ = rom()->Close();
|
||||||
|
rom_assets_loaded_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
if (BeginMenu("Project")) {
|
||||||
|
if (MenuItem("Create New Project")) {
|
||||||
|
// Create a new project
|
||||||
|
new_project_menu = true;
|
||||||
|
}
|
||||||
|
if (MenuItem("Open Project")) {
|
||||||
|
// Open an existing project
|
||||||
|
status_ =
|
||||||
|
current_project_.Open(FileDialogWrapper::ShowOpenFileDialog());
|
||||||
|
if (status_.ok()) {
|
||||||
|
status_ = OpenProject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (MenuItem("Save Project")) {
|
||||||
|
// Save the current project
|
||||||
|
status_ = current_project_.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Options")) {
|
||||||
|
MenuItem("Backup ROM", "", &backup_rom_);
|
||||||
|
MenuItem("Save New Auto", "", &save_new_auto_);
|
||||||
|
Separator();
|
||||||
|
if (BeginMenu("Experiment Flags")) {
|
||||||
|
static FlagsMenu flags_menu;
|
||||||
|
flags_menu.Draw();
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
if (MenuItem("Quit", "Ctrl+Q")) {
|
||||||
|
quit_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save_as_menu) {
|
||||||
|
static std::string save_as_filename = "";
|
||||||
|
Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
|
InputText("Filename", &save_as_filename);
|
||||||
|
if (Button("Save", gui::kDefaultModalSize)) {
|
||||||
|
SaveRom();
|
||||||
|
save_as_menu = false;
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button("Cancel", gui::kDefaultModalSize)) {
|
||||||
|
save_as_menu = false;
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_project_menu) {
|
||||||
|
Begin("New Project", &new_project_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
|
static std::string save_as_filename = "";
|
||||||
|
InputText("Project Name", &save_as_filename);
|
||||||
|
if (Button("Destination Filepath", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.filepath = FileDialogWrapper::ShowOpenFolderDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.filepath.c_str());
|
||||||
|
if (Button("ROM File", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.rom_filename_ = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.rom_filename_.c_str());
|
||||||
|
if (Button("Labels File", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.labels_filename_ =
|
||||||
|
FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.labels_filename_.c_str());
|
||||||
|
if (Button("Code Folder", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.code_folder_ = FileDialogWrapper::ShowOpenFolderDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.code_folder_.c_str());
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
if (Button("Create", gui::kDefaultModalSize)) {
|
||||||
|
new_project_menu = false;
|
||||||
|
status_ = current_project_.Create(save_as_filename);
|
||||||
|
if (status_.ok()) {
|
||||||
|
status_ = current_project_.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button("Cancel", gui::kDefaultModalSize)) {
|
||||||
|
new_project_menu = false;
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawEditMenu() {
|
||||||
|
if (BeginMenu("Edit")) {
|
||||||
|
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); }
|
||||||
|
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); }
|
||||||
|
Separator();
|
||||||
|
MENU_ITEM2("Cut", "Ctrl+X") { status_ = current_editor_->Cut(); }
|
||||||
|
MENU_ITEM2("Copy", "Ctrl+C") { status_ = current_editor_->Copy(); }
|
||||||
|
MENU_ITEM2("Paste", "Ctrl+V") { status_ = current_editor_->Paste(); }
|
||||||
|
Separator();
|
||||||
|
MENU_ITEM2("Find", "Ctrl+F") {}
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawViewMenu() {
|
||||||
|
static bool show_imgui_metrics = false;
|
||||||
|
static bool show_memory_editor = false;
|
||||||
|
static bool show_asm_editor = false;
|
||||||
|
static bool show_imgui_demo = false;
|
||||||
|
static bool show_palette_editor = false;
|
||||||
|
static bool show_emulator = false;
|
||||||
|
|
||||||
|
if (show_emulator) {
|
||||||
|
Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar);
|
||||||
|
emulator_.Run();
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_imgui_metrics) {
|
||||||
|
ShowMetricsWindow(&show_imgui_metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_memory_editor) {
|
||||||
|
memory_editor_.Update(show_memory_editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_imgui_demo) {
|
||||||
|
ShowDemoWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_asm_editor) {
|
||||||
|
assembly_editor_.Update(show_asm_editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_palette_editor) {
|
||||||
|
Begin("Palette Editor", &show_palette_editor);
|
||||||
|
status_ = palette_editor_.Update();
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("View")) {
|
||||||
|
MenuItem("Emulator", nullptr, &show_emulator);
|
||||||
|
Separator();
|
||||||
|
MenuItem("Memory Editor", nullptr, &show_memory_editor);
|
||||||
|
MenuItem("Assembly Editor", nullptr, &show_asm_editor);
|
||||||
|
MenuItem("Palette Editor", nullptr, &show_palette_editor);
|
||||||
|
Separator();
|
||||||
|
MENU_ITEM("ROM Information") rom_info_ = true;
|
||||||
|
Separator();
|
||||||
|
MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||||
|
MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawTestMenu() {
|
||||||
|
static bool show_tests_ = false;
|
||||||
|
|
||||||
|
if (BeginMenu("Tests")) {
|
||||||
|
MenuItem("Run Tests", nullptr, &show_tests_);
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawProjectMenu() {
|
||||||
|
static bool show_resource_label_manager = false;
|
||||||
|
|
||||||
|
if (current_project_.project_opened_) {
|
||||||
|
// Project Menu
|
||||||
|
if (BeginMenu("Project")) {
|
||||||
|
Text("Name: %s", current_project_.name.c_str());
|
||||||
|
Text("ROM: %s", current_project_.rom_filename_.c_str());
|
||||||
|
Text("Labels: %s", current_project_.labels_filename_.c_str());
|
||||||
|
Text("Code: %s", current_project_.code_folder_.c_str());
|
||||||
|
Separator();
|
||||||
|
MenuItem("Resource Labels", nullptr, &show_resource_label_manager);
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (show_resource_label_manager) {
|
||||||
|
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
|
||||||
|
if (current_project_.project_opened_ &&
|
||||||
|
!current_project_.labels_filename_.empty()) {
|
||||||
|
current_project_.labels_filename_ = rom()->resource_label()->filename_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::DrawHelpMenu() {
|
||||||
|
static bool open_rom_help = false;
|
||||||
|
static bool open_supported_features = false;
|
||||||
|
static bool open_manage_project = false;
|
||||||
|
if (BeginMenu("Help")) {
|
||||||
|
if (MenuItem("How to open a ROM")) open_rom_help = true;
|
||||||
|
if (MenuItem("Supported Features")) open_supported_features = true;
|
||||||
|
if (MenuItem("How to manage a project")) open_manage_project = true;
|
||||||
|
|
||||||
|
if (MenuItem("About", "F1")) about_ = true;
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open_supported_features) OpenPopup("Supported Features");
|
||||||
|
if (BeginPopupModal("Supported Features", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Overworld");
|
||||||
|
BulletText("LW/DW/SW Tilemap Editing");
|
||||||
|
BulletText("LW/DW/SW Map Properties");
|
||||||
|
BulletText("Create/Delete/Update Entrances");
|
||||||
|
BulletText("Create/Delete/Update Exits");
|
||||||
|
BulletText("Create/Delete/Update Sprites");
|
||||||
|
BulletText("Create/Delete/Update Items");
|
||||||
|
|
||||||
|
Text("Dungeon");
|
||||||
|
BulletText("View Room Header Properties");
|
||||||
|
BulletText("View Entrance Properties");
|
||||||
|
|
||||||
|
Text("Graphics");
|
||||||
|
BulletText("View Decompressed Graphics Sheets");
|
||||||
|
BulletText("View/Update Graphics Groups");
|
||||||
|
|
||||||
|
Text("Palettes");
|
||||||
|
BulletText("View Palette Groups");
|
||||||
|
|
||||||
|
Text("Saveable");
|
||||||
|
BulletText("All Listed Overworld Features");
|
||||||
|
BulletText("Hex Editor Changes");
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
open_supported_features = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open_rom_help) OpenPopup("Open a ROM");
|
||||||
|
if (BeginPopupModal("Open a ROM", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("File -> Open");
|
||||||
|
Text("Select a ROM file to open");
|
||||||
|
Text("Supported ROMs (headered or unheadered):");
|
||||||
|
Text("The Legend of Zelda: A Link to the Past");
|
||||||
|
Text("US Version 1.0");
|
||||||
|
Text("JP Version 1.0");
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
open_rom_help = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open_manage_project) OpenPopup("Manage Project");
|
||||||
|
if (BeginPopupModal("Manage Project", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Project Menu");
|
||||||
|
Text("Create a new project or open an existing one.");
|
||||||
|
Text("Save the project to save the current state of the project.");
|
||||||
|
Text(
|
||||||
|
"To save a project, you need to first open a ROM and initialize your "
|
||||||
|
"code path and labels file. Label resource manager can be found in "
|
||||||
|
"the "
|
||||||
|
"View menu. Code path is set in the Code editor after opening a "
|
||||||
|
"folder.");
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
open_manage_project = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::LoadRom() {
|
||||||
|
if (flags()->kNewFileDialogWrapper) {
|
||||||
|
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
PRINT_IF_ERROR(rom()->LoadFromFile(file_name));
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
manager.AddFile(file_name);
|
||||||
|
manager.Save();
|
||||||
|
} else {
|
||||||
|
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
||||||
|
".sfc,.smc", ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MasterEditor::SaveRom() {
|
||||||
|
if (flags()->kSaveDungeonMaps) {
|
||||||
|
status_ = screen_editor_.SaveDungeonMaps();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
if (flags()->overworld.kSaveOverworldMaps) {
|
||||||
|
RETURN_VOID_IF_ERROR(
|
||||||
|
status_ = overworld_editor_.overworld()->CreateTile32Tilemap());
|
||||||
|
status_ = overworld_editor_.overworld()->SaveMap32Tiles();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
status_ = overworld_editor_.overworld()->SaveMap16Tiles();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
status_ = overworld_editor_.overworld()->SaveOverworldMaps();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
if (flags()->overworld.kSaveOverworldEntrances) {
|
||||||
|
status_ = overworld_editor_.overworld()->SaveEntrances();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
if (flags()->overworld.kSaveOverworldExits) {
|
||||||
|
status_ = overworld_editor_.overworld()->SaveExits();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
if (flags()->overworld.kSaveOverworldItems) {
|
||||||
|
status_ = overworld_editor_.overworld()->SaveItems();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
if (flags()->overworld.kSaveOverworldProperties) {
|
||||||
|
status_ = overworld_editor_.overworld()->SaveMapProperties();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = rom()->SaveToFile(backup_rom_, save_new_auto_);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MasterEditor::OpenProject() {
|
||||||
|
RETURN_IF_ERROR(rom()->LoadFromFile(current_project_.rom_filename_));
|
||||||
|
|
||||||
|
if (!rom()->resource_label()->LoadLabels(current_project_.labels_filename_)) {
|
||||||
|
return absl::InternalError(
|
||||||
|
"Could not load labels file, update your project file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
manager.AddFile(current_project_.filepath + "/" + current_project_.name +
|
||||||
|
".yaze");
|
||||||
|
manager.Save();
|
||||||
|
|
||||||
|
assembly_editor_.OpenFolder(current_project_.code_folder_);
|
||||||
|
|
||||||
|
current_project_.project_opened_ = true;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
141
src/app/editor/master_editor.h
Normal file
141
src/app/editor/master_editor.h
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_MASTER_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/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/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"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class MasterEditor
|
||||||
|
* @brief The MasterEditor class represents the main editor for a Rom in the
|
||||||
|
* Yaze application.
|
||||||
|
*
|
||||||
|
* This class inherits from SharedRom, GfxContext, and ExperimentFlags, and
|
||||||
|
* provides functionality for setting up the screen, updating the editor, and
|
||||||
|
* shutting down the editor. It also includes methods for drawing various menus
|
||||||
|
* and popups, saving the Rom, and managing editor-specific flags.
|
||||||
|
*
|
||||||
|
* The MasterEditor class contains instances of various editor classes such as
|
||||||
|
* AssemblyEditor, DungeonEditor, GraphicsEditor, MusicEditor, OverworldEditor,
|
||||||
|
* PaletteEditor, ScreenEditor, and SpriteEditor. The current_editor_ member
|
||||||
|
* variable points to the currently active editor in the tab view.
|
||||||
|
*
|
||||||
|
* @note This class assumes the presence of an SDL_Renderer object for rendering
|
||||||
|
* graphics.
|
||||||
|
*/
|
||||||
|
class MasterEditor : public SharedRom,
|
||||||
|
public context::GfxContext,
|
||||||
|
public core::ExperimentFlags {
|
||||||
|
public:
|
||||||
|
MasterEditor() {
|
||||||
|
current_editor_ = &overworld_editor_;
|
||||||
|
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();
|
||||||
|
void DrawEditMenu();
|
||||||
|
void DrawViewMenu();
|
||||||
|
void DrawTestMenu();
|
||||||
|
void DrawProjectMenu();
|
||||||
|
void DrawHelpMenu();
|
||||||
|
|
||||||
|
void LoadRom();
|
||||||
|
void SaveRom();
|
||||||
|
|
||||||
|
absl::Status OpenProject();
|
||||||
|
|
||||||
|
bool quit_ = false;
|
||||||
|
bool about_ = false;
|
||||||
|
bool rom_info_ = false;
|
||||||
|
bool backup_rom_ = 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_;
|
||||||
|
|
||||||
|
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_;
|
||||||
|
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
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||||
10
src/app/editor/master_editor_test.cc
Normal file
10
src/app/editor/master_editor_test.cc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#include "master_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
177
src/app/editor/message/message_data.h
Normal file
177
src/app/editor/message/message_data.h
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
|
||||||
|
#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
const uint8_t MESSAGETERMINATOR = 0x7F;
|
||||||
|
|
||||||
|
static std::string AddNewLinesToCommands(std::string str);
|
||||||
|
static std::string ReplaceAllDictionaryWords(std::string str);
|
||||||
|
static std::vector<uint8_t> ParseMessageToData(std::string str);
|
||||||
|
|
||||||
|
const std::string CHEESE = "\uBEBE"; // Inserted into commands to protect
|
||||||
|
// them from dictionary replacements.
|
||||||
|
|
||||||
|
struct MessageData {
|
||||||
|
int ID;
|
||||||
|
int Address;
|
||||||
|
std::string RawString;
|
||||||
|
std::string ContentsParsed;
|
||||||
|
std::vector<uint8_t> Data;
|
||||||
|
std::vector<uint8_t> DataParsed;
|
||||||
|
|
||||||
|
MessageData() = default;
|
||||||
|
MessageData(int id, int address, const std::string& rawString,
|
||||||
|
const std::vector<uint8_t>& rawData,
|
||||||
|
const std::string& parsedString,
|
||||||
|
const std::vector<uint8_t>& parsedData)
|
||||||
|
: ID(id),
|
||||||
|
Address(address),
|
||||||
|
RawString(rawString),
|
||||||
|
Data(rawData),
|
||||||
|
DataParsed(parsedData),
|
||||||
|
ContentsParsed(parsedString) {}
|
||||||
|
|
||||||
|
// Copy constructor
|
||||||
|
MessageData(const MessageData& other) {
|
||||||
|
ID = other.ID;
|
||||||
|
Address = other.Address;
|
||||||
|
RawString = other.RawString;
|
||||||
|
Data = other.Data;
|
||||||
|
DataParsed = other.DataParsed;
|
||||||
|
ContentsParsed = other.ContentsParsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMessage(std::string messageString) {
|
||||||
|
ContentsParsed = messageString;
|
||||||
|
RawString = OptimizeMessageForDictionary(messageString);
|
||||||
|
RecalculateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToString() {
|
||||||
|
return absl::StrFormat("%0X - %s", ID, ContentsParsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetReadableDumpedContents() {
|
||||||
|
std::stringstream stringBuilder;
|
||||||
|
for (const auto& b : Data) {
|
||||||
|
stringBuilder << absl::StrFormat("%0X ", b);
|
||||||
|
}
|
||||||
|
stringBuilder << absl::StrFormat("%00X", MESSAGETERMINATOR);
|
||||||
|
|
||||||
|
return absl::StrFormat(
|
||||||
|
"[[[[\r\nMessage "
|
||||||
|
"%000X]]]]\r\n[Contents]\r\n%s\r\n\r\n[Data]\r\n%s"
|
||||||
|
"\r\n\r\n\r\n\r\n",
|
||||||
|
ID, AddNewLinesToCommands(ContentsParsed), stringBuilder.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetDumpedContents() {
|
||||||
|
return absl::StrFormat("%000X : %s\r\n\r\n", ID, ContentsParsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OptimizeMessageForDictionary(std::string messageString) {
|
||||||
|
std::stringstream protons;
|
||||||
|
bool command = false;
|
||||||
|
for (const auto& c : messageString) {
|
||||||
|
if (c == '[') {
|
||||||
|
command = true;
|
||||||
|
} else if (c == ']') {
|
||||||
|
command = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protons << c;
|
||||||
|
if (command) {
|
||||||
|
protons << CHEESE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string protonsString = protons.str();
|
||||||
|
std::string replacedString = ReplaceAllDictionaryWords(protonsString);
|
||||||
|
std::string finalString =
|
||||||
|
absl::StrReplaceAll(replacedString, {{CHEESE, ""}});
|
||||||
|
|
||||||
|
return finalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecalculateData() {
|
||||||
|
Data = ParseMessageToData(RawString);
|
||||||
|
DataParsed = ParseMessageToData(ContentsParsed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextElement {
|
||||||
|
uint8_t ID;
|
||||||
|
std::string Token;
|
||||||
|
std::string GenericToken;
|
||||||
|
std::string Pattern;
|
||||||
|
std::string StrictPattern;
|
||||||
|
std::string Description;
|
||||||
|
bool HasArgument;
|
||||||
|
|
||||||
|
TextElement() = default;
|
||||||
|
TextElement(uint8_t id, std::string token, bool arg,
|
||||||
|
std::string description) {
|
||||||
|
ID = id;
|
||||||
|
Token = token;
|
||||||
|
if (arg) {
|
||||||
|
GenericToken = absl::StrFormat("[%s:##]", Token);
|
||||||
|
} else {
|
||||||
|
GenericToken = absl::StrFormat("[%s]", Token);
|
||||||
|
}
|
||||||
|
HasArgument = arg;
|
||||||
|
Description = description;
|
||||||
|
Pattern =
|
||||||
|
arg ? "\\[" + Token + ":?([0-9A-F]{1,2})\\]" : "\\[" + Token + "\\]";
|
||||||
|
Pattern = absl::StrReplaceAll(Pattern, {{"[", "\\["}, {"]", "\\]"}});
|
||||||
|
StrictPattern = absl::StrCat("^", Pattern, "$");
|
||||||
|
StrictPattern = "^" + Pattern + "$";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetParameterizedToken(uint8_t value = 0) {
|
||||||
|
if (HasArgument) {
|
||||||
|
return absl::StrFormat("[%s:%02X]", Token, value);
|
||||||
|
} else {
|
||||||
|
return absl::StrFormat("[%s]", Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToString() {
|
||||||
|
return absl::StrFormat("%s %s", GenericToken, Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::smatch MatchMe(std::string dfrag) const {
|
||||||
|
std::regex pattern(StrictPattern);
|
||||||
|
std::smatch match;
|
||||||
|
std::regex_match(dfrag, match, pattern);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Empty() { return ID == 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedElement {
|
||||||
|
TextElement Parent;
|
||||||
|
uint8_t Value;
|
||||||
|
bool Active = false;
|
||||||
|
|
||||||
|
ParsedElement() = default;
|
||||||
|
ParsedElement(TextElement textElement, uint8_t value) {
|
||||||
|
Parent = textElement;
|
||||||
|
Value = value;
|
||||||
|
Active = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
|
||||||
770
src/app/editor/message/message_editor.cc
Normal file
770
src/app/editor/message/message_editor.cc
Normal file
@@ -0,0 +1,770 @@
|
|||||||
|
#include "message_editor.h"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_replace.h"
|
||||||
|
#include "absl/strings/str_split.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::Begin;
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::End;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::InputText;
|
||||||
|
using ImGui::InputTextMultiline;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
using ImGui::TextWrapped;
|
||||||
|
using ImGui::TreeNode;
|
||||||
|
|
||||||
|
static ParsedElement FindMatchingElement(string str) {
|
||||||
|
std::smatch match;
|
||||||
|
for (auto& textElement : TextCommands) {
|
||||||
|
match = textElement.MatchMe(str);
|
||||||
|
if (match.size() > 0) {
|
||||||
|
if (textElement.HasArgument) {
|
||||||
|
return ParsedElement(textElement,
|
||||||
|
std::stoi(match[1].str(), nullptr, 16));
|
||||||
|
} else {
|
||||||
|
return ParsedElement(textElement, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match = DictionaryElement.MatchMe(str);
|
||||||
|
if (match.size() > 0) {
|
||||||
|
return ParsedElement(DictionaryElement,
|
||||||
|
DICTOFF + std::stoi(match[1].str(), nullptr, 16));
|
||||||
|
}
|
||||||
|
return ParsedElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
static string ReplaceAllDictionaryWords(string str) {
|
||||||
|
string temp = str;
|
||||||
|
for (const auto& entry : AllDictionaries) {
|
||||||
|
if (absl::StrContains(temp, entry.Contents)) {
|
||||||
|
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> ParseMessageToData(string str) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
string tempString = str;
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
while (pos < tempString.size()) {
|
||||||
|
// Get next text fragment.
|
||||||
|
if (tempString[pos] == '[') {
|
||||||
|
int next = tempString.find(']', pos);
|
||||||
|
if (next == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedElement parsedElement =
|
||||||
|
FindMatchingElement(tempString.substr(pos, next - pos + 1));
|
||||||
|
if (!parsedElement.Active) {
|
||||||
|
break; // TODO: handle badness.
|
||||||
|
// } else if (parsedElement.Parent == DictionaryElement) {
|
||||||
|
// bytes.push_back(parsedElement.Value);
|
||||||
|
} else {
|
||||||
|
bytes.push_back(parsedElement.Parent.ID);
|
||||||
|
|
||||||
|
if (parsedElement.Parent.HasArgument) {
|
||||||
|
bytes.push_back(parsedElement.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = next + 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
uint8_t bb = MessageEditor::FindMatchingCharacter(tempString[pos++]);
|
||||||
|
|
||||||
|
if (bb != 0xFF) {
|
||||||
|
// TODO: handle badness.
|
||||||
|
bytes.push_back(bb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Update() {
|
||||||
|
if (rom()->is_loaded() && !data_loaded_) {
|
||||||
|
RETURN_IF_ERROR(Initialize());
|
||||||
|
CurrentMessage = ListOfTexts[1];
|
||||||
|
data_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTable("##MessageEditor", 3,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
|
||||||
|
TableSetupColumn("List");
|
||||||
|
TableSetupColumn("Contents");
|
||||||
|
TableSetupColumn("Commands");
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawMessageList();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawCurrentMessage();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawTextCommands();
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawMessageList() {
|
||||||
|
if (InputText("Search", &search_text_)) {
|
||||||
|
DisplayedMessages.clear();
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
if (absl::StrContains(message.ContentsParsed, search_text_)) {
|
||||||
|
DisplayedMessages.push_back(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginChild("##MessagesList", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
if (BeginTable("##MessagesTable", 3,
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders |
|
||||||
|
ImGuiTableFlags_Resizable)) {
|
||||||
|
TableSetupColumn("ID");
|
||||||
|
TableSetupColumn("Contents");
|
||||||
|
TableSetupColumn("Data");
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(core::UppercaseHexWord(message.ID).c_str())) {
|
||||||
|
CurrentMessage = message;
|
||||||
|
DrawMessagePreview();
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
TextWrapped("%s", ParsedMessages[message.ID].c_str());
|
||||||
|
TableNextColumn();
|
||||||
|
TextWrapped(
|
||||||
|
"%s",
|
||||||
|
core::UppercaseHexLong(ListOfTexts[message.ID].Address).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawCurrentMessage() {
|
||||||
|
Button(absl::StrCat("Message ", CurrentMessage.ID).c_str());
|
||||||
|
if (InputTextMultiline("##MessageEditor", &ParsedMessages[CurrentMessage.ID],
|
||||||
|
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
|
||||||
|
CurrentMessage.Data = ParseMessageToData(message_text_box_.text);
|
||||||
|
DrawMessagePreview();
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
Text("Font Graphics");
|
||||||
|
gui::BeginPadding(1);
|
||||||
|
BeginChild("MessageEditorCanvas", ImVec2(0, 130));
|
||||||
|
font_gfx_canvas_.DrawBackground();
|
||||||
|
font_gfx_canvas_.DrawContextMenu();
|
||||||
|
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
|
||||||
|
font_gfx_canvas_.DrawGrid();
|
||||||
|
font_gfx_canvas_.DrawOverlay();
|
||||||
|
EndChild();
|
||||||
|
gui::EndPadding();
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
Text("Message Preview");
|
||||||
|
if (Button("Refresh Bitmap")) {
|
||||||
|
rom()->UpdateBitmap(¤t_font_gfx16_bitmap_);
|
||||||
|
}
|
||||||
|
gui::BeginPadding(1);
|
||||||
|
BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
current_font_gfx16_canvas_.DrawBackground();
|
||||||
|
gui::EndPadding();
|
||||||
|
current_font_gfx16_canvas_.DrawContextMenu();
|
||||||
|
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
|
||||||
|
current_font_gfx16_canvas_.DrawGrid();
|
||||||
|
current_font_gfx16_canvas_.DrawOverlay();
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawTextCommands() {
|
||||||
|
if (BeginChild("##TextCommands", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
for (const auto& text_element : TextCommands) {
|
||||||
|
if (Button(text_element.GenericToken.c_str())) {
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
TextWrapped("%s", text_element.Description.c_str());
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Initialize() {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
width_array[i] = rom()->data()[kCharactersWidth + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildDictionaryEntries();
|
||||||
|
ReadAllTextData();
|
||||||
|
|
||||||
|
font_preview_colors_.AddColor(0x7FFF); // White
|
||||||
|
font_preview_colors_.AddColor(0x7C00); // Red
|
||||||
|
font_preview_colors_.AddColor(0x03E0); // Green
|
||||||
|
font_preview_colors_.AddColor(0x001F); // Blue
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(0x4000, 0);
|
||||||
|
for (int i = 0; i < 0x4000; i++) {
|
||||||
|
data[i] = rom()->data()[kGfxFont + i];
|
||||||
|
}
|
||||||
|
font_gfx16_data = gfx::SnesTo8bppSheet(data, /*bpp=*/2);
|
||||||
|
|
||||||
|
// 4bpp
|
||||||
|
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
|
||||||
|
128, 128, 8, font_gfx16_data, font_gfx_bitmap_, font_preview_colors_))
|
||||||
|
|
||||||
|
current_font_gfx16_data_.reserve(172 * 4096);
|
||||||
|
for (int i = 0; i < 172 * 4096; i++) {
|
||||||
|
current_font_gfx16_data_.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8bpp
|
||||||
|
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
|
||||||
|
172, 4096, 64, current_font_gfx16_data_, current_font_gfx16_bitmap_,
|
||||||
|
font_preview_colors_))
|
||||||
|
|
||||||
|
gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
|
||||||
|
for (int i = 0; i < font_preview_colors_.size(); i++) {
|
||||||
|
*color_palette.mutable_color(i) = font_preview_colors_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
*font_gfx_bitmap_.mutable_palette() = color_palette;
|
||||||
|
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
DisplayedMessages.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& each_message : ListOfTexts) {
|
||||||
|
// Each string has a [:XX] char encoded
|
||||||
|
// The corresponding character is found in CharEncoder unordered_map
|
||||||
|
std::string parsed_message = "";
|
||||||
|
for (const auto& byte : each_message.Data) {
|
||||||
|
// Find the char byte in the CharEncoder map
|
||||||
|
if (CharEncoder.contains(byte)) {
|
||||||
|
parsed_message.push_back(CharEncoder.at(byte));
|
||||||
|
} else {
|
||||||
|
// If the byte is not found in the CharEncoder map, it is a command
|
||||||
|
// or a dictionary entry
|
||||||
|
if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
|
||||||
|
// Dictionary entry
|
||||||
|
auto dictionaryEntry = GetDictionaryFromID(byte - DICTOFF);
|
||||||
|
parsed_message.append(dictionaryEntry.Contents);
|
||||||
|
} else {
|
||||||
|
// Command
|
||||||
|
TextElement textElement = FindMatchingCommand(byte);
|
||||||
|
if (!textElement.Empty()) {
|
||||||
|
// If the element is line 2, 3 or V we add a newline
|
||||||
|
if (textElement.ID == kScrollVertical || textElement.ID == kLine2 ||
|
||||||
|
textElement.ID == kLine3)
|
||||||
|
parsed_message.append("\n");
|
||||||
|
|
||||||
|
parsed_message.append(textElement.GenericToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParsedMessages.push_back(parsed_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawMessagePreview();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::BuildDictionaryEntries() {
|
||||||
|
for (int i = 0; i < 97; i++) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
std::stringstream stringBuilder;
|
||||||
|
|
||||||
|
int address = core::SnesToPc(
|
||||||
|
0x0E0000 + (rom()->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
|
||||||
|
rom()->data()[kPointersDictionaries + (i * 2)]);
|
||||||
|
|
||||||
|
int temppush_backress = core::SnesToPc(
|
||||||
|
0x0E0000 +
|
||||||
|
(rom()->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
|
||||||
|
rom()->data()[kPointersDictionaries + ((i + 1) * 2)]);
|
||||||
|
|
||||||
|
while (address < temppush_backress) {
|
||||||
|
uint8_t uint8_tDictionary = rom()->data()[address++];
|
||||||
|
bytes.push_back(uint8_tDictionary);
|
||||||
|
stringBuilder << ParseTextDataByte(uint8_tDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllDictionaries[i] = DictionaryEntry{(uint8_t)i, stringBuilder.str()};
|
||||||
|
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllDictionaries.OrderByDescending(dictionary = > dictionary.Length);
|
||||||
|
AllDictionaries[0].Length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::ReadAllTextData() {
|
||||||
|
int messageID = 0;
|
||||||
|
uint8_t current_byte;
|
||||||
|
int pos = kTextData;
|
||||||
|
std::vector<uint8_t> temp_bytes_raw;
|
||||||
|
std::vector<uint8_t> temp_bytes_parsed;
|
||||||
|
|
||||||
|
std::string current_message_raw;
|
||||||
|
std::string current_message_parsed;
|
||||||
|
TextElement text_element;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
current_byte = rom()->data()[pos++];
|
||||||
|
|
||||||
|
if (current_byte == MESSAGETERMINATOR) {
|
||||||
|
auto message =
|
||||||
|
MessageData(messageID++, pos, current_message_raw, temp_bytes_raw,
|
||||||
|
current_message_parsed, temp_bytes_parsed);
|
||||||
|
|
||||||
|
ListOfTexts.push_back(message);
|
||||||
|
|
||||||
|
temp_bytes_raw.clear();
|
||||||
|
temp_bytes_parsed.clear();
|
||||||
|
current_message_raw.clear();
|
||||||
|
current_message_parsed.clear();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if (current_byte == 0xFF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_bytes_raw.push_back(current_byte);
|
||||||
|
|
||||||
|
// Check for command.
|
||||||
|
text_element = FindMatchingCommand(current_byte);
|
||||||
|
|
||||||
|
if (!text_element.Empty()) {
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
if (text_element.HasArgument) {
|
||||||
|
current_byte = rom()->data()[pos++];
|
||||||
|
temp_bytes_raw.push_back(current_byte);
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_message_raw.append(
|
||||||
|
text_element.GetParameterizedToken(current_byte));
|
||||||
|
current_message_parsed.append(
|
||||||
|
text_element.GetParameterizedToken(current_byte));
|
||||||
|
|
||||||
|
if (text_element.Token == BANKToken) {
|
||||||
|
pos = kTextData2;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for special characters.
|
||||||
|
text_element = FindMatchingSpecial(current_byte);
|
||||||
|
|
||||||
|
if (!text_element.Empty()) {
|
||||||
|
current_message_raw.append(text_element.GetParameterizedToken());
|
||||||
|
current_message_parsed.append(text_element.GetParameterizedToken());
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dictionary.
|
||||||
|
int dictionary = FindDictionaryEntry(current_byte);
|
||||||
|
|
||||||
|
if (dictionary >= 0) {
|
||||||
|
current_message_raw.append("[");
|
||||||
|
current_message_raw.append(DICTIONARYTOKEN);
|
||||||
|
current_message_raw.append(":");
|
||||||
|
current_message_raw.append(core::UppercaseHexWord(dictionary));
|
||||||
|
current_message_raw.append("]");
|
||||||
|
|
||||||
|
uint32_t address = core::Get24LocalFromPC(
|
||||||
|
rom()->data(), kPointersDictionaries + (dictionary * 2));
|
||||||
|
uint32_t address_end = core::Get24LocalFromPC(
|
||||||
|
rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));
|
||||||
|
|
||||||
|
for (uint32_t i = address; i < address_end; i++) {
|
||||||
|
temp_bytes_parsed.push_back(rom()->data()[i]);
|
||||||
|
current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else.
|
||||||
|
if (CharEncoder.contains(current_byte)) {
|
||||||
|
std::string str = "";
|
||||||
|
str.push_back(CharEncoder.at(current_byte));
|
||||||
|
current_message_raw.append(str);
|
||||||
|
current_message_parsed.append(str);
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextElement MessageEditor::FindMatchingCommand(uint8_t b) {
|
||||||
|
TextElement empty_element;
|
||||||
|
for (const auto text_element : TextCommands) {
|
||||||
|
if (text_element.ID == b) {
|
||||||
|
return text_element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return empty_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextElement MessageEditor::FindMatchingSpecial(uint8_t value) {
|
||||||
|
TextElement empty_element;
|
||||||
|
for (const auto text_element : SpecialChars) {
|
||||||
|
if (text_element.ID == value) {
|
||||||
|
return text_element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return empty_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEditor::DictionaryEntry MessageEditor::GetDictionaryFromID(
|
||||||
|
uint8_t value) {
|
||||||
|
if (value < 0 || value >= AllDictionaries.size()) {
|
||||||
|
return DictionaryEntry();
|
||||||
|
}
|
||||||
|
return AllDictionaries[value];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MessageEditor::FindDictionaryEntry(uint8_t value) {
|
||||||
|
if (value < DICTOFF || value == 0xFF) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value - DICTOFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MessageEditor::FindMatchingCharacter(char value) {
|
||||||
|
for (const auto [key, char_value] : CharEncoder) {
|
||||||
|
if (value == char_value) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
string MessageEditor::ParseTextDataByte(uint8_t value) {
|
||||||
|
if (CharEncoder.contains(value)) {
|
||||||
|
char c = CharEncoder.at(value);
|
||||||
|
string str = "";
|
||||||
|
str.push_back(c);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for command.
|
||||||
|
TextElement textElement = FindMatchingCommand(value);
|
||||||
|
if (!textElement.Empty()) {
|
||||||
|
return textElement.GenericToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for special characters.
|
||||||
|
textElement = FindMatchingSpecial(value);
|
||||||
|
if (!textElement.Empty()) {
|
||||||
|
return textElement.GenericToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dictionary.
|
||||||
|
int dictionary = FindDictionaryEntry(value);
|
||||||
|
if (dictionary >= 0) {
|
||||||
|
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
|
||||||
|
int sizex, int sizey) {
|
||||||
|
int drawid = srcx + (srcy * 32);
|
||||||
|
for (int yl = 0; yl < sizey * 8; yl++) {
|
||||||
|
for (int xl = 0; xl < 4; xl++) {
|
||||||
|
int mx = xl;
|
||||||
|
int my = yl;
|
||||||
|
|
||||||
|
// Formula information to get tile index position in the array.
|
||||||
|
// ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
|
||||||
|
int tx = ((drawid / 16) * 512) + ((drawid - ((drawid / 16) * 16)) * 4);
|
||||||
|
uint8_t pixel = font_gfx16_data[tx + (yl * 64) + xl];
|
||||||
|
|
||||||
|
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
|
||||||
|
// position
|
||||||
|
int index = x + (y * 172) + (mx * 2) + (my * 172);
|
||||||
|
if ((pixel & 0x0F) != 0) {
|
||||||
|
current_font_gfx16_data_[index + 1] =
|
||||||
|
(uint8_t)((pixel & 0x0F) + (0 * 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((pixel >> 4) & 0x0F) != 0) {
|
||||||
|
current_font_gfx16_data_[index + 0] =
|
||||||
|
(uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawStringToPreview(string str) {
|
||||||
|
for (const auto c : str) {
|
||||||
|
DrawCharacterToPreview(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawCharacterToPreview(char c) {
|
||||||
|
DrawCharacterToPreview(FindMatchingCharacter(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
|
||||||
|
for (const uint8_t& value : text) {
|
||||||
|
if (skip_next) {
|
||||||
|
skip_next = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 100) {
|
||||||
|
int srcy = value / 16;
|
||||||
|
int srcx = value - (value & (~0xF));
|
||||||
|
|
||||||
|
if (text_pos >= 170) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawTileToPreview(text_pos, text_line * 16, srcx, srcy, 0, 1, 2);
|
||||||
|
text_pos += width_array[value];
|
||||||
|
} else if (value == kLine1) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line = 0;
|
||||||
|
} else if (value == kScrollVertical) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line += 1;
|
||||||
|
} else if (value == kLine2) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line = 1;
|
||||||
|
} else if (value == kLine3) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line = 2;
|
||||||
|
} else if (value == 0x6B || value == 0x6D || value == 0x6E ||
|
||||||
|
value == 0x77 || value == 0x78 || value == 0x79 ||
|
||||||
|
value == 0x7A) {
|
||||||
|
skip_next = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if (value == 0x6C) // BCD numbers.
|
||||||
|
{
|
||||||
|
DrawCharacterToPreview('0');
|
||||||
|
skip_next = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if (value == 0x6A) {
|
||||||
|
// Includes parentheses to be longer, since player names can be up to 6
|
||||||
|
// characters.
|
||||||
|
DrawStringToPreview("(NAME)");
|
||||||
|
} else if (value >= DICTOFF && value < (DICTOFF + 97)) {
|
||||||
|
auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
|
||||||
|
DrawCharacterToPreview(dictionaryEntry.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawMessagePreview() // From Parsing.
|
||||||
|
{
|
||||||
|
text_line = 0;
|
||||||
|
for (int i = 0; i < (172 * 4096); i++) {
|
||||||
|
current_font_gfx16_data_[i] = 0;
|
||||||
|
}
|
||||||
|
text_pos = 0;
|
||||||
|
DrawCharacterToPreview(CurrentMessage.Data);
|
||||||
|
shown_lines = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Cut() {
|
||||||
|
// Ensure that text is currently selected in the text box.
|
||||||
|
if (!message_text_box_.text.empty()) {
|
||||||
|
// Cut the selected text in the control and paste it into the Clipboard.
|
||||||
|
message_text_box_.Cut();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Paste() {
|
||||||
|
// Determine if there is any text in the Clipboard to paste into the
|
||||||
|
if (ImGui::GetClipboardText() != nullptr) {
|
||||||
|
// Paste the text from the Clipboard into the text box.
|
||||||
|
message_text_box_.Paste();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Copy() {
|
||||||
|
// Ensure that text is selected in the text box.
|
||||||
|
if (message_text_box_.selection_length > 0) {
|
||||||
|
// Copy the selected text to the Clipboard.
|
||||||
|
message_text_box_.Copy();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Undo() {
|
||||||
|
// Determine if last operation can be undone in text box.
|
||||||
|
if (message_text_box_.can_undo) {
|
||||||
|
// Undo the last operation.
|
||||||
|
message_text_box_.Undo();
|
||||||
|
|
||||||
|
// clear the undo buffer to prevent last action from being redone.
|
||||||
|
message_text_box_.clearUndo();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Save() {
|
||||||
|
std::vector<uint8_t> backup = rom()->vector();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
RETURN_IF_ERROR(rom()->Write(kCharactersWidth + i, width_array[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = kTextData;
|
||||||
|
bool in_second_bank = false;
|
||||||
|
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
for (const auto value : message.Data) {
|
||||||
|
RETURN_IF_ERROR(rom()->Write(pos, value));
|
||||||
|
|
||||||
|
if (value == kBlockTerminator) {
|
||||||
|
// Make sure we didn't go over the space available in the first block.
|
||||||
|
// 0x7FFF available.
|
||||||
|
if ((!in_second_bank & pos) > kTextDataEnd) {
|
||||||
|
return absl::InternalError(DisplayTextOverflowError(pos, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to the second block.
|
||||||
|
pos = kTextData2 - 1;
|
||||||
|
in_second_bank = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
rom()->Write(pos++, MESSAGETERMINATOR)); // , true, "Terminator text"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we didn't go over the space available for the second block.
|
||||||
|
// 0x14BF available.
|
||||||
|
if ((in_second_bank & pos) > kTextData2End) {
|
||||||
|
// rom()->data() = backup;
|
||||||
|
return absl::InternalError(DisplayTextOverflowError(pos, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(rom()->Write(pos, 0xFF)); // , true, "End of text"
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
|
||||||
|
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
|
||||||
|
string bankSTR = bank ? "1st" : "2nd";
|
||||||
|
string posSTR = bank ? absl::StrFormat("%X4", pos & 0xFFFF)
|
||||||
|
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
|
||||||
|
std::string message = absl::StrFormat(
|
||||||
|
"There is too much text data in the %s block to save.\n"
|
||||||
|
"Available: %X4 | Used: %s",
|
||||||
|
bankSTR, space, posSTR);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// push_backs a command to the text field when the push_back command button is
|
||||||
|
// pressed or the command is double clicked in the list.
|
||||||
|
void MessageEditor::InsertCommandButton_Click_1() {
|
||||||
|
// InsertSelectedText(
|
||||||
|
// TextCommands[TextCommandList.SelectedIndex].GetParameterizedToken(
|
||||||
|
// (uint8_t)ParamsBox.HexValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
// push_backs a special character to the text field when the push_back command
|
||||||
|
// button is pressed or the character is double clicked in the list.
|
||||||
|
void MessageEditor::InsertSpecialButton_Click() {
|
||||||
|
// InsertSelectedText(
|
||||||
|
// SpecialChars[SpecialsList.SelectedIndex].GetParameterizedToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::InsertSelectedText(string str) {
|
||||||
|
int textboxPos = message_text_box_.selection_start;
|
||||||
|
from_form = true;
|
||||||
|
// message_text_box_.Text = message_text_box_.Text.Insert(textboxPos, str);
|
||||||
|
from_form = false;
|
||||||
|
message_text_box_.selection_start = textboxPos + str.size();
|
||||||
|
message_text_box_.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::Delete() {
|
||||||
|
// Determine if any text is selected in the TextBox control.
|
||||||
|
if (message_text_box_.selection_length == 0) {
|
||||||
|
// clear all of the text in the textbox.
|
||||||
|
message_text_box_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::SelectAll() {
|
||||||
|
// Determine if any text is selected in the TextBox control.
|
||||||
|
if (message_text_box_.selection_length == 0) {
|
||||||
|
// Select all text in the text box.
|
||||||
|
message_text_box_.SelectAll();
|
||||||
|
|
||||||
|
// Move the cursor to the text box.
|
||||||
|
message_text_box_.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
366
src/app/editor/message/message_editor.h
Normal file
366
src/app/editor/message/message_editor.h
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_replace.h"
|
||||||
|
#include "absl/strings/str_split.h"
|
||||||
|
#include "app/editor/message/message_data.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
// TEXT EDITOR RELATED CONSTANTS
|
||||||
|
const int kGfxFont = 0x70000; // 2bpp format
|
||||||
|
const int kTextData = 0xE0000;
|
||||||
|
const int kTextDataEnd = 0xE7FFF;
|
||||||
|
const int kTextData2 = 0x75F40;
|
||||||
|
const int kTextData2End = 0x773FF;
|
||||||
|
const int kPointersDictionaries = 0x74703;
|
||||||
|
const int kCharactersWidth = 0x74ADF;
|
||||||
|
|
||||||
|
const string DICTIONARYTOKEN = "D";
|
||||||
|
const uint8_t DICTOFF = 0x88;
|
||||||
|
const string BANKToken = "BANK";
|
||||||
|
const uint8_t BANKID = 0x80;
|
||||||
|
|
||||||
|
constexpr uint8_t kBlockTerminator = 0x80;
|
||||||
|
|
||||||
|
static std::vector<uint8_t> ParseMessageToData(string str);
|
||||||
|
|
||||||
|
static ParsedElement FindMatchingElement(string str);
|
||||||
|
|
||||||
|
constexpr uint8_t kScrollVertical = 0x73;
|
||||||
|
constexpr uint8_t kLine1 = 0x74;
|
||||||
|
constexpr uint8_t kLine2 = 0x75;
|
||||||
|
constexpr uint8_t kLine3 = 0x76;
|
||||||
|
|
||||||
|
static const TextElement TextCommands[] = {
|
||||||
|
TextElement(0x6B, "W", true, "Window border"),
|
||||||
|
TextElement(0x6D, "P", true, "Window position"),
|
||||||
|
TextElement(0x6E, "SPD", true, "Scroll speed"),
|
||||||
|
TextElement(0x7A, "S", true, "Text draw speed"),
|
||||||
|
TextElement(0x77, "C", true, "Text color"),
|
||||||
|
TextElement(0x6A, "L", false, "Player name"),
|
||||||
|
TextElement(0x74, "1", false, "Line 1"),
|
||||||
|
TextElement(0x75, "2", false, "Line 2"),
|
||||||
|
TextElement(0x76, "3", false, "Line 3"),
|
||||||
|
TextElement(0x7E, "K", false, "Wait for key"),
|
||||||
|
TextElement(0x73, "V", false, "Scroll text"),
|
||||||
|
TextElement(0x78, "WT", true, "Delay X"),
|
||||||
|
TextElement(0x6C, "N", true, "BCD number"),
|
||||||
|
TextElement(0x79, "SFX", true, "Sound effect"),
|
||||||
|
TextElement(0x71, "CH3", false, "Choose 3"),
|
||||||
|
TextElement(0x72, "CH2", false, "Choose 2 high"),
|
||||||
|
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
|
||||||
|
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
|
||||||
|
TextElement(0x69, "CHI", false, "Choose item"),
|
||||||
|
TextElement(0x67, "IMG", false, "Next attract image"),
|
||||||
|
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
|
||||||
|
TextElement(0x70, "NONO", false, "Crash"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<TextElement> SpecialChars = {
|
||||||
|
TextElement(0x43, "...", false, "Ellipsis …"),
|
||||||
|
TextElement(0x4D, "UP", false, "Arrow ↑"),
|
||||||
|
TextElement(0x4E, "DOWN", false, "Arrow ↓"),
|
||||||
|
TextElement(0x4F, "LEFT", false, "Arrow ←"),
|
||||||
|
TextElement(0x50, "RIGHT", false, "Arrow →"),
|
||||||
|
TextElement(0x5B, "A", false, "Button Ⓐ"),
|
||||||
|
TextElement(0x5C, "B", false, "Button Ⓑ"),
|
||||||
|
TextElement(0x5D, "X", false, "Button ⓧ"),
|
||||||
|
TextElement(0x5E, "Y", false, "Button ⓨ"),
|
||||||
|
TextElement(0x52, "HP1L", false, "1 HP left"),
|
||||||
|
TextElement(0x53, "HP1R", false, "1 HP right"),
|
||||||
|
TextElement(0x54, "HP2L", false, "2 HP left"),
|
||||||
|
TextElement(0x55, "HP3L", false, "3 HP left"),
|
||||||
|
TextElement(0x56, "HP3R", false, "3 HP right"),
|
||||||
|
TextElement(0x57, "HP4L", false, "4 HP left"),
|
||||||
|
TextElement(0x58, "HP4R", false, "4 HP right"),
|
||||||
|
TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
|
||||||
|
TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
|
||||||
|
TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
|
||||||
|
TextElement(0x4A, "LFL", false, "Link face left"),
|
||||||
|
TextElement(0x4B, "LFR", false, "Link face right"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
|
||||||
|
{0x00, 'A'},
|
||||||
|
{0x01, 'B'},
|
||||||
|
{0x02, 'C'},
|
||||||
|
{0x03, 'D'},
|
||||||
|
{0x04, 'E'},
|
||||||
|
{0x05, 'F'},
|
||||||
|
{0x06, 'G'},
|
||||||
|
{0x07, 'H'},
|
||||||
|
{0x08, 'I'},
|
||||||
|
{0x09, 'J'},
|
||||||
|
{0x0A, 'K'},
|
||||||
|
{0x0B, 'L'},
|
||||||
|
{0x0C, 'M'},
|
||||||
|
{0x0D, 'N'},
|
||||||
|
{0x0E, 'O'},
|
||||||
|
{0x0F, 'P'},
|
||||||
|
{0x10, 'Q'},
|
||||||
|
{0x11, 'R'},
|
||||||
|
{0x12, 'S'},
|
||||||
|
{0x13, 'T'},
|
||||||
|
{0x14, 'U'},
|
||||||
|
{0x15, 'V'},
|
||||||
|
{0x16, 'W'},
|
||||||
|
{0x17, 'X'},
|
||||||
|
{0x18, 'Y'},
|
||||||
|
{0x19, 'Z'},
|
||||||
|
{0x1A, 'a'},
|
||||||
|
{0x1B, 'b'},
|
||||||
|
{0x1C, 'c'},
|
||||||
|
{0x1D, 'd'},
|
||||||
|
{0x1E, 'e'},
|
||||||
|
{0x1F, 'f'},
|
||||||
|
{0x20, 'g'},
|
||||||
|
{0x21, 'h'},
|
||||||
|
{0x22, 'i'},
|
||||||
|
{0x23, 'j'},
|
||||||
|
{0x24, 'k'},
|
||||||
|
{0x25, 'l'},
|
||||||
|
{0x26, 'm'},
|
||||||
|
{0x27, 'n'},
|
||||||
|
{0x28, 'o'},
|
||||||
|
{0x29, 'p'},
|
||||||
|
{0x2A, 'q'},
|
||||||
|
{0x2B, 'r'},
|
||||||
|
{0x2C, 's'},
|
||||||
|
{0x2D, 't'},
|
||||||
|
{0x2E, 'u'},
|
||||||
|
{0x2F, 'v'},
|
||||||
|
{0x30, 'w'},
|
||||||
|
{0x31, 'x'},
|
||||||
|
{0x32, 'y'},
|
||||||
|
{0x33, 'z'},
|
||||||
|
{0x34, '0'},
|
||||||
|
{0x35, '1'},
|
||||||
|
{0x36, '2'},
|
||||||
|
{0x37, '3'},
|
||||||
|
{0x38, '4'},
|
||||||
|
{0x39, '5'},
|
||||||
|
{0x3A, '6'},
|
||||||
|
{0x3B, '7'},
|
||||||
|
{0x3C, '8'},
|
||||||
|
{0x3D, '9'},
|
||||||
|
{0x3E, '!'},
|
||||||
|
{0x3F, '?'},
|
||||||
|
{0x40, '-'},
|
||||||
|
{0x41, '.'},
|
||||||
|
{0x42, ','},
|
||||||
|
{0x44, '>'},
|
||||||
|
{0x45, '('},
|
||||||
|
{0x46, ')'},
|
||||||
|
{0x4C, '"'},
|
||||||
|
{0x51, '\''},
|
||||||
|
{0x59, ' '},
|
||||||
|
{0x5A, '<'},
|
||||||
|
// {0x5F, '¡'}, {0x60, '¡'}, {0x61, '¡'}, {0x62, ' '}, {0x63, ' '}, {0x64,
|
||||||
|
// ' '},
|
||||||
|
{0x65, ' '},
|
||||||
|
{0x66, '_'},
|
||||||
|
};
|
||||||
|
|
||||||
|
static TextElement DictionaryElement =
|
||||||
|
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
|
||||||
|
|
||||||
|
class MessageEditor : public Editor, public SharedRom {
|
||||||
|
public:
|
||||||
|
struct DictionaryEntry {
|
||||||
|
uint8_t ID;
|
||||||
|
std::string Contents;
|
||||||
|
std::vector<uint8_t> Data;
|
||||||
|
int Length;
|
||||||
|
std::string Token;
|
||||||
|
|
||||||
|
DictionaryEntry() = default;
|
||||||
|
DictionaryEntry(uint8_t i, std::string s)
|
||||||
|
: Contents(s), ID(i), Length(s.length()) {
|
||||||
|
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
|
||||||
|
Data = ParseMessageToData(Contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContainedInString(std::string s) {
|
||||||
|
return s.find(Contents) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReplaceInstancesOfIn(std::string s) {
|
||||||
|
std::string replacedString = s;
|
||||||
|
size_t pos = replacedString.find(Contents);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
replacedString.replace(pos, Contents.length(), Token);
|
||||||
|
pos = replacedString.find(Contents, pos + Token.length());
|
||||||
|
}
|
||||||
|
return replacedString;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MessageEditor() { type_ = EditorType::kMessage; }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
void DrawMessageList();
|
||||||
|
void DrawCurrentMessage();
|
||||||
|
void DrawTextCommands();
|
||||||
|
|
||||||
|
absl::Status Initialize();
|
||||||
|
void ReadAllTextData();
|
||||||
|
void BuildDictionaryEntries();
|
||||||
|
|
||||||
|
absl::Status Cut() override;
|
||||||
|
absl::Status Copy() override;
|
||||||
|
absl::Status Paste() override;
|
||||||
|
absl::Status Undo() override;
|
||||||
|
absl::Status Redo() override {
|
||||||
|
return absl::UnimplementedError("Redo not implemented");
|
||||||
|
}
|
||||||
|
absl::Status Find() override {
|
||||||
|
return absl::UnimplementedError("Find not implemented");
|
||||||
|
}
|
||||||
|
absl::Status Save();
|
||||||
|
void Delete();
|
||||||
|
void SelectAll();
|
||||||
|
// void RegisterTests(ImGuiTestEngine* e) override;
|
||||||
|
|
||||||
|
TextElement FindMatchingCommand(uint8_t byte);
|
||||||
|
TextElement FindMatchingSpecial(uint8_t value);
|
||||||
|
string ParseTextDataByte(uint8_t value);
|
||||||
|
DictionaryEntry GetDictionaryFromID(uint8_t value);
|
||||||
|
|
||||||
|
static uint8_t FindDictionaryEntry(uint8_t value);
|
||||||
|
static uint8_t FindMatchingCharacter(char value);
|
||||||
|
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
|
||||||
|
int sizex = 1, int sizey = 1);
|
||||||
|
void DrawCharacterToPreview(char c);
|
||||||
|
void DrawCharacterToPreview(const std::vector<uint8_t>& text);
|
||||||
|
|
||||||
|
void DrawStringToPreview(string str);
|
||||||
|
void DrawMessagePreview();
|
||||||
|
std::string DisplayTextOverflowError(int pos, bool bank);
|
||||||
|
|
||||||
|
void InsertCommandButton_Click_1();
|
||||||
|
void InsertSpecialButton_Click();
|
||||||
|
void InsertSelectedText(string str);
|
||||||
|
|
||||||
|
static const std::vector<DictionaryEntry> AllDicts;
|
||||||
|
|
||||||
|
uint8_t width_array[100];
|
||||||
|
string romname = "";
|
||||||
|
|
||||||
|
int text_line = 0;
|
||||||
|
int text_pos = 0;
|
||||||
|
int shown_lines = 0;
|
||||||
|
int selected_tile = 0;
|
||||||
|
|
||||||
|
bool skip_next = false;
|
||||||
|
bool from_form = false;
|
||||||
|
|
||||||
|
std::vector<MessageData> ListOfTexts;
|
||||||
|
std::vector<MessageData> DisplayedMessages;
|
||||||
|
std::vector<std::string> ParsedMessages;
|
||||||
|
|
||||||
|
MessageData CurrentMessage;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const TextElement DictionaryElement;
|
||||||
|
|
||||||
|
bool data_loaded_ = false;
|
||||||
|
int current_message_id_ = 0;
|
||||||
|
|
||||||
|
std::string search_text_ = "";
|
||||||
|
|
||||||
|
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)};
|
||||||
|
gui::Canvas current_font_gfx16_canvas_{"##CurrentMessageGfx",
|
||||||
|
ImVec2(172, 4096)};
|
||||||
|
|
||||||
|
gfx::Bitmap font_gfx_bitmap_;
|
||||||
|
gfx::Bitmap current_font_gfx16_bitmap_;
|
||||||
|
|
||||||
|
Bytes font_gfx16_data;
|
||||||
|
Bytes current_font_gfx16_data_;
|
||||||
|
|
||||||
|
gfx::SnesPalette font_preview_colors_;
|
||||||
|
|
||||||
|
struct TextBox {
|
||||||
|
std::string text;
|
||||||
|
std::string buffer;
|
||||||
|
int cursor_pos = 0;
|
||||||
|
int selection_start = 0;
|
||||||
|
int selection_end = 0;
|
||||||
|
int selection_length = 0;
|
||||||
|
bool has_selection = false;
|
||||||
|
bool has_focus = false;
|
||||||
|
bool changed = false;
|
||||||
|
bool can_undo = false;
|
||||||
|
|
||||||
|
void Undo() {
|
||||||
|
text = buffer;
|
||||||
|
cursor_pos = selection_start;
|
||||||
|
has_selection = false;
|
||||||
|
}
|
||||||
|
void clearUndo() { can_undo = false; }
|
||||||
|
void Copy() { ImGui::SetClipboardText(text.c_str()); }
|
||||||
|
void Cut() {
|
||||||
|
Copy();
|
||||||
|
text.erase(selection_start, selection_end - selection_start);
|
||||||
|
cursor_pos = selection_start;
|
||||||
|
has_selection = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
void Paste() {
|
||||||
|
text.erase(selection_start, selection_end - selection_start);
|
||||||
|
text.insert(selection_start, ImGui::GetClipboardText());
|
||||||
|
std::string str = ImGui::GetClipboardText();
|
||||||
|
cursor_pos = selection_start + str.size();
|
||||||
|
has_selection = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
void clear() {
|
||||||
|
text.clear();
|
||||||
|
buffer.clear();
|
||||||
|
cursor_pos = 0;
|
||||||
|
selection_start = 0;
|
||||||
|
selection_end = 0;
|
||||||
|
selection_length = 0;
|
||||||
|
has_selection = false;
|
||||||
|
has_focus = false;
|
||||||
|
changed = false;
|
||||||
|
can_undo = false;
|
||||||
|
}
|
||||||
|
void SelectAll() {
|
||||||
|
selection_start = 0;
|
||||||
|
selection_end = text.size();
|
||||||
|
selection_length = text.size();
|
||||||
|
has_selection = true;
|
||||||
|
}
|
||||||
|
void Focus() { has_focus = true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TextBox message_text_box_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<MessageEditor::DictionaryEntry> AllDictionaries;
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_MESSAGE_EDITOR_H
|
||||||
7
src/app/editor/message/message_editor_test.cc
Normal file
7
src/app/editor/message/message_editor_test.cc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "message_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
222
src/app/editor/music/music_editor.cc
Normal file
222
src/app/editor/music/music_editor.cc
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
#include "music_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.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 {
|
||||||
|
|
||||||
|
absl::Status MusicEditor::Update() {
|
||||||
|
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("Assembly");
|
||||||
|
ImGui::TableSetupColumn("Composition");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
assembly_editor_.InlineUpdate();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawToolset();
|
||||||
|
DrawChannels();
|
||||||
|
DrawPianoRoll();
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawChannels() {
|
||||||
|
if (ImGui::BeginTabBar("MyTabBar", ImGuiTabBarFlags_None)) {
|
||||||
|
for (int i = 1; i <= 8; ++i) {
|
||||||
|
if (ImGui::BeginTabItem(absl::StrFormat("%d", i).data())) {
|
||||||
|
DrawPianoStaff();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int NUM_KEYS = 25;
|
||||||
|
static bool keys[NUM_KEYS];
|
||||||
|
|
||||||
|
void MusicEditor::DrawPianoStaff() {
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
||||||
|
ImGui::BeginChild(child_id, ImVec2(0, 170), false)) {
|
||||||
|
const int NUM_LINES = 5;
|
||||||
|
const int LINE_THICKNESS = 2;
|
||||||
|
const int LINE_SPACING = 40;
|
||||||
|
|
||||||
|
// Get the draw list for the current window
|
||||||
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
// Draw the staff lines
|
||||||
|
ImVec2 canvas_p0 =
|
||||||
|
ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
|
||||||
|
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + ImGui::GetContentRegionAvail().y);
|
||||||
|
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(32, 32, 32, 255));
|
||||||
|
for (int i = 0; i < NUM_LINES; i++) {
|
||||||
|
auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING);
|
||||||
|
auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + i * LINE_SPACING);
|
||||||
|
draw_list->AddLine(line_start, line_end, IM_COL32(200, 200, 200, 255),
|
||||||
|
LINE_THICKNESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the ledger lines
|
||||||
|
const int NUM_LEDGER_LINES = 3;
|
||||||
|
for (int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) {
|
||||||
|
if (i % 2 == 0) continue; // skip every other line
|
||||||
|
auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2);
|
||||||
|
auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + i * LINE_SPACING / 2);
|
||||||
|
draw_list->AddLine(line_start, line_end, IM_COL32(150, 150, 150, 255),
|
||||||
|
LINE_THICKNESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawPianoRoll() {
|
||||||
|
// Render the piano roll
|
||||||
|
float key_width = ImGui::GetContentRegionAvail().x / NUM_KEYS;
|
||||||
|
float white_key_height = ImGui::GetContentRegionAvail().y * 0.8f;
|
||||||
|
float black_key_height = ImGui::GetContentRegionAvail().y * 0.5f;
|
||||||
|
ImGui::Text("Piano Roll");
|
||||||
|
ImGui::Separator();
|
||||||
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
// Draw the staff lines
|
||||||
|
ImVec2 canvas_p0 =
|
||||||
|
ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
|
||||||
|
ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
|
||||||
|
canvas_p0.y + ImGui::GetContentRegionAvail().y);
|
||||||
|
draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(200, 200, 200, 255));
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.f, 0.f));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.f);
|
||||||
|
for (int i = 0; i < NUM_KEYS; i++) {
|
||||||
|
// Calculate the position and size of the key
|
||||||
|
ImVec2 key_pos = ImVec2(i * key_width, 0.0f);
|
||||||
|
ImVec2 key_size;
|
||||||
|
ImVec4 key_color;
|
||||||
|
ImVec4 text_color;
|
||||||
|
if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 ||
|
||||||
|
i % 12 == 10) {
|
||||||
|
// This is a black key
|
||||||
|
key_size = ImVec2(key_width * 0.6f, black_key_height);
|
||||||
|
key_color = ImVec4(0, 0, 0, 255);
|
||||||
|
text_color = ImVec4(255, 255, 255, 255);
|
||||||
|
} else {
|
||||||
|
// This is a white key
|
||||||
|
key_size = ImVec2(key_width, white_key_height);
|
||||||
|
key_color = ImVec4(255, 255, 255, 255);
|
||||||
|
text_color = ImVec4(0, 0, 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushID(i);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, key_color);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
|
||||||
|
if (ImGui::Button(kSongNotes[i].data(), key_size)) {
|
||||||
|
keys[i] ^= 1;
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
ImVec2 button_pos = ImGui::GetItemRectMin();
|
||||||
|
ImVec2 button_size = ImGui::GetItemRectSize();
|
||||||
|
if (keys[i]) {
|
||||||
|
ImVec2 dest;
|
||||||
|
dest.x = button_pos.x + button_size.x;
|
||||||
|
dest.y = button_pos.y + button_size.y;
|
||||||
|
ImGui::GetWindowDrawList()->AddRectFilled(button_pos, dest,
|
||||||
|
IM_COL32(200, 200, 255, 200));
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
ImGui::SameLine();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawSongToolset() {
|
||||||
|
if (ImGui::BeginTable("DWToolset", 8, toolset_table_flags_, ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("#1");
|
||||||
|
ImGui::TableSetupColumn("#play");
|
||||||
|
ImGui::TableSetupColumn("#rewind");
|
||||||
|
ImGui::TableSetupColumn("#fastforward");
|
||||||
|
ImGui::TableSetupColumn("volumeController");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MusicEditor::DrawToolset() {
|
||||||
|
static bool is_playing = false;
|
||||||
|
static int selected_option = 0;
|
||||||
|
static int current_volume = 0;
|
||||||
|
static bool has_loaded_song = false;
|
||||||
|
const int MAX_VOLUME = 100;
|
||||||
|
|
||||||
|
if (is_playing && !has_loaded_song) {
|
||||||
|
has_loaded_song = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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", 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) {
|
||||||
|
has_loaded_song = false;
|
||||||
|
}
|
||||||
|
is_playing = !is_playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
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", ¤t_volume, 0, 100);
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
const int SONG_DURATION = 120; // duration of the song in seconds
|
||||||
|
static int current_time = 0; // current time in the song in seconds
|
||||||
|
|
||||||
|
// Display the current time in the song
|
||||||
|
gui::ItemLabel("Current Time: ", gui::ItemLabelFlags::Left);
|
||||||
|
ImGui::Text("%d:%02d", current_time / 60, current_time % 60);
|
||||||
|
ImGui::SameLine();
|
||||||
|
// Display the song duration/progress using a progress bar
|
||||||
|
ImGui::ProgressBar((float)current_time / SONG_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
104
src/app/editor/music/music_editor.h
Normal file
104
src/app/editor/music/music_editor.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.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 {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
static const char* kGameSongs[] = {"Title",
|
||||||
|
"Light World",
|
||||||
|
"Beginning",
|
||||||
|
"Rabbit",
|
||||||
|
"Forest",
|
||||||
|
"Intro",
|
||||||
|
"Town",
|
||||||
|
"Warp",
|
||||||
|
"Dark world",
|
||||||
|
"Master sword",
|
||||||
|
"File select",
|
||||||
|
"Soldier",
|
||||||
|
"Mountain",
|
||||||
|
"Shop",
|
||||||
|
"Fanfare",
|
||||||
|
"Castle",
|
||||||
|
"Palace (Pendant)",
|
||||||
|
"Cave (Same as Secret Way)",
|
||||||
|
"Clear (Dungeon end)",
|
||||||
|
"Church",
|
||||||
|
"Boss",
|
||||||
|
"Dungeon (Crystal)",
|
||||||
|
"Psychic",
|
||||||
|
"Secret Way (Same as Cave)",
|
||||||
|
"Rescue",
|
||||||
|
"Crystal",
|
||||||
|
"Fountain",
|
||||||
|
"Pyramid",
|
||||||
|
"Kill Agahnim",
|
||||||
|
"Ganon Room",
|
||||||
|
"Last Boss"};
|
||||||
|
|
||||||
|
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
|
||||||
|
* @brief A class for editing music data in a Rom.
|
||||||
|
*/
|
||||||
|
class MusicEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
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();
|
||||||
|
void DrawPianoStaff();
|
||||||
|
void DrawPianoRoll();
|
||||||
|
void DrawSongToolset();
|
||||||
|
void DrawToolset();
|
||||||
|
|
||||||
|
zelda3::music::Tracker music_tracker_;
|
||||||
|
|
||||||
|
// Mix_Music* current_song_ = NULL;
|
||||||
|
|
||||||
|
AssemblyEditor assembly_editor_;
|
||||||
|
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
||||||
|
ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit |
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_Reorderable;
|
||||||
|
|
||||||
|
ImGuiTableFlags channel_table_flags_ =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable |
|
||||||
|
ImGuiTableFlags_SortMulti | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV |
|
||||||
|
ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
490
src/app/editor/overworld/entity.cc
Normal file
490
src/app/editor/overworld/entity.cc
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
#include "app/editor/overworld/entity.h"
|
||||||
|
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginGroup;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Selectable;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
|
||||||
|
ImVec2 canvas_p0, ImVec2 scrolling) {
|
||||||
|
// Get the mouse position relative to the canvas
|
||||||
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
|
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||||
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||||
|
|
||||||
|
// Check if the mouse is hovering over the entity
|
||||||
|
if (mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 &&
|
||||||
|
mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool free_movement) {
|
||||||
|
// Get the mouse position relative to the canvas
|
||||||
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
|
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||||
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||||
|
|
||||||
|
// Calculate the new position on the 16x16 grid
|
||||||
|
int new_x = static_cast<int>(mouse_pos.x) / 16 * 16;
|
||||||
|
int new_y = static_cast<int>(mouse_pos.y) / 16 * 16;
|
||||||
|
if (free_movement) {
|
||||||
|
new_x = static_cast<int>(mouse_pos.x) / 8 * 8;
|
||||||
|
new_y = static_cast<int>(mouse_pos.y) / 8 * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the entity position
|
||||||
|
entity->set_x(new_x);
|
||||||
|
entity->set_y(new_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool &is_dragging_entity,
|
||||||
|
zelda3::OverworldEntity *&dragged_entity,
|
||||||
|
zelda3::OverworldEntity *¤t_entity,
|
||||||
|
bool free_movement) {
|
||||||
|
std::string entity_type = "Entity";
|
||||||
|
if (entity->type_ == zelda3::OverworldEntity::EntityType::kExit) {
|
||||||
|
entity_type = "Exit";
|
||||||
|
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kEntrance) {
|
||||||
|
entity_type = "Entrance";
|
||||||
|
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kSprite) {
|
||||||
|
entity_type = "Sprite";
|
||||||
|
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kItem) {
|
||||||
|
entity_type = "Item";
|
||||||
|
}
|
||||||
|
const auto is_hovering =
|
||||||
|
IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling);
|
||||||
|
|
||||||
|
const auto drag_or_clicked = ImGui::IsMouseDragging(ImGuiMouseButton_Left) ||
|
||||||
|
ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||||
|
|
||||||
|
if (is_hovering && drag_or_clicked && !is_dragging_entity) {
|
||||||
|
dragged_entity = entity;
|
||||||
|
is_dragging_entity = true;
|
||||||
|
} else if (is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
current_entity = entity;
|
||||||
|
ImGui::OpenPopup(absl::StrFormat("%s editor", entity_type.c_str()).c_str());
|
||||||
|
} else if (is_dragging_entity && dragged_entity == entity &&
|
||||||
|
ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||||
|
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
|
||||||
|
entity->UpdateMapProperties(entity->map_id_);
|
||||||
|
is_dragging_entity = false;
|
||||||
|
dragged_entity = nullptr;
|
||||||
|
} else if (is_dragging_entity && dragged_entity == entity) {
|
||||||
|
if (ImGui::BeginDragDropSource()) {
|
||||||
|
ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
|
||||||
|
sizeof(zelda3::OverworldEntity));
|
||||||
|
Text("Moving %s ID: %s", entity_type.c_str(),
|
||||||
|
core::UppercaseHexByte(entity->entity_id_).c_str());
|
||||||
|
ImGui::EndDragDropSource();
|
||||||
|
}
|
||||||
|
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
|
||||||
|
entity->x_ = dragged_entity->x_;
|
||||||
|
entity->y_ = dragged_entity->y_;
|
||||||
|
entity->UpdateMapProperties(entity->map_id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawEntranceInserterPopup() {
|
||||||
|
bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("Entrance Inserter")) {
|
||||||
|
static int entrance_id = 0;
|
||||||
|
gui::InputHex("Entrance ID", &entrance_id);
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldEntrance objects, currently only hides them
|
||||||
|
bool DrawOverworldEntrancePopup(
|
||||||
|
zelda3::overworld::OverworldEntrance &entrance) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Entrance editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
gui::InputHex("Map ID", &entrance.map_id_);
|
||||||
|
gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
|
||||||
|
kInputFieldSize + 20);
|
||||||
|
gui::InputHex("X", &entrance.x_);
|
||||||
|
gui::InputHex("Y", &entrance.y_);
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
entrance.deleted = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldExit objects
|
||||||
|
void DrawExitInserterPopup() {
|
||||||
|
if (ImGui::BeginPopup("Exit Inserter")) {
|
||||||
|
static int exit_id = 0;
|
||||||
|
gui::InputHex("Exit ID", &exit_id);
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Exit editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
// Normal door: None = 0, Wooden = 1, Bombable = 2
|
||||||
|
static int doorType = exit.door_type_1_;
|
||||||
|
// Fancy door: None = 0, Sanctuary = 1, Palace = 2
|
||||||
|
static int fancyDoorType = exit.door_type_2_;
|
||||||
|
|
||||||
|
static int xPos = 0;
|
||||||
|
static int yPos = 0;
|
||||||
|
|
||||||
|
// Special overworld exit properties
|
||||||
|
static int centerY = 0;
|
||||||
|
static int centerX = 0;
|
||||||
|
static int unk1 = 0;
|
||||||
|
static int unk2 = 0;
|
||||||
|
static int linkPosture = 0;
|
||||||
|
static int spriteGFX = 0;
|
||||||
|
static int bgGFX = 0;
|
||||||
|
static int palette = 0;
|
||||||
|
static int sprPal = 0;
|
||||||
|
static int top = 0;
|
||||||
|
static int bottom = 0;
|
||||||
|
static int left = 0;
|
||||||
|
static int right = 0;
|
||||||
|
static int leftEdgeOfMap = 0;
|
||||||
|
|
||||||
|
gui::InputHexWord("Room", &exit.room_id_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHex("Entity ID", &exit.entity_id_, 4);
|
||||||
|
gui::InputHex("Map", &exit.map_id_);
|
||||||
|
SameLine();
|
||||||
|
Checkbox("Automatic", &exit.is_automatic_);
|
||||||
|
|
||||||
|
gui::InputHex("X Positon", &exit.x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHex("Y Position", &exit.y_);
|
||||||
|
|
||||||
|
gui::InputHexByte("X Camera", &exit.x_camera_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("Y Camera", &exit.y_camera_);
|
||||||
|
|
||||||
|
gui::InputHexWord("X Scroll", &exit.x_scroll_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Y Scroll", &exit.y_scroll_);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
static bool show_properties = false;
|
||||||
|
Checkbox("Show properties", &show_properties);
|
||||||
|
if (show_properties) {
|
||||||
|
Text("Deleted? %s", exit.deleted_ ? "true" : "false");
|
||||||
|
Text("Hole? %s", exit.is_hole_ ? "true" : "false");
|
||||||
|
Text("Large Map? %s", exit.large_map_ ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
gui::TextWithSeparators("Unimplemented below");
|
||||||
|
|
||||||
|
ImGui::RadioButton("None", &doorType, 0);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Wooden", &doorType, 1);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Bombable", &doorType, 2);
|
||||||
|
// If door type is not None, input positions
|
||||||
|
if (doorType != 0) {
|
||||||
|
gui::InputHex("Door X pos", &xPos);
|
||||||
|
gui::InputHex("Door Y pos", &yPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::RadioButton("None##Fancy", &fancyDoorType, 0);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Sanctuary", &fancyDoorType, 1);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Palace", &fancyDoorType, 2);
|
||||||
|
// If fancy door type is not None, input positions
|
||||||
|
if (fancyDoorType != 0) {
|
||||||
|
// Placeholder for fancy door's X position
|
||||||
|
gui::InputHex("Fancy Door X pos", &xPos);
|
||||||
|
// Placeholder for fancy door's Y position
|
||||||
|
gui::InputHex("Fancy Door Y pos", &yPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool special_exit = false;
|
||||||
|
Checkbox("Special exit", &special_exit);
|
||||||
|
if (special_exit) {
|
||||||
|
gui::InputHex("Center X", ¢erX);
|
||||||
|
|
||||||
|
gui::InputHex("Center Y", ¢erY);
|
||||||
|
gui::InputHex("Unk1", &unk1);
|
||||||
|
gui::InputHex("Unk2", &unk2);
|
||||||
|
|
||||||
|
gui::InputHex("Link's posture", &linkPosture);
|
||||||
|
gui::InputHex("Sprite GFX", &spriteGFX);
|
||||||
|
gui::InputHex("BG GFX", &bgGFX);
|
||||||
|
gui::InputHex("Palette", &palette);
|
||||||
|
gui::InputHex("Spr Pal", &sprPal);
|
||||||
|
|
||||||
|
gui::InputHex("Top", &top);
|
||||||
|
gui::InputHex("Bottom", &bottom);
|
||||||
|
gui::InputHex("Left", &left);
|
||||||
|
gui::InputHex("Right", &right);
|
||||||
|
|
||||||
|
gui::InputHex("Left edge of map", &leftEdgeOfMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
exit.deleted_ = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawItemInsertPopup() {
|
||||||
|
// Contents of the Context Menu
|
||||||
|
if (ImGui::BeginPopup("Item Inserter")) {
|
||||||
|
static int new_item_id = 0;
|
||||||
|
Text("Add Item");
|
||||||
|
BeginChild("ScrollRegion", ImVec2(150, 150), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
|
||||||
|
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
|
||||||
|
i == new_item_id)) {
|
||||||
|
new_item_id = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
// Add the new item to the overworld
|
||||||
|
new_item_id = 0;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldItem objects, currently only hides them
|
||||||
|
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Item editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
BeginChild("ScrollRegion", ImVec2(150, 150), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
|
||||||
|
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
|
||||||
|
item.id == i)) {
|
||||||
|
item.id = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndGroup();
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CLOSE)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
item.deleted = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImGuiTableSortSpecs *SpriteItem::s_current_sort_specs = nullptr;
|
||||||
|
|
||||||
|
void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
|
||||||
|
static ImGuiTextFilter filter;
|
||||||
|
static int selected_id = 0;
|
||||||
|
static std::vector<SpriteItem> items;
|
||||||
|
|
||||||
|
// Initialize items if empty
|
||||||
|
if (items.empty()) {
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
items.push_back(SpriteItem{i, core::kSpriteDefaultNames[i].data()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.Draw("Filter", 180);
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("##sprites", 2,
|
||||||
|
ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
|
||||||
|
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
|
||||||
|
MyItemColumnID_ID);
|
||||||
|
ImGui::TableSetupColumn("Name", 0, 0.0f, MyItemColumnID_Name);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Handle sorting
|
||||||
|
if (ImGuiTableSortSpecs *sort_specs = ImGui::TableGetSortSpecs()) {
|
||||||
|
if (sort_specs->SpecsDirty) {
|
||||||
|
SpriteItem::SortWithSortSpecs(sort_specs, items);
|
||||||
|
sort_specs->SpecsDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display filtered and sorted items
|
||||||
|
for (const auto &item : items) {
|
||||||
|
if (filter.PassFilter(item.name)) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
Text("%d", item.id);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
|
||||||
|
if (Selectable(item.name, selected_id == item.id,
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns)) {
|
||||||
|
selected_id = item.id;
|
||||||
|
onSpriteSelect(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldSprite objects
|
||||||
|
void DrawSpriteInserterPopup() {
|
||||||
|
if (ImGui::BeginPopup("Sprite Inserter")) {
|
||||||
|
static int new_sprite_id = 0;
|
||||||
|
Text("Add Sprite");
|
||||||
|
BeginChild("ScrollRegion", ImVec2(250, 250), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
DrawSpriteTable([](int selected_id) { new_sprite_id = selected_id; });
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
// Add the new item to the overworld
|
||||||
|
new_sprite_id = 0;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Sprite editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
BeginChild("ScrollRegion", ImVec2(350, 350), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
Text("%s", sprite.name().c_str());
|
||||||
|
|
||||||
|
DrawSpriteTable([&sprite](int selected_id) {
|
||||||
|
sprite.set_id(selected_id);
|
||||||
|
sprite.UpdateMapProperties(sprite.map_id());
|
||||||
|
});
|
||||||
|
ImGui::EndGroup();
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CLOSE)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
sprite.set_deleted(true);
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
88
src/app/editor/overworld/entity.h
Normal file
88
src/app/editor/overworld/entity.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
|
||||||
|
#define YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "app/editor/overworld_editor.h"
|
||||||
|
#include "app/zelda3/common.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
|
||||||
|
ImVec2 canvas_p0, ImVec2 scrolling);
|
||||||
|
|
||||||
|
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool free_movement = false);
|
||||||
|
|
||||||
|
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool &is_dragging_entity,
|
||||||
|
zelda3::OverworldEntity *&dragged_entity,
|
||||||
|
zelda3::OverworldEntity *¤t_entity,
|
||||||
|
bool free_movement = false);
|
||||||
|
|
||||||
|
bool DrawEntranceInserterPopup();
|
||||||
|
bool DrawOverworldEntrancePopup(zelda3::overworld::OverworldEntrance &entrance);
|
||||||
|
|
||||||
|
void DrawExitInserterPopup();
|
||||||
|
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit);
|
||||||
|
|
||||||
|
void DrawItemInsertPopup();
|
||||||
|
|
||||||
|
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item);
|
||||||
|
|
||||||
|
enum MyItemColumnID {
|
||||||
|
MyItemColumnID_ID,
|
||||||
|
MyItemColumnID_Name,
|
||||||
|
MyItemColumnID_Action,
|
||||||
|
MyItemColumnID_Quantity,
|
||||||
|
MyItemColumnID_Description
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteItem {
|
||||||
|
int id;
|
||||||
|
const char *name;
|
||||||
|
static const ImGuiTableSortSpecs *s_current_sort_specs;
|
||||||
|
|
||||||
|
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs,
|
||||||
|
std::vector<SpriteItem> &items) {
|
||||||
|
s_current_sort_specs =
|
||||||
|
sort_specs; // Store for access by the compare function.
|
||||||
|
if (items.size() > 1)
|
||||||
|
std::sort(items.begin(), items.end(), SpriteItem::CompareWithSortSpecs);
|
||||||
|
s_current_sort_specs = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool CompareWithSortSpecs(const SpriteItem &a, const SpriteItem &b) {
|
||||||
|
for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) {
|
||||||
|
const ImGuiTableColumnSortSpecs *sort_spec =
|
||||||
|
&s_current_sort_specs->Specs[n];
|
||||||
|
int delta = 0;
|
||||||
|
switch (sort_spec->ColumnUserID) {
|
||||||
|
case MyItemColumnID_ID:
|
||||||
|
delta = (a.id - b.id);
|
||||||
|
break;
|
||||||
|
case MyItemColumnID_Name:
|
||||||
|
delta = strcmp(a.name + 2, b.name + 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (delta != 0)
|
||||||
|
return (sort_spec->SortDirection == ImGuiSortDirection_Ascending)
|
||||||
|
? delta < 0
|
||||||
|
: delta > 0;
|
||||||
|
}
|
||||||
|
return a.id < b.id; // Fallback
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void DrawSpriteTable(std::function<void(int)> onSpriteSelect);
|
||||||
|
void DrawSpriteInserterPopup();
|
||||||
|
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite);
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
|
||||||
157
src/app/editor/overworld/refresh.cc
Normal file
157
src/app/editor/overworld/refresh.cc
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#include "app/editor/overworld_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
void OverworldEditor::RefreshChildMap(int map_index) {
|
||||||
|
overworld_.mutable_overworld_map(map_index)->LoadAreaGraphics();
|
||||||
|
status_ = overworld_.mutable_overworld_map(map_index)->BuildTileset();
|
||||||
|
PRINT_IF_ERROR(status_);
|
||||||
|
status_ = overworld_.mutable_overworld_map(map_index)->BuildTiles16Gfx(
|
||||||
|
overworld_.tiles16().size());
|
||||||
|
PRINT_IF_ERROR(status_);
|
||||||
|
status_ = overworld_.mutable_overworld_map(map_index)->BuildBitmap(
|
||||||
|
overworld_.GetMapTiles(current_world_));
|
||||||
|
maps_bmp_[map_index].set_data(
|
||||||
|
overworld_.mutable_overworld_map(map_index)->bitmap_data());
|
||||||
|
maps_bmp_[map_index].set_modified(true);
|
||||||
|
PRINT_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEditor::RefreshOverworldMap() {
|
||||||
|
std::vector<std::future<void>> futures;
|
||||||
|
int indices[4];
|
||||||
|
|
||||||
|
auto refresh_map_async = [this](int map_index) {
|
||||||
|
RefreshChildMap(map_index);
|
||||||
|
};
|
||||||
|
|
||||||
|
int source_map_id = current_map_;
|
||||||
|
bool is_large = overworld_.overworld_map(current_map_)->is_large_map();
|
||||||
|
if (is_large) {
|
||||||
|
source_map_id = current_parent_;
|
||||||
|
// We need to update the map and its siblings if it's a large map
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
int sibling_index = overworld_.overworld_map(source_map_id)->parent() + i;
|
||||||
|
if (i >= 2) sibling_index += 6;
|
||||||
|
futures.push_back(
|
||||||
|
std::async(std::launch::async, refresh_map_async, sibling_index));
|
||||||
|
indices[i] = sibling_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indices[0] = source_map_id;
|
||||||
|
futures.push_back(
|
||||||
|
std::async(std::launch::async, refresh_map_async, source_map_id));
|
||||||
|
|
||||||
|
for (auto &each : futures) {
|
||||||
|
each.get();
|
||||||
|
}
|
||||||
|
int n = is_large ? 4 : 1;
|
||||||
|
// We do texture updating on the main thread
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
rom()->UpdateBitmap(&maps_bmp_[indices[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status OverworldEditor::RefreshMapPalette() {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
overworld_.mutable_overworld_map(current_map_)->LoadPalette());
|
||||||
|
const auto current_map_palette = overworld_.AreaPalette();
|
||||||
|
|
||||||
|
if (overworld_.overworld_map(current_map_)->is_large_map()) {
|
||||||
|
// We need to update the map and its siblings if it's a large map
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
int sibling_index = overworld_.overworld_map(current_map_)->parent() + i;
|
||||||
|
if (i >= 2) sibling_index += 6;
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
overworld_.mutable_overworld_map(sibling_index)->LoadPalette());
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
maps_bmp_[sibling_index].ApplyPalette(current_map_palette));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(maps_bmp_[current_map_].ApplyPalette(current_map_palette));
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEditor::RefreshMapProperties() {
|
||||||
|
auto ¤t_ow_map = *overworld_.mutable_overworld_map(current_map_);
|
||||||
|
if (current_ow_map.is_large_map()) {
|
||||||
|
// We need to copy the properties from the parent map to the children
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
int sibling_index = current_ow_map.parent() + i;
|
||||||
|
if (i >= 2) {
|
||||||
|
sibling_index += 6;
|
||||||
|
}
|
||||||
|
auto &map = *overworld_.mutable_overworld_map(sibling_index);
|
||||||
|
map.set_area_graphics(current_ow_map.area_graphics());
|
||||||
|
map.set_area_palette(current_ow_map.area_palette());
|
||||||
|
map.set_sprite_graphics(game_state_,
|
||||||
|
current_ow_map.sprite_graphics(game_state_));
|
||||||
|
map.set_sprite_palette(game_state_,
|
||||||
|
current_ow_map.sprite_palette(game_state_));
|
||||||
|
map.set_message_id(current_ow_map.message_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status OverworldEditor::RefreshTile16Blockset() {
|
||||||
|
if (current_blockset_ ==
|
||||||
|
overworld_.overworld_map(current_map_)->area_graphics()) {
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
current_blockset_ = overworld_.overworld_map(current_map_)->area_graphics();
|
||||||
|
|
||||||
|
overworld_.set_current_map(current_map_);
|
||||||
|
palette_ = overworld_.AreaPalette();
|
||||||
|
// Create the tile16 blockset image
|
||||||
|
rom()->UpdateBitmap(&tile16_blockset_bmp_);
|
||||||
|
RETURN_IF_ERROR(tile16_blockset_bmp_.ApplyPalette(palette_));
|
||||||
|
|
||||||
|
// Copy the tile16 data into individual tiles.
|
||||||
|
auto tile16_data = overworld_.Tile16Blockset();
|
||||||
|
|
||||||
|
std::vector<std::future<void>> futures;
|
||||||
|
// Loop through the tiles and copy their pixel data into separate vectors
|
||||||
|
for (int i = 0; i < 4096; i++) {
|
||||||
|
futures.push_back(std::async(
|
||||||
|
std::launch::async,
|
||||||
|
[&](int index) {
|
||||||
|
// Create a new vector for the pixel data of the current tile
|
||||||
|
Bytes tile_data(16 * 16, 0x00); // More efficient initialization
|
||||||
|
|
||||||
|
// Copy the pixel data for the current tile into the vector
|
||||||
|
for (int ty = 0; ty < 16; ty++) {
|
||||||
|
for (int tx = 0; tx < 16; tx++) {
|
||||||
|
int position = tx + (ty * 0x10);
|
||||||
|
uint8_t value =
|
||||||
|
tile16_data[(index % 8 * 16) + (index / 8 * 16 * 0x80) +
|
||||||
|
(ty * 0x80) + tx];
|
||||||
|
tile_data[position] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the vector for the current tile to the vector of tile pixel
|
||||||
|
// data
|
||||||
|
tile16_individual_[index].set_data(tile_data);
|
||||||
|
},
|
||||||
|
i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &future : futures) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the bitmaps of each tile.
|
||||||
|
for (int id = 0; id < 4096; id++) {
|
||||||
|
RETURN_IF_ERROR(tile16_individual_[id].ApplyPalette(palette_));
|
||||||
|
rom()->UpdateBitmap(&tile16_individual_[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
1365
src/app/editor/overworld_editor.cc
Normal file
1365
src/app/editor/overworld_editor.cc
Normal file
File diff suppressed because it is too large
Load Diff
300
src/app/editor/overworld_editor.h
Normal file
300
src/app/editor/overworld_editor.h
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "absl/container/flat_hash_map.h"
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_format.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/overworld.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
static constexpr uint k4BPP = 4;
|
||||||
|
static constexpr uint kByteSize = 3;
|
||||||
|
static constexpr uint kMessageIdSize = 5;
|
||||||
|
static constexpr uint kNumSheetsToLoad = 223;
|
||||||
|
static constexpr uint kTile8DisplayHeight = 64;
|
||||||
|
static constexpr float kInputFieldSize = 30.f;
|
||||||
|
|
||||||
|
static constexpr absl::string_view kToolsetColumnNames[] = {
|
||||||
|
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
|
||||||
|
"#zoomInTool", "#separator", "#drawTool", "#history",
|
||||||
|
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
||||||
|
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
|
||||||
|
"propertiesTool"};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 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 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();
|
||||||
|
|
||||||
|
absl::Status DrawTile16Selector();
|
||||||
|
void DrawTile8Selector();
|
||||||
|
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] = "";
|
||||||
|
char spr_palette_[3] = "";
|
||||||
|
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;
|
||||||
|
|
||||||
|
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::vector<Bytes> tile16_individual_data_;
|
||||||
|
std::vector<gfx::Bitmap> tile16_individual_;
|
||||||
|
|
||||||
|
std::vector<Bytes> tile8_individual_data_;
|
||||||
|
std::vector<gfx::Bitmap> tile8_individual_;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
85
src/app/editor/settings_editor.cc
Normal file
85
src/app/editor/settings_editor.cc
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/utils/flags.h"
|
||||||
|
#include "app/editor/utils/keyboard_shortcuts.h"
|
||||||
|
#include "app/editor/settings_editor.h"
|
||||||
|
#include "app/editor/utils/flags.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginMenu;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndMenu;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::TableHeader;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetBgColor;
|
||||||
|
using ImGui::TableSetColumnIndex;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
absl::Status SettingsEditor::Update() {
|
||||||
|
if (BeginTabBar("Settings", ImGuiTabBarFlags_None)) {
|
||||||
|
if (BeginTabItem("General")) {
|
||||||
|
DrawGeneralSettings();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
if (BeginTabItem("Keyboard Shortcuts")) {
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsEditor::DrawGeneralSettings() {
|
||||||
|
if (BeginTable("##SettingsTable", 2,
|
||||||
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
|
||||||
|
TableSetupColumn("Experiment Flags", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
250.0f);
|
||||||
|
TableSetupColumn("General Setting", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
0.0f);
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (BeginChild("##GeneralSettingsStyleWrapper", ImVec2(0, 0),
|
||||||
|
ImGuiChildFlags_FrameStyle)) {
|
||||||
|
static FlagsMenu flags;
|
||||||
|
flags.Draw();
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (BeginChild("##GeneralSettingsWrapper", ImVec2(0, 0),
|
||||||
|
ImGuiChildFlags_FrameStyle)) {
|
||||||
|
Text("TODO: Add some settings here");
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status SettingsEditor::DrawKeyboardShortcuts() {
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
232
src/app/editor/settings_editor.h
Normal file
232
src/app/editor/settings_editor.h
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SETTINGS_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SETTINGS_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
// Simple representation for a tree
|
||||||
|
// (this is designed to be simple to understand for our demos, not to be
|
||||||
|
// efficient etc.)
|
||||||
|
struct ExampleTreeNode {
|
||||||
|
char Name[28];
|
||||||
|
ImGuiID UID = 0;
|
||||||
|
ExampleTreeNode* Parent = NULL;
|
||||||
|
ImVector<ExampleTreeNode*> Childs;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
bool HasData = false; // All leaves have data
|
||||||
|
bool DataIsEnabled = false;
|
||||||
|
int DataInt = 128;
|
||||||
|
ImVec2 DataVec2 = ImVec2(0.0f, 3.141592f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple representation of struct metadata/serialization data.
|
||||||
|
// (this is a minimal version of what a typical advanced application may
|
||||||
|
// provide)
|
||||||
|
struct ExampleMemberInfo {
|
||||||
|
const char* Name;
|
||||||
|
ImGuiDataType DataType;
|
||||||
|
int DataCount;
|
||||||
|
int Offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Metadata description of ExampleTreeNode struct.
|
||||||
|
static const ExampleMemberInfo ExampleTreeNodeMemberInfos[]{
|
||||||
|
{"Enabled", ImGuiDataType_Bool, 1,
|
||||||
|
offsetof(ExampleTreeNode, DataIsEnabled)},
|
||||||
|
{"MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataInt)},
|
||||||
|
{"MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataVec2)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static ExampleTreeNode* ExampleTree_CreateNode(const char* name,
|
||||||
|
const ImGuiID uid,
|
||||||
|
ExampleTreeNode* parent) {
|
||||||
|
ExampleTreeNode* node = IM_NEW(ExampleTreeNode);
|
||||||
|
snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name);
|
||||||
|
node->UID = uid;
|
||||||
|
node->Parent = parent;
|
||||||
|
if (parent) parent->Childs.push_back(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create example tree data
|
||||||
|
static ExampleTreeNode* ExampleTree_CreateDemoTree() {
|
||||||
|
static const char* root_names[] = {"Apple", "Banana", "Cherry",
|
||||||
|
"Kiwi", "Mango", "Orange",
|
||||||
|
"Pineapple", "Strawberry", "Watermelon"};
|
||||||
|
char name_buf[32];
|
||||||
|
ImGuiID uid = 0;
|
||||||
|
ExampleTreeNode* node_L0 = ExampleTree_CreateNode("<ROOT>", ++uid, NULL);
|
||||||
|
for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * 2; idx_L0++) {
|
||||||
|
snprintf(name_buf, 32, "%s %d", root_names[idx_L0 / 2], idx_L0 % 2);
|
||||||
|
ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0);
|
||||||
|
const int number_of_childs = (int)strlen(node_L1->Name);
|
||||||
|
for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) {
|
||||||
|
snprintf(name_buf, 32, "Child %d", idx_L1);
|
||||||
|
ExampleTreeNode* node_L2 =
|
||||||
|
ExampleTree_CreateNode(name_buf, ++uid, node_L1);
|
||||||
|
node_L2->HasData = true;
|
||||||
|
if (idx_L1 == 0) {
|
||||||
|
snprintf(name_buf, 32, "Sub-child %d", 0);
|
||||||
|
ExampleTreeNode* node_L3 =
|
||||||
|
ExampleTree_CreateNode(name_buf, ++uid, node_L2);
|
||||||
|
node_L3->HasData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node_L0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExampleAppPropertyEditor {
|
||||||
|
ImGuiTextFilter Filter;
|
||||||
|
|
||||||
|
void Draw(ExampleTreeNode* root_node) {
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F,
|
||||||
|
ImGuiInputFlags_Tooltip);
|
||||||
|
ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
|
||||||
|
if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf,
|
||||||
|
IM_ARRAYSIZE(Filter.InputBuf),
|
||||||
|
ImGuiInputTextFlags_EscapeClearsAll))
|
||||||
|
Filter.Build();
|
||||||
|
ImGui::PopItemFlag();
|
||||||
|
|
||||||
|
ImGuiTableFlags table_flags = ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_ScrollY |
|
||||||
|
ImGuiTableFlags_RowBg;
|
||||||
|
if (ImGui::BeginTable("##split", 2, table_flags)) {
|
||||||
|
ImGui::TableSetupColumn("Object", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
1.0f);
|
||||||
|
ImGui::TableSetupColumn("Contents", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
2.0f); // Default twice larger
|
||||||
|
// ImGui::TableSetupScrollFreeze(0, 1);
|
||||||
|
// ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (ExampleTreeNode* node : root_node->Childs)
|
||||||
|
if (Filter.PassFilter(node->Name)) // Filter root node
|
||||||
|
DrawTreeNode(node);
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTreeNode(ExampleTreeNode* node) {
|
||||||
|
// Object tree node
|
||||||
|
ImGui::PushID((int)node->UID);
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None;
|
||||||
|
tree_flags |=
|
||||||
|
ImGuiTreeNodeFlags_SpanAllColumns |
|
||||||
|
ImGuiTreeNodeFlags_AllowOverlap; // Highlight whole row for visibility
|
||||||
|
tree_flags |=
|
||||||
|
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||||
|
ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we
|
||||||
|
// are likely to want to add
|
||||||
|
// selection afterwards
|
||||||
|
tree_flags |=
|
||||||
|
ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Left arrow support
|
||||||
|
bool node_open =
|
||||||
|
ImGui::TreeNodeEx("##Object", tree_flags, "%s", node->Name);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::TextDisabled("UID: 0x%08X", node->UID);
|
||||||
|
|
||||||
|
// Display child and data
|
||||||
|
if (node_open)
|
||||||
|
for (ExampleTreeNode* child : node->Childs) DrawTreeNode(child);
|
||||||
|
if (node_open && node->HasData) {
|
||||||
|
// In a typical application, the structure description would be derived
|
||||||
|
// from a data-driven system.
|
||||||
|
// - We try to mimic this with our ExampleMemberInfo structure and the
|
||||||
|
// ExampleTreeNodeMemberInfos[] array.
|
||||||
|
// - Limits and some details are hard-coded to simplify the demo.
|
||||||
|
// - Text and Selectable are less high than framed widgets, using
|
||||||
|
// AlignTextToFramePadding() we add vertical spacing to make the
|
||||||
|
// selectable lines equal high.
|
||||||
|
for (const ExampleMemberInfo& field_desc : ExampleTreeNodeMemberInfos) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop | ImGuiItemFlags_NoNav,
|
||||||
|
true);
|
||||||
|
ImGui::Selectable(field_desc.Name, false,
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns |
|
||||||
|
ImGuiSelectableFlags_AllowOverlap);
|
||||||
|
ImGui::PopItemFlag();
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::PushID(field_desc.Name);
|
||||||
|
void* field_ptr = (void*)(((unsigned char*)node) + field_desc.Offset);
|
||||||
|
switch (field_desc.DataType) {
|
||||||
|
case ImGuiDataType_Bool: {
|
||||||
|
IM_ASSERT(field_desc.DataCount == 1);
|
||||||
|
ImGui::Checkbox("##Editor", (bool*)field_ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImGuiDataType_S32: {
|
||||||
|
int v_min = INT_MIN, v_max = INT_MAX;
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
ImGui::DragScalarN("##Editor", field_desc.DataType, field_ptr,
|
||||||
|
field_desc.DataCount, 1.0f, &v_min, &v_max);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImGuiDataType_Float: {
|
||||||
|
float v_min = 0.0f, v_max = 1.0f;
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
ImGui::SliderScalarN("##Editor", field_desc.DataType, field_ptr,
|
||||||
|
field_desc.DataCount, &v_min, &v_max);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node_open) ImGui::TreePop();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Demonstrate creating a simple property editor.
|
||||||
|
static void ShowExampleAppPropertyEditor(bool* p_open) {
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("Example: Property editor", p_open)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ExampleAppPropertyEditor property_editor;
|
||||||
|
static ExampleTreeNode* tree_data = ExampleTree_CreateDemoTree();
|
||||||
|
property_editor.Draw(tree_data);
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsEditor : public Editor {
|
||||||
|
public:
|
||||||
|
SettingsEditor() : Editor() { type_ = EditorType::kSettings; }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
|
||||||
|
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
|
||||||
|
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
|
||||||
|
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
|
||||||
|
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawGeneralSettings();
|
||||||
|
|
||||||
|
absl::Status DrawKeyboardShortcuts();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_SETTINGS_EDITOR_H_
|
||||||
278
src/app/editor/sprite/sprite_editor.cc
Normal file
278
src/app/editor/sprite/sprite_editor.cc
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
#include "sprite_editor.h"
|
||||||
|
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/editor/sprite/zsprite.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::Selectable;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
absl::Status SpriteEditor::Update() {
|
||||||
|
if (rom()->is_loaded() && !sheets_loaded_) {
|
||||||
|
// Load the values for current_sheets_ array
|
||||||
|
sheets_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("##SpriteEditorTabs")) {
|
||||||
|
if (ImGui::BeginTabItem("Vanilla")) {
|
||||||
|
DrawVanillaSpriteEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginTabItem("Custom")) {
|
||||||
|
DrawCustomSprites();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return status_.ok() ? absl::OkStatus() : status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawVanillaSpriteEditor() {
|
||||||
|
if (ImGui::BeginTable("##SpriteCanvasTable", 3, ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Sprites List", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
ImGui::GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawSpritesList();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("SpriteTabBar", kSpriteTabBarFlags)) {
|
||||||
|
if (ImGui::TabItemButton(ICON_MD_ADD, kSpriteTabBarFlags)) {
|
||||||
|
if (std::find(active_sprites_.begin(), active_sprites_.end(),
|
||||||
|
current_sprite_id_) != active_sprites_.end()) {
|
||||||
|
// Room is already open
|
||||||
|
next_tab_id++;
|
||||||
|
}
|
||||||
|
active_sprites_.push_back(next_tab_id++); // Add new tab
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit our regular tabs
|
||||||
|
for (int n = 0; n < active_sprites_.Size;) {
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
if (active_sprites_[n] > sizeof(core::kSpriteDefaultNames) / 4) {
|
||||||
|
active_sprites_.erase(active_sprites_.Data + n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(
|
||||||
|
core::kSpriteDefaultNames[active_sprites_[n]].data(), &open,
|
||||||
|
ImGuiTabItemFlags_None)) {
|
||||||
|
DrawSpriteCanvas();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
active_sprites_.erase(active_sprites_.Data + n);
|
||||||
|
else
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (sheets_loaded_) {
|
||||||
|
DrawCurrentSheets();
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawSpriteCanvas() {
|
||||||
|
static bool flip_x = false;
|
||||||
|
static bool flip_y = false;
|
||||||
|
if (ImGui::BeginChild(gui::GetID("##SpriteCanvas"),
|
||||||
|
ImGui::GetContentRegionAvail(), true)) {
|
||||||
|
sprite_canvas_.DrawBackground();
|
||||||
|
sprite_canvas_.DrawContextMenu();
|
||||||
|
sprite_canvas_.DrawGrid();
|
||||||
|
sprite_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
// Draw a table with OAM configuration
|
||||||
|
// X, Y, Tile, Palette, Priority, Flip X, Flip Y
|
||||||
|
if (ImGui::BeginTable("##OAMTable", 7, ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("X", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Y", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Tile", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Palette", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Priority", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Flip X", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Flip Y", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexWord("", &oam_config_.x);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexWord("", &oam_config_.y);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexByte("", &oam_config_.tile);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexByte("", &oam_config_.palette);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexByte("", &oam_config_.priority);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (ImGui::Checkbox("##XFlip", &flip_x)) {
|
||||||
|
oam_config_.flip_x = flip_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (ImGui::Checkbox("##YFlip", &flip_y)) {
|
||||||
|
oam_config_.flip_y = flip_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawAnimationFrames();
|
||||||
|
|
||||||
|
DrawCustomSpritesMetadata();
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawCurrentSheets() {
|
||||||
|
if (ImGui::BeginChild(gui::GetID("sheet_label"),
|
||||||
|
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
|
||||||
|
ImGuiWindowFlags_NoDecoration)) {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
std::string sheet_label = absl::StrFormat("Sheet %d", i);
|
||||||
|
gui::InputHexByte(sheet_label.c_str(), ¤t_sheets_[i]);
|
||||||
|
if (i % 2 == 0) ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics_sheet_canvas_.DrawBackground();
|
||||||
|
graphics_sheet_canvas_.DrawContextMenu();
|
||||||
|
graphics_sheet_canvas_.DrawTileSelector(32);
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
graphics_sheet_canvas_.DrawBitmap(
|
||||||
|
rom()->bitmap_manager()[current_sheets_[i]], 1, (i * 0x40) + 1, 2);
|
||||||
|
}
|
||||||
|
graphics_sheet_canvas_.DrawGrid();
|
||||||
|
graphics_sheet_canvas_.DrawOverlay();
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawSpritesList() {
|
||||||
|
if (ImGui::BeginChild(gui::GetID("##SpritesList"),
|
||||||
|
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
|
||||||
|
ImGuiWindowFlags_NoDecoration)) {
|
||||||
|
int i = 0;
|
||||||
|
for (const auto each_sprite_name : core::kSpriteDefaultNames) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_sprite_id_ == i, "Sprite Names", core::UppercaseHexByte(i),
|
||||||
|
core::kSpriteDefaultNames[i].data());
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_sprite_id_ = i;
|
||||||
|
if (!active_sprites_.contains(i)) {
|
||||||
|
active_sprites_.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawAnimationFrames() {
|
||||||
|
if (ImGui::Button("Add Frame")) {
|
||||||
|
// Add a new frame
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Remove Frame")) {
|
||||||
|
// Remove the current frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawCustomSprites() {
|
||||||
|
if (BeginTable("##CustomSpritesTable", 3,
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders |
|
||||||
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Metadata", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableSetupColumn("TIlesheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
TableNextColumn();
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
DrawCustomSpritesMetadata();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawSpriteCanvas();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawCurrentSheets();
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawCustomSpritesMetadata() {
|
||||||
|
// ZSprite Maker format open file dialog
|
||||||
|
if (ImGui::Button("Open ZSprite")) {
|
||||||
|
// Open ZSprite file
|
||||||
|
std::string file_path = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
if (!file_path.empty()) {
|
||||||
|
zsprite::ZSprite zsprite;
|
||||||
|
status_ = zsprite.Load(file_path);
|
||||||
|
if (status_.ok()) {
|
||||||
|
custom_sprites_.push_back(zsprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto custom_sprite : custom_sprites_) {
|
||||||
|
Selectable("%s", custom_sprite.sprName.c_str());
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_sprite_id_ = 256 + stoi(custom_sprite.property_sprid.Text);
|
||||||
|
if (!active_sprites_.contains(current_sprite_id_)) {
|
||||||
|
active_sprites_.push_back(current_sprite_id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto custom_sprite : custom_sprites_) {
|
||||||
|
// Draw the custom sprite metadata
|
||||||
|
Text("Sprite ID: %s", custom_sprite.property_sprid.Text.c_str());
|
||||||
|
Text("Sprite Name: %s", custom_sprite.property_sprname.Text.c_str());
|
||||||
|
Text("Sprite Palette: %s", custom_sprite.property_palette.Text.c_str());
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
117
src/app/editor/sprite/sprite_editor.h
Normal file
117
src/app/editor/sprite/sprite_editor.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/sprite/zsprite.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
constexpr ImGuiTabItemFlags kSpriteTabFlags =
|
||||||
|
ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip;
|
||||||
|
|
||||||
|
constexpr ImGuiTabBarFlags kSpriteTabBarFlags =
|
||||||
|
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||||
|
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||||
|
ImGuiTabBarFlags_TabListPopupButton;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kSpriteTableFlags =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||||
|
ImGuiTableFlags_BordersV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SpriteEditor
|
||||||
|
* @brief Allows the user to edit sprites.
|
||||||
|
*
|
||||||
|
* This class provides functionality for updating the sprite editor, drawing the
|
||||||
|
* editor table, drawing the sprite canvas, and drawing the current sheets.
|
||||||
|
*/
|
||||||
|
class SpriteEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
SpriteEditor() { type_ = EditorType::kSprite; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the sprite editor.
|
||||||
|
*
|
||||||
|
* @return An absl::Status indicating the success or failure of the update.
|
||||||
|
*/
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
|
||||||
|
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
|
||||||
|
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
|
||||||
|
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
|
||||||
|
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawVanillaSpriteEditor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the sprites list.
|
||||||
|
*/
|
||||||
|
void DrawSpritesList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the sprite canvas.
|
||||||
|
*/
|
||||||
|
void DrawSpriteCanvas();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the current sheets.
|
||||||
|
*/
|
||||||
|
void DrawCurrentSheets();
|
||||||
|
|
||||||
|
void DrawCustomSprites();
|
||||||
|
|
||||||
|
void DrawCustomSpritesMetadata();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the animation frames manager.
|
||||||
|
*/
|
||||||
|
void DrawAnimationFrames();
|
||||||
|
|
||||||
|
ImVector<int> active_sprites_; /**< Active sprites. */
|
||||||
|
|
||||||
|
int current_sprite_id_; /**< Current sprite ID. */
|
||||||
|
uint8_t current_sheets_[8] = {0x00, 0x0A, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
bool sheets_loaded_ =
|
||||||
|
false; /**< Flag indicating whether the sheets are loaded or not. */
|
||||||
|
|
||||||
|
// OAM Configuration
|
||||||
|
struct OAMConfig {
|
||||||
|
uint16_t x; /**< X offset. */
|
||||||
|
uint16_t y; /**< Y offset. */
|
||||||
|
uint8_t tile; /**< Tile number. */
|
||||||
|
uint8_t palette; /**< Palette number. */
|
||||||
|
uint8_t priority; /**< Priority. */
|
||||||
|
bool flip_x; /**< Flip X. */
|
||||||
|
bool flip_y; /**< Flip Y. */
|
||||||
|
};
|
||||||
|
|
||||||
|
OAMConfig oam_config_; /**< OAM configuration. */
|
||||||
|
gui::Bitmap oam_bitmap_; /**< OAM bitmap. */
|
||||||
|
|
||||||
|
gui::Canvas sprite_canvas_{
|
||||||
|
"SpriteCanvas", ImVec2(0x200, 0x200),
|
||||||
|
gui::CanvasGridSize::k32x32}; /**< Sprite canvas. */
|
||||||
|
|
||||||
|
gui::Canvas graphics_sheet_canvas_{
|
||||||
|
"GraphicsSheetCanvas", ImVec2(0x80 * 2 + 2, 0x40 * 8 + 2),
|
||||||
|
gui::CanvasGridSize::k16x16}; /**< Graphics sheet canvas. */
|
||||||
|
|
||||||
|
std::vector<zsprite::ZSprite> custom_sprites_; /**< Sprites. */
|
||||||
|
|
||||||
|
absl::Status status_; /**< Status. */
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||||
396
src/app/editor/sprite/zsprite.h
Normal file
396
src/app/editor/sprite/zsprite.h
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
|
||||||
|
#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
/**
|
||||||
|
* @brief Namespace for the ZSprite format from Zarby's ZSpriteMaker.
|
||||||
|
*/
|
||||||
|
namespace zsprite {
|
||||||
|
|
||||||
|
struct OamTile {
|
||||||
|
OamTile(uint8_t x, uint8_t y, bool mx, bool my, uint16_t id, uint8_t pal,
|
||||||
|
bool s, uint8_t p)
|
||||||
|
: x(x),
|
||||||
|
y(y),
|
||||||
|
mirror_x(mx),
|
||||||
|
mirror_y(my),
|
||||||
|
id(id),
|
||||||
|
palette(pal),
|
||||||
|
size(s),
|
||||||
|
priority(p) {}
|
||||||
|
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
bool mirror_x;
|
||||||
|
bool mirror_y;
|
||||||
|
uint16_t id;
|
||||||
|
uint8_t palette;
|
||||||
|
bool size;
|
||||||
|
uint8_t priority;
|
||||||
|
uint8_t z;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimationGroup {
|
||||||
|
AnimationGroup() = default;
|
||||||
|
AnimationGroup(uint8_t fs, uint8_t fe, uint8_t fsp, std::string fn)
|
||||||
|
: frame_start(fs), frame_end(fe), frame_speed(fsp), frame_name(fn) {}
|
||||||
|
|
||||||
|
std::string frame_name;
|
||||||
|
uint8_t frame_start;
|
||||||
|
uint8_t frame_end;
|
||||||
|
uint8_t frame_speed;
|
||||||
|
std::vector<OamTile> Tiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserRoutine {
|
||||||
|
UserRoutine(std::string n, std::string c) : name(n), code(c) {}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::string code;
|
||||||
|
int Count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SubEditor {
|
||||||
|
std::vector<AnimationGroup> Frames;
|
||||||
|
std::vector<UserRoutine> user_routines;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteProperty {
|
||||||
|
bool IsChecked;
|
||||||
|
std::string Text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ZSprite {
|
||||||
|
public:
|
||||||
|
absl::Status Load(const std::string& filename) {
|
||||||
|
std::ifstream fs(filename, std::ios::binary);
|
||||||
|
if (!fs.is_open()) {
|
||||||
|
return absl::NotFoundError("File not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> buffer(std::istreambuf_iterator<char>(fs), {});
|
||||||
|
|
||||||
|
int animation_count = *reinterpret_cast<int32_t*>(&buffer[0]);
|
||||||
|
int offset = sizeof(int);
|
||||||
|
|
||||||
|
for (int i = 0; i < animation_count; i++) {
|
||||||
|
std::string aname = std::string(&buffer[offset]);
|
||||||
|
offset += aname.size() + 1;
|
||||||
|
uint8_t afs = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t afe = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t afspeed = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
|
||||||
|
animations.push_back(AnimationGroup(afs, afe, afspeed, aname));
|
||||||
|
}
|
||||||
|
// RefreshAnimations();
|
||||||
|
|
||||||
|
int frame_count = *reinterpret_cast<int32_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (int i = 0; i < frame_count; i++) {
|
||||||
|
// editor.Frames[i] = new Frame();
|
||||||
|
editor.Frames.emplace_back();
|
||||||
|
// editor.AddUndo(i);
|
||||||
|
int tCount = *reinterpret_cast<int*>(&buffer[offset]);
|
||||||
|
offset += sizeof(int);
|
||||||
|
|
||||||
|
for (int j = 0; j < tCount; j++) {
|
||||||
|
ushort tid = *reinterpret_cast<ushort*>(&buffer[offset]);
|
||||||
|
offset += sizeof(ushort);
|
||||||
|
uint8_t tpal = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
bool tmx = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
bool tmy = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
uint8_t tprior = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
bool tsize = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
uint8_t tx = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t ty = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t tz = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
OamTile to(tx, ty, tmx, tmy, tid, tpal, tsize, tprior);
|
||||||
|
to.z = tz;
|
||||||
|
editor.Frames[i].Tiles.push_back(to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all sprites properties
|
||||||
|
property_blockable.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_canfall.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_collisionlayer.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_customdeath.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_damagesound.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_deflectarrows.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_deflectprojectiles.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_fast.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_harmless.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_impervious.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_imperviousarrow.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_imperviousmelee.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_interaction.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_isboss.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_persist.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_shadow.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_smallshadow.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_statis.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_statue.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_watersprite.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
|
||||||
|
property_prize.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_palette.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_oamnbr.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_hitbox.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_health.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_damage.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
|
||||||
|
if (offset != buffer.size()) {
|
||||||
|
property_sprname.Text = std::string(&buffer[offset]);
|
||||||
|
offset += property_sprname.Text.size() + 1;
|
||||||
|
|
||||||
|
int actionL = buffer[offset];
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (int i = 0; i < actionL; i++) {
|
||||||
|
std::string a = std::string(&buffer[offset]);
|
||||||
|
offset += a.size() + 1;
|
||||||
|
std::string b = std::string(&buffer[offset]);
|
||||||
|
offset += b.size() + 1;
|
||||||
|
userRoutines.push_back(UserRoutine(a, b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset != buffer.size()) {
|
||||||
|
property_sprid.Text = std::string(&buffer[offset]);
|
||||||
|
fs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserRoutines();
|
||||||
|
// userroutinesListbox.SelectedIndex = 0;
|
||||||
|
// RefreshScreen();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Save(const std::string& filename) {
|
||||||
|
std::ofstream fs(filename, std::ios::binary);
|
||||||
|
if (fs.is_open()) {
|
||||||
|
// Write data to the file
|
||||||
|
fs.write(reinterpret_cast<const char*>(animations.size()), sizeof(int));
|
||||||
|
for (const AnimationGroup& anim : animations) {
|
||||||
|
fs.write(anim.frame_name.c_str(), anim.frame_name.size() + 1);
|
||||||
|
fs.write(reinterpret_cast<const char*>(&anim.frame_start),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&anim.frame_end),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&anim.frame_speed),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(editor.Frames.size()),
|
||||||
|
sizeof(int));
|
||||||
|
for (int i = 0; i < editor.Frames.size(); i++) {
|
||||||
|
fs.write(reinterpret_cast<const char*>(editor.Frames[i].Tiles.size()),
|
||||||
|
sizeof(int));
|
||||||
|
|
||||||
|
for (int j = 0; j < editor.Frames[i].Tiles.size(); j++) {
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].id),
|
||||||
|
sizeof(ushort));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].palette),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(
|
||||||
|
&editor.Frames[i].Tiles[j].mirror_x),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(
|
||||||
|
&editor.Frames[i].Tiles[j].mirror_y),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(
|
||||||
|
&editor.Frames[i].Tiles[j].priority),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].size),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].x),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].y),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].z),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write other properties
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_blockable.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_canfall.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_collisionlayer.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_customdeath.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_damagesound.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_deflectarrows.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_deflectprojectiles.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_fast.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_harmless.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_impervious.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_imperviousarrow.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_imperviousmelee.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_interaction.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_isboss.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_persist.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_shadow.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_smallshadow.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_statis.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_statue.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_watersprite.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_prize.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_palette.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_oamnbr.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_hitbox.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_health.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_damage.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
|
||||||
|
fs.write(sprName.c_str(), sprName.size() + 1);
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(userRoutines.size()), sizeof(int));
|
||||||
|
for (const UserRoutine& userR : userRoutines) {
|
||||||
|
fs.write(userR.name.c_str(), userR.name.size() + 1);
|
||||||
|
fs.write(userR.code.c_str(), userR.code.size() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_sprid.Text),
|
||||||
|
sizeof(property_sprid.Text));
|
||||||
|
|
||||||
|
fs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sprName;
|
||||||
|
std::vector<AnimationGroup> animations;
|
||||||
|
std::vector<UserRoutine> userRoutines;
|
||||||
|
SubEditor editor;
|
||||||
|
|
||||||
|
SpriteProperty property_blockable;
|
||||||
|
SpriteProperty property_canfall;
|
||||||
|
SpriteProperty property_collisionlayer;
|
||||||
|
SpriteProperty property_customdeath;
|
||||||
|
SpriteProperty property_damagesound;
|
||||||
|
SpriteProperty property_deflectarrows;
|
||||||
|
SpriteProperty property_deflectprojectiles;
|
||||||
|
SpriteProperty property_fast;
|
||||||
|
SpriteProperty property_harmless;
|
||||||
|
SpriteProperty property_impervious;
|
||||||
|
SpriteProperty property_imperviousarrow;
|
||||||
|
SpriteProperty property_imperviousmelee;
|
||||||
|
SpriteProperty property_interaction;
|
||||||
|
SpriteProperty property_isboss;
|
||||||
|
SpriteProperty property_persist;
|
||||||
|
SpriteProperty property_shadow;
|
||||||
|
SpriteProperty property_smallshadow;
|
||||||
|
SpriteProperty property_statis;
|
||||||
|
SpriteProperty property_statue;
|
||||||
|
SpriteProperty property_watersprite;
|
||||||
|
SpriteProperty property_sprname;
|
||||||
|
|
||||||
|
SpriteProperty property_prize;
|
||||||
|
SpriteProperty property_palette;
|
||||||
|
SpriteProperty property_oamnbr;
|
||||||
|
SpriteProperty property_hitbox;
|
||||||
|
SpriteProperty property_health;
|
||||||
|
SpriteProperty property_damage;
|
||||||
|
|
||||||
|
SpriteProperty property_sprid;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace zsprite
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
|
||||||
65
src/app/editor/utils/editor.h
Normal file
65
src/app/editor/utils/editor.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_EDITOR_H
|
||||||
|
#define YAZE_APP_CORE_EDITOR_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace yaze::app::editor
|
||||||
|
* @brief Editors are the view controllers for the application.
|
||||||
|
*/
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
enum class EditorType {
|
||||||
|
kAssembly,
|
||||||
|
kDungeon,
|
||||||
|
kGraphics,
|
||||||
|
kMusic,
|
||||||
|
kOverworld,
|
||||||
|
kPalette,
|
||||||
|
kScreen,
|
||||||
|
kSprite,
|
||||||
|
kMessage,
|
||||||
|
kSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 10> kEditorNames = {
|
||||||
|
"Assembly", "Dungeon", "Graphics", "Music", "Overworld",
|
||||||
|
"Palette", "Screen", "Sprite", "Message", "Settings",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Editor
|
||||||
|
* @brief Interface for editor classes.
|
||||||
|
*
|
||||||
|
* Provides basic editing operations that each editor should implement.
|
||||||
|
*/
|
||||||
|
class Editor {
|
||||||
|
public:
|
||||||
|
Editor() = default;
|
||||||
|
virtual ~Editor() = default;
|
||||||
|
|
||||||
|
virtual absl::Status Cut() = 0;
|
||||||
|
virtual absl::Status Copy() = 0;
|
||||||
|
virtual absl::Status Paste() = 0;
|
||||||
|
|
||||||
|
virtual absl::Status Undo() = 0;
|
||||||
|
virtual absl::Status Redo() = 0;
|
||||||
|
|
||||||
|
virtual absl::Status Update() = 0;
|
||||||
|
|
||||||
|
virtual absl::Status Find() = 0;
|
||||||
|
|
||||||
|
EditorType type() const { return type_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
EditorType type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_EDITOR_H
|
||||||
68
src/app/editor/utils/flags.h
Normal file
68
src/app/editor/utils/flags.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_UTILS_FLAGS_H
|
||||||
|
#define YAZE_APP_EDITOR_UTILS_FLAGS_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "core/common.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginMenu;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::EndMenu;
|
||||||
|
using ImGui::MenuItem;
|
||||||
|
using ImGui::Separator;
|
||||||
|
|
||||||
|
struct FlagsMenu : public core::ExperimentFlags {
|
||||||
|
void Draw() {
|
||||||
|
if (BeginMenu("Overworld Flags")) {
|
||||||
|
Checkbox("Enable Overworld Sprites",
|
||||||
|
&mutable_flags()->overworld.kDrawOverworldSprites);
|
||||||
|
Separator();
|
||||||
|
Checkbox("Save Overworld Maps",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldMaps);
|
||||||
|
Checkbox("Save Overworld Entrances",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldEntrances);
|
||||||
|
Checkbox("Save Overworld Exits",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldExits);
|
||||||
|
Checkbox("Save Overworld Items",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldItems);
|
||||||
|
Checkbox("Save Overworld Properties",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldProperties);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Dungeon Flags")) {
|
||||||
|
Checkbox("Draw Dungeon Room Graphics",
|
||||||
|
&mutable_flags()->kDrawDungeonRoomGraphics);
|
||||||
|
Separator();
|
||||||
|
Checkbox("Save Dungeon Maps", &mutable_flags()->kSaveDungeonMaps);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Emulator Flags")) {
|
||||||
|
Checkbox("Load Audio Device", &mutable_flags()->kLoadAudioDevice);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkbox("Use built-in file dialog",
|
||||||
|
&mutable_flags()->kNewFileDialogWrapper);
|
||||||
|
Checkbox("Enable Console Logging", &mutable_flags()->kLogToConsole);
|
||||||
|
Checkbox("Enable Texture Streaming",
|
||||||
|
&mutable_flags()->kLoadTexturesAsStreaming);
|
||||||
|
Checkbox("Use Bitmap Manager", &mutable_flags()->kUseBitmapManager);
|
||||||
|
Checkbox("Log Instructions to Debugger",
|
||||||
|
&mutable_flags()->kLogInstructions);
|
||||||
|
Checkbox("Save All Palettes", &mutable_flags()->kSaveAllPalettes);
|
||||||
|
Checkbox("Save Gfx Groups", &mutable_flags()->kSaveGfxGroups);
|
||||||
|
Checkbox("Use New ImGui Input", &mutable_flags()->kUseNewImGuiInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_UTILS_FLAGS_H_
|
||||||
26
src/app/editor/utils/gfx_context.cc
Normal file
26
src/app/editor/utils/gfx_context.cc
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "app/editor/utils/gfx_context.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
namespace context {
|
||||||
|
|
||||||
|
std::unordered_map<uint8_t, gfx::Paletteset> GfxContext::palettesets_;
|
||||||
|
|
||||||
|
}
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
36
src/app/editor/utils/gfx_context.h
Normal file
36
src/app/editor/utils/gfx_context.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||||
|
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
namespace context {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shared graphical context across editors.
|
||||||
|
*/
|
||||||
|
class GfxContext {
|
||||||
|
protected:
|
||||||
|
// Palettesets for the tile16 individual tiles
|
||||||
|
static std::unordered_map<uint8_t, gfx::Paletteset> palettesets_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace context
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||||
25
src/app/editor/utils/keyboard_shortcuts.h
Normal file
25
src/app/editor/utils/keyboard_shortcuts.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
|
||||||
|
#define YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
struct KeyboardShortcuts {
|
||||||
|
enum class ShortcutType {
|
||||||
|
kCut,
|
||||||
|
kCopy,
|
||||||
|
kPaste,
|
||||||
|
kUndo,
|
||||||
|
kRedo,
|
||||||
|
kFind,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H_
|
||||||
64
src/app/editor/utils/recent_files.h
Normal file
64
src/app/editor/utils/recent_files.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
|
||||||
|
#define YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
class RecentFilesManager {
|
||||||
|
public:
|
||||||
|
RecentFilesManager(const std::string& filename) : filename_(filename) {}
|
||||||
|
|
||||||
|
void AddFile(const std::string& filePath) {
|
||||||
|
// Add a file to the list, avoiding duplicates
|
||||||
|
auto it = std::find(recentFiles_.begin(), recentFiles_.end(), filePath);
|
||||||
|
if (it == recentFiles_.end()) {
|
||||||
|
recentFiles_.push_back(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Save() {
|
||||||
|
std::ofstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return; // Handle the error appropriately
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& filePath : recentFiles_) {
|
||||||
|
file << filePath << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load() {
|
||||||
|
std::ifstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return; // Handle the error appropriately
|
||||||
|
}
|
||||||
|
|
||||||
|
recentFiles_.clear();
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
if (!line.empty()) {
|
||||||
|
recentFiles_.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& GetRecentFiles() const {
|
||||||
|
return recentFiles_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string filename_;
|
||||||
|
std::vector<std::string> recentFiles_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
|
||||||
23
src/app/emu/CMakeLists.txt
Normal file
23
src/app/emu/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
add_executable(
|
||||||
|
yaze_emu
|
||||||
|
app/rom.cc
|
||||||
|
app/emu/debug/emu.cc
|
||||||
|
${YAZE_APP_EMU_SRC}
|
||||||
|
${YAZE_APP_CORE_SRC}
|
||||||
|
${YAZE_APP_EDITOR_SRC}
|
||||||
|
${YAZE_APP_GFX_SRC}
|
||||||
|
${YAZE_APP_ZELDA3_SRC}
|
||||||
|
${YAZE_GUI_SRC}
|
||||||
|
${IMGUI_SRC}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
yaze_emu PUBLIC
|
||||||
|
lib/
|
||||||
|
app/
|
||||||
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
|
${PNG_INCLUDE_DIRS}
|
||||||
|
${SDL2_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(yaze_emu PUBLIC ${ABSL_TARGETS} ${SDL_TARGETS} ${PNG_LIBRARIES} ${CMAKE_DL_LIBS} ImGui)
|
||||||
207
src/app/emu/audio/apu.cc
Normal file
207
src/app/emu/audio/apu.cc
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
#include "app/emu/audio/apu.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/emu/audio/dsp.h"
|
||||||
|
#include "app/emu/audio/spc700.h"
|
||||||
|
#include "app/emu/cpu/clock.h"
|
||||||
|
#include "app/emu/memory/memory.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
|
||||||
|
static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312 * 50.0);
|
||||||
|
|
||||||
|
static const uint8_t bootRom[0x40] = {
|
||||||
|
0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa,
|
||||||
|
0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19,
|
||||||
|
0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb,
|
||||||
|
0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e,
|
||||||
|
0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4,
|
||||||
|
0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff};
|
||||||
|
|
||||||
|
void Apu::Init() {
|
||||||
|
ram.resize(0x10000);
|
||||||
|
for (int i = 0; i < 0x10000; i++) {
|
||||||
|
ram[i] = 0;
|
||||||
|
}
|
||||||
|
// Copy the boot rom into the ram at ffc0
|
||||||
|
for (int i = 0; i < 0x40; i++) {
|
||||||
|
ram[0xffc0 + i] = bootRom[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apu::Reset() {
|
||||||
|
spc700_.Reset(true);
|
||||||
|
dsp_.Reset();
|
||||||
|
for (int i = 0; i < 0x10000; i++) {
|
||||||
|
ram[i] = 0;
|
||||||
|
}
|
||||||
|
// Copy the boot rom into the ram at ffc0
|
||||||
|
for (int i = 0; i < 0x40; i++) {
|
||||||
|
ram[0xffc0 + i] = bootRom[i];
|
||||||
|
}
|
||||||
|
rom_readable_ = true;
|
||||||
|
dsp_adr_ = 0;
|
||||||
|
cycles_ = 0;
|
||||||
|
std::fill(in_ports_.begin(), in_ports_.end(), 0);
|
||||||
|
std::fill(out_ports_.begin(), out_ports_.end(), 0);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
timer_[i].cycles = 0;
|
||||||
|
timer_[i].divider = 0;
|
||||||
|
timer_[i].target = 0;
|
||||||
|
timer_[i].counter = 0;
|
||||||
|
timer_[i].enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apu::RunCycles(uint64_t cycles) {
|
||||||
|
uint64_t sync_to =
|
||||||
|
(uint64_t)cycles *
|
||||||
|
(memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster);
|
||||||
|
|
||||||
|
while (cycles_ < sync_to) {
|
||||||
|
spc700_.RunOpcode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apu::Cycle() {
|
||||||
|
if ((cycles_ & 0x1f) == 0) {
|
||||||
|
// every 32 cycles
|
||||||
|
dsp_.Cycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle timers
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (timer_[i].cycles == 0) {
|
||||||
|
timer_[i].cycles = i == 2 ? 16 : 128;
|
||||||
|
if (timer_[i].enabled) {
|
||||||
|
timer_[i].divider++;
|
||||||
|
if (timer_[i].divider == timer_[i].target) {
|
||||||
|
timer_[i].divider = 0;
|
||||||
|
timer_[i].counter++;
|
||||||
|
timer_[i].counter &= 0xf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timer_[i].cycles--;
|
||||||
|
}
|
||||||
|
|
||||||
|
cycles_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Apu::Read(uint16_t adr) {
|
||||||
|
switch (adr) {
|
||||||
|
case 0xf0:
|
||||||
|
case 0xf1:
|
||||||
|
case 0xfa:
|
||||||
|
case 0xfb:
|
||||||
|
case 0xfc: {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case 0xf2: {
|
||||||
|
return dsp_adr_;
|
||||||
|
}
|
||||||
|
case 0xf3: {
|
||||||
|
return dsp_.Read(dsp_adr_ & 0x7f);
|
||||||
|
}
|
||||||
|
case 0xf4:
|
||||||
|
case 0xf5:
|
||||||
|
case 0xf6:
|
||||||
|
case 0xf7:
|
||||||
|
case 0xf8:
|
||||||
|
case 0xf9: {
|
||||||
|
return in_ports_[adr - 0xf4];
|
||||||
|
}
|
||||||
|
case 0xfd:
|
||||||
|
case 0xfe:
|
||||||
|
case 0xff: {
|
||||||
|
uint8_t ret = timer_[adr - 0xfd].counter;
|
||||||
|
timer_[adr - 0xfd].counter = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rom_readable_ && adr >= 0xffc0) {
|
||||||
|
return bootRom[adr - 0xffc0];
|
||||||
|
}
|
||||||
|
return ram[adr];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apu::Write(uint16_t adr, uint8_t val) {
|
||||||
|
switch (adr) {
|
||||||
|
case 0xf0: {
|
||||||
|
break; // test register
|
||||||
|
}
|
||||||
|
case 0xf1: {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (!timer_[i].enabled && (val & (1 << i))) {
|
||||||
|
timer_[i].divider = 0;
|
||||||
|
timer_[i].counter = 0;
|
||||||
|
}
|
||||||
|
timer_[i].enabled = val & (1 << i);
|
||||||
|
}
|
||||||
|
if (val & 0x10) {
|
||||||
|
in_ports_[0] = 0;
|
||||||
|
in_ports_[1] = 0;
|
||||||
|
}
|
||||||
|
if (val & 0x20) {
|
||||||
|
in_ports_[2] = 0;
|
||||||
|
in_ports_[3] = 0;
|
||||||
|
}
|
||||||
|
rom_readable_ = val & 0x80;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf2: {
|
||||||
|
dsp_adr_ = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf3: {
|
||||||
|
if (dsp_adr_ < 0x80) dsp_.Write(dsp_adr_, val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf4:
|
||||||
|
case 0xf5:
|
||||||
|
case 0xf6:
|
||||||
|
case 0xf7: {
|
||||||
|
out_ports_[adr - 0xf4] = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf8:
|
||||||
|
case 0xf9: {
|
||||||
|
in_ports_[adr - 0xf4] = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xfa:
|
||||||
|
case 0xfb:
|
||||||
|
case 0xfc: {
|
||||||
|
timer_[adr - 0xfa].target = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ram[adr] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Apu::SpcRead(uint16_t adr) {
|
||||||
|
Cycle();
|
||||||
|
return Read(adr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apu::SpcWrite(uint16_t adr, uint8_t val) {
|
||||||
|
Cycle();
|
||||||
|
Write(adr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apu::SpcIdle(bool waiting) { Cycle(); }
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
100
src/app/emu/audio/apu.h
Normal file
100
src/app/emu/audio/apu.h
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#ifndef YAZE_APP_EMU_APU_H_
|
||||||
|
#define YAZE_APP_EMU_APU_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/emu/audio/dsp.h"
|
||||||
|
#include "app/emu/audio/spc700.h"
|
||||||
|
#include "app/emu/memory/memory.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
using namespace memory;
|
||||||
|
|
||||||
|
typedef struct Timer {
|
||||||
|
uint8_t cycles;
|
||||||
|
uint8_t divider;
|
||||||
|
uint8_t target;
|
||||||
|
uint8_t counter;
|
||||||
|
bool enabled;
|
||||||
|
} Timer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Apu
|
||||||
|
* @brief The Apu class represents the Audio Processing Unit (APU) of a system.
|
||||||
|
*
|
||||||
|
* The Apu class is responsible for generating audio samples and managing the
|
||||||
|
* APU state. It interacts with the Memory, AudioRam, and Clock classes to
|
||||||
|
* read/write data and update the clock. The class also implements the Observer
|
||||||
|
* interface to receive notifications from the system.
|
||||||
|
*
|
||||||
|
* @par IPL ROM Info
|
||||||
|
* 64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700.
|
||||||
|
* Some regions of this space are overlaid with special hardware functions.
|
||||||
|
*
|
||||||
|
* @par Range Note
|
||||||
|
* $0000-00EF Zero Page RAM
|
||||||
|
* $00F0-00FF Sound CPU Registers
|
||||||
|
* $0100-01FF Stack Page RAM
|
||||||
|
* $0200-FFBF RAM
|
||||||
|
* $FFC0-FFFF IPL ROM or RAM
|
||||||
|
*
|
||||||
|
* The region at $FFC0-FFFF will normally read from the 64-byte IPL ROM, but the
|
||||||
|
* underlying RAM can always be written to, and the high bit of the Control
|
||||||
|
* register $F1 can be cleared to unmap the IPL ROM and allow read access to
|
||||||
|
* this RAM.
|
||||||
|
*/
|
||||||
|
class Apu {
|
||||||
|
public:
|
||||||
|
Apu(MemoryImpl &memory) : memory_(memory) {}
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
void RunCycles(uint64_t cycles);
|
||||||
|
uint8_t SpcRead(uint16_t address);
|
||||||
|
void SpcWrite(uint16_t address, uint8_t data);
|
||||||
|
void SpcIdle(bool waiting);
|
||||||
|
|
||||||
|
void Cycle();
|
||||||
|
|
||||||
|
uint8_t Read(uint16_t address);
|
||||||
|
void Write(uint16_t address, uint8_t data);
|
||||||
|
|
||||||
|
auto dsp() -> Dsp & { return dsp_; }
|
||||||
|
auto spc700() -> Spc700 & { return spc700_; }
|
||||||
|
|
||||||
|
// Port buffers (equivalent to $2140 to $2143 for the main CPU)
|
||||||
|
std::array<uint8_t, 6> in_ports_; // includes 2 bytes of ram
|
||||||
|
std::array<uint8_t, 4> out_ports_;
|
||||||
|
std::vector<uint8_t> ram = std::vector<uint8_t>(0x10000, 0);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool rom_readable_ = false;
|
||||||
|
|
||||||
|
uint8_t dsp_adr_ = 0;
|
||||||
|
uint32_t cycles_ = 0;
|
||||||
|
|
||||||
|
MemoryImpl &memory_;
|
||||||
|
std::array<Timer, 3> timer_;
|
||||||
|
|
||||||
|
ApuCallbacks callbacks_ = {
|
||||||
|
[&](uint16_t adr, uint8_t val) { SpcWrite(adr, val); },
|
||||||
|
[&](uint16_t adr) { return SpcRead(adr); },
|
||||||
|
[&](bool waiting) { SpcIdle(waiting); },
|
||||||
|
};
|
||||||
|
Dsp dsp_{ram};
|
||||||
|
Spc700 spc700_{callbacks_};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
637
src/app/emu/audio/dsp.cc
Normal file
637
src/app/emu/audio/dsp.cc
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
#include "app/emu/audio/dsp.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "app/emu/memory/memory.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
static const int rateValues[32] = {0, 2048, 1536, 1280, 1024, 768, 640, 512,
|
||||||
|
384, 320, 256, 192, 160, 128, 96, 80,
|
||||||
|
64, 48, 40, 32, 24, 20, 16, 12,
|
||||||
|
10, 8, 6, 5, 4, 3, 2, 1};
|
||||||
|
|
||||||
|
static const int rateOffsets[32] = {0, 0, 1040, 536, 0, 1040, 536, 0, 1040,
|
||||||
|
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
|
||||||
|
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
|
||||||
|
536, 0, 1040, 536, 0};
|
||||||
|
|
||||||
|
static const int gaussValues[512] = {
|
||||||
|
0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
|
||||||
|
0x000, 0x000, 0x000, 0x000, 0x000, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
|
||||||
|
0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002,
|
||||||
|
0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004,
|
||||||
|
0x005, 0x005, 0x005, 0x005, 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007,
|
||||||
|
0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00a, 0x00a, 0x00a, 0x00b, 0x00b,
|
||||||
|
0x00b, 0x00c, 0x00c, 0x00d, 0x00d, 0x00e, 0x00e, 0x00f, 0x00f, 0x00f, 0x010,
|
||||||
|
0x010, 0x011, 0x011, 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016,
|
||||||
|
0x017, 0x017, 0x018, 0x018, 0x019, 0x01a, 0x01b, 0x01b, 0x01c, 0x01d, 0x01d,
|
||||||
|
0x01e, 0x01f, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026,
|
||||||
|
0x027, 0x028, 0x029, 0x02a, 0x02b, 0x02c, 0x02d, 0x02e, 0x02f, 0x030, 0x031,
|
||||||
|
0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x03a, 0x03b, 0x03c, 0x03d,
|
||||||
|
0x03e, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04a, 0x04c,
|
||||||
|
0x04d, 0x04e, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05a, 0x05c,
|
||||||
|
0x05e, 0x05f, 0x061, 0x063, 0x064, 0x066, 0x068, 0x06a, 0x06b, 0x06d, 0x06f,
|
||||||
|
0x071, 0x073, 0x075, 0x076, 0x078, 0x07a, 0x07c, 0x07e, 0x080, 0x082, 0x084,
|
||||||
|
0x086, 0x089, 0x08b, 0x08d, 0x08f, 0x091, 0x093, 0x096, 0x098, 0x09a, 0x09c,
|
||||||
|
0x09f, 0x0a1, 0x0a3, 0x0a6, 0x0a8, 0x0ab, 0x0ad, 0x0af, 0x0b2, 0x0b4, 0x0b7,
|
||||||
|
0x0ba, 0x0bc, 0x0bf, 0x0c1, 0x0c4, 0x0c7, 0x0c9, 0x0cc, 0x0cf, 0x0d2, 0x0d4,
|
||||||
|
0x0d7, 0x0da, 0x0dd, 0x0e0, 0x0e3, 0x0e6, 0x0e9, 0x0ec, 0x0ef, 0x0f2, 0x0f5,
|
||||||
|
0x0f8, 0x0fb, 0x0fe, 0x101, 0x104, 0x107, 0x10b, 0x10e, 0x111, 0x114, 0x118,
|
||||||
|
0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, 0x130, 0x133, 0x137, 0x13a, 0x13e,
|
||||||
|
0x141, 0x145, 0x148, 0x14c, 0x150, 0x153, 0x157, 0x15b, 0x15f, 0x162, 0x166,
|
||||||
|
0x16a, 0x16e, 0x172, 0x176, 0x17a, 0x17d, 0x181, 0x185, 0x189, 0x18d, 0x191,
|
||||||
|
0x195, 0x19a, 0x19e, 0x1a2, 0x1a6, 0x1aa, 0x1ae, 0x1b2, 0x1b7, 0x1bb, 0x1bf,
|
||||||
|
0x1c3, 0x1c8, 0x1cc, 0x1d0, 0x1d5, 0x1d9, 0x1dd, 0x1e2, 0x1e6, 0x1eb, 0x1ef,
|
||||||
|
0x1f3, 0x1f8, 0x1fc, 0x201, 0x205, 0x20a, 0x20f, 0x213, 0x218, 0x21c, 0x221,
|
||||||
|
0x226, 0x22a, 0x22f, 0x233, 0x238, 0x23d, 0x241, 0x246, 0x24b, 0x250, 0x254,
|
||||||
|
0x259, 0x25e, 0x263, 0x267, 0x26c, 0x271, 0x276, 0x27b, 0x280, 0x284, 0x289,
|
||||||
|
0x28e, 0x293, 0x298, 0x29d, 0x2a2, 0x2a6, 0x2ab, 0x2b0, 0x2b5, 0x2ba, 0x2bf,
|
||||||
|
0x2c4, 0x2c9, 0x2ce, 0x2d3, 0x2d8, 0x2dc, 0x2e1, 0x2e6, 0x2eb, 0x2f0, 0x2f5,
|
||||||
|
0x2fa, 0x2ff, 0x304, 0x309, 0x30e, 0x313, 0x318, 0x31d, 0x322, 0x326, 0x32b,
|
||||||
|
0x330, 0x335, 0x33a, 0x33f, 0x344, 0x349, 0x34e, 0x353, 0x357, 0x35c, 0x361,
|
||||||
|
0x366, 0x36b, 0x370, 0x374, 0x379, 0x37e, 0x383, 0x388, 0x38c, 0x391, 0x396,
|
||||||
|
0x39b, 0x39f, 0x3a4, 0x3a9, 0x3ad, 0x3b2, 0x3b7, 0x3bb, 0x3c0, 0x3c5, 0x3c9,
|
||||||
|
0x3ce, 0x3d2, 0x3d7, 0x3dc, 0x3e0, 0x3e5, 0x3e9, 0x3ed, 0x3f2, 0x3f6, 0x3fb,
|
||||||
|
0x3ff, 0x403, 0x408, 0x40c, 0x410, 0x415, 0x419, 0x41d, 0x421, 0x425, 0x42a,
|
||||||
|
0x42e, 0x432, 0x436, 0x43a, 0x43e, 0x442, 0x446, 0x44a, 0x44e, 0x452, 0x455,
|
||||||
|
0x459, 0x45d, 0x461, 0x465, 0x468, 0x46c, 0x470, 0x473, 0x477, 0x47a, 0x47e,
|
||||||
|
0x481, 0x485, 0x488, 0x48c, 0x48f, 0x492, 0x496, 0x499, 0x49c, 0x49f, 0x4a2,
|
||||||
|
0x4a6, 0x4a9, 0x4ac, 0x4af, 0x4b2, 0x4b5, 0x4b7, 0x4ba, 0x4bd, 0x4c0, 0x4c3,
|
||||||
|
0x4c5, 0x4c8, 0x4cb, 0x4cd, 0x4d0, 0x4d2, 0x4d5, 0x4d7, 0x4d9, 0x4dc, 0x4de,
|
||||||
|
0x4e0, 0x4e3, 0x4e5, 0x4e7, 0x4e9, 0x4eb, 0x4ed, 0x4ef, 0x4f1, 0x4f3, 0x4f5,
|
||||||
|
0x4f6, 0x4f8, 0x4fa, 0x4fb, 0x4fd, 0x4ff, 0x500, 0x502, 0x503, 0x504, 0x506,
|
||||||
|
0x507, 0x508, 0x50a, 0x50b, 0x50c, 0x50d, 0x50e, 0x50f, 0x510, 0x511, 0x511,
|
||||||
|
0x512, 0x513, 0x514, 0x514, 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518,
|
||||||
|
0x518, 0x518, 0x518, 0x518, 0x519, 0x519};
|
||||||
|
|
||||||
|
void Dsp::Reset() {
|
||||||
|
memset(ram, 0, sizeof(ram));
|
||||||
|
ram[0x7c] = 0xff; // set ENDx
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].pitch = 0;
|
||||||
|
channel[i].pitchCounter = 0;
|
||||||
|
channel[i].pitchModulation = false;
|
||||||
|
memset(channel[i].decodeBuffer, 0, sizeof(channel[i].decodeBuffer));
|
||||||
|
channel[i].bufferOffset = 0;
|
||||||
|
channel[i].srcn = 0;
|
||||||
|
channel[i].decodeOffset = 0;
|
||||||
|
channel[i].blockOffset = 0;
|
||||||
|
channel[i].brrHeader = 0;
|
||||||
|
channel[i].useNoise = false;
|
||||||
|
channel[i].startDelay = 0;
|
||||||
|
memset(channel[i].adsrRates, 0, sizeof(channel[i].adsrRates));
|
||||||
|
channel[i].adsrState = 0;
|
||||||
|
channel[i].sustainLevel = 0;
|
||||||
|
channel[i].gainSustainLevel = 0;
|
||||||
|
channel[i].useGain = false;
|
||||||
|
channel[i].gainMode = 0;
|
||||||
|
channel[i].directGain = false;
|
||||||
|
channel[i].gainValue = 0;
|
||||||
|
channel[i].preclampGain = 0;
|
||||||
|
channel[i].gain = 0;
|
||||||
|
channel[i].keyOn = false;
|
||||||
|
channel[i].keyOff = false;
|
||||||
|
channel[i].sampleOut = 0;
|
||||||
|
channel[i].volumeL = 0;
|
||||||
|
channel[i].volumeR = 0;
|
||||||
|
channel[i].echoEnable = false;
|
||||||
|
}
|
||||||
|
counter = 0;
|
||||||
|
dirPage = 0;
|
||||||
|
evenCycle = true;
|
||||||
|
mute = true;
|
||||||
|
reset = true;
|
||||||
|
masterVolumeL = 0;
|
||||||
|
masterVolumeR = 0;
|
||||||
|
sampleOutL = 0;
|
||||||
|
sampleOutR = 0;
|
||||||
|
echoOutL = 0;
|
||||||
|
echoOutR = 0;
|
||||||
|
noiseSample = 0x4000;
|
||||||
|
noiseRate = 0;
|
||||||
|
echoWrites = false;
|
||||||
|
echoVolumeL = 0;
|
||||||
|
echoVolumeR = 0;
|
||||||
|
feedbackVolume = 0;
|
||||||
|
echoBufferAdr = 0;
|
||||||
|
echoDelay = 0;
|
||||||
|
echoLength = 0;
|
||||||
|
echoBufferIndex = 0;
|
||||||
|
firBufferIndex = 0;
|
||||||
|
memset(firValues, 0, sizeof(firValues));
|
||||||
|
memset(firBufferL, 0, sizeof(firBufferL));
|
||||||
|
memset(firBufferR, 0, sizeof(firBufferR));
|
||||||
|
memset(sampleBuffer, 0, sizeof(sampleBuffer));
|
||||||
|
sampleOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::NewFrame() {
|
||||||
|
lastFrameBoundary = sampleOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::Cycle() {
|
||||||
|
sampleOutL = 0;
|
||||||
|
sampleOutR = 0;
|
||||||
|
echoOutL = 0;
|
||||||
|
echoOutR = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
CycleChannel(i);
|
||||||
|
}
|
||||||
|
HandleEcho(); // also applies master volume
|
||||||
|
counter = counter == 0 ? 30720 : counter - 1;
|
||||||
|
HandleNoise();
|
||||||
|
evenCycle = !evenCycle;
|
||||||
|
// handle mute flag
|
||||||
|
if (mute) {
|
||||||
|
sampleOutL = 0;
|
||||||
|
sampleOutR = 0;
|
||||||
|
}
|
||||||
|
// put final sample in the samplebuffer
|
||||||
|
sampleBuffer[(sampleOffset & 0x3ff) * 2] = sampleOutL;
|
||||||
|
sampleBuffer[(sampleOffset++ & 0x3ff) * 2 + 1] = sampleOutR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clamp16(int val) {
|
||||||
|
return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clip16(int val) { return (int16_t)(val & 0xffff); }
|
||||||
|
|
||||||
|
bool Dsp::CheckCounter(int rate) {
|
||||||
|
if (rate == 0) return false;
|
||||||
|
return ((counter + rateOffsets[rate]) % rateValues[rate]) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::HandleEcho() {
|
||||||
|
// increment fir buffer index
|
||||||
|
firBufferIndex++;
|
||||||
|
firBufferIndex &= 0x7;
|
||||||
|
// get value out of ram
|
||||||
|
uint16_t adr = echoBufferAdr + echoBufferIndex;
|
||||||
|
int16_t ramSample = aram_[adr] | (aram_[(adr + 1) & 0xffff] << 8);
|
||||||
|
firBufferL[firBufferIndex] = ramSample >> 1;
|
||||||
|
ramSample = aram_[(adr + 2) & 0xffff] | (aram_[(adr + 3) & 0xffff] << 8);
|
||||||
|
firBufferR[firBufferIndex] = ramSample >> 1;
|
||||||
|
// calculate FIR-sum
|
||||||
|
int sumL = 0, sumR = 0;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
sumL += (firBufferL[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
|
||||||
|
sumR += (firBufferR[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
|
||||||
|
if (i == 6) {
|
||||||
|
// clip to 16-bit before last addition
|
||||||
|
sumL = clip16(sumL);
|
||||||
|
sumR = clip16(sumR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sumL = clamp16(sumL) & ~1;
|
||||||
|
sumR = clamp16(sumR) & ~1;
|
||||||
|
// apply master volume and modify output with sum
|
||||||
|
sampleOutL = clamp16(((sampleOutL * masterVolumeL) >> 7) +
|
||||||
|
((sumL * echoVolumeL) >> 7));
|
||||||
|
sampleOutR = clamp16(((sampleOutR * masterVolumeR) >> 7) +
|
||||||
|
((sumR * echoVolumeR) >> 7));
|
||||||
|
// get echo value
|
||||||
|
int echoL = clamp16(echoOutL + clip16((sumL * feedbackVolume) >> 7)) & ~1;
|
||||||
|
int echoR = clamp16(echoOutR + clip16((sumR * feedbackVolume) >> 7)) & ~1;
|
||||||
|
// write it to ram
|
||||||
|
if (echoWrites) {
|
||||||
|
aram_[adr] = echoL & 0xff;
|
||||||
|
aram_[(adr + 1) & 0xffff] = echoL >> 8;
|
||||||
|
aram_[(adr + 2) & 0xffff] = echoR & 0xff;
|
||||||
|
aram_[(adr + 3) & 0xffff] = echoR >> 8;
|
||||||
|
}
|
||||||
|
// handle indexes
|
||||||
|
if (echoBufferIndex == 0) {
|
||||||
|
echoLength = echoDelay * 4;
|
||||||
|
}
|
||||||
|
echoBufferIndex += 4;
|
||||||
|
if (echoBufferIndex >= echoLength) {
|
||||||
|
echoBufferIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::CycleChannel(int ch) {
|
||||||
|
// handle pitch counter
|
||||||
|
int pitch = channel[ch].pitch;
|
||||||
|
if (ch > 0 && channel[ch].pitchModulation) {
|
||||||
|
pitch += ((channel[ch - 1].sampleOut >> 5) * pitch) >> 10;
|
||||||
|
}
|
||||||
|
// get current brr header and get sample address
|
||||||
|
channel[ch].brrHeader = aram_[channel[ch].decodeOffset];
|
||||||
|
uint16_t samplePointer = dirPage + 4 * channel[ch].srcn;
|
||||||
|
if (channel[ch].startDelay == 0) samplePointer += 2;
|
||||||
|
uint16_t sampleAdr =
|
||||||
|
aram_[samplePointer] | (aram_[(samplePointer + 1) & 0xffff] << 8);
|
||||||
|
// handle starting of sample
|
||||||
|
if (channel[ch].startDelay > 0) {
|
||||||
|
if (channel[ch].startDelay == 5) {
|
||||||
|
// first keyed on
|
||||||
|
channel[ch].decodeOffset = sampleAdr;
|
||||||
|
channel[ch].blockOffset = 1;
|
||||||
|
channel[ch].bufferOffset = 0;
|
||||||
|
channel[ch].brrHeader = 0;
|
||||||
|
ram[0x7c] &= ~(1 << ch); // clear ENDx
|
||||||
|
}
|
||||||
|
channel[ch].gain = 0;
|
||||||
|
channel[ch].startDelay--;
|
||||||
|
channel[ch].pitchCounter = 0;
|
||||||
|
if (channel[ch].startDelay > 0 && channel[ch].startDelay < 4) {
|
||||||
|
channel[ch].pitchCounter = 0x4000;
|
||||||
|
}
|
||||||
|
pitch = 0;
|
||||||
|
}
|
||||||
|
// get sample
|
||||||
|
int sample = 0;
|
||||||
|
if (channel[ch].useNoise) {
|
||||||
|
sample = clip16(noiseSample * 2);
|
||||||
|
} else {
|
||||||
|
sample = GetSample(ch);
|
||||||
|
}
|
||||||
|
sample = ((sample * channel[ch].gain) >> 11) & ~1;
|
||||||
|
// handle reset and release
|
||||||
|
if (reset || (channel[ch].brrHeader & 0x03) == 1) {
|
||||||
|
channel[ch].adsrState = 3; // go to release
|
||||||
|
channel[ch].gain = 0;
|
||||||
|
}
|
||||||
|
// handle keyon/keyoff
|
||||||
|
if (evenCycle) {
|
||||||
|
if (channel[ch].keyOff) {
|
||||||
|
channel[ch].adsrState = 3; // go to release
|
||||||
|
}
|
||||||
|
if (channel[ch].keyOn) {
|
||||||
|
channel[ch].startDelay = 5;
|
||||||
|
channel[ch].adsrState = 0; // go to attack
|
||||||
|
channel[ch].keyOn = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// handle envelope
|
||||||
|
if (channel[ch].startDelay == 0) {
|
||||||
|
HandleGain(ch);
|
||||||
|
}
|
||||||
|
// decode new brr samples if needed and update offsets
|
||||||
|
if (channel[ch].pitchCounter >= 0x4000) {
|
||||||
|
DecodeBrr(ch);
|
||||||
|
if (channel[ch].blockOffset >= 7) {
|
||||||
|
if (channel[ch].brrHeader & 0x1) {
|
||||||
|
channel[ch].decodeOffset = sampleAdr;
|
||||||
|
ram[0x7c] |= 1 << ch; // set ENDx
|
||||||
|
} else {
|
||||||
|
channel[ch].decodeOffset += 9;
|
||||||
|
}
|
||||||
|
channel[ch].blockOffset = 1;
|
||||||
|
} else {
|
||||||
|
channel[ch].blockOffset += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update pitch counter
|
||||||
|
channel[ch].pitchCounter &= 0x3fff;
|
||||||
|
channel[ch].pitchCounter += pitch;
|
||||||
|
if (channel[ch].pitchCounter > 0x7fff) channel[ch].pitchCounter = 0x7fff;
|
||||||
|
// set outputs
|
||||||
|
ram[(ch << 4) | 8] = channel[ch].gain >> 4;
|
||||||
|
ram[(ch << 4) | 9] = sample >> 8;
|
||||||
|
channel[ch].sampleOut = sample;
|
||||||
|
sampleOutL = clamp16(sampleOutL + ((sample * channel[ch].volumeL) >> 7));
|
||||||
|
sampleOutR = clamp16(sampleOutR + ((sample * channel[ch].volumeR) >> 7));
|
||||||
|
if (channel[ch].echoEnable) {
|
||||||
|
echoOutL = clamp16(echoOutL + ((sample * channel[ch].volumeL) >> 7));
|
||||||
|
echoOutR = clamp16(echoOutR + ((sample * channel[ch].volumeR) >> 7));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::HandleGain(int ch) {
|
||||||
|
int newGain = channel[ch].gain;
|
||||||
|
int rate = 0;
|
||||||
|
// handle gain mode
|
||||||
|
if (channel[ch].adsrState == 3) { // release
|
||||||
|
rate = 31;
|
||||||
|
newGain -= 8;
|
||||||
|
} else {
|
||||||
|
if (!channel[ch].useGain) {
|
||||||
|
rate = channel[ch].adsrRates[channel[ch].adsrState];
|
||||||
|
switch (channel[ch].adsrState) {
|
||||||
|
case 0:
|
||||||
|
newGain += rate == 31 ? 1024 : 32;
|
||||||
|
break; // attack
|
||||||
|
case 1:
|
||||||
|
newGain -= ((newGain - 1) >> 8) + 1;
|
||||||
|
break; // decay
|
||||||
|
case 2:
|
||||||
|
newGain -= ((newGain - 1) >> 8) + 1;
|
||||||
|
break; // sustain
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!channel[ch].directGain) {
|
||||||
|
rate = channel[ch].adsrRates[3];
|
||||||
|
switch (channel[ch].gainMode) {
|
||||||
|
case 0:
|
||||||
|
newGain -= 32;
|
||||||
|
break; // linear decrease
|
||||||
|
case 1:
|
||||||
|
newGain -= ((newGain - 1) >> 8) + 1;
|
||||||
|
break; // exponential decrease
|
||||||
|
case 2:
|
||||||
|
newGain += 32;
|
||||||
|
break; // linear increase
|
||||||
|
case 3:
|
||||||
|
newGain += (channel[ch].preclampGain < 0x600) ? 32 : 8;
|
||||||
|
break; // bent increase
|
||||||
|
}
|
||||||
|
} else { // direct gain
|
||||||
|
rate = 31;
|
||||||
|
newGain = channel[ch].gainValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// use sustain level according to mode
|
||||||
|
int sustainLevel = channel[ch].useGain ? channel[ch].gainSustainLevel
|
||||||
|
: channel[ch].sustainLevel;
|
||||||
|
if (channel[ch].adsrState == 1 && (newGain >> 8) == sustainLevel) {
|
||||||
|
channel[ch].adsrState = 2; // go to sustain
|
||||||
|
}
|
||||||
|
// store pre-clamped gain (for bent increase)
|
||||||
|
channel[ch].preclampGain = newGain & 0xffff;
|
||||||
|
// clamp gain
|
||||||
|
if (newGain < 0 || newGain > 0x7ff) {
|
||||||
|
newGain = newGain < 0 ? 0 : 0x7ff;
|
||||||
|
if (channel[ch].adsrState == 0) {
|
||||||
|
channel[ch].adsrState = 1; // go to decay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// store new value
|
||||||
|
if (CheckCounter(rate)) channel[ch].gain = newGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t Dsp::GetSample(int ch) {
|
||||||
|
int pos = (channel[ch].pitchCounter >> 12) + channel[ch].bufferOffset;
|
||||||
|
int offset = (channel[ch].pitchCounter >> 4) & 0xff;
|
||||||
|
int16_t news = channel[ch].decodeBuffer[(pos + 3) % 12];
|
||||||
|
int16_t olds = channel[ch].decodeBuffer[(pos + 2) % 12];
|
||||||
|
int16_t olders = channel[ch].decodeBuffer[(pos + 1) % 12];
|
||||||
|
int16_t oldests = channel[ch].decodeBuffer[pos % 12];
|
||||||
|
int out = (gaussValues[0xff - offset] * oldests) >> 11;
|
||||||
|
out += (gaussValues[0x1ff - offset] * olders) >> 11;
|
||||||
|
out += (gaussValues[0x100 + offset] * olds) >> 11;
|
||||||
|
out = clip16(out) + ((gaussValues[offset] * news) >> 11);
|
||||||
|
return clamp16(out) & ~1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::DecodeBrr(int ch) {
|
||||||
|
int shift = channel[ch].brrHeader >> 4;
|
||||||
|
int filter = (channel[ch].brrHeader & 0xc) >> 2;
|
||||||
|
int bOff = channel[ch].bufferOffset;
|
||||||
|
int old = channel[ch].decodeBuffer[bOff == 0 ? 11 : bOff - 1] >> 1;
|
||||||
|
int older = channel[ch].decodeBuffer[bOff == 0 ? 10 : bOff - 2] >> 1;
|
||||||
|
uint8_t curByte = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int s = 0;
|
||||||
|
if (i & 1) {
|
||||||
|
s = curByte & 0xf;
|
||||||
|
} else {
|
||||||
|
curByte = aram_[(channel[ch].decodeOffset + channel[ch].blockOffset +
|
||||||
|
(i >> 1)) &
|
||||||
|
0xffff];
|
||||||
|
s = curByte >> 4;
|
||||||
|
}
|
||||||
|
if (s > 7) s -= 16;
|
||||||
|
if (shift <= 0xc) {
|
||||||
|
s = (s << shift) >> 1;
|
||||||
|
} else {
|
||||||
|
s = (s >> 3) << 12;
|
||||||
|
}
|
||||||
|
switch (filter) {
|
||||||
|
case 1:
|
||||||
|
s += old + (-old >> 4);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
s += 2 * old + ((3 * -old) >> 5) - older + (older >> 4);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
s += 2 * old + ((13 * -old) >> 6) - older + ((3 * older) >> 4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
channel[ch].decodeBuffer[bOff + i] = clamp16(s) * 2; // cuts off bit 15
|
||||||
|
older = old;
|
||||||
|
old = channel[ch].decodeBuffer[bOff + i] >> 1;
|
||||||
|
}
|
||||||
|
channel[ch].bufferOffset += 4;
|
||||||
|
if (channel[ch].bufferOffset >= 12) channel[ch].bufferOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::HandleNoise() {
|
||||||
|
if (CheckCounter(noiseRate)) {
|
||||||
|
int bit = (noiseSample & 1) ^ ((noiseSample >> 1) & 1);
|
||||||
|
noiseSample = ((noiseSample >> 1) & 0x3fff) | (bit << 14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Dsp::Read(uint8_t adr) { return ram[adr]; }
|
||||||
|
|
||||||
|
void Dsp::Write(uint8_t adr, uint8_t val) {
|
||||||
|
int ch = adr >> 4;
|
||||||
|
switch (adr) {
|
||||||
|
case 0x00:
|
||||||
|
case 0x10:
|
||||||
|
case 0x20:
|
||||||
|
case 0x30:
|
||||||
|
case 0x40:
|
||||||
|
case 0x50:
|
||||||
|
case 0x60:
|
||||||
|
case 0x70: {
|
||||||
|
channel[ch].volumeL = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x01:
|
||||||
|
case 0x11:
|
||||||
|
case 0x21:
|
||||||
|
case 0x31:
|
||||||
|
case 0x41:
|
||||||
|
case 0x51:
|
||||||
|
case 0x61:
|
||||||
|
case 0x71: {
|
||||||
|
channel[ch].volumeR = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x02:
|
||||||
|
case 0x12:
|
||||||
|
case 0x22:
|
||||||
|
case 0x32:
|
||||||
|
case 0x42:
|
||||||
|
case 0x52:
|
||||||
|
case 0x62:
|
||||||
|
case 0x72: {
|
||||||
|
channel[ch].pitch = (channel[ch].pitch & 0x3f00) | val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x03:
|
||||||
|
case 0x13:
|
||||||
|
case 0x23:
|
||||||
|
case 0x33:
|
||||||
|
case 0x43:
|
||||||
|
case 0x53:
|
||||||
|
case 0x63:
|
||||||
|
case 0x73: {
|
||||||
|
channel[ch].pitch = ((channel[ch].pitch & 0x00ff) | (val << 8)) & 0x3fff;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x04:
|
||||||
|
case 0x14:
|
||||||
|
case 0x24:
|
||||||
|
case 0x34:
|
||||||
|
case 0x44:
|
||||||
|
case 0x54:
|
||||||
|
case 0x64:
|
||||||
|
case 0x74: {
|
||||||
|
channel[ch].srcn = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x05:
|
||||||
|
case 0x15:
|
||||||
|
case 0x25:
|
||||||
|
case 0x35:
|
||||||
|
case 0x45:
|
||||||
|
case 0x55:
|
||||||
|
case 0x65:
|
||||||
|
case 0x75: {
|
||||||
|
channel[ch].adsrRates[0] = (val & 0xf) * 2 + 1;
|
||||||
|
channel[ch].adsrRates[1] = ((val & 0x70) >> 4) * 2 + 16;
|
||||||
|
channel[ch].useGain = (val & 0x80) == 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x06:
|
||||||
|
case 0x16:
|
||||||
|
case 0x26:
|
||||||
|
case 0x36:
|
||||||
|
case 0x46:
|
||||||
|
case 0x56:
|
||||||
|
case 0x66:
|
||||||
|
case 0x76: {
|
||||||
|
channel[ch].adsrRates[2] = val & 0x1f;
|
||||||
|
channel[ch].sustainLevel = (val & 0xe0) >> 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x07:
|
||||||
|
case 0x17:
|
||||||
|
case 0x27:
|
||||||
|
case 0x37:
|
||||||
|
case 0x47:
|
||||||
|
case 0x57:
|
||||||
|
case 0x67:
|
||||||
|
case 0x77: {
|
||||||
|
channel[ch].directGain = (val & 0x80) == 0;
|
||||||
|
channel[ch].gainMode = (val & 0x60) >> 5;
|
||||||
|
channel[ch].adsrRates[3] = val & 0x1f;
|
||||||
|
channel[ch].gainValue = (val & 0x7f) * 16;
|
||||||
|
channel[ch].gainSustainLevel = (val & 0xe0) >> 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0c: {
|
||||||
|
masterVolumeL = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x1c: {
|
||||||
|
masterVolumeR = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x2c: {
|
||||||
|
echoVolumeL = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x3c: {
|
||||||
|
echoVolumeR = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x4c: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].keyOn = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x5c: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].keyOff = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6c: {
|
||||||
|
reset = val & 0x80;
|
||||||
|
mute = val & 0x40;
|
||||||
|
echoWrites = (val & 0x20) == 0;
|
||||||
|
noiseRate = val & 0x1f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x7c: {
|
||||||
|
val = 0; // any write clears ENDx
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0d: {
|
||||||
|
feedbackVolume = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x2d: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].pitchModulation = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x3d: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].useNoise = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x4d: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].echoEnable = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x5d: {
|
||||||
|
dirPage = val << 8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6d: {
|
||||||
|
echoBufferAdr = val << 8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x7d: {
|
||||||
|
echoDelay =
|
||||||
|
(val & 0xf) * 512; // 2048-byte steps, stereo sample is 4 bytes
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0f:
|
||||||
|
case 0x1f:
|
||||||
|
case 0x2f:
|
||||||
|
case 0x3f:
|
||||||
|
case 0x4f:
|
||||||
|
case 0x5f:
|
||||||
|
case 0x6f:
|
||||||
|
case 0x7f: {
|
||||||
|
firValues[ch] = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ram[adr] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame,
|
||||||
|
bool pal_timing) {
|
||||||
|
// resample from 534 / 641 samples per frame to wanted value
|
||||||
|
float wantedSamples = (pal_timing ? 641.0 : 534.0);
|
||||||
|
double adder = wantedSamples / samples_per_frame;
|
||||||
|
double location = lastFrameBoundary - wantedSamples;
|
||||||
|
for (int i = 0; i < samples_per_frame; i++) {
|
||||||
|
sample_data[i * 2] = sample_buffer_[(((int)location) & 0x3ff) * 2];
|
||||||
|
sample_data[i * 2 + 1] = sample_buffer_[(((int)location) & 0x3ff) * 2 + 1];
|
||||||
|
location += adder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
163
src/app/emu/audio/dsp.h
Normal file
163
src/app/emu/audio/dsp.h
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#ifndef YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||||
|
#define YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/emu/audio/spc700.h"
|
||||||
|
#include "app/emu/memory/memory.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
typedef struct DspChannel {
|
||||||
|
// pitch
|
||||||
|
uint16_t pitch;
|
||||||
|
uint16_t pitchCounter;
|
||||||
|
bool pitchModulation;
|
||||||
|
// brr decoding
|
||||||
|
int16_t decodeBuffer[12];
|
||||||
|
uint8_t bufferOffset;
|
||||||
|
uint8_t srcn;
|
||||||
|
uint16_t decodeOffset;
|
||||||
|
uint8_t blockOffset; // offset within brr block
|
||||||
|
uint8_t brrHeader;
|
||||||
|
bool useNoise;
|
||||||
|
uint8_t startDelay;
|
||||||
|
// adsr, envelope, gain
|
||||||
|
uint8_t adsrRates[4]; // attack, decay, sustain, gain
|
||||||
|
uint8_t adsrState; // 0: attack, 1: decay, 2: sustain, 3: release
|
||||||
|
uint8_t sustainLevel;
|
||||||
|
uint8_t gainSustainLevel;
|
||||||
|
bool useGain;
|
||||||
|
uint8_t gainMode;
|
||||||
|
bool directGain;
|
||||||
|
uint16_t gainValue; // for direct gain
|
||||||
|
uint16_t preclampGain; // for bent increase
|
||||||
|
uint16_t gain;
|
||||||
|
// keyon/off
|
||||||
|
bool keyOn;
|
||||||
|
bool keyOff;
|
||||||
|
// output
|
||||||
|
int16_t sampleOut; // final sample, to be multiplied by channel volume
|
||||||
|
int8_t volumeL;
|
||||||
|
int8_t volumeR;
|
||||||
|
bool echoEnable;
|
||||||
|
} DspChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The S-DSP is a digital signal processor generating the sound data.
|
||||||
|
*
|
||||||
|
* A DSP register can be selected with $F2, after which it can be read or
|
||||||
|
* written at $F3. Often it is useful to load the register address into A, and
|
||||||
|
* the value to send in Y, so that MOV $F2, YA can be used to do both in one
|
||||||
|
* 16-bit instruction.
|
||||||
|
*
|
||||||
|
* The DSP register address space only has 7 bits. The high bit of $F2, if set,
|
||||||
|
* will make the selected register read-only via $F3.
|
||||||
|
*
|
||||||
|
* When initializing the DSP registers for the first time, take care not to
|
||||||
|
* accidentally enable echo writeback via FLG, because it will immediately begin
|
||||||
|
* overwriting values in RAM.
|
||||||
|
*
|
||||||
|
* Voices
|
||||||
|
* There are 8 voices, numbered 0 to 7.
|
||||||
|
* Each voice X has 10 registers in the range $X0-$X9.
|
||||||
|
*
|
||||||
|
* | Name | Address | Bits | Notes |
|
||||||
|
* |---------|---------|-----------|--------------------------------------------------------|
|
||||||
|
* | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | | VOL (R) |
|
||||||
|
* $X1 | SVVV VVVV | Right channel volume, signed. | | P (L) | $X2 |
|
||||||
|
* LLLL LLLL | Low 8 bits of sample pitch. | | P (H)
|
||||||
|
* | $X3 | --HH HHHH | High 6 bits of sample pitch. | | SCRN | $X4 |
|
||||||
|
* SSSS SSSS | Selects a sample source entry from the directory. | | ADSR
|
||||||
|
* (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A).
|
||||||
|
* | | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | |
|
||||||
|
* GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | | ENVX |
|
||||||
|
* $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. | |
|
||||||
|
* OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample
|
||||||
|
* wave | | | | | multiplied by ENVX, before
|
||||||
|
* applying VOL. |
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Dsp {
|
||||||
|
public:
|
||||||
|
Dsp(std::vector<uint8_t>& aram) : aram_(aram) {}
|
||||||
|
|
||||||
|
void NewFrame();
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
void Cycle();
|
||||||
|
|
||||||
|
void HandleEcho();
|
||||||
|
void CycleChannel(int ch);
|
||||||
|
|
||||||
|
void HandleNoise();
|
||||||
|
void HandleGain(int ch);
|
||||||
|
|
||||||
|
bool CheckCounter(int rate);
|
||||||
|
|
||||||
|
void DecodeBrr(int ch);
|
||||||
|
|
||||||
|
uint8_t Read(uint8_t adr);
|
||||||
|
void Write(uint8_t adr, uint8_t val);
|
||||||
|
|
||||||
|
int16_t GetSample(int ch);
|
||||||
|
|
||||||
|
void GetSamples(int16_t* sample_data, int samples_per_frame, bool pal_timing);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int16_t sample_buffer_[0x400 * 2]; // (1024 samples, *2 for stereo)
|
||||||
|
int16_t sample_offset_; // current offset in samplebuffer
|
||||||
|
|
||||||
|
std::vector<uint8_t>& aram_;
|
||||||
|
|
||||||
|
// mirror ram
|
||||||
|
uint8_t ram[0x80];
|
||||||
|
// 8 channels
|
||||||
|
DspChannel channel[8];
|
||||||
|
// overarching
|
||||||
|
uint16_t counter;
|
||||||
|
uint16_t dirPage;
|
||||||
|
bool evenCycle;
|
||||||
|
bool mute;
|
||||||
|
bool reset;
|
||||||
|
int8_t masterVolumeL;
|
||||||
|
int8_t masterVolumeR;
|
||||||
|
// accumulation
|
||||||
|
int16_t sampleOutL;
|
||||||
|
int16_t sampleOutR;
|
||||||
|
int16_t echoOutL;
|
||||||
|
int16_t echoOutR;
|
||||||
|
// noise
|
||||||
|
int16_t noiseSample;
|
||||||
|
uint8_t noiseRate;
|
||||||
|
// echo
|
||||||
|
bool echoWrites;
|
||||||
|
int8_t echoVolumeL;
|
||||||
|
int8_t echoVolumeR;
|
||||||
|
int8_t feedbackVolume;
|
||||||
|
uint16_t echoBufferAdr;
|
||||||
|
uint16_t echoDelay;
|
||||||
|
uint16_t echoLength;
|
||||||
|
uint16_t echoBufferIndex;
|
||||||
|
uint8_t firBufferIndex;
|
||||||
|
int8_t firValues[8];
|
||||||
|
int16_t firBufferL[8];
|
||||||
|
int16_t firBufferR[8];
|
||||||
|
// sample ring buffer (1024 samples, *2 for stereo)
|
||||||
|
int16_t sampleBuffer[0x400 * 2];
|
||||||
|
uint16_t sampleOffset; // current offset in samplebuffer
|
||||||
|
uint32_t lastFrameBoundary;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EMU_AUDIO_S_DSP_H
|
||||||
152
src/app/emu/audio/internal/addressing.cc
Normal file
152
src/app/emu/audio/internal/addressing.cc
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#include "app/emu/audio/spc700.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
// adressing modes
|
||||||
|
|
||||||
|
uint16_t Spc700::ind() {
|
||||||
|
read(PC);
|
||||||
|
return X | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::idx() {
|
||||||
|
uint8_t pointer = ReadOpcode();
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return read_word(((pointer + X) & 0xff) | (PSW.P << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dpx() {
|
||||||
|
uint16_t res = ((ReadOpcode() + X) & 0xff) | (PSW.P << 8);
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dp_y() {
|
||||||
|
uint16_t res = ((ReadOpcode() + Y) & 0xff) | (PSW.P << 8);
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::abs_x() {
|
||||||
|
uint16_t res = (ReadOpcodeWord() + X) & 0xffff;
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::abs_y() {
|
||||||
|
uint16_t res = (ReadOpcodeWord() + Y) & 0xffff;
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::idy() {
|
||||||
|
uint8_t pointer = ReadOpcode();
|
||||||
|
uint16_t adr = read_word(pointer | (PSW.P << 8));
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return (adr + Y) & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dp_imm(uint8_t* srcVal) {
|
||||||
|
*srcVal = ReadOpcode();
|
||||||
|
return ReadOpcode() | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::ind_ind(uint8_t* srcVal) {
|
||||||
|
read(PC);
|
||||||
|
*srcVal = read(Y | (PSW.P << 8));
|
||||||
|
return X | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Spc700::abs_bit(uint16_t* adr) {
|
||||||
|
uint16_t adrBit = ReadOpcodeWord();
|
||||||
|
*adr = adrBit & 0x1fff;
|
||||||
|
return adrBit >> 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dp_word(uint16_t* low) {
|
||||||
|
uint8_t adr = ReadOpcode();
|
||||||
|
*low = adr | (PSW.P << 8);
|
||||||
|
return ((adr + 1) & 0xff) | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::ind_p() {
|
||||||
|
read(PC);
|
||||||
|
return X++ | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediate
|
||||||
|
uint16_t Spc700::imm() { return PC++; }
|
||||||
|
|
||||||
|
// Direct page
|
||||||
|
uint8_t Spc700::dp() {
|
||||||
|
return ReadOpcode() | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct page indexed by X
|
||||||
|
uint8_t Spc700::dp_plus_x() {
|
||||||
|
PC++;
|
||||||
|
uint8_t offset = read(PC);
|
||||||
|
return read((PSW.P << 8) + offset + X);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct page indexed by Y
|
||||||
|
uint8_t Spc700::dp_plus_y() {
|
||||||
|
PC++;
|
||||||
|
uint8_t offset = read(PC);
|
||||||
|
return read((PSW.P << 8) + offset + Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexed indirect (add index before 16-bit lookup).
|
||||||
|
uint16_t Spc700::dp_plus_x_indirect() {
|
||||||
|
PC++;
|
||||||
|
uint16_t addr = read_word(PC + X);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indirect indexed (add index after 16-bit lookup).
|
||||||
|
uint16_t Spc700::dp_indirect_plus_y() {
|
||||||
|
PC++;
|
||||||
|
uint16_t offset = read_word(PC);
|
||||||
|
return offset + Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dp_dp(uint8_t* src) {
|
||||||
|
*src = read(ReadOpcode() | (PSW.P << 8));
|
||||||
|
return ReadOpcode() | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::abs() { return ReadOpcodeWord(); }
|
||||||
|
|
||||||
|
int8_t Spc700::rel() {
|
||||||
|
PC++;
|
||||||
|
return static_cast<int8_t>(read(PC));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Spc700::i() { return read((PSW.P << 8) + X); }
|
||||||
|
|
||||||
|
uint8_t Spc700::i_postinc() {
|
||||||
|
uint8_t value = read((PSW.P << 8) + X);
|
||||||
|
X++;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::addr_plus_i() {
|
||||||
|
PC++;
|
||||||
|
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||||
|
return read(addr) + X;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::addr_plus_i_indexed() {
|
||||||
|
PC++;
|
||||||
|
uint16_t addr = read(PC) | (read(PC) << 8);
|
||||||
|
addr += X;
|
||||||
|
return read(addr) | (read(addr + 1) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
487
src/app/emu/audio/internal/instructions.cc
Normal file
487
src/app/emu/audio/internal/instructions.cc
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
#include "app/emu/audio/spc700.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
// opcode functions
|
||||||
|
|
||||||
|
void Spc700::MOVX(uint16_t adr) {
|
||||||
|
X = read(adr);
|
||||||
|
PSW.Z = (X == 0);
|
||||||
|
PSW.N = (X & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOVY(uint16_t adr) {
|
||||||
|
Y = read(adr);
|
||||||
|
PSW.Z = (Y == 0);
|
||||||
|
PSW.N = (Y & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOVS(uint16_t adr) {
|
||||||
|
switch (bstep) {
|
||||||
|
case 0: read(adr); break;
|
||||||
|
case 1: write(adr, A); bstep = 0; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOVSX(uint16_t adr) {
|
||||||
|
switch (bstep) {
|
||||||
|
case 0: read(adr); break;
|
||||||
|
case 1: write(adr, X); bstep = 0; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOVSY(uint16_t adr) {
|
||||||
|
switch (bstep) {
|
||||||
|
case 0: read(adr); break;
|
||||||
|
case 1: write(adr, Y); bstep = 0; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOV(uint16_t adr) {
|
||||||
|
A = read(adr);
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOV_ADDR(uint16_t address, uint8_t operand) {
|
||||||
|
write(address, operand);
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ADC(uint16_t adr) {
|
||||||
|
uint8_t value = read(adr);
|
||||||
|
uint16_t result = A + value + PSW.C;
|
||||||
|
PSW.V = ((A ^ result) & (adr ^ result) & 0x80);
|
||||||
|
PSW.C = (result > 0xFF);
|
||||||
|
PSW.H = ((A ^ adr ^ result) & 0x10);
|
||||||
|
A = result & 0xFF;
|
||||||
|
PSW.Z = ((A & 0xFF) == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ADCM(uint16_t& dest, uint8_t operand) {
|
||||||
|
uint8_t applyOn = read(dest);
|
||||||
|
int result = applyOn + operand + PSW.C;
|
||||||
|
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
|
||||||
|
(operand & 0x80) != (result & 0x80);
|
||||||
|
PSW.H = ((applyOn & 0xf) + (operand & 0xf) + PSW.C) > 0xf;
|
||||||
|
PSW.C = result > 0xff;
|
||||||
|
write(dest, result);
|
||||||
|
PSW.Z = ((result & 0xFF) == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::SBC(uint16_t adr) {
|
||||||
|
uint8_t value = read(adr) ^ 0xff;
|
||||||
|
int result = A + value + PSW.C;
|
||||||
|
PSW.V = (A & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80);
|
||||||
|
PSW.H = ((A & 0xf) + (value & 0xf) + PSW.C) > 0xf;
|
||||||
|
PSW.C = result > 0xff;
|
||||||
|
A = result;
|
||||||
|
PSW.Z = ((A & 0xFF) == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::SBCM(uint16_t& dest, uint8_t operand) {
|
||||||
|
operand ^= 0xff;
|
||||||
|
uint8_t applyOn = read(dest);
|
||||||
|
int result = applyOn + operand + PSW.C;
|
||||||
|
PSW.V = (applyOn & 0x80) == (operand & 0x80) &&
|
||||||
|
(operand & 0x80) != (operand & 0x80);
|
||||||
|
PSW.H = ((applyOn & 0xF) + (operand & 0xF) + PSW.C) > 0xF;
|
||||||
|
PSW.C = result > 0xFF;
|
||||||
|
write(dest, result);
|
||||||
|
PSW.Z = ((A & 0xFF) == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::CMPX(uint16_t adr) {
|
||||||
|
uint8_t value = read(adr) ^ 0xff;
|
||||||
|
int result = X + value + 1;
|
||||||
|
PSW.C = result > 0xff;
|
||||||
|
PSW.Z = (result == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::CMPY(uint16_t adr) {
|
||||||
|
uint8_t value = read(adr) ^ 0xff;
|
||||||
|
int result = Y + value + 1;
|
||||||
|
PSW.C = result > 0xff;
|
||||||
|
PSW.Z = (result == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::CMPM(uint16_t dst, uint8_t value) {
|
||||||
|
value ^= 0xff;
|
||||||
|
int result = read(dst) + value + 1;
|
||||||
|
PSW.C = result > 0xff;
|
||||||
|
callbacks_.idle(false);
|
||||||
|
PSW.Z = (result == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::CMP(uint16_t adr) {
|
||||||
|
uint8_t value = read(adr) ^ 0xff;
|
||||||
|
int result = A + value + 1;
|
||||||
|
PSW.C = result > 0xff;
|
||||||
|
PSW.Z = ((result & 0xFF) == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::AND(uint16_t adr) {
|
||||||
|
A &= read(adr);
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ANDM(uint16_t dest, uint8_t operand) {
|
||||||
|
uint8_t result = read(dest) & operand;
|
||||||
|
write(dest, result);
|
||||||
|
PSW.Z = (result == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::OR(uint16_t adr) {
|
||||||
|
A |= read(adr);
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ORM(uint16_t dst, uint8_t value) {
|
||||||
|
uint8_t result = read(dst) | value;
|
||||||
|
write(dst, result);
|
||||||
|
PSW.Z = (result == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::EOR(uint16_t adr) {
|
||||||
|
A ^= read(adr);
|
||||||
|
PSW.Z = (A == 0);
|
||||||
|
PSW.N = (A & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::EORM(uint16_t dest, uint8_t operand) {
|
||||||
|
uint8_t result = read(dest) ^ operand;
|
||||||
|
write(dest, result);
|
||||||
|
PSW.Z = (result == 0);
|
||||||
|
PSW.N = (result & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ASL(uint16_t operand) {
|
||||||
|
uint8_t val = read(operand);
|
||||||
|
write(operand, val);
|
||||||
|
PSW.C = (val & 0x80);
|
||||||
|
val <<= 1;
|
||||||
|
PSW.Z = (val == 0);
|
||||||
|
PSW.N = (val & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::LSR(uint16_t adr) {
|
||||||
|
uint8_t val = read(adr);
|
||||||
|
PSW.C = (val & 0x01);
|
||||||
|
val >>= 1;
|
||||||
|
write(adr, val);
|
||||||
|
PSW.Z = (val == 0);
|
||||||
|
PSW.N = (val & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ROR(uint16_t adr) {
|
||||||
|
uint8_t val = read(adr);
|
||||||
|
bool newC = val & 1;
|
||||||
|
val = (val >> 1) | (PSW.C << 7);
|
||||||
|
PSW.C = newC;
|
||||||
|
write(adr, val);
|
||||||
|
PSW.Z = (val == 0);
|
||||||
|
PSW.N = (val & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ROL(uint16_t adr) {
|
||||||
|
uint8_t val = read(adr);
|
||||||
|
bool newC = val & 0x80;
|
||||||
|
val = (val << 1) | PSW.C;
|
||||||
|
PSW.C = newC;
|
||||||
|
write(adr, val);
|
||||||
|
|
||||||
|
PSW.Z = (val == 0);
|
||||||
|
PSW.N = (val & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::XCN(uint8_t operand, bool isImmediate) {
|
||||||
|
uint8_t value = isImmediate ? imm() : operand;
|
||||||
|
value = ((value & 0xF0) >> 4) | ((value & 0x0F) << 4);
|
||||||
|
PSW.Z = (value == 0);
|
||||||
|
PSW.N = (value & 0x80);
|
||||||
|
// operand = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::INC(uint16_t adr) {
|
||||||
|
uint8_t val = read(adr) + 1;
|
||||||
|
write(adr, val);
|
||||||
|
PSW.Z = (val == 0);
|
||||||
|
PSW.N = (val & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::DEC(uint16_t operand) {
|
||||||
|
uint8_t val = read(operand) - 1;
|
||||||
|
write(operand, val);
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOVW(uint16_t& dest, uint16_t operand) {
|
||||||
|
dest = operand;
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::INCW(uint16_t& operand) {
|
||||||
|
operand++;
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::DECW(uint16_t& operand) {
|
||||||
|
operand--;
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::ADDW(uint16_t& dest, uint16_t operand) {
|
||||||
|
uint32_t result = dest + operand;
|
||||||
|
PSW.C = (result > 0xFFFF);
|
||||||
|
PSW.Z = ((result & 0xFFFF) == 0);
|
||||||
|
PSW.N = (result & 0x8000);
|
||||||
|
PSW.V = ((dest ^ result) & (operand ^ result) & 0x8000);
|
||||||
|
dest = result & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::SUBW(uint16_t& dest, uint16_t operand) {
|
||||||
|
uint32_t result = dest - operand;
|
||||||
|
PSW.C = (result < 0x10000);
|
||||||
|
PSW.Z = ((result & 0xFFFF) == 0);
|
||||||
|
PSW.N = (result & 0x8000);
|
||||||
|
PSW.V = ((dest ^ result) & (dest ^ operand) & 0x8000);
|
||||||
|
dest = result & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::CMPW(uint16_t operand) {
|
||||||
|
uint32_t result = YA - operand;
|
||||||
|
PSW.C = (result < 0x10000);
|
||||||
|
PSW.Z = ((result & 0xFFFF) == 0);
|
||||||
|
PSW.N = (result & 0x8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MUL(uint8_t operand) {
|
||||||
|
uint16_t result = A * operand;
|
||||||
|
YA = result;
|
||||||
|
PSW.Z = (result == 0);
|
||||||
|
PSW.N = (result & 0x8000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::DIV(uint8_t operand) {
|
||||||
|
if (operand == 0) {
|
||||||
|
// Handle divide by zero error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t quotient = A / operand;
|
||||||
|
uint8_t remainder = A % operand;
|
||||||
|
A = quotient;
|
||||||
|
Y = remainder;
|
||||||
|
PSW.Z = (quotient == 0);
|
||||||
|
PSW.N = (quotient & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BRA(int8_t offset) { PC += offset; }
|
||||||
|
|
||||||
|
void Spc700::BEQ(int8_t offset) {
|
||||||
|
if (PSW.Z) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BNE(int8_t offset) {
|
||||||
|
if (!PSW.Z) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BCS(int8_t offset) {
|
||||||
|
if (PSW.C) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BCC(int8_t offset) {
|
||||||
|
if (!PSW.C) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BVS(int8_t offset) {
|
||||||
|
if (PSW.V) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BVC(int8_t offset) {
|
||||||
|
if (!PSW.V) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BMI(int8_t offset) {
|
||||||
|
if (PSW.N) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BPL(int8_t offset) {
|
||||||
|
if (!PSW.N) {
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BBS(uint8_t bit, uint8_t operand) {
|
||||||
|
if (operand & (1 << bit)) {
|
||||||
|
PC += rel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BBC(uint8_t bit, uint8_t operand) {
|
||||||
|
if (!(operand & (1 << bit))) {
|
||||||
|
PC += rel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CBNE DBNZ
|
||||||
|
// JMP
|
||||||
|
void Spc700::JMP(uint16_t address) { PC = address; }
|
||||||
|
|
||||||
|
void Spc700::CALL(uint16_t address) {
|
||||||
|
uint16_t return_address = PC + 2;
|
||||||
|
write(SP, return_address & 0xFF);
|
||||||
|
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||||
|
SP -= 2;
|
||||||
|
PC = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::PCALL(uint8_t offset) {
|
||||||
|
uint16_t return_address = PC + 2;
|
||||||
|
write(SP, return_address & 0xFF);
|
||||||
|
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||||
|
SP -= 2;
|
||||||
|
PC += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::TCALL(uint8_t offset) {
|
||||||
|
uint16_t return_address = PC + 2;
|
||||||
|
write(SP, return_address & 0xFF);
|
||||||
|
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||||
|
SP -= 2;
|
||||||
|
PC = 0xFFDE + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::BRK() {
|
||||||
|
uint16_t return_address = PC + 2;
|
||||||
|
write(SP, return_address & 0xFF);
|
||||||
|
write(SP - 1, (return_address >> 8) & 0xFF);
|
||||||
|
SP -= 2;
|
||||||
|
PC = 0xFFDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::RET() {
|
||||||
|
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
||||||
|
SP += 2;
|
||||||
|
PC = return_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::RETI() {
|
||||||
|
uint16_t return_address = read(SP) | (read(SP + 1) << 8);
|
||||||
|
SP += 2;
|
||||||
|
PC = return_address;
|
||||||
|
PSW.I = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::PUSH(uint8_t operand) {
|
||||||
|
write(SP, operand);
|
||||||
|
SP--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::POP(uint8_t& operand) {
|
||||||
|
SP++;
|
||||||
|
operand = read(SP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::SET1(uint8_t bit, uint8_t& operand) { operand |= (1 << bit); }
|
||||||
|
|
||||||
|
void Spc700::CLR1(uint8_t bit, uint8_t& operand) { operand &= ~(1 << bit); }
|
||||||
|
|
||||||
|
void Spc700::TSET1(uint8_t bit, uint8_t& operand) {
|
||||||
|
PSW.C = (operand & (1 << bit));
|
||||||
|
operand |= (1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::TCLR1(uint8_t bit, uint8_t& operand) {
|
||||||
|
PSW.C = (operand & (1 << bit));
|
||||||
|
operand &= ~(1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::AND1(uint8_t bit, uint8_t& operand) {
|
||||||
|
operand &= (1 << bit);
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::OR1(uint8_t bit, uint8_t& operand) {
|
||||||
|
operand |= (1 << bit);
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::EOR1(uint8_t bit, uint8_t& operand) {
|
||||||
|
operand ^= (1 << bit);
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::NOT1(uint8_t bit, uint8_t& operand) {
|
||||||
|
operand ^= (1 << bit);
|
||||||
|
PSW.Z = (operand == 0);
|
||||||
|
PSW.N = (operand & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::MOV1(uint8_t bit, uint8_t& operand) {
|
||||||
|
PSW.C = (operand & (1 << bit));
|
||||||
|
operand |= (1 << bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spc700::CLRC() { PSW.C = 0; }
|
||||||
|
|
||||||
|
void Spc700::SETC() { PSW.C = 1; }
|
||||||
|
|
||||||
|
void Spc700::NOTC() { PSW.C = !PSW.C; }
|
||||||
|
|
||||||
|
void Spc700::CLRV() { PSW.V = 0; }
|
||||||
|
|
||||||
|
void Spc700::CLRP() { PSW.P = 0; }
|
||||||
|
|
||||||
|
void Spc700::SETP() { PSW.P = 1; }
|
||||||
|
|
||||||
|
void Spc700::EI() { PSW.I = 1; }
|
||||||
|
|
||||||
|
void Spc700::DI() { PSW.I = 0; }
|
||||||
|
|
||||||
|
void Spc700::NOP() { PC++; }
|
||||||
|
|
||||||
|
void Spc700::SLEEP() {}
|
||||||
|
|
||||||
|
void Spc700::STOP() {}
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
|
} // namespace emu
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
263
src/app/emu/audio/internal/opcodes.h
Normal file
263
src/app/emu/audio/internal/opcodes.h
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
const std::unordered_map<uint8_t, std::string> spc_opcode_map = {
|
||||||
|
{0x00, "NOP"},
|
||||||
|
{0x01, "TCALL0"},
|
||||||
|
{0x02, "SET1 direct.0"},
|
||||||
|
{0x03, "BBS direct.0,rel"},
|
||||||
|
{0x04, "OR A,direct"},
|
||||||
|
{0x05, "OR A,abs"},
|
||||||
|
{0x06, "OR A,(X)"},
|
||||||
|
{0x07, "OR A,(direct+X)"},
|
||||||
|
{0x08, "OR A,#imm"},
|
||||||
|
{0x09, "OR direct,imm"},
|
||||||
|
{0x0A, "OR1 C,membit"},
|
||||||
|
{0x0B, "ASL direct"},
|
||||||
|
{0x0C, "ASL abs"},
|
||||||
|
{0x0D, "PUSH PSW"},
|
||||||
|
{0x0E, "TSET1 abs"},
|
||||||
|
{0x0F, "BRK"},
|
||||||
|
{0x10, "BPL rel"},
|
||||||
|
{0x11, "TCALL1"},
|
||||||
|
{0x12, "CLR1 direct.0"},
|
||||||
|
{0x13, "BBC direct.0,rel"},
|
||||||
|
{0x14, "OR A,direct+X"},
|
||||||
|
{0x15, "OR A,abs+X"},
|
||||||
|
{0x16, "OR A,abs+Y"},
|
||||||
|
{0x17, "OR A,(direct)+Y"},
|
||||||
|
{0x18, "OR direct,direct"},
|
||||||
|
{0x19, "OR (X),(Y)"},
|
||||||
|
{0x1A, "DECW direct"},
|
||||||
|
{0x1B, "ASL direct+X"},
|
||||||
|
{0x1C, "ASL A"},
|
||||||
|
{0x1D, "DEC X"},
|
||||||
|
{0x1E, "CMP X,abs"},
|
||||||
|
{0x1F, "JMP (abs+X)"},
|
||||||
|
{0x20, "CLRP"},
|
||||||
|
{0x21, "TCALL2"},
|
||||||
|
{0x22, "SET1 direct.1"},
|
||||||
|
{0x23, "BBS direct.1,rel"},
|
||||||
|
{0x24, "AND A,direct"},
|
||||||
|
{0x25, "AND A,abs"},
|
||||||
|
{0x26, "AND A,(X)"},
|
||||||
|
{0x27, "AND A,(direct+X)"},
|
||||||
|
{0x28, "AND A,#imm"},
|
||||||
|
{0x29, "AND direct,imm"},
|
||||||
|
{0x2A, "OR1 C,/membit"},
|
||||||
|
{0x2B, "ROL direct"},
|
||||||
|
{0x2C, "ROL abs"},
|
||||||
|
{0x2D, "PUSH A"},
|
||||||
|
{0x2E, "CBNE direct,rel"},
|
||||||
|
{0x2F, "BRA rel"},
|
||||||
|
{0x30, "BMI rel"},
|
||||||
|
{0x31, "TCALL3"},
|
||||||
|
{0x32, "CLR1 direct.1"},
|
||||||
|
{0x33, "BBC direct.1,rel"},
|
||||||
|
{0x34, "AND A,direct+X"},
|
||||||
|
{0x35, "AND A,abs+X"},
|
||||||
|
{0x36, "AND A,abs+Y"},
|
||||||
|
{0x37, "AND A,(direct)+Y"},
|
||||||
|
{0x38, "AND direct,direct"},
|
||||||
|
{0x39, "AND (X),(Y)"},
|
||||||
|
{0x3A, "INCW direct"},
|
||||||
|
{0x3B, "ROL direct+X"},
|
||||||
|
{0x3C, "ROL A"},
|
||||||
|
{0x3D, "INC X"},
|
||||||
|
{0x3E, "CMP X,direct"},
|
||||||
|
{0x3F, "CALL abs"},
|
||||||
|
{0x40, "SETP"},
|
||||||
|
{0x41, "TCALL4"},
|
||||||
|
{0x42, "SET1 direct.2"},
|
||||||
|
{0x43, "BBS direct.2,rel"},
|
||||||
|
{0x44, "EOR A,direct"},
|
||||||
|
{0x45, "EOR A,abs"},
|
||||||
|
{0x46, "EOR A,(X)"},
|
||||||
|
{0x47, "EOR A,(direct+X)"},
|
||||||
|
{0x48, "EOR A,#imm"},
|
||||||
|
{0x49, "EOR direct,imm"},
|
||||||
|
{0x4A, "AND1 C,membit"},
|
||||||
|
{0x4B, "LSR direct"},
|
||||||
|
{0x4C, "LSR abs"},
|
||||||
|
{0x4D, "PUSH X"},
|
||||||
|
{0x4E, "TCLR1 abs"},
|
||||||
|
{0x4F, "PCALL addr"},
|
||||||
|
{0x50, "BVC rel"},
|
||||||
|
{0x51, "TCALL5"},
|
||||||
|
{0x52, "CLR1 direct.2"},
|
||||||
|
{0x53, "BBC direct.2,rel"},
|
||||||
|
{0x54, "EOR A,direct+X"},
|
||||||
|
{0x55, "EOR A,abs+X"},
|
||||||
|
{0x56, "EOR A,abs+Y"},
|
||||||
|
{0x57, "EOR A,(direct)+Y"},
|
||||||
|
{0x58, "EOR direct,direct"},
|
||||||
|
{0x59, "EOR (X),(Y)"},
|
||||||
|
{0x5A, "CMPW YA,direct"},
|
||||||
|
{0x5B, "LSR direct+X"},
|
||||||
|
{0x5C, "LSR A"},
|
||||||
|
{0x5D, "MOV X,A"},
|
||||||
|
{0x5E, "CMP Y,abs"},
|
||||||
|
{0x5F, "JMP abs"},
|
||||||
|
{0x60, "CLRC"},
|
||||||
|
{0x61, "TCALL6"},
|
||||||
|
{0x62, "SET1 direct.3"},
|
||||||
|
{0x63, "BBS direct.3,rel"},
|
||||||
|
{0x64, "CMP A,direct"},
|
||||||
|
{0x65, "CMP A,abs"},
|
||||||
|
{0x66, "CMP A,(X)"},
|
||||||
|
{0x67, "CMP A,(direct+X)"},
|
||||||
|
{0x68, "CMP A,#imm"},
|
||||||
|
{0x69, "CMP direct,imm"},
|
||||||
|
{0x6A, "AND1 C,/membit"},
|
||||||
|
{0x6B, "ROR direct"},
|
||||||
|
{0x6C, "ROR abs"},
|
||||||
|
{0x6D, "PUSH Y"},
|
||||||
|
{0x6E, "DBNZ direct,rel"},
|
||||||
|
{0x6F, "RET"},
|
||||||
|
{0x70, "BVS rel"},
|
||||||
|
{0x71, "TCALL7"},
|
||||||
|
{0x72, "CLR1 direct.3"},
|
||||||
|
{0x73, "BBC direct.3,rel"},
|
||||||
|
{0x74, "CMP A,direct+X"},
|
||||||
|
{0x75, "CMP A,abs+X"},
|
||||||
|
{0x76, "CMP A,abs+Y"},
|
||||||
|
{0x77, "CMP A,(direct)+Y"},
|
||||||
|
{0x78, "CMP direct,direct"},
|
||||||
|
{0x79, "CMP (X),(Y)"},
|
||||||
|
{0x7A, "ADDW YA,direct"},
|
||||||
|
{0x7B, "ROR direct+X"},
|
||||||
|
{0x7C, "ROR A"},
|
||||||
|
{0x7D, "MOV A,X"},
|
||||||
|
{0x7E, "CMP Y,direct"},
|
||||||
|
{0x7F, "RETI"},
|
||||||
|
{0x80, "SETC"},
|
||||||
|
{0x81, "TCALL8"},
|
||||||
|
{0x82, "SET1 direct.4"},
|
||||||
|
{0x83, "BBS direct.4,rel"},
|
||||||
|
{0x84, "ADC A,direct"},
|
||||||
|
{0x85, "ADC A,abs"},
|
||||||
|
{0x86, "ADC A,(X)"},
|
||||||
|
{0x87, "ADC A,(direct+X)"},
|
||||||
|
{0x88, "ADC A,#imm"},
|
||||||
|
{0x89, "ADC direct,imm"},
|
||||||
|
{0x8A, "EOR1 C,membit"},
|
||||||
|
{0x8B, "DEC direct"},
|
||||||
|
{0x8C, "DEC abs"},
|
||||||
|
{0x8D, "MOV Y,#imm"},
|
||||||
|
{0x8E, "POP PSW"},
|
||||||
|
{0x8F, "MOV direct,#imm"},
|
||||||
|
{0x90, "BCC rel"},
|
||||||
|
{0x91, "TCALL9"},
|
||||||
|
{0x92, "CLR1 direct.4"},
|
||||||
|
{0x93, "BBC direct.4,rel"},
|
||||||
|
{0x94, "ADC A,direct+X"},
|
||||||
|
{0x95, "ADC A,abs+X"},
|
||||||
|
{0x96, "ADC A,abs+Y"},
|
||||||
|
{0x97, "ADC A,(direct)+Y"},
|
||||||
|
{0x98, "ADC direct,direct"},
|
||||||
|
{0x99, "ADC (X),(Y)"},
|
||||||
|
{0x9A, "SUBW YA,direct"},
|
||||||
|
{0x9B, "DEC direct+X"},
|
||||||
|
{0x9C, "DEC A"},
|
||||||
|
{0x9D, "MOV X,SP"},
|
||||||
|
{0x9E, "DIV YA,X"},
|
||||||
|
{0x9F, "XCN A"},
|
||||||
|
{0xA0, "EI"},
|
||||||
|
{0xA1, "TCALL10"},
|
||||||
|
{0xA2, "SET1 direct.5"},
|
||||||
|
{0xA3, "BBS direct.5,rel"},
|
||||||
|
{0xA4, "SBC A,direct"},
|
||||||
|
{0xA5, "SBC A,abs"},
|
||||||
|
{0xA6, "SBC A,(X)"},
|
||||||
|
{0xA7, "SBC A,(direct+X)"},
|
||||||
|
{0xA8, "SBC A,#imm"},
|
||||||
|
{0xA9, "SBC direct,imm"},
|
||||||
|
{0xAA, "MOV1 C,membit"},
|
||||||
|
{0xAB, "INC direct"},
|
||||||
|
{0xAC, "INC abs"},
|
||||||
|
{0xAD, "CMP Y,#imm"},
|
||||||
|
{0xAE, "POP A"},
|
||||||
|
{0xAF, "MOV (X)+,A"},
|
||||||
|
{0xB0, "BCS rel"},
|
||||||
|
{0xB1, "TCALL11"},
|
||||||
|
{0xB2, "CLR1 direct.5"},
|
||||||
|
{0xB3, "BBC direct.5,rel"},
|
||||||
|
{0xB4, "SBC A,direct+X"},
|
||||||
|
{0xB5, "SBC A,abs+X"},
|
||||||
|
{0xB6, "SBC A,abs+Y"},
|
||||||
|
{0xB7, "SBC A,(direct)+Y"},
|
||||||
|
{0xB8, "SBC direct,direct"},
|
||||||
|
{0xB9, "SBC (X),(Y)"},
|
||||||
|
{0xBA, "MOVW YA,direct"},
|
||||||
|
{0xBB, "INC direct+X"},
|
||||||
|
{0xBC, "INC A"},
|
||||||
|
{0xBD, "MOV SP,X"},
|
||||||
|
{0xBE, "DAS"},
|
||||||
|
{0xBF, "MOV A,(X)+"},
|
||||||
|
{0xC0, "DI"},
|
||||||
|
{0xC1, "TCALL12"},
|
||||||
|
{0xC2, "SET1 direct.6"},
|
||||||
|
{0xC3, "BBS direct.6,rel"},
|
||||||
|
{0xC4, "MOV direct,A"},
|
||||||
|
{0xC5, "MOV abs,A"},
|
||||||
|
{0xC6, "MOV (X),A"},
|
||||||
|
{0xC7, "MOV (direct+X),A"},
|
||||||
|
{0xC8, "CMP X,#imm"},
|
||||||
|
{0xC9, "MOV abs,X"},
|
||||||
|
{0xCA, "MOV1 membit,C"},
|
||||||
|
{0xCB, "MOV direct,Y"},
|
||||||
|
{0xCC, "MOV abs,Y"},
|
||||||
|
{0xCD, "MOV X,#imm"},
|
||||||
|
{0xCE, "POP X"},
|
||||||
|
{0xCF, "MUL YA"},
|
||||||
|
{0xD0, "BNE rel"},
|
||||||
|
{0xD1, "TCALL13"},
|
||||||
|
{0xD2, "CLR1 direct.6"},
|
||||||
|
{0xD3, "BBC direct.6,rel"},
|
||||||
|
{0xD4, "MOV direct+X,A"},
|
||||||
|
{0xD5, "MOV abs+X,A"},
|
||||||
|
{0xD6, "MOV abs+Y,A"},
|
||||||
|
{0xD7, "MOV (direct)+Y,A"},
|
||||||
|
{0xD8, "MOV direct,X"},
|
||||||
|
{0xD9, "MOV direct+Y,X"},
|
||||||
|
{0xDA, "MOVW direct,YA"},
|
||||||
|
{0xDB, "MOV direct+X,Y"},
|
||||||
|
{0xDC, "DEC Y"},
|
||||||
|
{0xDD, "MOV A,Y"},
|
||||||
|
{0xDE, "CBNE direct+X,rel"},
|
||||||
|
{0xDF, "DAA"},
|
||||||
|
{0xE0, "CLRV"},
|
||||||
|
{0xE1, "TCALL14"},
|
||||||
|
{0xE2, "SET1 direct.7"},
|
||||||
|
{0xE3, "BBS direct.7,rel"},
|
||||||
|
{0xE4, "MOV A,direct"},
|
||||||
|
{0xE5, "MOV A,abs"},
|
||||||
|
{0xE6, "MOV A,(X)"},
|
||||||
|
{0xE7, "MOV A,(direct+X)"},
|
||||||
|
{0xE8, "MOV A,#imm"},
|
||||||
|
{0xE9, "MOV X,abs"},
|
||||||
|
{0xEA, "NOT1 membit"},
|
||||||
|
{0xEB, "MOV Y,direct"},
|
||||||
|
{0xEC, "MOV Y,abs"},
|
||||||
|
{0xED, "NOTC"},
|
||||||
|
{0xEE, "POP Y"},
|
||||||
|
{0xEF, "SLEEP"},
|
||||||
|
{0xF0, "BEQ rel"},
|
||||||
|
{0xF1, "TCALL15"},
|
||||||
|
{0xF2, "CLR1 direct.7"},
|
||||||
|
{0xF3, "BBC direct.7,rel"},
|
||||||
|
{0xF4, "MOV A,direct+X"},
|
||||||
|
{0xF5, "MOV A,abs+X"},
|
||||||
|
{0xF6, "MOV A,abs+Y"},
|
||||||
|
{0xF7, "MOV A,(direct)+Y"},
|
||||||
|
{0xF8, "MOV X,direct"},
|
||||||
|
{0xF9, "MOV X,direct+Y"},
|
||||||
|
{0xFA, "MOV direct,S"},
|
||||||
|
{0xFB, "MOV Y,direct+X"},
|
||||||
|
{0xFC, "INC Y"},
|
||||||
|
{0xFD, "MOV Y,A"},
|
||||||
|
{0xFE, "DBNZ Y,rel"},
|
||||||
|
{0xFF, "STOP"}};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user