Compare commits

...

122 Commits

Author SHA1 Message Date
c6fdf19055 [nostalgia/studio] Bump version to d2025.02.1
All checks were successful
Build / build (push) Successful in 1m28s
2025-03-24 20:51:02 -05:00
ed5f424eb0 [keel] Add missing new line to log message
All checks were successful
Build / build (push) Successful in 1m28s
2025-03-24 20:44:33 -05:00
a44c5acc4b [studio] Fix closing tab with unsaved changes 2025-03-24 20:43:34 -05:00
13e256b3a9 [nostalgia/studio] Update version to d2025.02.0
All checks were successful
Build / build (push) Successful in 1m28s
2025-02-25 20:15:07 -06:00
6bd74611cd [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m28s
2025-02-25 20:02:52 -06:00
c3f9cf9a64 [studio] Fix New Project opening project, disable New if no project open
Some checks failed
Build / build (push) Has been cancelled
2025-02-25 20:01:33 -06:00
646ab1283f [nostalgia/gfx] Cleanup
All checks were successful
Build / build (push) Successful in 1m26s
2025-02-24 21:45:45 -06:00
74cf055610 [nostalgia] Cleanup
All checks were successful
Build / build (push) Successful in 1m32s
2025-02-24 19:43:10 -06:00
0d8ba1b154 [nostalgia/gfx] Cleanup formatting mistake
All checks were successful
Build / build (push) Successful in 1m26s
2025-02-23 23:21:59 -06:00
20edbb7f38 [buildcore] Map aarch64 to arm64
All checks were successful
Build / build (push) Successful in 1m27s
2025-02-23 01:00:12 -06:00
6febc7cc73 [nostalgia] Fix build
All checks were successful
Build / build (push) Successful in 1m32s
2025-02-23 00:51:30 -06:00
b94d6b5061 [nostalgia] Remove scene package, finish stubbing out sound
Some checks failed
Build / build (push) Failing after 21s
2025-02-23 00:49:58 -06:00
b3952cabbc [nostalgia] Add build upload step to CI
All checks were successful
Build / build (push) Successful in 1m39s
2025-02-22 21:30:35 -06:00
2ffc11b04e Merge commit 'e723ead864edb4bc160e4d69713309174ad9e82e'
All checks were successful
Build / build (push) Successful in 1m34s
2025-02-22 20:55:17 -06:00
96cace2cbb [studio] Cleanup 2025-02-22 19:51:55 -06:00
472f5702bd [nostalgia/gfx/studio/tilesheet] Change max export scale to 135
All checks were successful
Build / build (push) Successful in 1m32s
2025-02-22 15:31:39 -06:00
c0ac4345d3 [studio] Cleanup
All checks were successful
Build / build (push) Successful in 1m30s
2025-02-22 15:16:26 -06:00
fbebf4ef83 [nostalgia/gfx/studio/tilesheet] Fix export for 4bpp images
All checks were successful
Build / build (push) Successful in 1m27s
2025-02-22 00:58:12 -06:00
20513f7749 [nostalgia/sample_project] Add type descriptors 2025-02-21 00:41:04 -06:00
25a7873ea2 [nostalgia,studio] Fix crash that occurred when navigating to file that is not already open
All checks were successful
Build / build (push) Successful in 1m32s
2025-02-20 23:57:02 -06:00
d0a32e247e [ox/std] Add Vector::remove
All checks were successful
Build / build (push) Successful in 1m42s
2025-02-20 23:34:36 -06:00
03d4a5736e [nostalgia,studio] Add ability to navigate from tile sheet to palette color
All checks were successful
Build / build (push) Successful in 1m52s
2025-02-20 23:30:50 -06:00
a2e41e6527 Merge commit '4e94c925686cdda4b1ac777045dd7a17c7dc0329'
All checks were successful
Build / build (push) Successful in 1m38s
2025-02-20 20:11:03 -06:00
40a7caff90 [ox/std] Make bounds checking its own option enable-able in release builds 2025-02-20 20:05:07 -06:00
26fc5565e8 [nostalgia/gfx] Make dangling reference warning suppressions check for GCC 13
All checks were successful
Build / build (push) Successful in 1m30s
2025-02-20 19:40:08 -06:00
388541ce32 [nostalgia/player] Cleanup
All checks were successful
Build / build (push) Successful in 1m24s
2025-02-20 00:01:29 -06:00
6c194667b9 [nostalgia] Fix NostalgiaGfx lib name, stub out sound package
All checks were successful
Build / build (push) Successful in 1m27s
2025-02-19 22:19:16 -06:00
62d0579f40 [ox/fs] Restructure stat error handling to make easier to debug
All checks were successful
Build / build (push) Successful in 1m25s
2025-02-19 21:47:47 -06:00
202595b2a6 [keel] Fix loading assets by path 2025-02-19 21:47:00 -06:00
cb21ff3f04 Merge commit 'a6b9657268eb3fe139b0c22df27c2cb2efc0013c' 2025-02-19 00:34:26 -06:00
2a8e3c2dc4 [nostalgia/gfx] Remove unnecessary cast
All checks were successful
Build / build (push) Successful in 1m26s
2025-02-18 23:01:15 -06:00
998066d377 [ox/std] Add comparison functions
All checks were successful
Build / build (push) Successful in 1m24s
2025-02-18 21:46:41 -06:00
fefb876fe7 [nostalgia/gfx] Add checks for GCC version for warning suppression
All checks were successful
Build / build (push) Successful in 1m24s
2025-02-18 20:33:29 -06:00
5979e9885e [jsoncpp] Up required CMake version 2025-02-18 20:26:47 -06:00
a17abe4639 [nfde] Up required CMake version 2025-02-18 20:26:47 -06:00
d62f913855 [nostalgia/gfx] Suppress some superfluous warnings
Some checks failed
Build / build (push) Failing after 1m9s
2025-02-18 20:22:56 -06:00
12bb7475fc [nostalgia/gfx/studio/tilesheet] Adjust pixel line size on Windows
All checks were successful
Build / build (push) Successful in 1m25s
2025-02-18 20:19:51 -06:00
df2c7e2b67 [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m26s
2025-02-08 18:10:49 -06:00
713aec887b [buildcore] Change mypy invokation
All checks were successful
Build / build (push) Successful in 1m28s
2025-02-07 20:38:44 -06:00
3089cd7afc Change builder type to olympic
Some checks failed
Build / build (push) Has been cancelled
2025-02-07 20:34:22 -06:00
00638bc812 [nostalgia/gfx/studio/tilesheet] Mark DrawCommands as obsolete if no changes
All checks were successful
Build / build (push) Successful in 3m37s
2025-02-05 20:26:47 -06:00
e002109829 [studio] Make undo/redo skip over obsolete commands 2025-02-05 20:26:03 -06:00
b4798fd2ab [nostalgia/gfx/studio/tilesheet] Make rotate only available for square subsheets or selections
All checks were successful
Build / build (push) Successful in 3m39s
2025-02-05 01:54:41 -06:00
3c804bf62a [studio] Give MakeCopy popup an error message for files that already exist
All checks were successful
Build / build (push) Successful in 3m36s
2025-02-03 23:30:07 -06:00
d39d552bd9 [nostalgia/studio] Update icon to higher resolution 2025-02-03 23:29:26 -06:00
b7202a2b0d [nostalgia/player] Disable Keel mods on GBA
All checks were successful
Build / build (push) Successful in 3m34s
2025-02-03 22:48:07 -06:00
4e27a4c1f5 [nostalgia/core/studio/tilesheet] Fix palette path display update
All checks were successful
Build / build (push) Successful in 3m37s
2025-02-03 22:43:20 -06:00
4ef31762d0 [nostalgia/core/studio/tilesheet] Cleanup 2025-02-03 22:43:02 -06:00
8b22a8f339 [keel] Make buildUuidMap only read the first 40 bytes of each file 2025-02-03 20:29:06 -06:00
d45ff05bcd [ox/fs] Add new partial file read functions 2025-02-03 20:28:25 -06:00
671dd86206 [keel,studio] Add Make Copy option to ProjectExplorer
All checks were successful
Build / build (push) Successful in 3m46s
2025-02-03 02:01:40 -06:00
0abadc1850 [studio] Fix QuestionPopup to only emit a response when there is a response
All checks were successful
Build / build (push) Successful in 3m36s
2025-02-03 00:35:37 -06:00
4e068d628c [studio] Fix misrender flash on tab close 2025-02-03 00:19:14 -06:00
4461f99fa4 [studio] Add Ctrl-W shortcut for closing active tab
All checks were successful
Build / build (push) Successful in 3m36s
2025-02-02 23:13:15 -06:00
cd1f4bdaa3 [studio] Add confirmation for closing file with unsaved changes 2025-02-02 23:07:59 -06:00
4728699585 [studio] Add combobox that will take string views
All checks were successful
Build / build (push) Successful in 3m47s
2025-02-02 20:46:08 -06:00
105a1e5559 [nostalgia/core/studio/tilesheet] Rework operation ctrls into a dropbox
Some checks failed
Build / build (push) Failing after 1m1s
2025-02-02 20:43:01 -06:00
1bc18e34a8 [nostalgia/core/studio/tilesheet] Add ability to rotate a selection 2025-02-02 20:22:20 -06:00
fb8d295fcb [nostalgia/core/studio/tilesheet] Add rotate functionality 2025-02-02 14:46:21 -06:00
8459d3baea Merge commit 'c42adc290cd8a27d01bb6d9877032dd2c963a4b7' 2025-02-01 22:55:46 -06:00
804d78e116 [nostalgia/gfx/studio] Cleanup
All checks were successful
Build / build (push) Successful in 3m32s
2025-02-01 15:14:24 -06:00
5351e9aa0a [nostalgia/core/studio/tilesheet] Add line drawing tool
All checks were successful
Build / build (push) Successful in 3m36s
2025-02-01 14:14:09 -06:00
b5954f15c5 [studio] Restore context menu for root dir, but exclude Delete
All checks were successful
Build / build (push) Successful in 3m33s
2025-01-29 18:47:48 -06:00
5dce9dd377 [studio] Suppress context menu for root dir in ProjectExplorer
All checks were successful
Build / build (push) Successful in 3m32s
2025-01-28 01:27:51 -06:00
0570f76236 [ox/fs] Fix PassThroughFS::stripSlash
All checks were successful
Build / build (push) Successful in 3m32s
2025-01-28 01:18:44 -06:00
e22b658a67 [studio] Fix isParentOf check in Project to ensure child dir path ends with /
Some checks failed
Build / build (push) Has been cancelled
2025-01-28 01:04:17 -06:00
56b9cb6ebf [studio] Fix file explorer to treat empty directories as directories
All checks were successful
Build / build (push) Successful in 3m32s
2025-01-27 23:31:58 -06:00
eaa9a2415e [keel] Make reloadAsset check if file is loaded 2025-01-27 21:59:57 -06:00
95256a9a0d [studio] Make rename file give error message if the file already exists
All checks were successful
Build / build (push) Successful in 3m32s
2025-01-27 00:54:58 -06:00
2286238abc [studio] Make rename file accept input upon pressing Enter if text input is focused
All checks were successful
Build / build (push) Successful in 3m36s
2025-01-27 00:33:14 -06:00
13f0bf57e4 [studio] Make deleting a file close tabs associated with it 2025-01-27 00:30:27 -06:00
8eb1ac215b [studio] Fix not to try moving a parent directory to its child
All checks were successful
Build / build (push) Successful in 3m34s
2025-01-27 00:10:25 -06:00
e132f2fd1b [studio] Make file move do nothing if the file already exists 2025-01-26 23:59:13 -06:00
12f6b22c8b [nostalgia/gfx/studio/palette] Cleanup 2025-01-26 23:35:03 -06:00
6c858e0c4e [nostalgia/gfx/studio/tilesheet] UI cleanup
All checks were successful
Build / build (push) Successful in 3m35s
2025-01-26 22:30:39 -06:00
c6b58f7c63 [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 3m35s
2025-01-26 22:16:04 -06:00
a22aafaf96 [nostalgia/gfx/studio/palette] Add ability to reorder Palette pages 2025-01-26 22:12:57 -06:00
6298ac3a21 [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 3m28s
2025-01-26 20:58:22 -06:00
cd63afacfe [studio] Remove Ctrl-0 tab shortcut 2025-01-26 20:53:47 -06:00
2859183742 [nostalgia/gfx/studio/tilesheet] Add the ability to move subsheets 2025-01-26 20:46:15 -06:00
8d04af691e Merge commit 'ab760b064fd6a302bad13274e0e02b2b2c957b67' 2025-01-26 15:42:50 -06:00
055165974e [nostalgia/sample_project] Update test assets 2025-01-26 15:41:40 -06:00
be51838775 [nostalgia/gfx/studio/tilesheet] Add flip x and flip y functionality
All checks were successful
Build / build (push) Successful in 3m33s
2025-01-26 15:41:13 -06:00
1207dadee8 [studio] Add ability to move directories
All checks were successful
Build / build (push) Successful in 3m29s
2025-01-26 09:38:27 -06:00
109e1898cc [studio] Add ability to drag files between directories
All checks were successful
Build / build (push) Successful in 3m29s
2025-01-26 02:03:54 -06:00
a24bf7ffb9 [studio] Fix config to update when open file name changes 2025-01-26 01:01:48 -06:00
046834c2b9 [studio,nostalgia] Update tab name when corresponding file's name changes
All checks were successful
Build / build (push) Successful in 3m30s
2025-01-26 00:52:11 -06:00
f840240aac [nostalgia/gfx/studio/tilesheeteditor] Rework system for tracking current palette path
All checks were successful
Build / build (push) Successful in 3m29s
2025-01-25 22:59:51 -06:00
cfa91d3d39 [keel,studio] Add ability to rename files 2025-01-25 22:59:01 -06:00
f7a7a66a6a [ox/event] Add Signal::connectionCnt 2025-01-25 22:58:18 -06:00
5145595d57 [ox/std] Fix HashMap collision handling 2025-01-25 22:16:42 -06:00
f01d303381 [ox/std] Fix UPtr compare with nullptr 2025-01-25 20:13:47 -06:00
098c8cb844 [nostalgia/gfx/studio] Make move color commands affect all pages
All checks were successful
Build / build (push) Successful in 3m25s
2025-01-24 23:46:26 -06:00
04ad0f0264 [studio] Add drag/drop functions that use model TypeName for name
All checks were successful
Build / build (push) Successful in 3m23s
2025-01-24 23:26:30 -06:00
695e7a4561 [nostalgia/gfx/studio/paletteeditor] Change move color mechanism to use drag/drop
Some checks failed
Build / build (push) Failing after 54s
2025-01-24 23:19:45 -06:00
7d53028faf [studio] Cleanup 2025-01-24 00:21:28 -06:00
6c34198f58 Merge commit '897a59cdad66e593fd45eece9414d8414fa7f1ae' 2025-01-23 23:51:13 -06:00
7e3e046109 [ox/model] Fix possible infinite recursion
All checks were successful
Build / build (push) Successful in 3m26s
2025-01-23 23:48:40 -06:00
f63c58169f [studio] Add filepickerpopup.hpp to studio.hpp 2025-01-23 22:19:59 -06:00
e40b11246d [nostalgia/gfx/studio/paletteeditor] Fix num key shortcuts to ignore if ctrl is down
All checks were successful
Build / build (push) Successful in 3m25s
2025-01-23 21:56:47 -06:00
161194c8b2 [nostalgia/gfx/studio/tilesheeteditor] Add FilePicker to for choosing a Palette
All checks were successful
Build / build (push) Successful in 3m25s
2025-01-23 21:24:10 -06:00
48603ea2c5 [studio] Make tabs not draw while closing 2025-01-23 21:24:10 -06:00
e2f2a17315 [studio] Add FilePickerPopup 2025-01-23 21:24:10 -06:00
e8a0ce88c5 Merge commit 'dff9f81e073bb994d5ce96a6eaa1bfa547f1fdf4'
Some checks are pending
Build / build (push) Waiting to run
2025-01-23 21:21:58 -06:00
82e2ea747f [studio] Fix NewMenu to track prev stage correctly when going back two stages 2025-01-23 21:13:58 -06:00
ff666eda9b [studio] Make NewMenu default Name field to focus when it appears
All checks were successful
Build / build (push) Successful in 3m30s
2025-01-23 00:55:10 -06:00
0d8b82ba49 [studio] Cleanup
All checks were successful
Build / build (push) Successful in 3m10s
2025-01-23 00:32:03 -06:00
5598dfdd87 [nostalgia/player] Update hardcoded tilesheet refs to new file ext
All checks were successful
Build / build (push) Successful in 3m16s
2025-01-23 00:19:35 -06:00
6ef462adcc [keel] Add clearer Error handling 2025-01-23 00:15:55 -06:00
9511cb5719 [studio] Fix prev tracking
All checks were successful
Build / build (push) Successful in 3m16s
2025-01-22 23:37:44 -06:00
1cc1d561e2 [studio] Add a file explorer to NewMenu to choose where new files go
All checks were successful
Build / build (push) Successful in 4m16s
2025-01-22 23:11:08 -06:00
d15a0df7da [studio] Make reusable FileTreeModel 2025-01-22 01:04:25 -06:00
e1282b6bae [studio] Fix build
All checks were successful
Build / build (push) Successful in 3m23s
2025-01-22 00:58:43 -06:00
5fe7c14ccb [nostalgia/sample_project] Rename TileSheet files using new file ext
Some checks failed
Build / build (push) Failing after 52s
2025-01-21 23:40:19 -06:00
42165ba2d6 [nostalgia/gfx] Change default file extension for TileSheets to nts 2025-01-21 23:35:55 -06:00
1af4da43ad [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 3m22s
2025-01-21 22:56:59 -06:00
4fa879a09e [nostalgia/sample_project] Update NS_Logo.ng to final TileSheetV5 format
Some checks failed
Build / build (push) Has been cancelled
2025-01-21 22:54:27 -06:00
fd8f1a29c6 [nostalgia] Add release notes document
All checks were successful
Build / build (push) Successful in 3m24s
2025-01-21 22:45:55 -06:00
9fda2763ba [nostalgia/gfx] Make TileSheetV5::defaultPalette a string instead of FileAddress 2025-01-21 22:44:55 -06:00
cda23ac4af [ox/std] Change ox::String::operator[](size_t) const return a reference 2025-01-21 22:43:57 -06:00
c36b244dd3 [nostalgia/gfx] Cleanup, add PaletteV5, restore mistakenly removed function 2025-01-21 22:17:13 -06:00
335d278f5e [ox/oc] Fix integer read for signed/unsigned 2025-01-21 22:15:36 -06:00
167 changed files with 5091 additions and 1553 deletions

View File

@ -4,7 +4,7 @@ on: [push]
jobs:
build:
runs-on: nostalgia
runs-on: olympic
steps:
- name: Check out repository code
uses: actions/checkout@v3
@ -17,3 +17,10 @@ jobs:
- run: make purge configure-release
- run: make build
- run: make test
- run: make install
- run: mv dist/linux-x86_64-release nostalgia-linux-x86_64
- run: tar cf nostalgia-linux-x86_64.tar nostalgia-linux-x86_64
- uses: actions/upload-artifact@v3
with:
name: nostalgia-linux-x86_64
path: nostalgia-linux-x86_64.tar

View File

@ -93,7 +93,7 @@ purge:
${BC_CMD_RM_RF} compile_commands.json
.PHONY: test
test: build
${BC_CMD_ENVRUN} mypy ${BC_VAR_SCRIPTS}
${BC_CMD_ENVRUN} ${BC_CMD_PY3} -m mypy ${BC_VAR_SCRIPTS}
${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} test
.PHONY: test-verbose
test-verbose: build

30
deps/buildcore/scripts/file-to-cpp.py vendored Executable file
View File

@ -0,0 +1,30 @@
#! /usr/bin/env python3
#
# Copyright 2016 - 2025 gary@drinkingtea.net
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
import argparse
import sys
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--file', help='path to file')
parser.add_argument('--out-cpp', help='path to output cpp file')
parser.add_argument('--out-hpp', help='path to output hpp file')
parser.add_argument('--namespace', help='path to output hpp file')
args = parser.parse_args()
return 0
if __name__ == '__main__':
try:
err = main()
sys.exit(err)
except KeyboardInterrupt:
sys.exit(1)

View File

@ -35,4 +35,6 @@ def get_arch() -> str:
arch = platform.machine().lower()
if arch == 'amd64':
arch = 'x86_64'
elif arch == 'aarch64':
arch = 'arm64'
return arch

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.19)
project(nativefiledialog-extended VERSION 1.1.1)
set(nfd_ROOT_PROJECT OFF)

View File

@ -12,7 +12,7 @@
# CMake versions greater than the JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION policies will
# continue to generate policy warnings "CMake Warning (dev)...Policy CMP0XXX is not set:"
#
set(JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION "3.8.0")
set(JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION "3.13.2")
set(JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION "3.13.2")
cmake_minimum_required(VERSION ${JSONCPP_OLDEST_VALIDATED_POLICIES_VERSION})
if("${CMAKE_VERSION}" VERSION_LESS "${JSONCPP_NEWEST_VALIDATED_POLICIES_VERSION}")

View File

@ -143,6 +143,11 @@ class Signal {
Error disconnectObject(const void *receiver) const noexcept;
[[nodiscard]]
size_t connectionCnt() const noexcept {
return m_slots.size();
}
void emit(Args... args) const;
Error emitCheckError(Args... args) const noexcept;
@ -319,6 +324,11 @@ class Signal<Error(Args...)> {
Error disconnectObject(const void *receiver) const noexcept;
[[nodiscard]]
size_t connectionCnt() const noexcept {
return m_slots.size();
}
void emit(Args... args) const noexcept;
Error emitCheckError(Args... args) const noexcept;

View File

@ -37,6 +37,30 @@ Error FileSystem::read(const FileAddress &addr, void *buffer, std::size_t size)
}
}
Result<Buffer> FileSystem::read(FileAddress const &addr, size_t const size) noexcept {
Result<Buffer> out;
out.value.resize(size);
switch (addr.type()) {
case FileAddressType::Inode:
OX_RETURN_ERROR(readFileInode(addr.getInode().value, out.value.data(), size));
break;
case FileAddressType::ConstPath:
case FileAddressType::Path:
OX_RETURN_ERROR(readFilePath(StringView{addr.getPath().value}, out.value.data(), size));
break;
default:
return ox::Error{1};
}
return out;
}
Result<Buffer> FileSystem::read(StringViewCR path, size_t const size) noexcept {
Result<Buffer> out;
out.value.resize(size);
OX_RETURN_ERROR(readFilePath(path, out.value.data(), size));
return out;
}
Result<Buffer> FileSystem::read(const FileAddress &addr) noexcept {
OX_REQUIRE(s, stat(addr));
Buffer buff(static_cast<std::size_t>(s.size));
@ -51,18 +75,33 @@ Result<Buffer> FileSystem::read(StringViewCR path) noexcept {
return buff;
}
Error FileSystem::read(const FileAddress &addr, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept {
Error FileSystem::read(
FileAddress const &addr,
std::size_t const readStart,
std::size_t const readSize,
void *buffer,
std::size_t *size) noexcept {
switch (addr.type()) {
case FileAddressType::Inode:
return read(addr.getInode().value, readStart, readSize, buffer, size);
return readFileInodeRange(addr.getInode().value, readStart, readSize, buffer, size);
case FileAddressType::ConstPath:
case FileAddressType::Path:
return ox::Error(2, "Unsupported for path lookups");
return readFilePathRange(addr.getPath().value, readStart, readSize, buffer, size);
default:
return ox::Error(1);
}
}
Result<size_t> FileSystem::read(
StringViewCR path,
std::size_t const readStart,
std::size_t const readSize,
Span<char> buff) noexcept {
size_t szOut{buff.size()};
OX_RETURN_ERROR(readFilePathRange(path, readStart, readSize, buff.data(), &szOut));
return szOut;
}
Error FileSystem::write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType) noexcept {
switch (addr.type()) {
case FileAddressType::Inode:

View File

@ -41,6 +41,10 @@ class FileSystem {
Error read(const FileAddress &addr, void *buffer, std::size_t size) noexcept;
Result<Buffer> read(FileAddress const &addr, size_t size) noexcept;
Result<Buffer> read(StringViewCR path, size_t size) noexcept;
Result<Buffer> read(const FileAddress &addr) noexcept;
Result<Buffer> read(StringViewCR path) noexcept;
@ -53,7 +57,24 @@ class FileSystem {
return readFileInode(inode, buffer, buffSize);
}
Error read(const FileAddress &addr, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept;
Error read(
FileAddress const &addr,
size_t readStart,
size_t readSize,
void *buffer,
size_t *size) noexcept;
/**
*
* @param path
* @param readStart
* @param readSize
* @param buffer
* @param size
* @return error or number of bytes read
*/
Result<size_t> read(
StringViewCR path, size_t readStart, size_t readSize, ox::Span<char> buff) noexcept;
virtual Result<Vector<String>> ls(StringViewCR dir) const noexcept = 0;
@ -140,7 +161,10 @@ class FileSystem {
virtual Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept = 0;
virtual Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept = 0;
virtual Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept = 0;
virtual Error readFileInodeRange(uint64_t inode, size_t readStart, size_t readSize, void *buffer, size_t *size) noexcept = 0;
virtual Error removePath(StringViewCR path, bool recursive) noexcept = 0;
@ -211,6 +235,9 @@ class FileSystemTemplate: public MemFS {
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
Error removePath(StringViewCR path, bool recursive) noexcept override;
Result<const char*> directAccessInode(uint64_t) const noexcept override;
@ -358,6 +385,13 @@ Error FileSystemTemplate<FileStore, Directory>::readFileInodeRange(uint64_t inod
return m_fs.read(inode, readStart, readSize, reinterpret_cast<uint8_t*>(buffer), size);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
OX_REQUIRE(s, stat(path));
return readFileInodeRange(s.inode, readStart, readSize, buffer, buffSize);
}
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::removePath(StringViewCR path, bool recursive) noexcept {
OX_REQUIRE(fd, fileSystemData());

View File

@ -93,7 +93,9 @@ Result<FileStat> PassThroughFS::statPath(StringViewCR path) const noexcept {
oxTracef("ox.fs.PassThroughFS.statInode", "{} {}", ec.message(), path);
const uint64_t size = type == FileType::Directory ? 0 : std::filesystem::file_size(p, ec);
oxTracef("ox.fs.PassThroughFS.statInode.size", "{} {}", path, size);
OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: stat failed"));
if (auto err = ec.value()) {
return ox::Error{static_cast<ox::ErrorCode>(err), "PassThroughFS: stat failed"};
}
return FileStat{0, 0, size, type};
}
@ -154,6 +156,25 @@ Error PassThroughFS::readFileInode(uint64_t, void*, std::size_t) noexcept {
return ox::Error(1, "readFileInode(uint64_t, void*, std::size_t) is not supported by PassThroughFS");
}
Error PassThroughFS::readFilePathRange(
StringViewCR path, size_t const readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
try {
std::ifstream file(m_path / stripSlash(path), std::ios::binary | std::ios::ate);
auto const size = static_cast<size_t>(file.tellg());
readSize = ox::min(readSize, size);
file.seekg(static_cast<off_t>(readStart), std::ios::beg);
if (readSize > *buffSize) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
return ox::Error{1};
}
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(readSize));
return {};
} catch (std::fstream::failure const &f) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
return ox::Error{2};
}
}
Error PassThroughFS::readFileInodeRange(uint64_t, std::size_t, std::size_t, void*, std::size_t*) noexcept {
// unsupported
return ox::Error(1, "read(uint64_t, std::size_t, std::size_t, void*, std::size_t*) is not supported by PassThroughFS");
@ -185,8 +206,7 @@ Error PassThroughFS::writeFileInode(uint64_t, const void*, uint64_t, FileType) n
}
std::string_view PassThroughFS::stripSlash(StringView path) noexcept {
const auto pathLen = ox::strlen(path);
for (auto i = 0u; i < pathLen && path[0] == '/'; i++) {
for (auto i = 0u; i < path.len() && path[0] == '/'; i++) {
path = substr(path, 1);
}
return {path.data(), path.bytes()};

View File

@ -71,6 +71,9 @@ class PassThroughFS: public FileSystem {
Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override;
Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
Error removePath(StringViewCR path, bool recursive) noexcept override;

View File

@ -58,7 +58,11 @@ class TypeStore {
if (!std::is_constant_evaluated()) {
OX_REQUIRE_M(dt, loadDescriptor(typeId));
for (auto &f : dt->fieldList) {
OX_RETURN_ERROR(this->getLoad(f.typeId).moveTo(f.type));
if (typeId == f.typeId) {
f.type = dt.get();
} else {
OX_RETURN_ERROR(this->getLoad(f.typeId).moveTo(f.type));
}
}
auto &out = m_cache[typeId];
out = std::move(dt);

View File

@ -144,7 +144,11 @@ Error OrganicClawReader::field(const char *key, T *val) noexcept {
if (jv.empty()) {
*val = 0;
} else if (rightType) {
*val = static_cast<T>(jv.asUInt());
if constexpr(ox::is_signed_v<T>) {
*val = static_cast<T>(jv.asInt64());
} else {
*val = static_cast<T>(jv.asUInt64());
}
} else {
err = ox::Error(1, "Type mismatch");
}
@ -172,7 +176,8 @@ Error OrganicClawReader::field(const char *key, T *val) noexcept {
err = ox::Error(1, "Type mismatch");
}
}
} catch (Json::LogicError const&) {
} catch (Json::LogicError const&e) {
oxDebugf("JSON error: {}", e.what());
err = ox::Error(1, "error reading JSON data");
}
++m_fieldIt;

View File

@ -181,13 +181,13 @@ constexpr Array<T, ArraySize> &Array<T, ArraySize>::operator=(Array &&other) noe
template<typename T, std::size_t ArraySize>
constexpr T &Array<T, ArraySize>::operator[](std::size_t i) noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Array access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Array access overflow");
return m_items[i];
}
template<typename T, std::size_t ArraySize>
constexpr const T &Array<T, ArraySize>::operator[](std::size_t i) const noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Array access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Array access overflow");
return m_items[i];
}

View File

@ -41,6 +41,12 @@ constexpr auto Debug = true;
constexpr auto Debug = false;
#endif
#if defined(OX_CHECK_BOUNDS)
constexpr auto CheckBounds = true;
#else
constexpr auto CheckBounds = Debug;
#endif
#if defined(NDEBUG)
constexpr auto NDebug = true;
#else

View File

@ -330,4 +330,17 @@ constexpr void primitiveAssert(char const*file, int line, bool pass, char const*
}
}
constexpr void boundsCheck(
char const*file,
int const line,
size_t const i,
size_t const sz,
char const*msg) noexcept {
if constexpr(defines::CheckBounds) {
if (i >= sz) [[unlikely]] {
panic(file, line, msg, ox::Error{1});
}
}
}
}

View File

@ -11,6 +11,7 @@
#include "algorithm.hpp"
#include "hash.hpp"
#include "ignore.hpp"
#include "optional.hpp"
#include "stringview.hpp"
#include "strops.hpp"
#include "vector.hpp"
@ -26,11 +27,12 @@ class HashMap {
private:
struct Pair {
UPtr<Pair> next;
K key = {};
T value{};
};
Vector<K> m_keys;
Vector<Pair*> m_pairs;
Vector<UPtr<Pair>> m_pairs;
public:
explicit constexpr HashMap(std::size_t size = 127);
@ -73,10 +75,10 @@ class HashMap {
constexpr void expand();
template<typename KK>
constexpr Pair *const&access(Vector<Pair*> const&pairs, KK const&key) const;
constexpr UPtr<Pair> const &access(Vector<UPtr<Pair>> const &pairs, KK const &key) const;
template<typename KK>
constexpr Pair *&access(Vector<Pair*> &pairs, KK const&key);
constexpr UPtr<Pair> &access(Vector<UPtr<Pair>> &pairs, KK const &key);
};
@ -85,14 +87,13 @@ constexpr HashMap<K, T>::HashMap(std::size_t size): m_pairs(size) {
}
template<typename K, typename T>
constexpr HashMap<K, T>::HashMap(HashMap<K, T> const&other) {
m_pairs = other.m_pairs;
constexpr HashMap<K, T>::HashMap(HashMap const &other) {
operator=(other);
}
template<typename K, typename T>
constexpr HashMap<K, T>::HashMap(HashMap<K, T> &&other) noexcept {
m_keys = std::move(other.m_keys);
m_pairs = std::move(other.m_pairs);
constexpr HashMap<K, T>::HashMap(HashMap &&other) noexcept {
operator=(std::move(other));
}
template<typename K, typename T>
@ -101,7 +102,7 @@ constexpr HashMap<K, T>::~HashMap() {
}
template<typename K, typename T>
constexpr bool HashMap<K, T>::operator==(HashMap const&other) const {
constexpr bool HashMap<K, T>::operator==(HashMap const &other) const {
if (m_keys != other.m_keys) {
return false;
}
@ -115,19 +116,25 @@ constexpr bool HashMap<K, T>::operator==(HashMap const&other) const {
}
template<typename K, typename T>
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap<K, T> const&other) {
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap const &other) {
if (this != &other) {
clear();
m_keys = other.m_keys;
m_pairs = other.m_pairs;
m_pairs.resize(other.m_pairs.size());
for (auto const&k : m_keys) {
auto const &src = access(other.m_pairs, k);
auto &dst = access(m_pairs, k);
dst = ox::make_unique<Pair>();
dst->key = src->key;
dst->value = src->value;
}
}
return *this;
}
template<typename K, typename T>
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap<K, T> &&other) noexcept {
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap &&other) noexcept {
if (this != &other) {
clear();
m_keys = std::move(other.m_keys);
m_pairs = std::move(other.m_pairs);
}
@ -135,60 +142,52 @@ constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap<K, T> &&other) noexcep
}
template<typename K, typename T>
constexpr T &HashMap<K, T>::operator[](MaybeView_t<K> const&k) {
auto p = &access(m_pairs, k);
constexpr T &HashMap<K, T>::operator[](MaybeView_t<K> const &key) {
auto p = &access(m_pairs, key);
if (*p == nullptr) {
if (static_cast<double>(m_pairs.size()) * 0.7 <
static_cast<double>(m_keys.size())) {
expand();
p = &access(m_pairs, k);
p = &access(m_pairs, key);
}
*p = new Pair;
(*p)->key = k;
m_keys.emplace_back(k);
*p = ox::make_unique<Pair>();
(*p)->key = key;
m_keys.emplace_back(key);
}
return (*p)->value;
}
template<typename K, typename T>
constexpr Result<T*> HashMap<K, T>::at(MaybeView_t<K> const&k) noexcept {
auto p = access(m_pairs, k);
constexpr Result<T*> HashMap<K, T>::at(MaybeView_t<K> const &key) noexcept {
auto &p = access(m_pairs, key);
if (!p) {
return {nullptr, ox::Error(1, "value not found for given key")};
return ox::Error{1, "value not found for given key"};
}
return &p->value;
}
template<typename K, typename T>
constexpr Result<const T*> HashMap<K, T>::at(MaybeView_t<K> const&k) const noexcept {
auto p = access(m_pairs, k);
constexpr Result<const T*> HashMap<K, T>::at(MaybeView_t<K> const &key) const noexcept {
auto &p = access(m_pairs, key);
if (!p) {
return {nullptr, ox::Error(1, "value not found for given key")};
return ox::Error{1, "value not found for given key"};
}
return &p->value;
}
template<typename K, typename T>
constexpr void HashMap<K, T>::erase(MaybeView_t<K> const&k) {
if (!contains(k)) {
constexpr void HashMap<K, T>::erase(MaybeView_t<K> const &key) {
if (!contains(key)) {
return;
}
auto h = ox::hash<MaybeView_t<K>>{}(k) % m_pairs.size();
while (true) {
const auto &p = m_pairs[h];
if (p == nullptr || p->key == k) {
std::ignore = m_pairs.erase(h);
break;
} else {
h = ox::hash<MaybeView_t<K>>{}(k) % m_pairs.size();
}
}
std::ignore = m_keys.erase(ox::find(m_keys.begin(), m_keys.end(), k));
auto &c = access(m_pairs, key);
c = std::move(c->next);
std::ignore = m_keys.erase(ox::find(m_keys.begin(), m_keys.end(), key));
}
template<typename K, typename T>
constexpr bool HashMap<K, T>::contains(MaybeView_t<K> const&k) const noexcept {
return access(m_pairs, k) != nullptr;
constexpr bool HashMap<K, T>::contains(MaybeView_t<K> const &key) const noexcept {
return access(m_pairs, key).get() != nullptr;
}
template<typename K, typename T>
@ -204,27 +203,26 @@ constexpr Vector<K> const&HashMap<K, T>::keys() const noexcept {
template<typename K, typename T>
constexpr Vector<T> HashMap<K, T>::values() const noexcept {
Vector<T> out;
out.reserve(m_pairs.size());
for (auto const&p : m_pairs) {
out.emplace_back(p->value);
out.reserve(m_keys.size());
for (auto const &p : m_pairs) {
if (out) {
out.emplace_back(p->value);
}
}
return out;
}
template<typename K, typename T>
constexpr void HashMap<K, T>::clear() {
for (std::size_t i = 0; i < m_pairs.size(); i++) {
delete m_pairs[i];
}
m_pairs.clear();
m_pairs.resize(127);
}
template<typename K, typename T>
constexpr void HashMap<K, T>::expand() {
Vector<Pair*> r(m_pairs.size() * 2);
Vector<UPtr<Pair>> r{m_pairs.size() * 2};
for (std::size_t i = 0; i < m_keys.size(); ++i) {
auto const&k = m_keys[i];
auto const &k = m_keys[i];
access(r, k) = std::move(access(m_pairs, k));
}
m_pairs = std::move(r);
@ -232,29 +230,39 @@ constexpr void HashMap<K, T>::expand() {
template<typename K, typename T>
template<typename KK>
constexpr typename HashMap<K, T>::Pair *const&HashMap<K, T>::access(Vector<Pair*> const&pairs, KK const&k) const {
auto h = static_cast<std::size_t>(ox::hash<KK>{}(k) % pairs.size());
constexpr UPtr<typename HashMap<K, T>::Pair> const &HashMap<K, T>::access(
Vector<UPtr<Pair>> const& pairs,
KK const &key) const {
auto const h = static_cast<std::size_t>(ox::hash<KK>{}(key) % pairs.size());
auto const &p = *pairs.at(h).unwrap();
if (p == nullptr || p->key == key) {
return p;
}
auto c = &p->next;
while (true) {
auto const&p = *pairs.at(h).unwrap();
if (p == nullptr || p->key == k) {
return p;
} else {
h = (h + 1) % pairs.size();
if (*c == nullptr || (*c)->key == key) {
return *c;
}
c = &(*c)->next;
}
}
template<typename K, typename T>
template<typename KK>
constexpr typename HashMap<K, T>::Pair *&HashMap<K, T>::access(Vector<Pair*> &pairs, KK const&k) {
auto h = static_cast<std::size_t>(ox::hash<KK>{}(k) % pairs.size());
constexpr UPtr<typename HashMap<K, T>::Pair> &HashMap<K, T>::access(
Vector<UPtr<Pair>> &pairs,
KK const &key) {
auto const h = static_cast<std::size_t>(ox::hash<KK>{}(key) % pairs.size());
auto &p = *pairs.at(h).unwrap();
if (p == nullptr || p->key == key) {
return p;
}
auto c = &p->next;
while (true) {
auto &p = *pairs.at(h).unwrap();
if (p == nullptr || p->key == k) {
return p;
} else {
h = (h + 1) % pairs.size();
if (*c == nullptr || (*c)->key == key) {
return *c;
}
c = &(*c)->next;
}
}

View File

@ -133,17 +133,17 @@ struct SpanIterator {
}
constexpr PtrType operator->() const noexcept {
ox::primitiveAssert(__FILE__, __LINE__, m_offset < m_max, "SpanIterator access overflow");
boundsCheck(__FILE__, __LINE__, m_offset, m_max, "SpanIterator access overflow");
return &m_t[m_offset];
}
constexpr RefType operator*() const noexcept {
ox::primitiveAssert(__FILE__, __LINE__, m_offset < m_max, "SpanIterator access overflow");
boundsCheck(__FILE__, __LINE__, m_offset, m_max, "SpanIterator access overflow");
return m_t[m_offset];
}
constexpr RefType operator[](std::size_t s) const noexcept {
ox::primitiveAssert(__FILE__, __LINE__, s < m_max, "SpanIterator access overflow");
boundsCheck(__FILE__, __LINE__, s, m_max, "SpanIterator access overflow");
return m_t[s];
}

View File

@ -260,12 +260,12 @@ constexpr bool operator==(const UniquePtr<T> &p1, const UniquePtr<T> &p2) noexce
template<typename T>
constexpr bool operator==(const UniquePtr<T> &p1, std::nullptr_t) noexcept {
return p1.get();
return p1.get() == nullptr;
}
template<typename T>
constexpr bool operator==(std::nullptr_t, const UniquePtr<T> &p2) noexcept {
return p2.get();
return p2.get() == nullptr;
}

View File

@ -129,22 +129,22 @@ class Span {
}
constexpr T &operator[](std::size_t i) noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Span access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Span access overflow");
return m_items[i];
}
constexpr T const&operator[](std::size_t i) const noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Span access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Span access overflow");
return m_items[i];
}
constexpr Span operator+(size_t i) const noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Span access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Span access overflow");
return {m_items + i, m_size - i};
}
constexpr Span operator+=(size_t i) noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Span access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Span access overflow");
m_items += i;
m_size -= i;
return *this;

View File

@ -171,7 +171,7 @@ class BasicString {
constexpr bool operator>=(BasicString const&other) const noexcept;
constexpr char operator[](std::size_t i) const noexcept;
constexpr char const&operator[](std::size_t i) const noexcept;
constexpr char &operator[](std::size_t i) noexcept;
@ -490,7 +490,7 @@ constexpr bool BasicString<SmallStringSize_v>::operator>=(BasicString const&othe
}
template<std::size_t SmallStringSize_v>
constexpr char BasicString<SmallStringSize_v>::operator[](std::size_t i) const noexcept {
constexpr char const&BasicString<SmallStringSize_v>::operator[](std::size_t i) const noexcept {
return m_buff[i];
}

View File

@ -328,6 +328,16 @@ OX_CLANG_NOWARN_END
si["aoeu"] = 100;
oxAssert(si["asdf"] == 42, "asdf != 42");
oxAssert(si["aoeu"] == 100, "aoeu != 100");
si.erase("asdf");
oxAssert(!si.contains("asdf"), "wrongly contains asdf");
oxAssert(si.contains("aoeu"), "does not contains aoeu");
oxAssert(!si.at("asdf").ok(), "asdf != 0");
oxExpect(si["asdf"], 0);
oxAssert(si["aoeu"] == 100, "aoeu != 100");
auto si2 = si;
oxDebugf("{}", si2["asdf"]);
oxExpect(si2["asdf"], 0);
oxAssert(si2["aoeu"] == 100, "aoeu != 100");
ox::HashMap<int, int> ii;
ii[4] = 42;
ii[5] = 100;

View File

@ -27,6 +27,48 @@ constexpr void swap(T &a, T &b) noexcept {
b = std::move(temp);
}
template<typename T, typename U>
constexpr bool cmp_equal(T const t, U const u) noexcept {
if constexpr(ox::is_signed_v<T> == ox::is_signed_v<U>) {
return t == u;
} else if constexpr(ox::is_signed_v<T>) {
return ox::Signed<T>{t} == u;
} else {
return t == ox::Signed<U>{u};
}
}
template<typename T, typename U>
constexpr bool cmp_less(T const t, U const u) noexcept {
if constexpr(ox::is_signed_v<T> == ox::is_signed_v<U>) {
return t < u;
} else if constexpr(ox::is_signed_v<T>) {
return ox::Signed<T>{t} < u;
} else {
return t < ox::Signed<U>{u};
}
}
template<typename T, typename U>
constexpr bool cmp_not_equal(T const t, U const u) noexcept {
return !std::cmp_equal(t, u);
}
template<typename T, typename U>
constexpr bool cmp_greater(T const t, U const u) noexcept {
return std::cmp_less(u, t);
}
template<typename T, typename U>
constexpr bool cmp_less_equal(T const t, U const u) noexcept {
return !std::cmp_less(u, t);
}
template<typename T, typename U>
constexpr bool cmp_greater_equal(T const t, U const u) noexcept {
return !std::cmp_less(t, u);
}
}
#endif

View File

@ -311,6 +311,8 @@ class Vector: detail::VectorAllocator<T, Allocator, SmallVectorSize> {
*/
constexpr Error unordered_erase(std::size_t pos) noexcept(useNoexcept);
constexpr Error remove(T const &val);
constexpr void reserve(std::size_t cap) noexcept(useNoexcept);
constexpr void shrink_to_fit() noexcept(useNoexcept);
@ -427,13 +429,13 @@ constexpr Vector<T, SmallVectorSize, Allocator> &Vector<T, SmallVectorSize, Allo
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr T &Vector<T, SmallVectorSize, Allocator>::operator[](std::size_t i) noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Vector access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Vector access overflow");
return m_items[i];
}
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr const T &Vector<T, SmallVectorSize, Allocator>::operator[](std::size_t i) const noexcept {
ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Vector access overflow");
boundsCheck(__FILE__, __LINE__, i, size(), "Vector access overflow");
return m_items[i];
}
@ -659,6 +661,17 @@ constexpr Error Vector<T, SmallVectorSize, Allocator>::unordered_erase(std::size
return ox::Error(0);
}
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr ox::Error Vector<T, SmallVectorSize, Allocator>::remove(T const &val) {
for (size_t i{}; auto const &v : *this) {
if (v == val) {
return erase(i).error;
}
++i;
}
return ox::Error{1, "element not found"};
}
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr void Vector<T, SmallVectorSize, Allocator>::reserve(std::size_t cap) noexcept(useNoexcept) {
if (cap <= m_cap) {

23
release-notes.md Normal file
View File

@ -0,0 +1,23 @@
# d2025.02
* Rename core namespace to gfx.
* Add PaletteV5 to accommodate namespace change.
* Add TileSheetV5. TileSheetV5 retains the bpp field for the sake of
CompactTileSheet, but always store it pixel as 8 bpp for itself.
* Add ability to move subsheets in the subsheet tree.
* Add Flip X and Flip Y functionality to TileSheet Editor.
* Add rotate functionality to TileSheet Editor.
* Add draw line tool to TileSheet editor
* Replace file picker combo boxes with a browse button and file picker, and
support for dragging files from the project explorer.
* Add ability to jump to a color in a Palette by double clicking on the
color from the TileSheet editor
* Add ability to create directories.
* Add ability to add files to specific directories.
* Add ability to delete files from the project explorer.
* Ctrl-<num key> keyboard shortcuts for jumping between tabs.
* Fix Palette Editor to ignore keyboard input when popups are open.
* Palette Editor move color mechanism now uses drag and drop.
* Add ability to reorder Palette pages.
* Add warning for closing a tab with unsaved changes.
* Add ability to close a tab with Ctrl/Cmd-W

View File

@ -0,0 +1,23 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "pages",
"subscriptLevels" : 2,
"subscriptStack" :
[
{
"subscriptType" : 4
},
{
"subscriptType" : 4
}
],
"typeId" : "B.uint16;0"
}
],
"preloadable" : true,
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.gfx.CompactPalette",
"typeVersion" : 1
}

View File

@ -0,0 +1,28 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "bpp",
"typeId" : "B.int8;0"
},
{
"fieldName" : "defaultPalette",
"typeId" : "net.drinkingtea.ox.FileAddress;1"
},
{
"fieldName" : "pixels",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "B.uint8;0"
}
],
"preloadable" : true,
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.gfx.CompactTileSheet",
"typeVersion" : 1
}

View File

@ -0,0 +1,23 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "name",
"typeId" : "net.drinkingtea.ox.BasicString#8#;1"
},
{
"fieldName" : "colors",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.nostalgia.gfx.PaletteColor;2"
}
],
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.gfx.Palette.PalettePage",
"typeVersion" : 2
}

View File

@ -0,0 +1,31 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "colorNames",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.ox.BasicString#8#;1"
},
{
"fieldName" : "pages",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.nostalgia.gfx.Palette.PalettePage;2"
}
],
"preloadable" : true,
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.gfx.Palette",
"typeVersion" : 5
}

View File

@ -0,0 +1,24 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "r",
"typeId" : "B.uint8;0"
},
{
"fieldName" : "g",
"typeId" : "B.uint8;0"
},
{
"fieldName" : "b",
"typeId" : "B.uint8;0"
},
{
"fieldName" : "a",
"typeId" : "B.uint8;0"
}
],
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.gfx.PaletteColor",
"typeVersion" : 2
}

View File

@ -0,0 +1,46 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "id",
"typeId" : "B.int32;0"
},
{
"fieldName" : "name",
"typeId" : "net.drinkingtea.ox.BasicString#8#;1"
},
{
"fieldName" : "rows",
"typeId" : "B.int32;0"
},
{
"fieldName" : "columns",
"typeId" : "B.int32;0"
},
{
"fieldName" : "subsheets",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "net.drinkingtea.nostalgia.gfx.TileSheet.SubSheet;5"
},
{
"fieldName" : "pixels",
"subscriptLevels" : 1,
"subscriptStack" :
[
{
"subscriptType" : 4
}
],
"typeId" : "B.uint8;0"
}
],
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.gfx.TileSheet.SubSheet",
"typeVersion" : 5
}

View File

@ -0,0 +1,24 @@
O1;net.drinkingtea.ox.TypeDescriptor;1;{
"fieldList" :
[
{
"fieldName" : "bpp",
"typeId" : "B.int8;0"
},
{
"fieldName" : "idIt",
"typeId" : "B.int32;0"
},
{
"fieldName" : "defaultPalette",
"typeId" : "net.drinkingtea.ox.BasicString#8#;1"
},
{
"fieldName" : "subsheet",
"typeId" : "net.drinkingtea.nostalgia.gfx.TileSheet.SubSheet;5"
}
],
"primitiveType" : 5,
"typeName" : "net.drinkingtea.nostalgia.gfx.TileSheet",
"typeVersion" : 5
}

View File

@ -1 +1,28 @@
K1;0f75977f-1c52-45f8-9793-52ea2dc200a0;M2;net.drinkingtea.nostalgia.core.Palette;1;<03><><07><>
K1;0f75977f-1c52-45f8-9793-52ea2dc200a0;O1;net.drinkingtea.nostalgia.gfx.Palette;5;{
"colorNames" :
[
"Color 1",
"Color 2"
],
"pages" :
[
{
"colors" :
[
{
"a" : 1,
"b" : 31,
"g" : 31,
"r" : 31
},
{
"a" : 1,
"b" : 22,
"g" : 22,
"r" : 22
}
],
"name" : "Page 1"
}
]
}

View File

@ -1 +1,36 @@
K1;c79f21e2-f74f-4ad9-90ed-32b0ef7da6ed;M2;net.drinkingtea.nostalgia.core.Palette;1;P<>{<03><>C<>
K1;c79f21e2-f74f-4ad9-90ed-32b0ef7da6ed;O1;net.drinkingtea.nostalgia.gfx.Palette;5;{
"colorNames" :
[
"Color 1",
"Color 2",
"Color 3",
"Color 4"
],
"pages" :
[
{
"colors" :
[
{
"b" : 5
},
{
"b" : 22,
"g" : 22,
"r" : 22
},
{
"b" : 27,
"g" : 27,
"r" : 27
},
{
"b" : 20,
"g" : 8,
"r" : 8
}
],
"name" : "Page 1"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,352 @@
K1;5667c759-7ba1-470a-8860-72f0720dc58c;O1;net.drinkingtea.nostalgia.gfx.TileSheet;5;{
"bpp" : 4,
"defaultPalette" : "uuid://14fc3dd8-42ff-4bf9-81f1-a010cc5ac251",
"idIt" : 7,
"subsheet" :
{
"columns" : -1,
"name" : "Root",
"rows" : -1,
"subsheets" :
[
{
"columns" : 1,
"id" : 5,
"name" : "Blank",
"pixels" :
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"rows" : 1
},
{
"columns" : 2,
"id" : 6,
"name" : "Dirt",
"pixels" :
[
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
2,
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"rows" : 2
}
]
}
}

Binary file not shown.

View File

@ -0,0 +1,591 @@
K1;896a7d25-9dc2-46a0-b4da-c6923b6da01b;O1;net.drinkingtea.nostalgia.gfx.TileSheet;5;{
"bpp" : 4,
"defaultPalette" : "uuid://c79f21e2-f74f-4ad9-90ed-32b0ef7da6ed",
"idIt" : 2,
"subsheet" :
{
"columns" : 3,
"id" : 1,
"name" : "Root",
"pixels" :
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
1,
0,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
0,
1,
1,
1,
1,
0,
0,
1,
1,
1,
1,
1,
1,
0,
0,
1,
1,
1,
1,
1,
1,
0,
0,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
0,
1,
1,
1,
0,
0,
0,
0,
0,
1,
0,
1,
1,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
1,
0,
0,
1,
1,
1,
1,
1,
1,
0,
0,
1,
1,
1,
1,
1,
1,
0,
0,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
1,
0,
1,
1,
0,
0,
0,
0,
1,
1,
1,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"rows" : 3
}
}

Binary file not shown.

View File

@ -0,0 +1,591 @@
K1;f551fb8b-0e9f-45fc-8106-b98b7fd18ff5;O1;net.drinkingtea.nostalgia.gfx.TileSheet;5;{
"bpp" : 4,
"defaultPalette" : "uuid://c79f21e2-f74f-4ad9-90ed-32b0ef7da6ed",
"idIt" : 2,
"subsheet" :
{
"columns" : 3,
"id" : 1,
"name" : "Root",
"pixels" :
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
3,
3,
3,
3,
0,
0,
0,
3,
3,
3,
2,
3,
3,
0,
0,
3,
3,
2,
3,
3,
3,
0,
0,
0,
3,
3,
3,
3,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
1,
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"rows" : 3
}
}

Binary file not shown.

View File

@ -1,7 +1,7 @@
# module dir list
add_subdirectory(gfx)
add_subdirectory(scene)
add_subdirectory(sound)
# module libraries
@ -13,8 +13,8 @@ add_library(
target_link_libraries(
NostalgiaKeelModules PUBLIC
Keel
NostalgiaCore-Keel
NostalgiaScene-Keel
NostalgiaGfx-Keel
NostalgiaSound-Keel
)
install(
FILES
@ -32,8 +32,8 @@ if(NOSTALGIA_BUILD_STUDIO)
target_link_libraries(
NostalgiaStudioModules PUBLIC
StudioAppLib
NostalgiaCore-Studio-ImGui
NostalgiaScene-Studio
NostalgiaGfx-Studio-ImGui
NostalgiaSound-Studio-ImGui
)
install(
FILES

View File

@ -13,6 +13,7 @@ constexpr auto TileHeight = 8;
constexpr auto PixelsPerTile = TileWidth * TileHeight;
constexpr ox::StringLiteral FileExt_ng("ng");
constexpr ox::StringLiteral FileExt_nts("nts");
constexpr ox::StringLiteral FileExt_npal("npal");
}

View File

@ -33,7 +33,31 @@ OX_MODEL_BEGIN(PaletteColorV1)
OX_MODEL_FIELD(a)
OX_MODEL_END()
using PaletteColor = PaletteColorV1;
struct PaletteColorV2 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.PaletteColor";
static constexpr auto TypeVersion = 2;
uint8_t r{}, g{}, b{}, a{};
constexpr PaletteColorV2() noexcept = default;
constexpr PaletteColorV2(Color16 const c) noexcept:
r{red16(c)},
g{green16(c)},
b{blue16(c)},
a{alpha16(c)} {}
constexpr PaletteColorV2(uint8_t const r, uint8_t const g, uint8_t const b, uint8_t const a) noexcept:
r{r}, g{g}, b{b}, a{a} {}
constexpr operator Color16() const noexcept { return color16(r, g, b, a); }
};
OX_MODEL_BEGIN(PaletteColorV2)
OX_MODEL_FIELD(r)
OX_MODEL_FIELD(g)
OX_MODEL_FIELD(b)
OX_MODEL_FIELD(a)
OX_MODEL_END()
using PaletteColor = PaletteColorV2;
struct PalettePageV1 {
@ -58,7 +82,31 @@ OX_MODEL_BEGIN(PalettePageV1)
OX_MODEL_FIELD(colors)
OX_MODEL_END()
using PalettePage = PalettePageV1;
struct PalettePageV2 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.Palette.PalettePage";
static constexpr auto TypeVersion = 2;
ox::String name;
ox::Vector<PaletteColorV2> colors;
constexpr PalettePageV2() noexcept = default;
constexpr PalettePageV2(ox::StringParam pName, ox::Vector<PaletteColorV2> pColors) noexcept:
name(std::move(pName)), colors(std::move(pColors)) {}
constexpr PalettePageV2(ox::StringParam pName, ox::Vector<Color16> const&pColors) noexcept:
name(std::move(pName)) {
colors.reserve(pColors.size());
for (auto const c : pColors) {
colors.emplace_back(c);
}
}
};
OX_MODEL_BEGIN(PalettePageV2)
OX_MODEL_FIELD(name)
OX_MODEL_FIELD(colors)
OX_MODEL_END()
using PalettePage = PalettePageV2;
struct NostalgiaPalette {
@ -166,11 +214,41 @@ constexpr ox::Error repair(PaletteV4 &p) noexcept {
}
using Palette = PaletteV4;
struct PaletteV5 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.Palette";
static constexpr auto TypeVersion = 5;
static constexpr auto Preloadable = true;
ox::Vector<ox::String> colorNames;
ox::Vector<PalettePageV2> pages;
};
OX_MODEL_BEGIN(PaletteV5)
OX_MODEL_FIELD(colorNames)
OX_MODEL_FIELD(pages)
OX_MODEL_END()
[[nodiscard]]
constexpr bool valid(PaletteV5 const&p) noexcept {
auto const colors = p.colorNames.size();
return ox::all_of(p.pages.begin(), p.pages.end(), [colors](PalettePageV2 const&page) {
return page.colors.size() == colors;
});
}
constexpr ox::Error repair(PaletteV5 &p) noexcept {
auto const colors = p.colorNames.size();
for (auto &page : p.pages) {
page.colors.resize(colors);
}
return {};
}
using Palette = PaletteV5;
struct CompactPaletteV1 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactPalette";
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.CompactPalette";
static constexpr auto TypeVersion = 1;
static constexpr auto Preloadable = true;
ox::Vector<ox::Vector<Color16>> pages{};
@ -207,7 +285,7 @@ using CompactPalette = CompactPaletteV1;
[[nodiscard]]
constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
constexpr Color16 color(Palette const&pal, size_t const page, size_t const idx) noexcept {
if (page < pal.pages.size() && idx < pal.pages[page].colors.size()) [[likely]] {
return pal.pages[page].colors[idx];
}
@ -215,7 +293,7 @@ constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
}
[[nodiscard]]
constexpr Color16 color(CompactPalette const&pal, size_t page, size_t idx) noexcept {
constexpr Color16 color(CompactPalette const&pal, size_t const page, size_t const idx) noexcept {
if (page < pal.pages.size() && idx < pal.pages[page].size()) [[likely]] {
return pal.pages[page][idx];
}
@ -223,37 +301,37 @@ constexpr Color16 color(CompactPalette const&pal, size_t page, size_t idx) noexc
}
[[nodiscard]]
constexpr Color16 color(Palette const&pal, size_t idx) noexcept {
constexpr Color16 color(Palette const&pal, size_t const idx) noexcept {
return color(pal, 0, idx);
}
[[nodiscard]]
constexpr Color16 color(CompactPalette const&pal, size_t idx) noexcept {
constexpr Color16 color(CompactPalette const&pal, size_t const idx) noexcept {
return color(pal, 0, idx);
}
[[nodiscard]]
constexpr auto &colors(Palette &pal, size_t page = 0) noexcept {
constexpr auto &colors(Palette &pal, size_t const page = 0) noexcept {
return pal.pages[page].colors;
}
[[nodiscard]]
constexpr auto &colors(CompactPalette &pal, size_t page = 0) noexcept {
constexpr auto &colors(CompactPalette &pal, size_t const page = 0) noexcept {
return pal.pages[page];
}
[[nodiscard]]
constexpr auto &colors(Palette const&pal, size_t page = 0) noexcept {
constexpr auto &colors(Palette const&pal, size_t const page = 0) noexcept {
return pal.pages[page];
}
[[nodiscard]]
constexpr auto &colors(CompactPalette const&pal, size_t page = 0) noexcept {
constexpr auto &colors(CompactPalette const&pal, size_t const page = 0) noexcept {
return pal.pages[page];
}
[[nodiscard]]
constexpr size_t colorCnt(Palette const&pal, size_t page = 0) noexcept {
constexpr size_t colorCnt(Palette const&pal, size_t const page = 0) noexcept {
if (page < pal.pages.size()) [[likely]] {
return pal.pages[page].colors.size();
}
@ -261,7 +339,7 @@ constexpr size_t colorCnt(Palette const&pal, size_t page = 0) noexcept {
}
[[nodiscard]]
constexpr size_t colorCnt(CompactPalette const&pal, size_t page = 0) noexcept {
constexpr size_t colorCnt(CompactPalette const&pal, size_t const page = 0) noexcept {
if (page < pal.pages.size()) [[likely]] {
return pal.pages[page].size();
}

View File

@ -16,23 +16,6 @@
namespace nostalgia::gfx {
struct SubSheetTemplate {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.SubSheetTemplate";
static constexpr auto TypeVersion = 1;
ox::String name;
int32_t width{};
int32_t height{};
ox::Vector<SubSheetTemplate> subsheets;
};
OX_MODEL_BEGIN(SubSheetTemplate)
OX_MODEL_FIELD(name)
OX_MODEL_FIELD(width)
OX_MODEL_FIELD(height)
OX_MODEL_FIELD(subsheets)
OX_MODEL_END()
// Predecessor to TileSheet, kept for backward compatibility
struct TileSheetV1 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic";
@ -52,7 +35,7 @@ constexpr bool valid(TileSheetV1 const&ts) noexcept {
return (ts.bpp == 4 || ts.bpp == 8) && ts.pixels.size() == bytes;
}
constexpr ox::Error repair(TileSheetV1 &ts, int bpp) noexcept {
constexpr ox::Error repair(TileSheetV1 &ts, int const bpp) noexcept {
auto const bytes = static_cast<size_t>(ts.columns * ts.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
ts.pixels.resize(bytes);
return {};
@ -101,7 +84,7 @@ constexpr bool valid(TileSheetV2 const&ts) noexcept {
return (ts.bpp == 4 || ts.bpp == 8) && valid(ts.subsheet, ts.bpp);
}
constexpr void repair(TileSheetV2::SubSheet &ss, int bpp) noexcept {
constexpr void repair(TileSheetV2::SubSheet &ss, int const bpp) noexcept {
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
ss.pixels.resize(bytes);
for (auto &s : ss.subsheets) {
@ -171,7 +154,7 @@ constexpr bool valid(TileSheetV3 const&ts) noexcept {
return (ts.bpp == 4 || ts.bpp == 8) && valid(ts.subsheet, ts.bpp);
}
constexpr void repair(TileSheetV3::SubSheet &ss, int bpp) noexcept {
constexpr void repair(TileSheetV3::SubSheet &ss, int const bpp) noexcept {
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
ss.pixels.resize(bytes);
for (auto &s : ss.subsheets) {
@ -265,7 +248,7 @@ constexpr bool valid(TileSheetV4 const&ts) noexcept {
return (ts.bpp == 4 || ts.bpp == 8) && valid(ts.subsheet, ts.bpp);
}
constexpr void repair(TileSheetV4::SubSheet &ss, int bpp) noexcept {
constexpr void repair(TileSheetV4::SubSheet &ss, int const bpp) noexcept {
if (ss.subsheets.empty()) {
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
ss.pixels.resize(bytes);
@ -289,7 +272,7 @@ constexpr ox::Error repair(TileSheetV4 &ts) noexcept {
struct TileSheetV5 {
using SubSheetIdx = ox::Vector<std::size_t, 4>;
using SubSheetIdx = ox::Vector<uint32_t, 4>;
struct SubSheet {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.TileSheet.SubSheet";
@ -339,9 +322,13 @@ struct TileSheetV5 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.TileSheet";
static constexpr auto TypeVersion = 5;
/**
* bpp is unused for TileSheet, but it does get used in CompactTileSheet.
* All pixel in TileSheet are 8 bpp, regardless of what the bpp field says.
*/
int8_t bpp = 4;
SubSheetId idIt = 0;
ox::FileAddress defaultPalette;
ox::String defaultPalette;
SubSheet subsheet{0, "Root", 1, 1};
};
@ -363,7 +350,7 @@ constexpr bool valid(TileSheetV5 const&ts) noexcept {
return (ts.bpp == 4 || ts.bpp == 8) && valid(ts.subsheet);
}
constexpr void repair(TileSheetV5::SubSheet &ss, int const bpp) noexcept {
constexpr void repair(TileSheetV5::SubSheet &ss) noexcept {
if (ss.subsheets.empty()) {
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile);
ss.pixels.resize(bytes);
@ -373,7 +360,7 @@ constexpr void repair(TileSheetV5::SubSheet &ss, int const bpp) noexcept {
ss.rows = -1;
}
for (auto &s : ss.subsheets) {
repair(s, bpp);
repair(s);
}
}
@ -381,7 +368,7 @@ constexpr ox::Error repair(TileSheetV5 &ts) noexcept {
if (ts.bpp != 4 && ts.bpp != 8) {
return ox::Error{1, "Unable to repair TileSheet"};
}
repair(ts.subsheet, ts.bpp);
repair(ts.subsheet);
return {};
}
@ -400,34 +387,20 @@ TileSheet::SubSheet const*getSubsheet(TileSheet const&ts, SubSheetId id) noexcep
[[nodiscard]]
ox::Optional<size_t> getTileIdx(TileSheet const&ts, SubSheetId id) noexcept;
[[nodiscard]]
uint8_t getPixel4Bpp(TileSheet::SubSheet const&ss, std::size_t idx) noexcept;
[[nodiscard]]
uint8_t getPixel8Bpp(TileSheet::SubSheet const&ss, std::size_t idx) noexcept;
[[nodiscard]]
uint8_t getPixel(TileSheet::SubSheet const&ss, std::size_t idx) noexcept;
[[nodiscard]]
uint8_t getPixel4Bpp(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept;
[[nodiscard]]
uint8_t getPixel8Bpp(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept;
[[nodiscard]]
uint8_t getPixel(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept;
constexpr void walkPixels(TileSheet::SubSheet const&ss, int8_t, auto callback) noexcept {
for (std::size_t i = 0; i < ss.pixels.size(); ++i) {
callback(i, ss.pixels[i]);
}
}
void setPixel(TileSheet::SubSheet &ss, ox::Point const&pt, uint8_t palIdx) noexcept;
ox::Error setPixelCount(TileSheet::SubSheet &ss, std::size_t cnt) noexcept;
void flipX(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept;
void flipY(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept;
/**
* Gets a count of the pixels in this sheet, and not that of its children.
* @return a count of the pixels in this sheet
@ -438,7 +411,6 @@ unsigned pixelCnt(TileSheet::SubSheet const&ss) noexcept;
/**
*
* @param ss
* @param pBpp
* @param sz size of Subsheet in tiles (not pixels)
*/
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, ox::Size const&sz) noexcept;
@ -447,31 +419,35 @@ ox::Error resizeSubsheet(TileSheet::SubSheet &ss, ox::Size const&sz) noexcept;
* validateSubSheetIdx takes a SubSheetIdx and moves the index to the
* preceding or parent sheet if the current corresponding sheet does
* not exist.
* @param ts
* @param idx SubSheetIdx to validate and correct
* @return a valid version of idx
*/
[[nodiscard]]
TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubSheetIdx idx) noexcept;
[[nodiscard]]
TileSheet::SubSheet const&getSubSheet(
TileSheet::SubSheetIdx const&idx,
std::size_t idxIt,
TileSheet::SubSheet const&pSubsheet) noexcept;
[[nodiscard]]
TileSheet::SubSheet &getSubSheet(
TileSheet::SubSheetIdx const&idx,
ox::SpanView<uint32_t> const&idx,
std::size_t idxIt,
TileSheet::SubSheet &pSubsheet) noexcept;
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
[[nodiscard]]
TileSheet::SubSheet const&getSubSheet(TileSheet const&ts, TileSheet::SubSheetIdx const&idx) noexcept;
TileSheet::SubSheet const&getSubSheet(TileSheet const&ts, ox::SpanView<uint32_t> const &idx) noexcept;
[[nodiscard]]
TileSheet::SubSheet &getSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept;
TileSheet::SubSheet &getSubSheet(TileSheet &ts, ox::SpanView<uint32_t> const &idx) noexcept;
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic pop
#endif
ox::Error addSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept;
ox::Error addSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const &idx) noexcept;
ox::Error insertSubSheet(TileSheet &ts, ox::SpanView<uint32_t> const &idx, TileSheet::SubSheet ss) noexcept;
ox::Error rmSubSheet(
TileSheet &ts,
@ -482,13 +458,7 @@ ox::Error rmSubSheet(
ox::Error rmSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept;
[[nodiscard]]
uint8_t getPixel4Bpp(
TileSheet const&ts,
ox::Point const&pt,
TileSheet::SubSheetIdx const&subsheetIdx) noexcept;
[[nodiscard]]
uint8_t getPixel8Bpp(
uint8_t getPixel(
TileSheet const&ts,
ox::Point const&pt,
TileSheet::SubSheetIdx const&subsheetIdx) noexcept;
@ -508,7 +478,7 @@ ox::Vector<uint8_t> pixels(TileSheet &ts) noexcept;
struct CompactTileSheetV1 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactTileSheet";
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.CompactTileSheet";
static constexpr auto TypeVersion = 1;
static constexpr auto Preloadable = true;
int8_t bpp = 0;

View File

@ -1,5 +1,5 @@
add_library(
NostalgiaCore
NostalgiaGfx
gfx.cpp
tilesheet.cpp
)
@ -10,12 +10,12 @@ if(NOT BUILDCORE_TARGET STREQUAL "gba")
endif()
target_include_directories(
NostalgiaCore PUBLIC
NostalgiaGfx PUBLIC
../include
)
target_link_libraries(
NostalgiaCore PUBLIC
NostalgiaGfx PUBLIC
Turbine
)
@ -26,7 +26,7 @@ endif()
install(
TARGETS
NostalgiaCore
NostalgiaGfx
DESTINATION
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib

View File

@ -1,15 +1,15 @@
add_library(
NostalgiaCore-GBA OBJECT
NostalgiaGfx-GBA OBJECT
context.cpp
gfx.cpp
panic.cpp
)
target_include_directories(
NostalgiaCore-GBA PUBLIC
NostalgiaGfx-GBA PUBLIC
../../include
)
target_link_libraries(
NostalgiaCore-GBA PUBLIC
NostalgiaGfx-GBA PUBLIC
TeaGBA
Keel
Turbine
@ -17,5 +17,5 @@ target_link_libraries(
if(BUILDCORE_TARGET STREQUAL "gba")
set_source_files_properties(gfx.cpp PROPERTIES COMPILE_FLAGS -marm)
target_link_libraries(NostalgiaCore PUBLIC NostalgiaCore-GBA)
target_link_libraries(NostalgiaGfx PUBLIC NostalgiaGfx-GBA)
endif()

View File

@ -1,18 +1,18 @@
add_library(
NostalgiaCore-Keel
NostalgiaGfx-Keel
keelmodule.cpp
typeconv.cpp
)
target_link_libraries(
NostalgiaCore-Keel PUBLIC
NostalgiaGfx-Keel PUBLIC
Keel
NostalgiaCore
NostalgiaGfx
)
install(
TARGETS
NostalgiaCore-Keel
NostalgiaGfx-Keel
DESTINATION
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib

View File

@ -19,6 +19,7 @@ static class: public keel::Module {
PaletteV1ToPaletteV2Converter m_paletteV1ToPaletteV2Converter;
PaletteV2ToPaletteV3Converter m_paletteV2ToPaletteV3Converter;
PaletteV3ToPaletteV4Converter m_paletteV3ToPaletteV4Converter;
PaletteV4ToPaletteV5Converter m_paletteV4ToPaletteV5Converter;
PaletteToCompactPaletteConverter m_paletteToCompactPaletteConverter;
TileSheetV1ToTileSheetV2Converter m_tileSheetV1ToTileSheetV2Converter;
TileSheetV2ToTileSheetV3Converter m_tileSheetV2ToTileSheetV3Converter;
@ -45,6 +46,7 @@ static class: public keel::Module {
keel::generateTypeDesc<PaletteV2>,
keel::generateTypeDesc<PaletteV3>,
keel::generateTypeDesc<PaletteV4>,
keel::generateTypeDesc<PaletteV5>,
keel::generateTypeDesc<CompactPaletteV1>,
};
}
@ -56,6 +58,7 @@ static class: public keel::Module {
&m_paletteV1ToPaletteV2Converter,
&m_paletteV2ToPaletteV3Converter,
&m_paletteV3ToPaletteV4Converter,
&m_paletteV4ToPaletteV5Converter,
&m_paletteToCompactPaletteConverter,
&m_tileSheetV1ToTileSheetV2Converter,
&m_tileSheetV2ToTileSheetV3Converter,
@ -73,7 +76,8 @@ static class: public keel::Module {
if (typeId == ox::ModelTypeId_v<TileSheetV1> ||
typeId == ox::ModelTypeId_v<TileSheetV2> ||
typeId == ox::ModelTypeId_v<TileSheetV3> ||
typeId == ox::ModelTypeId_v<TileSheetV4>) {
typeId == ox::ModelTypeId_v<TileSheetV4> ||
typeId == ox::ModelTypeId_v<TileSheetV5>) {
OX_RETURN_ERROR(keel::convertBuffToBuff<CompactTileSheet>(
ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
return true;
@ -85,7 +89,8 @@ static class: public keel::Module {
typeId == ox::ModelTypeId_v<PaletteV1> ||
typeId == ox::ModelTypeId_v<PaletteV2> ||
typeId == ox::ModelTypeId_v<PaletteV3> ||
typeId == ox::ModelTypeId_v<PaletteV4>) {
typeId == ox::ModelTypeId_v<PaletteV4> ||
typeId == ox::ModelTypeId_v<PaletteV5>) {
OX_RETURN_ERROR(keel::convertBuffToBuff<CompactPalette>(
ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
return true;

View File

@ -52,6 +52,26 @@ ox::Error PaletteV3ToPaletteV4Converter::convert(
return {};
}
ox::Error PaletteV4ToPaletteV5Converter::convert(
keel::Context&,
PaletteV4 &src,
PaletteV5 &dst) const noexcept {
dst.colorNames = std::move(src.colorNames);
dst.pages.reserve(src.pages.size());
for (auto &s : src.pages) {
ox::Vector<PaletteColorV2> colors;
colors.reserve(s.colors.size());
for (auto const&c : s.colors) {
colors.emplace_back(c.r, c.g, c.b, c.a);
}
dst.pages.emplace_back(PalettePageV2{
std::move(s.name),
std::move(colors),
});
}
return {};
}
ox::Error PaletteToCompactPaletteConverter::convert(
keel::Context&,
Palette &src,
@ -137,9 +157,8 @@ ox::Error TileSheetV3ToTileSheetV4Converter::convert(
void TileSheetV4ToTileSheetV5Converter::convertSubsheet(
int const bpp,
TileSheetV4::SubSheet &src,
TileSheetV5::SubSheet &dst,
SubSheetId &idIt) noexcept {
dst.id = idIt;
TileSheetV5::SubSheet &dst) noexcept {
dst.id = src.id;
dst.name = std::move(src.name);
dst.columns = src.columns;
dst.rows = src.rows;
@ -150,14 +169,13 @@ void TileSheetV4ToTileSheetV5Converter::convertSubsheet(
dst.pixels.emplace_back(static_cast<uint8_t>(p & 0xf));
dst.pixels.emplace_back(static_cast<uint8_t>(p >> 4));
}
oxAssert(dst.pixels.size() == src.pixels.size() *2, "mismatch");
oxAssert(dst.pixels.size() == src.pixels.size() * 2, "mismatch");
} else {
dst.pixels = std::move(src.pixels);
}
++idIt;
dst.subsheets.resize(src.subsheets.size());
for (auto i = 0u; i < src.subsheets.size(); ++i) {
convertSubsheet(bpp, src.subsheets[i], dst.subsheets[i], idIt);
convertSubsheet(bpp, src.subsheets[i], dst.subsheets[i]);
}
}
@ -167,8 +185,8 @@ ox::Error TileSheetV4ToTileSheetV5Converter::convert(
TileSheetV5 &dst) const noexcept {
dst.bpp = src.bpp;
dst.idIt = src.idIt;
dst.defaultPalette = std::move(src.defaultPalette);
convertSubsheet(dst.bpp, src.subsheet, dst.subsheet, dst.idIt);
OX_RETURN_ERROR(src.defaultPalette.getPath().moveTo(dst.defaultPalette));
convertSubsheet(dst.bpp, src.subsheet, dst.subsheet);
return {};
}
@ -178,7 +196,7 @@ ox::Error TileSheetToCompactTileSheetConverter::convert(
TileSheet &src,
CompactTileSheet &dst) const noexcept {
dst.bpp = src.bpp;
dst.defaultPalette = std::move(src.defaultPalette);
dst.defaultPalette = ox::FileAddress{src.defaultPalette};
dst.pixels = pixels(src);
return {};
}

View File

@ -21,15 +21,19 @@ class NostalgiaPaletteToPaletteV1Converter: public keel::Converter<NostalgiaPale
};
class PaletteV1ToPaletteV2Converter: public keel::Converter<PaletteV1, PaletteV2> {
ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
};
class PaletteV2ToPaletteV3Converter: public keel::Converter<PaletteV2, PaletteV3> {
ox::Error convert(keel::Context&, PaletteV2 &src, PaletteV3 &dst) const noexcept final;
ox::Error convert(keel::Context&, PaletteV2 &src, PaletteV3 &dst) const noexcept final;
};
class PaletteV3ToPaletteV4Converter: public keel::Converter<PaletteV3, PaletteV4> {
ox::Error convert(keel::Context&, PaletteV3 &src, PaletteV4 &dst) const noexcept final;
ox::Error convert(keel::Context&, PaletteV3 &src, PaletteV4 &dst) const noexcept final;
};
class PaletteV4ToPaletteV5Converter: public keel::Converter<PaletteV4, PaletteV5> {
ox::Error convert(keel::Context&, PaletteV4 &src, PaletteV5 &dst) const noexcept final;
};
class PaletteToCompactPaletteConverter: public keel::Converter<Palette, CompactPalette> {
@ -60,8 +64,7 @@ class TileSheetV4ToTileSheetV5Converter final: public keel::Converter<TileSheetV
static void convertSubsheet(
int bpp,
TileSheetV4::SubSheet &src,
TileSheetV5::SubSheet &dst,
SubSheetId &idIt) noexcept;
TileSheetV5::SubSheet &dst) noexcept;
ox::Error convert(keel::Context&, TileSheetV4 &src, TileSheetV5 &dst) const noexcept override;
};

View File

@ -1,9 +1,9 @@
target_sources(
NostalgiaCore PRIVATE
NostalgiaGfx PRIVATE
context.cpp
gfx.cpp
)
target_link_libraries(
NostalgiaCore PUBLIC
NostalgiaGfx PUBLIC
GlUtils
)

View File

@ -449,7 +449,7 @@ static void setSprite(
++i;
};
if (!s.flipX) {
for (auto yIt = 0; yIt < static_cast<int>(dim.y); ++yIt) {
for (auto yIt = 0u; yIt < dim.y; ++yIt) {
for (auto xIt = 0u; xIt < dim.x; ++xIt) {
set(static_cast<int>(xIt), static_cast<int>(yIt), s.enabled);
}

View File

@ -1,25 +1,25 @@
add_library(NostalgiaCore-Studio)
add_library(NostalgiaGfx-Studio)
add_library(
NostalgiaCore-Studio-ImGui
NostalgiaGfx-Studio-ImGui
studiomodule.cpp
)
target_link_libraries(
NostalgiaCore-Studio PUBLIC
NostalgiaCore
NostalgiaGfx-Studio PUBLIC
NostalgiaGfx
Studio
)
target_link_libraries(
NostalgiaCore-Studio-ImGui PUBLIC
NostalgiaCore-Studio
NostalgiaGfx-Studio-ImGui PUBLIC
NostalgiaGfx-Studio
)
install(
TARGETS
NostalgiaCore-Studio-ImGui
NostalgiaCore-Studio
NostalgiaGfx-Studio-ImGui
NostalgiaGfx-Studio
LIBRARY DESTINATION
${NOSTALGIA_DIST_MODULE}
)

View File

@ -1,18 +1,6 @@
target_sources(
NostalgiaCore-Studio PRIVATE
commands/addcolorcommand.cpp
commands/addpagecommand.cpp
commands/applycolorallpagescommand.cpp
commands/duplicatepagecommand.cpp
commands/movecolorcommand.cpp
commands/removecolorcommand.cpp
commands/removepagecommand.cpp
commands/renamepagecommand.cpp
commands/updatecolorcommand.cpp
commands/updatecolorinfocommand.cpp
)
target_sources(
NostalgiaCore-Studio-ImGui PRIVATE
NostalgiaGfx-Studio-ImGui PRIVATE
paletteeditor-imgui.cpp
)
add_subdirectory(commands)

View File

@ -0,0 +1,15 @@
target_sources(
NostalgiaGfx-Studio PRIVATE
addcolorcommand.cpp
addpagecommand.cpp
applycolorallpagescommand.cpp
duplicatepagecommand.cpp
movecolorcommand.cpp
movepagecommand.cpp
removecolorcommand.cpp
removepagecommand.cpp
renamepagecommand.cpp
updatecolorcommand.cpp
updatecolorinfocommand.cpp
)

View File

@ -12,6 +12,7 @@ enum class PaletteEditorCommandId {
AddPage,
DuplicatePage,
RemovePage,
MovePage,
AddColor,
RemoveColor,
UpdateColorInfo,

View File

@ -9,9 +9,10 @@
namespace nostalgia::gfx {
MoveColorCommand::MoveColorCommand(
Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept:
Palette &pal,
size_t const srcIdx,
size_t const dstIdx) noexcept:
m_pal(pal),
m_page(page),
m_srcIdx(srcIdx),
m_dstIdx(dstIdx) {}
@ -29,10 +30,18 @@ ox::Error MoveColorCommand::undo() noexcept {
return {};
}
void MoveColorCommand::moveColor(size_t srcIdx, size_t dstIdx) noexcept {
auto const c = color(m_pal, m_page, srcIdx);
std::ignore = colors(m_pal, m_page).erase(srcIdx);
colors(m_pal, m_page).emplace(dstIdx, c);
void MoveColorCommand::moveColor(
size_t const srcIdx, size_t const dstIdx) noexcept {
for (size_t page{}; page < m_pal.pages.size(); ++page) {
auto const c = color(m_pal, page, srcIdx);
std::ignore = colors(m_pal, page).erase(srcIdx);
colors(m_pal, page).emplace(dstIdx, c);
}
{
auto name = std::move(m_pal.colorNames[srcIdx]);
std::ignore = m_pal.colorNames.erase(srcIdx);
m_pal.colorNames.emplace(dstIdx, std::move(name));
}
}
}

View File

@ -13,12 +13,11 @@ namespace nostalgia::gfx {
class MoveColorCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t const m_page = 0;
std::size_t const m_srcIdx = 0;
std::size_t const m_dstIdx = 0;
public:
MoveColorCommand(Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept;
MoveColorCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept;
~MoveColorCommand() noexcept override = default;

View File

@ -0,0 +1,40 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "commands.hpp"
#include "movepagecommand.hpp"
namespace nostalgia::gfx {
MovePageCommand::MovePageCommand(
Palette &pal,
size_t const srcIdx,
size_t const dstIdx) noexcept:
m_pal(pal),
m_srcIdx(srcIdx),
m_dstIdx(dstIdx) {}
int MovePageCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::MovePage);
}
ox::Error MovePageCommand::redo() noexcept {
movePage(m_srcIdx, m_dstIdx);
return {};
}
ox::Error MovePageCommand::undo() noexcept {
movePage(m_dstIdx, m_srcIdx);
return {};
}
void MovePageCommand::movePage(
size_t const srcIdx, size_t const dstIdx) noexcept {
auto page = std::move(m_pal.pages[srcIdx]);
std::ignore = m_pal.pages.erase(srcIdx);
m_pal.pages.emplace(dstIdx, std::move(page));
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <studio/studio.hpp>
#include <nostalgia/gfx/palette.hpp>
namespace nostalgia::gfx {
class MovePageCommand: public studio::UndoCommand {
private:
Palette &m_pal;
std::size_t const m_srcIdx = 0;
std::size_t const m_dstIdx = 0;
public:
MovePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept;
~MovePageCommand() noexcept override = default;
[[nodiscard]]
int commandId() const noexcept override;
ox::Error redo() noexcept override;
ox::Error undo() noexcept override;
private:
void movePage(size_t srcIdx, size_t dstIdx) noexcept;
};
}

View File

@ -11,6 +11,7 @@
#include "commands/applycolorallpagescommand.hpp"
#include "commands/duplicatepagecommand.hpp"
#include "commands/movecolorcommand.hpp"
#include "commands/movepagecommand.hpp"
#include "commands/removecolorcommand.hpp"
#include "commands/removepagecommand.hpp"
#include "commands/renamepagecommand.hpp"
@ -19,10 +20,30 @@
#include "paletteeditor-imgui.hpp"
namespace nostalgia::gfx {
namespace ig = studio::ig;
struct ColorDragDrop {
static constexpr auto TypeName = "nostalgia.gfx.ColorDragDrop";
static constexpr auto TypeVersion = 1;
uint32_t i{};
};
OX_MODEL_BEGIN(ColorDragDrop)
OX_MODEL_FIELD(i)
OX_MODEL_END()
struct PageDragDrop {
static constexpr auto TypeName = "nostalgia.gfx.PageDragDrop";
static constexpr auto TypeVersion = 1;
uint32_t page{};
};
OX_MODEL_BEGIN(PageDragDrop)
OX_MODEL_FIELD(page)
OX_MODEL_END()
void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept {
if (!m_show) {
@ -45,7 +66,7 @@ void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringParam path):
Editor(std::move(path)),
Editor(sctx, std::move(path)),
m_sctx(sctx),
m_tctx(sctx.tctx),
m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), itemPath()).unwrapThrow()) {
@ -73,6 +94,27 @@ ox::Error PaletteEditorImGui::saveItem() noexcept {
return m_sctx.project->writeObj(itemPath(), m_pal, ox::ClawFormat::Organic);
}
void PaletteEditorImGui::navigateTo(ox::StringViewCR arg) noexcept {
auto const args = ox::split<2>(arg, ';');
if (args.size() < 2) {
return;
}
auto const &color = args[0];
auto const &page = args[1];
{
auto const [c, err] = atoi(color);
if (!err && static_cast<size_t>(c) < colorCnt(m_pal)) {
m_selectedColorRow = static_cast<size_t>(c);
}
}
{
auto const [pg, err] = atoi(page);
if (!err && static_cast<size_t>(pg) < m_pal.pages.size()) {
m_page = static_cast<size_t>(pg);
}
}
}
void PaletteEditorImGui::drawColumnLeftAlign(ox::CStringView txt) noexcept {
ImGui::TableNextColumn();
ImGui::Text("%s", txt.c_str());
@ -125,26 +167,6 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow);
colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page);
}
ImGui::SameLine();
ImGui::BeginDisabled(m_selectedColorRow <= 0);
{
if (ImGui::Button("Move Up", sz)) {
std::ignore = pushCommand<MoveColorCommand>(
m_pal, m_page, m_selectedColorRow, m_selectedColorRow - 1);
--m_selectedColorRow;
}
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page) - 1);
{
if (ImGui::Button("Move Down", sz)) {
std::ignore = pushCommand<MoveColorCommand>(
m_pal, m_page, m_selectedColorRow, m_selectedColorRow + 1);
++m_selectedColorRow;
}
}
ImGui::EndDisabled();
}
ImGui::EndDisabled();
}
@ -179,6 +201,16 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
"##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
m_selectedColorRow = i;
}
std::ignore = ig::dragDropSource([this, i] {
ImGui::Text("%s", m_pal.colorNames[i].c_str());
return ig::setDragDropPayload(ColorDragDrop{i});
}, ImGuiDragDropFlags_SourceAllowNullID);
if (ig::DragDropTarget const d; d) {
auto const [src, err] = ig::getDragDropPayload<ColorDragDrop>();
if (!err) {
std::ignore = pushCommand<MoveColorCommand>(m_pal, src.i, i);
}
}
++i;
}
}
@ -236,6 +268,16 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) {
m_page = i;
}
std::ignore = ig::dragDropSource([this, i] {
ImGui::Text("%s", m_pal.pages[i].name.c_str());
return ig::setDragDropPayload(PageDragDrop{i});
}, ImGuiDragDropFlags_SourceAllowNullID);
if (ig::DragDropTarget const d; d) {
auto const [src, err] = ig::getDragDropPayload<PageDragDrop>();
if (!err) {
std::ignore = pushCommand<MovePageCommand>(m_pal, src.page, i);
}
}
}
}
ImGui::EndTable();
@ -258,7 +300,7 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
std::ignore = pushCommand<ApplyColorAllPagesCommand>(
m_pal, m_page, m_selectedColorRow);
}
if (ig::mainWinHasFocus()) {
if (ig::mainWinHasFocus() && !inputFocused && !ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
if (!ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
numShortcuts(m_selectedColorRow, largestPage(m_pal));
} else {

View File

@ -45,6 +45,8 @@ class PaletteEditorImGui: public studio::Editor {
protected:
ox::Error saveItem() noexcept final;
void navigateTo(ox::StringViewCR arg) noexcept override;
private:
static void drawColumnLeftAlign(ox::CStringView txt) noexcept;

View File

@ -14,14 +14,14 @@ namespace nostalgia::gfx {
static class: public studio::Module {
ox::Vector<studio::EditorMaker> editors(studio::StudioContext &ctx) const noexcept final {
return {
studio::editorMaker<TileSheetEditorImGui>(ctx, FileExt_ng),
studio::editorMaker<TileSheetEditorImGui>(ctx, {FileExt_ng, FileExt_nts}),
studio::editorMaker<PaletteEditorImGui>(ctx, FileExt_npal),
};
}
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(studio::StudioContext&) const noexcept final {
ox::Vector<ox::UniquePtr<studio::ItemMaker>> out;
out.emplace_back(ox::make<studio::ItemMakerT<TileSheet>>("Tile Sheet", "TileSheets", FileExt_ng));
out.emplace_back(ox::make<studio::ItemMakerT<TileSheet>>("Tile Sheet", "TileSheets", FileExt_nts));
out.emplace_back(ox::make<studio::ItemMakerT<Palette>>("Palette", "Palettes", FileExt_npal, Palette{
.colorNames = {},
.pages = {{"Page 1", ox::Vector<PaletteColor>{}}},

View File

@ -1,5 +1,5 @@
target_sources(
NostalgiaCore-Studio PRIVATE
NostalgiaGfx-Studio PRIVATE
tilesheeteditorview.cpp
tilesheeteditormodel.cpp
tilesheetpixelgrid.cpp
@ -7,12 +7,12 @@ target_sources(
)
target_sources(
NostalgiaCore-Studio-ImGui PRIVATE
NostalgiaGfx-Studio-ImGui PRIVATE
tilesheeteditor-imgui.cpp
)
target_link_libraries(
NostalgiaCore-Studio-ImGui PUBLIC
NostalgiaGfx-Studio-ImGui PUBLIC
lodepng
)

View File

@ -1,11 +1,14 @@
target_sources(
NostalgiaCore-Studio PRIVATE
NostalgiaGfx-Studio PRIVATE
addsubsheetcommand.cpp
cutpastecommand.cpp
deletetilescommand.cpp
drawcommand.cpp
flipcommand.cpp
movesubsheetcommand.cpp
inserttilescommand.cpp
palettechangecommand.cpp
rmsubsheetcommand.cpp
rotatecommand.cpp
updatesubsheetcommand.cpp
)

View File

@ -12,14 +12,18 @@ namespace nostalgia::gfx {
// Command IDs to use with QUndoCommand::id()
enum class CommandId {
Draw = 1,
AddSubSheet = 2,
RmSubSheet = 3,
DeleteTile = 4,
InsertTile = 4,
UpdateSubSheet = 5,
Cut = 6,
Paste = 7,
PaletteChange = 8,
AddSubSheet,
RmSubSheet,
DeleteTile,
FlipX,
FlipY,
Rotate,
InsertTile,
MoveSubSheet,
UpdateSubSheet,
Cut,
Paste,
PaletteChange,
};
constexpr bool operator==(CommandId c, int i) noexcept {

View File

@ -2,15 +2,68 @@
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <cmath>
#include "drawcommand.hpp"
namespace nostalgia::gfx {
constexpr void iterateLine(ox::Point const &a, ox::Point const &b, auto const &f) noexcept {
auto const rise = b.y - a.y;
auto const run = b.x - a.x;
auto const slope = static_cast<double>(rise) / static_cast<double>(run);
auto y = static_cast<double>(a.y);
auto const w = abs(run);
if (rise > 0) {
if (run > 0) {
auto constexpr xmod = 1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(ceil(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else if (run < 0) {
auto constexpr xmod = -1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(floor(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else {
f(a.x, a.y, rise);
}
} else if (rise < 0) {
if (run > 0) {
auto constexpr xmod = 1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(floor(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else if (run < 0) {
auto constexpr xmod = -1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(ceil(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else {
f(a.x, a.y, rise - 1);
}
} else {
auto const xmod = run > 0 ? 1 : -1;
for (int32_t i = 0; i < w; ++i) {
f(a.x + i * xmod, static_cast<int32_t>(y), 1);
}
}
}
DrawCommand::DrawCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
std::size_t idx,
int palIdx) noexcept:
int const palIdx) noexcept:
m_img(img),
m_subSheetIdx(std::move(subSheetIdx)),
m_palIdx(palIdx) {
@ -21,8 +74,8 @@ DrawCommand::DrawCommand(
DrawCommand::DrawCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Vector<std::size_t> const&idxList,
int palIdx) noexcept:
ox::SpanView<std::size_t> const&idxList,
int const palIdx) noexcept:
m_img(img),
m_subSheetIdx(std::move(subSheetIdx)),
m_palIdx(palIdx) {
@ -32,11 +85,12 @@ DrawCommand::DrawCommand(
}
}
bool DrawCommand::append(std::size_t idx) noexcept {
bool DrawCommand::append(std::size_t const idx) noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) {
// duplicate entries are bad
auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) {
auto existing = find_if(
m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) {
return c.idx == idx;
});
if (existing == m_changes.cend()) {
@ -48,7 +102,7 @@ bool DrawCommand::append(std::size_t idx) noexcept {
return false;
}
bool DrawCommand::append(ox::Vector<std::size_t> const&idxList) noexcept {
bool DrawCommand::append(ox::SpanView<std::size_t> const&idxList) noexcept {
auto out = false;
for (auto idx : idxList) {
out = append(idx) || out;
@ -56,6 +110,28 @@ bool DrawCommand::append(ox::Vector<std::size_t> const&idxList) noexcept {
return out;
}
void DrawCommand::lineUpdate(ox::Point a, ox::Point b) noexcept {
std::ignore = undo();
m_changes.clear();
auto &ss = getSubSheet(m_img, m_subSheetIdx);
if (a.x < b.x) { ++b.x; }
if (a.y < b.y) { ++b.y; }
if (a.x > b.x) { --b.x; }
iterateLine(a, b, [this, &ss](int32_t const x, int32_t const y, int32_t const h) {
int const mod = h < 0 ? -1 : 1;
auto const range = h * mod;
for (int32_t i{}; i < range; ++i) {
auto const idx = ptToIdx(x, y + i * mod, ss.columns * TileWidth);
if (idx < ss.pixels.size()) {
if (m_palIdx != getPixel(ss, idx)) {
m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(ss, idx));
}
}
}
});
std::ignore = redo();
}
ox::Error DrawCommand::redo() noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
for (auto const&c : m_changes) {
@ -80,4 +156,8 @@ TileSheet::SubSheetIdx const&DrawCommand::subsheetIdx() const noexcept {
return m_subSheetIdx;
}
void DrawCommand::finish() noexcept {
setObsolete(m_changes.empty());
}
}

View File

@ -33,12 +33,14 @@ class DrawCommand: public TileSheetCommand {
DrawCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Vector<std::size_t> const&idxList,
ox::SpanView<std::size_t> const&idxList,
int palIdx) noexcept;
bool append(std::size_t idx) noexcept;
bool append(ox::Vector<std::size_t> const&idxList) noexcept;
bool append(ox::SpanView<std::size_t> const&idxList) noexcept;
void lineUpdate(ox::Point a, ox::Point b) noexcept;
ox::Error redo() noexcept final;
@ -50,6 +52,8 @@ class DrawCommand: public TileSheetCommand {
[[nodiscard]]
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
void finish() noexcept;
};
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "flipcommand.hpp"
#include <utility>
namespace nostalgia::gfx {
FlipXCommand::FlipXCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Point const &a,
ox::Point const &b) noexcept:
m_img{img},
m_subSheetIdx{std::move(subSheetIdx)},
m_ptA{a},
m_ptB{b} {}
ox::Error FlipXCommand::redo() noexcept {
auto &ss = getSubSheet(m_img, m_subSheetIdx);
flipX(ss, m_ptA, m_ptB);
return {};
}
ox::Error FlipXCommand::undo() noexcept {
auto &ss = getSubSheet(m_img, m_subSheetIdx);
flipX(ss, m_ptA, m_ptB);
return {};
}
int FlipXCommand::commandId() const noexcept {
return static_cast<int>(CommandId::FlipX);
}
TileSheet::SubSheetIdx const &FlipXCommand::subsheetIdx() const noexcept {
return m_subSheetIdx;
}
FlipYCommand::FlipYCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Point const &a,
ox::Point const &b) noexcept:
m_img{img},
m_subSheetIdx{std::move(subSheetIdx)},
m_ptA{a},
m_ptB{b} {}
ox::Error FlipYCommand::redo() noexcept {
auto &ss = getSubSheet(m_img, m_subSheetIdx);
flipY(ss, m_ptA, m_ptB);
return {};
}
ox::Error FlipYCommand::undo() noexcept {
auto &ss = getSubSheet(m_img, m_subSheetIdx);
flipY(ss, m_ptA, m_ptB);
return {};
}
int FlipYCommand::commandId() const noexcept {
return static_cast<int>(CommandId::FlipY);
}
TileSheet::SubSheetIdx const &FlipYCommand::subsheetIdx() const noexcept {
return m_subSheetIdx;
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include "commands.hpp"
namespace nostalgia::gfx {
class FlipXCommand: public TileSheetCommand {
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_subSheetIdx;
ox::Point const m_ptA;
ox::Point const m_ptB;
public:
FlipXCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Point const &a,
ox::Point const &b) noexcept;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;
[[nodiscard]]
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
};
class FlipYCommand: public TileSheetCommand {
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_subSheetIdx;
ox::Point const m_ptA;
ox::Point const m_ptB;
public:
FlipYCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
ox::Point const &a,
ox::Point const &b) noexcept;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;
[[nodiscard]]
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
};
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "movesubsheetcommand.hpp"
namespace nostalgia::gfx {
MoveSubSheetCommand::MoveSubSheetCommand(
TileSheet &img,
TileSheet::SubSheetIdx src,
TileSheet::SubSheetIdx dst) noexcept:
m_img{img},
m_src{std::move(src)},
m_dst{std::move(dst)} {
}
ox::Error MoveSubSheetCommand::redo() noexcept {
m_active = &m_dst;
TileSheet::SubSheet ss = std::move(getSubSheet(m_img, m_src));
OX_RETURN_ERROR(rmSubSheet(m_img, m_src));
return insertSubSheet(m_img, m_dst, std::move(ss));
}
ox::Error MoveSubSheetCommand::undo() noexcept {
m_active = &m_src;
TileSheet::SubSheet ss = std::move(getSubSheet(m_img, m_dst));
OX_RETURN_ERROR(rmSubSheet(m_img, m_dst));
return insertSubSheet(m_img, m_src, std::move(ss));
}
int MoveSubSheetCommand::commandId() const noexcept {
return static_cast<int>(CommandId::MoveSubSheet);
}
TileSheet::SubSheetIdx const&MoveSubSheetCommand::subsheetIdx() const noexcept {
return *m_active;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include "commands.hpp"
namespace nostalgia::gfx {
class MoveSubSheetCommand: public TileSheetCommand {
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_src;
TileSheet::SubSheetIdx m_dst;
TileSheet::SubSheetIdx *m_active = &m_dst;
public:
MoveSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;
[[nodiscard]]
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
};
}

View File

@ -13,7 +13,7 @@ gfx::PaletteChangeCommand::PaletteChangeCommand(
m_img(img),
m_idx(std::move(idx)),
m_oldPalette(m_img.defaultPalette),
m_newPalette(ox::FileAddress(ox::sfmt<ox::IString<43>>("uuid://{}", newPalette))) {
m_newPalette(ox::sfmt<ox::IString<43>>("uuid://{}", newPalette)) {
}
ox::Error PaletteChangeCommand::redo() noexcept {

View File

@ -12,8 +12,8 @@ class PaletteChangeCommand: public TileSheetCommand {
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_idx;
ox::FileAddress m_oldPalette;
ox::FileAddress m_newPalette;
ox::String m_oldPalette;
ox::String m_newPalette;
public:
PaletteChangeCommand(

View File

@ -6,7 +6,7 @@
namespace nostalgia::gfx {
gfx::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept:
RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept:
m_img(img),
m_idx(std::move(idx)),
m_parentIdx(m_idx) {

View File

@ -0,0 +1,126 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "rotatecommand.hpp"
namespace nostalgia::gfx {
static void rotateLeft(
TileSheet::SubSheet &ss,
ox::Point const &pt,
ox::Point const &pt1,
ox::Point const &pt2,
int const depth = 0) noexcept {
if (depth >= 4) {
return;
}
auto const dstPt = ox::Point{pt1.x + pt.y, pt2.y - pt.x};
auto const srcIdx = ptToIdx(pt + pt1, ss.columns);
auto const dstIdx = ptToIdx(dstPt, ss.columns);
auto const src = ss.pixels[srcIdx];
auto &dst = ss.pixels[dstIdx];
rotateLeft(ss, dstPt - pt1, pt1, pt2, depth + 1);
dst = src;
}
static void rotateLeft(TileSheet::SubSheet &ss, ox::Point const &pt1, ox::Point const &pt2) noexcept {
auto const w = pt2.x - pt1.x;
auto const h = pt2.y - pt1.y;
for (int x = 0; x <= w / 2; ++x) {
for (int y = 0; y <= h / 2; ++y) {
rotateLeft(ss, {x, y}, pt1, pt2);
}
}
}
static void rotateRight(
TileSheet::SubSheet &ss,
ox::Point const &pt,
ox::Point const &pt1,
ox::Point const &pt2,
int const depth = 0) noexcept {
if (depth >= 4) {
return;
}
auto const dstPt = ox::Point{pt2.x - pt.y, pt1.y + pt.x};
auto const srcIdx = ptToIdx(pt + pt1, ss.columns);
auto const dstIdx = ptToIdx(dstPt, ss.columns);
auto const src = ss.pixels[srcIdx];
auto &dst = ss.pixels[dstIdx];
rotateRight(ss, dstPt - pt1, pt1, pt2, depth + 1);
dst = src;
}
static void rotateRight(TileSheet::SubSheet &ss, ox::Point const &pt1, ox::Point const &pt2) noexcept {
auto const w = pt2.x - pt1.x;
auto const h = pt2.y - pt1.y;
for (int x = 0; x <= w / 2; ++x) {
for (int y = 0; y <= h / 2; ++y) {
rotateRight(ss, {x, y}, pt1, pt2);
}
}
}
RotateCommand::RotateCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
Direction const dir) noexcept:
m_img(img),
m_idx(std::move(idx)),
m_pt2{[this] {
auto &ss = getSubSheet(m_img, m_idx);
return ox::Point{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
}()},
m_dir{dir} {
}
RotateCommand::RotateCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
ox::Point const &pt1,
ox::Point const &pt2,
Direction const dir) noexcept:
m_img(img),
m_idx(std::move(idx)),
m_pt1{pt1},
m_pt2{pt2},
m_dir{dir} {
}
ox::Error RotateCommand::redo() noexcept {
auto &ss = getSubSheet(m_img, m_idx);
switch (m_dir) {
case Direction::Left:
rotateLeft(ss, m_pt1, m_pt2);
break;
case Direction::Right:
rotateRight(ss, m_pt1, m_pt2);
break;
}
return {};
}
ox::Error RotateCommand::undo() noexcept {
auto &ss = getSubSheet(m_img, m_idx);
switch (m_dir) {
case Direction::Left:
rotateRight(ss, m_pt1, m_pt2);
break;
case Direction::Right:
rotateLeft(ss, m_pt1, m_pt2);
break;
}
return {};
}
int RotateCommand::commandId() const noexcept {
return static_cast<int>(CommandId::Rotate);
}
TileSheet::SubSheetIdx const&RotateCommand::subsheetIdx() const noexcept {
return m_idx;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include "commands.hpp"
namespace nostalgia::gfx {
class RotateCommand: public TileSheetCommand {
public:
enum class Direction {
Right,
Left,
};
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_idx;
ox::Point const m_pt1;
ox::Point const m_pt2;
Direction const m_dir;
public:
RotateCommand(TileSheet &img, TileSheet::SubSheetIdx idx, Direction dir) noexcept;
RotateCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
ox::Point const &pt1,
ox::Point const &pt2,
Direction dir) noexcept;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;
[[nodiscard]]
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
};
}

View File

@ -15,6 +15,16 @@ namespace nostalgia::gfx {
namespace ig = studio::ig;
struct SubSheetRef {
static constexpr auto TypeName = "nostalgia.gfx.studio.SubSheetRef";
static constexpr auto TypeVersion = 1;
TileSheet::SubSheetIdx subsheet{};
};
OX_MODEL_BEGIN(SubSheetRef)
OX_MODEL_FIELD(subsheet)
OX_MODEL_END()
struct TileSheetEditorConfig {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.studio.TileSheetEditorConfig";
static constexpr auto TypeVersion = 1;
@ -26,21 +36,12 @@ OX_MODEL_BEGIN(TileSheetEditorConfig)
OX_MODEL_END()
static ox::Vector<uint32_t> normalizePixelSizes(
ox::Vector<uint8_t> const&inPixels,
int const bpp) noexcept {
uint_t const bytesPerTile = bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
ox::Vector<uint8_t> const&inPixels) noexcept {
ox::Vector<uint32_t> outPixels;
if (bytesPerTile == 64) { // 8 BPP
outPixels.resize(inPixels.size());
for (std::size_t i = 0; i < inPixels.size(); ++i) {
outPixels[i] = inPixels[i];
}
} else { // 4 BPP
outPixels.resize(inPixels.size() * 2);
for (std::size_t i = 0; i < inPixels.size(); ++i) {
outPixels[i * 2 + 0] = inPixels[i] & 0xF;
outPixels[i * 2 + 1] = inPixels[i] >> 4;
}
outPixels.reserve(inPixels.size());
outPixels.resize(inPixels.size());
for (std::size_t i = 0; i < inPixels.size(); ++i) {
outPixels[i] = inPixels[i];
}
return outPixels;
}
@ -70,8 +71,8 @@ static ox::Error toPngFile(
ox::Vector<uint32_t> &&pixels,
Palette const&pal,
size_t page,
unsigned width,
unsigned height) noexcept {
unsigned const width,
unsigned const height) noexcept {
for (auto &c : pixels) {
c = color32(color(pal, page, c)) | static_cast<Color32>(0XFF << 24);
}
@ -87,16 +88,15 @@ static ox::Error toPngFile(
}
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path):
Editor(std::move(path)),
m_sctx(sctx),
m_tctx(m_sctx.tctx),
m_view(m_sctx, itemPath(), *undoStack()),
m_model(m_view.model()) {
std::ignore = setPaletteSelection();
Editor(sctx, std::move(path)),
m_sctx{sctx},
m_tctx{m_sctx.tctx},
m_palPicker{"Palette Chooser", keelCtx(sctx), FileExt_npal},
m_view{m_sctx, itemPath(), *undoStack()},
m_model{m_view.model()} {
// connect signal/slots
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
// load config
auto const&config = studio::readConfig<TileSheetEditorConfig>(
keelCtx(m_sctx), itemPath());
@ -125,17 +125,17 @@ bool TileSheetEditorImGui::acceptsClipboardPayload() const noexcept {
return m_model.acceptsClipboardPayload();
}
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const down) {
if (!down) {
return;
}
if (key == turbine::Key::Escape) {
m_subsheetEditor.close();
m_exportMenu.close();
m_palPicker.close();
}
auto const popupOpen = m_subsheetEditor.isOpen() || m_exportMenu.isOpen();
auto const pal = m_model.pal();
if (!popupOpen) {
if (ig::mainWinHasFocus() && !m_palPathFocused) {
auto const colorCnt = gfx::colorCnt(pal, m_model.palettePage());
if (key == turbine::Key::Alpha_D) {
m_tool = TileSheetTool::Draw;
@ -143,6 +143,12 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
setCutEnabled(false);
setPasteEnabled(false);
m_model.clearSelection();
} else if (key == turbine::Key::Alpha_E) {
m_tool = TileSheetTool::Line;
setCopyEnabled(false);
setCutEnabled(false);
setPasteEnabled(false);
m_model.clearSelection();
} else if (key == turbine::Key::Alpha_S) {
m_tool = TileSheetTool::Select;
setCopyEnabled(true);
@ -183,9 +189,8 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
}
void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
auto const popupOpen = m_subsheetEditor.isOpen() || m_exportMenu.isOpen();
if (!popupOpen && m_tool == TileSheetTool::Select) {
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
if (ig::mainWinHasFocus() && m_tool == TileSheetTool::Select) {
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) {
if (ImGui::IsKeyPressed(ImGuiKey_A)) {
auto const&img = m_model.activeSubSheet();
m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}});
@ -206,7 +211,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true);
{
auto const controlsSize = ImGui::GetContentRegionAvail();
ImGui::BeginChild("ToolBox", {s_palViewWidth - 24, 30}, true);
ImGui::BeginChild("ToolBox", {0, 32}, true);
{
auto const btnSz = ImVec2{45, 14};
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
@ -222,9 +227,38 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
m_tool = TileSheetTool::Fill;
m_model.clearSelection();
}
ImGui::SameLine();
if (ImGui::Selectable("Line", m_tool == TileSheetTool::Line, 0, btnSz)) {
m_tool = TileSheetTool::Line;
m_model.clearSelection();
}
//ImGui::SameLine();
//size_t i{};
//ig::ComboBox("##Operations", ox::Array<ox::CStringView, 1>{"Operations"}, i);
}
ImGui::EndChild();
auto const ySize = controlsSize.y - 38;
ImGui::BeginChild("OperationsBox", {0, 35}, ImGuiWindowFlags_NoTitleBar);
{
if (ImGui::BeginCombo("##Operations", "Operations", 0)) {
if (ImGui::Selectable("Flip X", false)) {
oxLogError(m_model.flipX());
}
if (ImGui::Selectable("Flip Y", false)) {
oxLogError(m_model.flipY());
}
ImGui::BeginDisabled(!m_model.rotateEligible());
if (ImGui::Selectable("Rotate Left", false)) {
oxLogError(m_model.rotateLeft());
}
if (ImGui::Selectable("Rotate Right", false)) {
oxLogError(m_model.rotateRight());
}
ImGui::EndDisabled();
ImGui::EndCombo();
}
}
ImGui::EndChild();
auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21);
// draw palette/color picker
ImGui::BeginChild("Palette", {s_palViewWidth - 24, ySize / 2.f}, true);
{
@ -277,6 +311,11 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
ImGui::EndChild();
m_subsheetEditor.draw(m_tctx);
m_exportMenu.draw(m_tctx);
if (auto pal = m_palPicker.draw(m_sctx)) {
if (*pal != m_model.palPath()) {
oxLogError(m_model.setPalette(*pal));
}
}
}
void TileSheetEditorImGui::drawSubsheetSelector(
@ -294,6 +333,16 @@ void TileSheetEditorImGui::drawSubsheetSelector(
| (rowSelected ? ImGuiTreeNodeFlags_Selected : 0);
ImGui::TableNextColumn();
auto const open = ImGui::TreeNodeEx(lbl.c_str(), flags);
std::ignore = ig::dragDropSource([&subsheet, &path] {
ImGui::Text("%s", subsheet.name.c_str());
return ig::setDragDropPayload(SubSheetRef{path});
});
if (ig::DragDropTarget const dragDropTarget; dragDropTarget) {
auto const [ref, err] = ig::getDragDropPayload<SubSheetRef>();
if (!err) {
oxLogError(m_model.moveSubSheet(ref.subsheet, path));
}
}
ImGui::SameLine();
if (ImGui::IsItemClicked()) {
setActiveSubsheet(path);
@ -315,7 +364,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(
ImGui::Text("--");
}
if (open) {
for (auto i = 0ul; auto &child : subsheet.subsheets) {
for (uint32_t i{}; auto &child : subsheet.subsheets) {
path.push_back(i);
ImGui::PushID(static_cast<int>(i));
ig::IndentStackItem const indentStackItem{-indentReduce};
@ -351,12 +400,11 @@ void TileSheetEditorImGui::showSubsheetEditor() noexcept {
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int const scale) const noexcept {
OX_REQUIRE(path, studio::saveFile({{"PNG", "png"}}));
// subsheet to png
auto const&img = m_model.img();
auto const&s = m_model.activeSubSheet();
auto const&pal = m_model.pal();
auto const width = s.columns * TileWidth;
auto const height = s.rows * TileHeight;
auto pixels = normalizePixelSizes(s.pixels, img.bpp);
auto pixels = normalizePixelSizes(s.pixels);
pixels = normalizePixelArrangement(pixels, s.columns, scale);
auto const err = toPngFile(
path,
@ -412,6 +460,9 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
case TileSheetTool::Fill:
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
break;
case TileSheetTool::Line:
m_view.clickLine(fbSize, clickPos(winPos, mousePos));
break;
case TileSheetTool::Select:
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
break;
@ -437,20 +488,26 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
}
void TileSheetEditorImGui::drawPaletteMenu() noexcept {
ig::IDStackItem const idStackItem{"PaletteMenu"};
auto constexpr comboWidthSub = 62;
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub);
auto constexpr palTags = ImGuiInputTextFlags_ReadOnly;
if (ig::InputText("Palette", m_selectedPalette, palTags)) {
oxLogError(m_model.setPalette(m_selectedPalette));
if (ig::InputTextWithHint("##Palette", "Path to Palette", m_model.palPath(), palTags)) {
oxLogError(m_model.setPalette(m_model.palPath()));
}
m_palPathFocused = ImGui::IsItemFocused();
if (ig::DragDropTarget const dragDropTarget; dragDropTarget) {
auto const [ref, err] = ig::getDragDropPayload<studio::FileRef>("FileRef");
if (!err && endsWith(ref.path, FileExt_npal)) {
if (ref.path != m_selectedPalette) {
if (ref.path != m_model.palPath()) {
oxLogError(m_model.setPalette(ref.path));
}
}
}
ImGui::SameLine();
if (ImGui::Button("Browse")) {
m_palPicker.open();
}
auto const pages = m_model.pal().pages.size();
if (pages > 1) {
ig::IndentStackItem const indentStackItem{20};
@ -498,9 +555,15 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
m_view.setPalIdx(i);
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
studio::navigateTo(
m_sctx,
m_model.palPath(),
ox::sfmt("{};{}", i, m_model.palettePage()));
}
// Column: color RGB
ImGui::TableNextColumn();
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
auto const ic = ImGui::GetColorU32({redf(c), greenf(c), bluef(c), 1});
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
ImGui::TableNextColumn();
auto const&name = i < pal.colorNames.size() ? pal.colorNames[i].c_str() : "";
@ -516,15 +579,11 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
}
}
ox::Error TileSheetEditorImGui::updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept {
ox::Error TileSheetEditorImGui::updateActiveSubsheet(
ox::StringView const&name, int const cols, int const rows) noexcept {
return m_model.updateSubsheet(m_model.activeSubSheetIdx(), name, cols, rows);
}
ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
m_selectedPalette = m_model.palPath();
return {};
}
void TileSheetEditorImGui::setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept {
m_model.setActiveSubsheet(path);
studio::editConfig<TileSheetEditorConfig>(keelCtx(m_sctx), itemPath(),
@ -583,7 +642,7 @@ void TileSheetEditorImGui::ExportMenu::draw(turbine::Context &tctx) noexcept {
constexpr auto popupSz = ImVec2{popupWidth, popupHeight};
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
ImGui::InputInt("Scale", &m_scale);
m_scale = ox::clamp(m_scale, 1, 50);
m_scale = ox::clamp(m_scale, 1, 135);
if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) {
inputSubmitted.emit(m_scale);
}

View File

@ -4,11 +4,11 @@
#pragma once
#include <ox/model/def.hpp>
#include <ox/std/vec.hpp>
#include <glutils/glutils.hpp>
#include <studio/editor.hpp>
#include <studio/filepickerpopup.hpp>
#include "tilesheetpixelgrid.hpp"
#include "tilesheetpixels.hpp"
@ -28,7 +28,7 @@ class TileSheetEditorImGui: public studio::Editor {
public:
ox::Signal<ox::Error(ox::StringViewCR name, int cols, int rows)> inputSubmitted;
void show(ox::StringViewCR name, int cols, int rows) noexcept;
void draw(turbine::Context &sctx) noexcept;
void draw(turbine::Context &tctx) noexcept;
void close() noexcept;
[[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; }
@ -40,23 +40,25 @@ class TileSheetEditorImGui: public studio::Editor {
public:
ox::Signal<ox::Error(int scale)> inputSubmitted;
void show() noexcept;
void draw(turbine::Context &sctx) noexcept;
void draw(turbine::Context &tctx) noexcept;
void close() noexcept;
[[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; }
};
static constexpr float s_palViewWidth = 300;
ox::String m_selectedPalette;
static constexpr float s_palViewWidth = 335;
studio::StudioContext &m_sctx;
turbine::Context &m_tctx;
ox::Vector<ox::String> m_paletteList;
SubSheetEditor m_subsheetEditor;
ExportMenu m_exportMenu;
studio::FilePickerPopup m_palPicker;
glutils::FrameBuffer m_framebuffer;
TileSheetEditorView m_view;
TileSheetEditorModel &m_model;
ox::Vec2 m_prevMouseDownPos;
TileSheetTool m_tool = TileSheetTool::Draw;
bool m_palPathFocused{};
ox::Vector<ox::UPtr<studio::UndoCommand>, 1> m_deferredCmds;
public:
TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path);
@ -97,8 +99,6 @@ class TileSheetEditorImGui: public studio::Editor {
ox::Error updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept;
ox::Error setPaletteSelection() noexcept;
// slots
private:
void setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept;

View File

@ -4,8 +4,6 @@
#include <ox/claw/read.hpp>
#include <ox/std/algorithm.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/memory.hpp>
#include <turbine/clipboard.hpp>
@ -13,14 +11,17 @@
#include <nostalgia/gfx/ptidxconv.hpp>
#include "commands/commands.hpp"
#include "commands/addsubsheetcommand.hpp"
#include "commands/commands.hpp"
#include "commands/cutpastecommand.hpp"
#include "commands/deletetilescommand.hpp"
#include "commands/drawcommand.hpp"
#include "commands/flipcommand.hpp"
#include "commands/inserttilescommand.hpp"
#include "commands/movesubsheetcommand.hpp"
#include "commands/palettechangecommand.hpp"
#include "commands/rmsubsheetcommand.hpp"
#include "commands/rotatecommand.hpp"
#include "commands/updatesubsheetcommand.hpp"
#include "tilesheeteditormodel.hpp"
@ -44,17 +45,19 @@ Palette const TileSheetEditorModel::s_defaultPalette = {
};
TileSheetEditorModel::TileSheetEditorModel(
studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack):
m_sctx(sctx),
m_tctx(m_sctx.tctx),
m_path(path),
m_img(*readObj<TileSheet>(keelCtx(m_tctx), m_path).unwrapThrow()),
// ignore failure to load palette
m_pal(readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).value),
m_undoStack(undoStack) {
studio::StudioContext &sctx, ox::StringParam path, studio::UndoStack &undoStack):
m_sctx(sctx),
m_tctx(m_sctx.tctx),
m_path(std::move(path)),
m_img(*readObj<TileSheet>(keelCtx(m_tctx), m_path).unwrapThrow()),
// ignore failure to load palette
m_pal(readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).value),
m_undoStack(undoStack) {
normalizeSubsheets(m_img.subsheet);
m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
setPalPath();
m_sctx.project->fileMoved.connect(this, &TileSheetEditorModel::handleFileRename);
}
void TileSheetEditorModel::cut() {
@ -75,7 +78,7 @@ void TileSheetEditorModel::cut() {
auto const pt1 = m_selection->a;
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
turbine::setClipboardObject(m_tctx, std::move(cb));
pushCommand(ox::make<CutPasteCommand>(
std::ignore = pushCommand(ox::make<CutPasteCommand>(
CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
}
@ -86,9 +89,9 @@ void TileSheetEditorModel::copy() {
auto cb = ox::make_unique<TileSheetClipboard>();
iterateSelectionRows(*m_selection, [&](int const x, int const y) {
auto pt = ox::Point{x, y};
const auto&s = activeSubSheet();
const auto idx = gfx::idx(s, pt);
const auto c = getPixel(s, idx);
auto const&s = activeSubSheet();
auto const idx = gfx::idx(s, pt);
auto const c = getPixel(s, idx);
pt -= m_selection->a;
cb->addPixel(pt, c);
});
@ -108,7 +111,7 @@ void TileSheetEditorModel::paste() {
auto const&s = activeSubSheet();
auto const pt1 = m_selection->a;
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
pushCommand(ox::make<CutPasteCommand>(
std::ignore = pushCommand(ox::make<CutPasteCommand>(
CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
}
@ -118,26 +121,12 @@ bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept {
}
ox::StringView TileSheetEditorModel::palPath() const noexcept {
auto [path, err] = m_img.defaultPalette.getPath();
if (err) {
return {};
}
constexpr ox::StringView uuidPrefix = "uuid://";
if (ox::beginsWith(path, uuidPrefix)) {
auto const uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes());
auto const out = keelCtx(m_tctx).uuidToPath.at(uuid);
if (out.error) {
return {};
}
return *out.value;
} else {
return path;
}
return m_palPath;
}
ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept {
OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path));
pushCommand(ox::make<PaletteChangeCommand>(
std::ignore = pushCommand(ox::make<PaletteChangeCommand>(
activeSubSheetIdx(), m_img, uuid->toString()));
return {};
}
@ -151,46 +140,73 @@ size_t TileSheetEditorModel::palettePage() const noexcept {
return m_palettePage;
}
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const palIdx) noexcept {
const auto &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const palIdx) noexcept {
if (m_lastDrawUpdatePt == pt) {
return;
}
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
return;
}
const auto idx = gfx::idx(activeSubSheet, pt);
m_lastDrawUpdatePt = pt;
auto const idx = gfx::idx(activeSubSheet, pt);
if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
} else if (getPixel(activeSubSheet, idx) != palIdx) {
pushCommand(ox::make<DrawCommand>(
std::ignore = pushCommand(ox::make<DrawCommand>(
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
}
}
void TileSheetEditorModel::drawLineCommand(ox::Point const &pt, std::size_t const palIdx) noexcept {
if (m_lastDrawUpdatePt == pt) {
return;
}
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
return;
}
m_lastDrawUpdatePt = pt;
auto const idx = gfx::idx(activeSubSheet, pt);
if (m_ongoingDrawCommand) {
m_ongoingDrawCommand->lineUpdate(m_lineStartPt, pt);
m_updated = true;
} else {
std::ignore = pushCommand(ox::make<DrawCommand>(
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
m_lineStartPt = pt;
}
}
void TileSheetEditorModel::endDrawCommand() noexcept {
m_ongoingDrawCommand = nullptr;
if (m_ongoingDrawCommand) {
m_ongoingDrawCommand->finish();
m_ongoingDrawCommand = nullptr;
}
}
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
std::ignore = pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
}
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
std::ignore = pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
}
void TileSheetEditorModel::insertTiles(
TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
std::ignore = pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
}
void TileSheetEditorModel::deleteTiles(
TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
std::ignore = pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
}
ox::Error TileSheetEditorModel::updateSubsheet(
TileSheet::SubSheetIdx const&idx, ox::StringViewCR name, int const cols, int const rows) noexcept {
OX_REQUIRE(cmd, ox::makeCatch<UpdateSubSheetCommand>(m_img, idx, name, cols, rows));
pushCommand(cmd);
std::ignore = pushCommand(cmd);
return {};
}
@ -220,10 +236,32 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept {
if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idxList);
} else if (getPixel(activeSubSheet, pt) != palIdx) {
pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
std::ignore = pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
}
}
ox::Error TileSheetEditorModel::rotateLeft() noexcept {
auto &ss = activeSubSheet();
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
pt1 = m_selection->a;
pt2 = m_selection->b;
}
return pushCommand(ox::make<RotateCommand>(
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Left));
}
ox::Error TileSheetEditorModel::rotateRight() noexcept {
auto &ss = activeSubSheet();
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
pt1 = m_selection->a;
pt2 = m_selection->b;
}
return pushCommand(ox::make<RotateCommand>(
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Right));
}
void TileSheetEditorModel::setSelection(studio::Selection const&sel) noexcept {
m_selection.emplace(sel);
m_updated = true;
@ -258,14 +296,17 @@ bool TileSheetEditorModel::updated() const noexcept {
ox::Error TileSheetEditorModel::markUpdatedCmdId(studio::UndoCommand const*cmd) noexcept {
m_updated = true;
const auto cmdId = cmd->commandId();
auto const cmdId = cmd->commandId();
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
OX_RETURN_ERROR(readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).moveTo(m_pal));
m_palettePage = ox::min<size_t>(m_pal->pages.size(), 0);
if (readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).moveTo(m_pal)) {
m_pal = keel::AssetRef<Palette>{};
}
m_palettePage = ox::min<size_t>(pal().pages.size(), 0);
setPalPath();
paletteChanged.emit();
}
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
auto idx = validateSubSheetIdx(m_img, tsCmd->subsheetIdx());
auto const tsCmd = dynamic_cast<TileSheetCommand const*>(cmd);
auto const idx = validateSubSheetIdx(m_img, tsCmd->subsheetIdx());
if (idx != m_activeSubsSheetIdx) {
setActiveSubsheet(idx);
}
@ -282,7 +323,7 @@ void TileSheetEditorModel::ackUpdate() noexcept {
}
ox::Error TileSheetEditorModel::saveFile() noexcept {
return m_sctx.project->writeObj(m_path, m_img, ox::ClawFormat::Metal);
return m_sctx.project->writeObj(m_path, m_img, ox::ClawFormat::Organic);
}
bool TileSheetEditorModel::pixelSelected(std::size_t const idx) const noexcept {
@ -291,6 +332,42 @@ bool TileSheetEditorModel::pixelSelected(std::size_t const idx) const noexcept {
return m_selection && m_selection->contains(pt);
}
ox::Error TileSheetEditorModel::flipX() noexcept {
auto const &ss = activeSubSheet();
ox::Point a;
ox::Point b{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
a = m_selection->a;
b = m_selection->b;
}
return pushCommand(ox::make<FlipXCommand>(m_img, m_activeSubsSheetIdx, a, b));
}
ox::Error TileSheetEditorModel::flipY() noexcept {
auto const &ss = activeSubSheet();
ox::Point a;
ox::Point b{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
a = m_selection->a;
b = m_selection->b;
}
return pushCommand(ox::make<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
}
bool TileSheetEditorModel::rotateEligible() const noexcept {
if (m_selection) {
auto const w = m_selection->b.x - m_selection->a.x;
auto const h = m_selection->b.y - m_selection->a.y;
return w == h;
}
auto const &ss = activeSubSheet();
return ss.rows == ss.columns;
}
ox::Error TileSheetEditorModel::moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept {
return pushCommand(ox::make<MoveSubSheetCommand>(m_img, std::move(src), std::move(dst)));
}
void TileSheetEditorModel::getFillPixels(
TileSheet::SubSheet const&activeSubSheet,
ox::Span<bool> pixels,
@ -321,10 +398,34 @@ void TileSheetEditorModel::getFillPixels(
}
}
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
void TileSheetEditorModel::setPalPath() noexcept {
auto &path = m_img.defaultPalette;
constexpr ox::StringView uuidPrefix = "uuid://";
if (ox::beginsWith(path, uuidPrefix)) {
auto const uuid = substr(path, uuidPrefix.bytes());
auto const out = keelCtx(m_tctx).uuidToPath.at(uuid);
if (!out.error) {
m_palPath = *out.value;
}
} else {
m_palPath = path;
}
}
ox::Error TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>{cmd});
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
m_updated = true;
return {};
}
ox::Error TileSheetEditorModel::handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept {
if ((beginsWith(m_img.defaultPalette, "uuid://") &&
substr(m_img.defaultPalette, 7) == id.toString()) ||
m_img.defaultPalette == newPath) {
m_palPath = newPath;
}
return {};
}
}

View File

@ -25,6 +25,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
studio::StudioContext &m_sctx;
turbine::Context &m_tctx;
ox::String m_path;
ox::String m_palPath;
TileSheet m_img;
TileSheet::SubSheetIdx m_activeSubsSheetIdx;
keel::AssetRef<Palette> m_pal;
@ -33,10 +34,12 @@ class TileSheetEditorModel: public ox::SignalHandler {
class DrawCommand *m_ongoingDrawCommand = nullptr;
studio::SelectionTracker m_selTracker;
ox::Optional<studio::Selection> m_selection;
ox::Point m_lineStartPt;
ox::Point m_lastDrawUpdatePt;
bool m_updated = false;
public:
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack);
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringParam path, studio::UndoStack &undoStack);
~TileSheetEditorModel() override = default;
@ -70,6 +73,8 @@ class TileSheetEditorModel: public ox::SignalHandler {
void drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
void drawLineCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
void endDrawCommand() noexcept;
void addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept;
@ -101,6 +106,10 @@ class TileSheetEditorModel: public ox::SignalHandler {
void fill(ox::Point const&pt, int palIdx) noexcept;
ox::Error rotateLeft() noexcept;
ox::Error rotateRight() noexcept;
void setSelection(studio::Selection const&sel) noexcept;
void select(ox::Point const&pt) noexcept;
@ -125,6 +134,15 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool pixelSelected(std::size_t idx) const noexcept;
ox::Error flipX() noexcept;
ox::Error flipY() noexcept;
[[nodiscard]]
bool rotateEligible() const noexcept;
ox::Error moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept;
private:
void getFillPixels(
TileSheet::SubSheet const&activeSubSheet,
@ -132,7 +150,11 @@ class TileSheetEditorModel: public ox::SignalHandler {
ox::Point const&pt,
int oldColor) const noexcept;
void pushCommand(studio::UndoCommand *cmd) noexcept;
void setPalPath() noexcept;
ox::Error pushCommand(studio::UndoCommand *cmd) noexcept;
ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept;
};

View File

@ -11,7 +11,8 @@
namespace nostalgia::gfx {
TileSheetEditorView::TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack):
TileSheetEditorView::TileSheetEditorView(
studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack):
m_model(sctx, path, undoStack),
m_pixelsDrawer(m_model) {
glBindVertexArray(0);
@ -76,6 +77,11 @@ void TileSheetEditorView::clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clic
m_model.drawCommand(pt, m_palIdx);
}
void TileSheetEditorView::clickLine(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
auto const pt = clickPoint(paneSize, clickPos);
m_model.drawLineCommand(pt, m_palIdx);
}
void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
auto const pt = clickPoint(paneSize, clickPos);
m_model.select(pt);
@ -90,6 +96,7 @@ void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
switch (tool) {
case TileSheetTool::Draw:
case TileSheetTool::Fill:
case TileSheetTool::Line:
m_model.endDrawCommand();
break;
case TileSheetTool::Select:

View File

@ -23,6 +23,7 @@ enum class TileSheetTool {
Select,
Draw,
Fill,
Line,
};
[[nodiscard]]
@ -34,6 +35,8 @@ constexpr auto toString(TileSheetTool t) noexcept {
return "Draw";
case TileSheetTool::Fill:
return "Fill";
case TileSheetTool::Line:
return "Line";
case TileSheetTool::None:
return "None";
}
@ -53,7 +56,7 @@ class TileSheetEditorView: public ox::SignalHandler {
std::size_t m_palIdx = 0;
public:
TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
TileSheetEditorView(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack);
~TileSheetEditorView() override = default;
@ -65,6 +68,8 @@ class TileSheetEditorView: public ox::SignalHandler {
void clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
void clickLine(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
void clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;

View File

@ -22,7 +22,12 @@ ox::Error TileSheetGrid::buildShader() noexcept {
}
void TileSheetGrid::draw(bool update, ox::Vec2 const&scroll) noexcept {
glLineWidth(3 * m_pixelSizeMod * 0.5f);
// the lines just show up bigger on Windows for some reason
if constexpr(ox::defines::OS == ox::OS::Windows) {
glLineWidth(3 * m_pixelSizeMod * 0.25f);
} else {
glLineWidth(3 * m_pixelSizeMod * 0.5f);
}
glUseProgram(m_shader);
glBindVertexArray(m_bufferSet.vao);
if (update) {

View File

@ -131,7 +131,8 @@ void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept {
m_bufferSet.vertices.resize(pixels * vboLen);
m_bufferSet.elements.resize(pixels * VertexEboLength);
// set pixels
walkPixels(subSheet, m_model.img().bpp, [&](std::size_t i, uint8_t p) {
for (size_t i = 0; i < subSheet.pixels.size(); ++i) {
auto const p = subSheet.pixels[i];
auto color = gfx::color(pal, m_model.palettePage(), p);
auto const pt = idxToPt(static_cast<int>(i), subSheet.columns);
auto const fx = static_cast<float>(pt.x);
@ -148,7 +149,7 @@ void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept {
color = applySelectionColor(color);
}
setPixelBufferObject(paneSize, static_cast<unsigned>(i * VertexVboRows), fx, fy, color, vbo, ebo);
});
}
}
}

View File

@ -70,30 +70,12 @@ ox::Optional<size_t> getTileIdx(TileSheet const&ts, SubSheetId const id) noexcep
return out ? ox::Optional<size_t>{ox::in_place, *out / PixelsPerTile} : out;
}
uint8_t getPixel4Bpp(TileSheet::SubSheet const&ss, std::size_t const idx) noexcept {
return ss.pixels[idx];
}
uint8_t getPixel8Bpp(TileSheet::SubSheet const&ss, std::size_t const idx) noexcept {
return ss.pixels[idx];
}
uint8_t getPixel(TileSheet::SubSheet const&ss, std::size_t const idx) noexcept {
return ss.pixels[idx];
}
uint8_t getPixel4Bpp(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
const auto idx = ptToIdx(pt, ss.columns);
return getPixel4Bpp(ss, idx);
}
uint8_t getPixel8Bpp(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
const auto idx = ptToIdx(pt, ss.columns);
return getPixel8Bpp(ss, idx);
}
uint8_t getPixel(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
const auto idx = ptToIdx(pt, ss.columns);
auto const idx = ptToIdx(pt, ss.columns);
return getPixel(ss, idx);
}
@ -121,6 +103,30 @@ ox::Error setPixelCount(TileSheet::SubSheet &ss, std::size_t const cnt) noexcept
return setPixelCount(ss.pixels, cnt);
}
void flipX(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept {
auto const w = (b.x - a.x) / 2 + 1;
auto const pixCols = ss.columns * TileWidth;
for (int32_t yi = a.y; yi <= b.y; ++yi) {
for (int32_t xi = 0; xi < w; ++xi) {
auto const aIdx = ptToIdx(a.x + xi, yi, pixCols);
auto const bIdx = ptToIdx(b.x - xi, yi, pixCols);
std::swap(ss.pixels[aIdx], ss.pixels[bIdx]);
}
}
}
void flipY(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept {
auto const h = (b.y - a.y) / 2 + 1;
auto const pixCols = ss.columns * TileWidth;
for (int32_t yi = 0; yi < h; ++yi) {
for (int32_t xi = a.x; xi <= b.x; ++xi) {
auto const aIdx = ptToIdx(xi, a.y + yi, pixCols);
auto const bIdx = ptToIdx(xi, b.y - yi, pixCols);
std::swap(ss.pixels[aIdx], ss.pixels[bIdx]);
}
}
}
unsigned pixelCnt(TileSheet::SubSheet const&ss) noexcept {
return static_cast<unsigned>(ss.pixels.size());
}
@ -171,7 +177,7 @@ TileSheet::SubSheetIdx validateSubSheetIdx(
pIdx.resize(pIdxIt);
return std::move(pIdx);
} else {
currentIdx = pSubsheet.subsheets.size() - 1;
currentIdx = static_cast<uint32_t>(pSubsheet.subsheets.size() - 1);
}
}
return validateSubSheetIdx(std::move(pIdx), pIdxIt + 1, pSubsheet.subsheets[currentIdx]);
@ -181,10 +187,14 @@ TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubShe
return validateSubSheetIdx(std::move(idx), 0, ts.subsheet);
}
TileSheet::SubSheet const&getSubSheet(
TileSheet::SubSheetIdx const&idx,
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
static TileSheet::SubSheet const&getSubSheet(
ox::SpanView<uint32_t> const &idx,
std::size_t const idxIt,
TileSheet::SubSheet const&pSubsheet) noexcept {
TileSheet::SubSheet const &pSubsheet) noexcept {
if (idxIt == idx.size()) {
return pSubsheet;
}
@ -194,9 +204,12 @@ TileSheet::SubSheet const&getSubSheet(
}
return getSubSheet(idx, idxIt + 1, pSubsheet.subsheets[currentIdx]);
}
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic pop
#endif
TileSheet::SubSheet &getSubSheet(
TileSheet::SubSheetIdx const&idx,
ox::SpanView<uint32_t> const &idx,
std::size_t const idxIt,
TileSheet::SubSheet &pSubsheet) noexcept {
if (idxIt == idx.size()) {
@ -205,13 +218,20 @@ TileSheet::SubSheet &getSubSheet(
return getSubSheet(idx, idxIt + 1, pSubsheet.subsheets[idx[idxIt]]);
}
TileSheet::SubSheet const&getSubSheet(TileSheet const&ts, TileSheet::SubSheetIdx const&idx) noexcept {
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"
#endif
TileSheet::SubSheet const&getSubSheet(TileSheet const &ts, ox::SpanView<uint32_t> const &idx) noexcept {
return gfx::getSubSheet(idx, 0, ts.subsheet);
}
TileSheet::SubSheet &getSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept {
TileSheet::SubSheet &getSubSheet(TileSheet &ts, ox::SpanView<uint32_t> const &idx) noexcept {
return gfx::getSubSheet(idx, 0, ts.subsheet);
}
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic pop
#endif
ox::Error addSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept {
auto &parent = getSubSheet(ts, idx);
@ -221,7 +241,20 @@ ox::Error addSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept
parent.subsheets.emplace_back(++ts.idIt, "Subsheet 0", parent.columns, parent.rows);
parent.subsheets.emplace_back(++ts.idIt, "Subsheet 1", 1, 1);
}
return ox::Error(0);
return {};
}
ox::Error insertSubSheet(TileSheet &ts, ox::SpanView<uint32_t> const&idx, TileSheet::SubSheet ss) noexcept {
if (idx.empty()) {
return ox::Error{1, "invalid insert idx"};
}
auto &parent = getSubSheet(ts, {idx.data(), idx.size() - 1});
auto const insertIdx = idx[idx.size() - 1];
if (insertIdx > parent.subsheets.size()) {
return ox::Error{1, "invalid insert idx"};
}
parent.subsheets.emplace(insertIdx, std::move(ss));
return {};
}
ox::Error rmSubSheet(
@ -239,24 +272,13 @@ ox::Error rmSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept {
return rmSubSheet(ts, idx, 0, ts.subsheet);
}
uint8_t getPixel4Bpp(
uint8_t getPixel(
TileSheet const&ts,
ox::Point const&pt,
TileSheet::SubSheetIdx const&subsheetIdx) noexcept {
oxAssert(ts.bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
auto &s = getSubSheet(ts, subsheetIdx);
auto const idx = ptToIdx(pt, s.columns);
return getPixel4Bpp(s, idx);
}
uint8_t getPixel8Bpp(
TileSheet const&ts,
ox::Point const&pt,
TileSheet::SubSheetIdx const&subsheetIdx) noexcept {
oxAssert(ts.bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
auto &s = getSubSheet(ts, subsheetIdx);
auto const idx = ptToIdx(pt, s.columns);
return getPixel8Bpp(s, idx);
return getPixel(s, idx);
}
uint8_t getPixel4Bpp(
@ -280,12 +302,18 @@ uint8_t getPixel8Bpp(
ox::Pair<uint8_t> get2Pixels4Bpp(
CompactTileSheet const&ts,
size_t const idx) noexcept {
return get2Pixels8Bpp(ts, idx);
oxAssert(ts.bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
auto const out = ts.pixels[idx / 2];
return {
static_cast<uint8_t>(out & 0x0f),
static_cast<uint8_t>(out >> 4),
};
}
ox::Pair<uint8_t> get2Pixels8Bpp(
CompactTileSheet const&ts,
size_t const idx) noexcept {
oxAssert(ts.bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
return {
static_cast<uint8_t>(ts.pixels[idx]),
static_cast<uint8_t>(ts.pixels[idx + 1]),

View File

@ -1,11 +1,11 @@
add_executable(
NostalgiaCoreTest
NostalgiaGfxTest
tests.cpp
)
target_link_libraries(
NostalgiaCoreTest
NostalgiaCore
NostalgiaGfxTest
NostalgiaGfx
)
add_test("[NostalgiaCore] readWriteTileSheet" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/NostalgiaCoreTest readWriteTileSheet)
add_test("[NostalgiaGfx] readWriteTileSheet" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/NostalgiaGfxTest readWriteTileSheet)

View File

@ -3,7 +3,7 @@
*/
#include <nostalgia/gfx/keelmodule.hpp>
#include <nostalgia/scene/keelmodule.hpp>
#include <nostalgia/sound/keelmodule.hpp>
namespace nostalgia {
@ -14,7 +14,7 @@ void registerKeelModules() noexcept {
}
modulesRegistered = true;
keel::registerModule(gfx::keelModule());
keel::registerModule(scene::keelModule());
keel::registerModule(sound::keelModule());
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <nostalgia/gfx/context.hpp>
#include "scenestatic.hpp"
namespace nostalgia::scene {
class Scene {
private:
SceneStatic const&m_sceneStatic;
public:
explicit Scene(SceneStatic const&sceneStatic) noexcept;
ox::Error setupDisplay(gfx::Context &ctx) const noexcept;
private:
void setupLayer(gfx::Context&, ox::Vector<uint16_t> const&layer, unsigned layerNo) const noexcept;
};
}

View File

@ -1,228 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <ox/std/error.hpp>
#include <ox/std/size.hpp>
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>
#include <nostalgia/gfx/tilesheet.hpp>
namespace nostalgia::scene {
struct SpriteDoc {
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SpriteDoc";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;
ox::String tilesheetPath;
ox::Vector<gfx::SubSheetId> subsheetId;
};
struct TileDoc {
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.TileDoc";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;
gfx::SubSheetId subsheetId = -1;
ox::String subsheetPath;
uint8_t type = 0;
ox::Array<uint8_t, 4> layerAttachments;
[[nodiscard]]
constexpr ox::Result<gfx::SubSheetId> getSubsheetId(gfx::TileSheet const&ts) const noexcept {
// prefer the already present ID
if (subsheetId > -1) {
return subsheetId;
}
return getIdFor(ts, subsheetPath);
}
[[nodiscard]]
constexpr ox::Result<ox::StringView> getSubsheetPath(
gfx::TileSheet const&ts) const noexcept {
// prefer the already present path
if (!subsheetPath.len()) {
return gfx::getNameFor(ts, subsheetId);
}
return ox::StringView(subsheetPath);
}
};
OX_MODEL_BEGIN(TileDoc)
OX_MODEL_FIELD_RENAME(subsheetId, subsheet_id)
OX_MODEL_FIELD_RENAME(subsheetPath, subsheet_path)
OX_MODEL_FIELD(type)
OX_MODEL_FIELD_RENAME(layerAttachments, layer_attachments)
OX_MODEL_END()
struct SceneDoc {
using TileMapRow = ox::Vector<TileDoc>;
using TileMapLayer = ox::Vector<TileMapRow>;
using TileMap = ox::Vector<TileMapLayer>;
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneDoc";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;
ox::String tilesheet; // path
ox::Vector<ox::String> palettes; // paths
TileMap tiles;
[[nodiscard]]
constexpr ox::Size size(std::size_t layerIdx) const noexcept {
const auto &layer = this->tiles[layerIdx];
const auto rowCnt = static_cast<int>(layer.size());
if (!rowCnt) {
return {};
}
auto colCnt = layer[0].size();
// find shortest row (they should all be the same, but you know this data
// could come from a file)
for (auto const&row : layer) {
colCnt = ox::min(colCnt, row.size());
}
return {static_cast<int>(colCnt), rowCnt};
}
};
OX_MODEL_BEGIN(SceneDoc)
OX_MODEL_FIELD(tilesheet)
OX_MODEL_FIELD(palettes)
OX_MODEL_FIELD(tiles)
OX_MODEL_END()
constexpr void setTopEdge(uint8_t &layerAttachments, unsigned val) noexcept {
const auto val8 = static_cast<uint8_t>(val);
layerAttachments = (layerAttachments & 0b11111100) | val8;
}
constexpr void setBottomEdge(uint8_t &layerAttachments, unsigned val) noexcept {
const auto val8 = static_cast<uint8_t>(val);
layerAttachments = (layerAttachments & 0b11110011) | static_cast<uint8_t>(val8 << 2);
}
constexpr void setLeftEdge(uint8_t &layerAttachments, unsigned val) noexcept {
const auto val8 = static_cast<uint8_t>(val);
layerAttachments = (layerAttachments & 0b11001111) | static_cast<uint8_t>(val8 << 4);
}
constexpr void setRightEdge(uint8_t &layerAttachments, unsigned val) noexcept {
const auto val8 = static_cast<uint8_t>(val);
layerAttachments = (layerAttachments & 0b00111111) | static_cast<uint8_t>(val8 << 6);
}
[[nodiscard]]
constexpr unsigned topEdge(uint8_t layerAttachments) noexcept {
return layerAttachments & 0b11;
}
[[nodiscard]]
constexpr unsigned bottomEdge(uint8_t layerAttachments) noexcept {
return (layerAttachments >> 2) & 0b11;
}
[[nodiscard]]
constexpr unsigned leftEdge(uint8_t layerAttachments) noexcept {
return (layerAttachments >> 4) & 0b11;
}
[[nodiscard]]
constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept {
return (layerAttachments >> 6) & 0b11;
}
struct SceneStatic {
constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneStatic";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;
struct Tile {
uint16_t &tileMapIdx;
uint8_t &tileType;
uint8_t &layerAttachments;
constexpr Tile(uint16_t &pTileMapIdx, uint8_t &pTileType, uint8_t &pLayerAttachments) noexcept:
tileMapIdx(pTileMapIdx),
tileType(pTileType),
layerAttachments(pLayerAttachments) {
}
};
struct Layer {
uint16_t &columns;
uint16_t &rows;
ox::Vector<uint16_t> &tileMapIdx;
ox::Vector<uint8_t> &tileType;
ox::Vector<uint8_t> &layerAttachments;
constexpr Layer(
uint16_t &pColumns,
uint16_t &pRows,
ox::Vector<uint16_t> &pTileMapIdx,
ox::Vector<uint8_t> &pTileType,
ox::Vector<uint8_t> &pLayerAttachments) noexcept:
columns(pColumns),
rows(pRows),
tileMapIdx(pTileMapIdx),
tileType(pTileType),
layerAttachments(pLayerAttachments) {
}
[[nodiscard]]
constexpr Tile tile(std::size_t i) noexcept {
return {tileMapIdx[i], tileType[i], layerAttachments[i]};
}
constexpr auto setDimensions(ox::Size dim) noexcept {
columns = static_cast<uint16_t>(dim.width);
rows = static_cast<uint16_t>(dim.height);
const auto tileCnt = static_cast<unsigned>(columns * rows);
tileMapIdx.resize(tileCnt);
tileType.resize(tileCnt);
layerAttachments.resize(tileCnt);
}
};
ox::FileAddress tilesheet;
ox::Vector<ox::FileAddress> palettes;
// tile layer data
ox::Vector<uint16_t> columns;
ox::Vector<uint16_t> rows;
ox::Vector<ox::Vector<uint16_t>> tileMapIdx;
ox::Vector<ox::Vector<uint8_t>> tileType;
ox::Vector<ox::Vector<uint8_t>> layerAttachments;
[[nodiscard]]
constexpr Layer layer(std::size_t i) noexcept {
return {
columns[i],
rows[i],
tileMapIdx[i],
tileType[i],
layerAttachments[i],
};
}
constexpr auto setLayerCnt(std::size_t layerCnt) noexcept {
this->layerAttachments.resize(layerCnt);
this->columns.resize(layerCnt);
this->rows.resize(layerCnt);
this->tileMapIdx.resize(layerCnt);
this->tileType.resize(layerCnt);
}
};
OX_MODEL_BEGIN(SceneStatic)
OX_MODEL_FIELD(tilesheet)
OX_MODEL_FIELD(palettes)
OX_MODEL_FIELD(columns)
OX_MODEL_FIELD(rows)
OX_MODEL_FIELD(tileMapIdx)
OX_MODEL_FIELD(tileType)
OX_MODEL_FIELD(layerAttachments)
OX_MODEL_END()
}

View File

@ -1,17 +0,0 @@
add_library(
NostalgiaScene-Keel
keelmodule.cpp
typeconv.cpp
)
target_link_libraries(
NostalgiaScene-Keel PUBLIC
Keel
NostalgiaScene
)
install(
TARGETS
NostalgiaScene-Keel
LIBRARY DESTINATION
${NOSTALGIA_DIST_MODULE}
)

View File

@ -1,51 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <keel/module.hpp>
#include <nostalgia/scene/scenestatic.hpp>
#include "typeconv.hpp"
namespace nostalgia::scene {
static class: public keel::Module {
private:
SceneDocToSceneStaticConverter m_sceneDocToSceneStaticConverter;
public:
[[nodiscard]]
ox::String id() const noexcept override {
return ox::String("net.drinkingtea.nostalgia.scene");
}
[[nodiscard]]
ox::Vector<keel::TypeDescGenerator> types() const noexcept override {
return {
keel::generateTypeDesc<SceneDoc>,
keel::generateTypeDesc<SceneStatic>,
};
}
[[nodiscard]]
ox::Vector<const keel::BaseConverter*> converters() const noexcept override {
return {
&m_sceneDocToSceneStaticConverter,
};
}
[[nodiscard]]
ox::Vector<keel::PackTransform> packTransforms() const noexcept override {
return {
keel::transformRule<SceneDoc, SceneStatic>,
};
}
} mod;
const keel::Module *keelModule() noexcept {
return &mod;
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/gfx/gfx.hpp>
#include <keel/media.hpp>
#include "typeconv.hpp"
namespace nostalgia::scene {
[[nodiscard]]
constexpr unsigned adjustLayerAttachment(unsigned layer, unsigned attachment) noexcept {
if (attachment == 0) {
return layer;
} else {
return attachment - 1;
}
}
constexpr void setLayerAttachments(unsigned layer, TileDoc const&srcTile, SceneStatic::Tile &dstTile) noexcept {
setTopEdge(
dstTile.layerAttachments,
adjustLayerAttachment(layer, srcTile.layerAttachments[0]));
setBottomEdge(
dstTile.layerAttachments,
adjustLayerAttachment(layer, srcTile.layerAttachments[1]));
setLeftEdge(
dstTile.layerAttachments,
adjustLayerAttachment(layer, srcTile.layerAttachments[2]));
setRightEdge(
dstTile.layerAttachments,
adjustLayerAttachment(layer, srcTile.layerAttachments[3]));
}
ox::Error SceneDocToSceneStaticConverter::convert(
keel::Context &ctx,
SceneDoc &src,
SceneStatic &dst) const noexcept {
OX_REQUIRE(ts, keel::readObj<gfx::TileSheet>(ctx, src.tilesheet));
const auto layerCnt = src.tiles.size();
dst.setLayerCnt(layerCnt);
dst.tilesheet = ox::FileAddress(src.tilesheet);
dst.palettes.reserve(src.palettes.size());
for (const auto &pal : src.palettes) {
dst.palettes.emplace_back(pal);
}
for (auto layerIdx = 0u; const auto &layer : src.tiles) {
const auto layerDim = src.size(layerIdx);
auto dstLayer = dst.layer(layerIdx);
dstLayer.setDimensions(layerDim);
for (auto tileIdx = 0u; const auto &row : layer) {
for (const auto &srcTile : row) {
auto dstTile = dstLayer.tile(tileIdx);
dstTile.tileType = srcTile.type;
OX_REQUIRE(path, srcTile.getSubsheetPath(*ts));
OX_REQUIRE(mapIdx, getTileOffset(*ts, path));
dstTile.tileMapIdx = static_cast<uint16_t>(mapIdx);
setLayerAttachments(layerIdx, srcTile, dstTile);
++tileIdx;
}
}
++layerIdx;
}
return {};
}
}

View File

@ -1,17 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <keel/typeconv.hpp>
#include <nostalgia/scene/scenestatic.hpp>
namespace nostalgia::scene {
class SceneDocToSceneStaticConverter: public keel::Converter<SceneDoc, SceneStatic> {
ox::Error convert(keel::Context&, SceneDoc &src, SceneStatic &dst) const noexcept final;
};
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <nostalgia/gfx/gfx.hpp>
#include <nostalgia/scene/scene.hpp>
namespace nostalgia::scene {
Scene::Scene(SceneStatic const&sceneStatic) noexcept:
m_sceneStatic(sceneStatic) {
}
ox::Error Scene::setupDisplay(gfx::Context &ctx) const noexcept {
if (m_sceneStatic.palettes.empty()) {
return ox::Error(1, "Scene has no palettes");
}
auto const&palette = m_sceneStatic.palettes[0];
OX_RETURN_ERROR(gfx::loadBgTileSheet(ctx, 0, m_sceneStatic.tilesheet));
OX_RETURN_ERROR(gfx::loadBgPalette(ctx, 0, palette));
// disable all backgrounds
gfx::setBgStatus(ctx, 0);
for (auto layerNo = 0u; auto const&layer : m_sceneStatic.tileMapIdx) {
setupLayer(ctx, layer, layerNo);
++layerNo;
}
return {};
}
void Scene::setupLayer(
gfx::Context &ctx,
ox::Vector<uint16_t> const&layer,
unsigned layerNo) const noexcept {
gfx::setBgStatus(ctx, layerNo, true);
gfx::setBgCbb(ctx, layerNo, 0);
auto x = 0;
auto y = 0;
const auto width = m_sceneStatic.rows[layerNo];
for (auto const&tile : layer) {
const auto tile8 = static_cast<uint8_t>(tile);
gfx::setBgTile(ctx, layerNo, x, y, tile8);
gfx::setBgTile(ctx, layerNo, x + 1, y, tile8 + 1);
gfx::setBgTile(ctx, layerNo, x, y + 1, tile8 + 2);
gfx::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3);
x += 2;
if (x >= width * 2) {
x = 0;
y += 2;
}
}
}
}

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