Squashed 'deps/nostalgia/' changes from 66cd5c4a..3c7652ef

3c7652ef [nostalgia/core/studio] Fix PaletteEditor to handle Palettes with 0 pages
941bc713 [studio] Fix NewMenu name input
e4ae23e1 [olympic/developer-handbook] Remove Ox submodules from project structure
67187d5e [olympic/developer-handbook] Elaborate more on exception usage
3271a371 [ox] Add Project Structure section to docs
ea9f50de [olympic] Add error handling back to developer-handbook.md
ea3c5e03 [olympic] Remove Ox from developer-handbook.md
c8c4177d [ox] Add ox-docs.md
76b540e3 [nostalgia/core] Cleanup, add missing FileAddress wrapper function
20627486 [keel] Cleanup
135f0e4c [nostalgia/core/studio/paletteeditor] Fix Alt shortcuts to respect keyboard focus
cb166876 [studio] Add variant of InputText that returns an IString
cb3ef0e7 [keel] Cleanup
0a62d900 [studio] Remove Editor::setRequiresConstantRefresh
ba7e3929 [nostalgia/core/studio] Make TileSheetEditor palette keys behave like PaletteEditor
36c4022b [nostalgia/core/studio] Fix PaletteEditor shortcuts to differentiate based on Alt key
e22b25e5 [studio] Remove Editor::requiresConstantRefresh
c6efabaa [studio,nostalgia] Fix PaletteEditor color update command merging, add setObsolete
1f6fefdb [nostalgia/core/studio] Disable PaletteEditor num key shorts when page rename is open
1e34f91e Merge commit '34b7779397bd4712603b4c5a39ffc57b74da0abd'
35cb2ece [nostalgia/core/studio] Fix PaletteEditor color name edit

git-subtree-dir: deps/nostalgia
git-subtree-split: 3c7652efc205cb3acdb993d7eeb1e2c2d894c2cb
This commit is contained in:
Gary Talent 2024-11-01 22:21:09 -05:00
parent 34b7779397
commit 9e11019b87
21 changed files with 679 additions and 640 deletions

482
deps/ox/ox-docs.md vendored Normal file
View File

@ -0,0 +1,482 @@
# Ox Docs
## Project Structure
All components have a platform indicator next to them:
(OB) - OS, Bare Metal
(-B) - Bare Metal
(O-) - OS
* Ox - Library of things useful for portable bare metal and userland code. Not really that external...
* clargs - Command Line Args processing (OB)
* claw - Reads and writes Metal or Organic Claw with header to indicate which
* event - Qt-like signal system (OB)
* fs - file system (OB)
* logconn - connects logging to Bullock (O-)
* mc - Metal Claw serialization, builds on model (OB)
* oc - Organic Claw serialization (wrapper around JsonCpp), builds on model (O-)
* model - Data structure modelling (OB)
* preloader - library for handling preloading of data (OB)
* std - Standard-ish Library with a lot missing and some things added (OB)
## Systems
### Error Handling
Ox provides ```ox::Error``` to report errors.
```ox::Error``` is a struct that has overloaded operators to behave like an
integer error code, plus some extra fields to enhance debuggability.
If instantiated through the ```OxError(x)``` macro, it will also include the
file and line of the error.
The ```OxError(x)``` macro should only be used for the initial instantiation of
an ```ox::Error```.
In addition to ```ox::Error``` there is also the template ```ox::Result<T>```.
```ox::Result``` simply wraps the type T value in a struct that also includes
error information, which allows the returning of a value and an error without
resorting to output parameters.
If a function returns an ```ox::Error``` or ```ox::Result``` it should be
declared as ```noexcept``` and all exceptions should be translated to an
```ox::Error```.
```ox::Result``` can be used as follows:
```cpp
ox::Result<int> foo(int i) noexcept {
if (i < 10) {
return i + 1; // implicitly calls ox::Result<T>::Result(T)
}
return OxError(1); // implicitly calls ox::Result<T>::Result(ox::Error)
}
int caller1() {
auto v = foo(argc);
if (v.error) {
return 1;
}
std::cout << v.value << '\n';
return 0;
}
int caller2() {
// it is also possible to capture the value and error in their own variables
auto [val, err] = foo(argc);
if (err) {
return 1;
}
std::cout << val << '\n';
return 0;
}
ox::Error caller3(int &i) {
return foo(i).moveTo(i);
}
ox::Error caller4(int &i) {
return foo(i).copyTo(i);
}
int caller5(int i) {
return foo(i).unwrap(); // unwrap will kill the program if there is an error
}
int caller6(int i) {
return foo(i).unwrapThrow(); // unwrap will throw if there is an error
}
int caller7(int i) {
return foo(i).or_value(0); // will return 0 if foo returned an error
}
ox::Result<uint64_t> caller8(int i) {
return foo(i).to<uint64_t>(); // will convert the result of foo to uint64_t
}
```
Lastly, there are a few macros available to help in passing ```ox::Error```s
back up the call stack, ```oxReturnError```, ```oxThrowError```, and
```oxRequire```.
```oxReturnError``` is by far the more helpful of the two.
```oxReturnError``` will return an ```ox::Error``` if it is not 0 and
```oxThrowError``` will throw an ```ox::Error``` if it is not 0.
Since ```ox::Error``` is always nodiscard, you must do something with them.
In rare cases, you may not have anything you can do with them or you may know
the code will never fail in that particular instance.
This should be used sparingly.
```cpp
void studioCode() {
auto [val, err] = foo(1);
oxThrowError(err);
doStuff(val);
}
ox::Error engineCode() noexcept {
auto [val, err] = foo(1);
oxReturnError(err);
doStuff(val);
return {};
}
void anyCode() {
auto [val, err] = foo(1);
std::ignore = err;
doStuff(val);
}
```
Both macros will also take the ```ox::Result``` directly:
```cpp
void studioCode() {
auto valerr = foo(1);
oxThrowError(valerr);
doStuff(valerr.value);
}
ox::Error engineCode() noexcept {
auto valerr = foo(1);
oxReturnError(valerr);
doStuff(valerr.value);
return {};
}
```
Ox also has the ```oxRequire``` macro, which will initialize a value if there is no error, and return if there is.
It aims to somewhat emulate the ```?``` operator in Rust and Swift.
Rust ```?``` operator:
```rust
fn f() -> Result<i32, i32> {
// do stuff
}
fn f2() -> Result<i32, i32> {
let i = f()?;
Ok(i + 4)
}
```
```oxRequire```:
```cpp
ox::Result<int> f() noexcept {
// do stuff
}
ox::Result<int> f2() noexcept {
oxRequire(i, f()); // const auto [out, oxConcat(oxRequire_err_, __LINE__)] = x; oxReturnError(oxConcat(oxRequire_err_, __LINE__))
return i + 4;
}
```
```oxRequire``` is not quite as versatile, but it should still cleanup a lot of otherwise less ideal code.
```oxRequire``` also has variants for throwing the error and for making to value non-const:
* ```oxRequireM``` - oxRequire Mutable
* ```oxRequireT``` - oxRequire Throw
* ```oxRequireMT``` - oxRequire Mutable Throw
The throw variants of ```oxRequire``` are generally legacy code.
```ox::Result::unwrapThrow``` is generally preferred now.
### Logging and Output
Ox provides for logging and debug prints via the ```oxTrace```, ```oxDebug```, and ```oxError``` macros.
Each of these also provides a format variation.
Ox also provide ```oxOut``` and ```oxErr``` for printing to stdout and stderr.
These are intended for permanent messages and always go to stdout and stderr.
Tracing functions do not go to stdout unless the OXTRACE environment variable is set.
They also print with the channel that they are on, along with file and line.
Debug statements go to stdout and go to the logger on the "debug" channel.
Where trace statements are intended to be written with thoughtfulness,
debug statements are intended to be quick and temporary insertions.
Debug statements trigger compilation failures if OX_NODEBUG is enabled when CMake is run,
as it is on Jenkins builds, so ```oxDebug``` statements should never be checked in.
This makes ```oxDebug``` preferable to other forms of logging, as temporary prints should
never be checked in.
```oxError``` always prints.
It includes file and line, and is prefixed with a red "ERROR:".
It should generally be used conservatively.
It shuld be used only when there is an error that is not technically fatal, but
the user almost certainly wants to know about it.
```oxTrace``` and ```oxTracef```:
```cpp
void f(int x, int y) { // x = 9, y = 4
oxTrace("nostalgia.core.sdl.gfx") << "f:" << x << y; // Output: "f: 9 4"
oxTracef("nostalgia.core.sdl.gfx", "f: {}, {}", x, y); // Output: "f: 9, 4"
}
```
```oxDebug``` and ```oxDebugf```:
```cpp
void f(int x, int y) { // x = 9, y = 4
oxDebug() << "f:" << x << y; // Output: "f: 9 4"
oxDebugf("f: {}, {}", x, y); // Output: "f: 9, 4"
}
```
```oxError``` and ```oxErrorf```:
```cpp
void f(int x, int y) { // x = 9, y = 4
oxError() << "f:" << x << y; // Output: "ERROR: (<file>:<line>): f: 9 4"
oxErrorf("f: {}, {}", x, y); // Output: "ERROR: (<file>:<line>): f: 9, 4"
}
```
### Model System
Ox has a model system that provides a sort of manual reflection mechanism.
Models require a model function for the type that you want to model.
It is also good to provide a type name and type version number, though that is not required.
The model function takes an instance of the type it is modelling and a template
parameter type.
The template parameter type must implement the API used in the models, but it
can do anything with the data provided to it.
Here is an example from the Nostalgia/Core package:
```cpp
struct NostalgiaPalette {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
static constexpr auto TypeVersion = 1;
ox::Vector<Color16> colors;
};
struct NostalgiaGraphic {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic";
static constexpr auto TypeVersion = 1;
int8_t bpp = 0;
// rows and columns are really only used by TileSheetEditor
int rows = 1;
int columns = 1;
ox::FileAddress defaultPalette;
NostalgiaPalette pal;
ox::Vector<uint8_t> pixels;
};
template<typename T>
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaPalette> auto *pal) noexcept {
h->template setTypeInfo<NostalgiaPalette>();
// it is also possible to provide the type name and type version as function arguments
//h->setTypeInfo("net.drinkingtea.nostalgia.core.NostalgiaPalette", 1);
oxReturnError(h->field("colors", &pal->colors));
return {};
}
template<typename T>
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaGraphic> auto *ng) noexcept {
h->template setTypeInfo<NostalgiaGraphic>();
oxReturnError(h->field("bpp", &ng->bpp));
oxReturnError(h->field("rows", &ng->rows));
oxReturnError(h->field("columns", &ng->columns));
oxReturnError(h->field("defaultPalette", &ng->defaultPalette));
oxReturnError(h->field("pal", &ng->pal));
oxReturnError(h->field("pixels", &ng->pixels));
return {};
}
```
The model system also provides for unions:
```cpp
#include <ox/model/types.hpp>
class FileAddress {
template<typename T>
friend constexpr Error model(T*, ox::CommonPtrWith<FileAddress> auto*) noexcept;
public:
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress";
union Data {
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress.Data";
char *path;
const char *constPath;
uint64_t inode;
};
protected:
FileAddressType m_type = FileAddressType::None;
Data m_data;
};
template<typename T>
constexpr Error model(T *h, ox::CommonPtrWith<FileAddress::Data> auto *obj) noexcept {
h->template setTypeInfo<FileAddress::Data>();
oxReturnError(h->fieldCString("path", &obj->path));
oxReturnError(h->fieldCString("constPath", &obj->path));
oxReturnError(h->field("inode", &obj->inode));
return {};
}
template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<FileAddress> auto *fa) noexcept {
io->template setTypeInfo<FileAddress>();
// cannot read from object in Reflect operation
if constexpr(ox_strcmp(T::opType(), OpType::Reflect) == 0) {
int8_t type = 0;
oxReturnError(io->field("type", &type));
oxReturnError(io->field("data", UnionView(&fa->m_data, 0)));
} else {
auto type = static_cast<int8_t>(fa->m_type);
oxReturnError(io->field("type", &type));
fa->m_type = static_cast<FileAddressType>(type);
oxReturnError(io->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
}
return {};
}
```
There are also macros in ```<ox/model/def.hpp>``` for simplifying the declaration of models:
```cpp
oxModelBegin(NostalgiaGraphic)
oxModelField(bpp)
oxModelField(rows)
oxModelField(columns)
oxModelField(defaultPalette)
oxModelField(pal)
oxModelField(pixels)
oxModelEnd()
```
### Serialization
Using the model system, Ox provides for serialization.
Ox has MetalClaw and OrganicClaw as its serialization format options.
MetalClaw is a custom binary format designed for minimal size.
OrganicClaw is a wrapper around JsonCpp, chosen because it technically
implements a superset of JSON.
OrganicClaw requires support for 64 bit integers, whereas normal JSON
technically does not.
These formats do not currently support floats.
There is also a wrapper format called Claw that provides a header at the
beginning of the file and can dynamically switch between the two depending on
what the header says is present.
The Claw header also includes information about the type and type version of
the data.
Claw header: ```M1;net.drinkingtea.nostalgia.core.NostalgiaPalette;1;```
That reads:
* Format is Metal Claw, version 1
* Type ID is net.drinkingtea.nostalgia.core.NostalgiaPalette
* Type version is 1
#### Metal Claw Example
##### Read
```cpp
#include <ox/mc/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readMC<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readMC(buff, pal));
return pal;
}
```
##### Write
```cpp
#include <ox/mc/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette const&pal) noexcept {
ox::Buffer buffer(ox::units::MB);
std::size_t sz = 0;
oxReturnError(ox::writeMC(buffer.data(), buffer.size(), pal, &sz));
buffer.resize(sz);
return buffer;
}
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
return ox::writeMC(pal);
}
```
#### Organic Claw Example
##### Read
```cpp
#include <ox/oc/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readOC<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readOC(buff, &pal));
return pal;
}
```
##### Write
```cpp
#include <ox/oc/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette const&pal) noexcept {
ox::Buffer buffer(ox::units::MB);
oxReturnError(ox::writeOC(buffer.data(), buffer.size(), pal));
return buffer;
}
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
return ox::writeOC(pal);
}
```
#### Claw Example
##### Read
```cpp
#include <ox/claw/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readClaw<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readClaw(buff, pal));
return pal;
}
```
##### Write
```cpp
#include <ox/claw/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette(NostalgiaPalette const&pal) noexcept {
return ox::writeClaw(pal);
}
```

View File

@ -1,4 +1,4 @@
# Nostalgia Developer Handbook # Olympic Developer Handbook
## About ## About
@ -44,21 +44,16 @@ All components have a platform indicator next to them:
* gba - GBA implementation (PG) * gba - GBA implementation (PG)
* glfw - GLFW implementation (P-) * glfw - GLFW implementation (P-)
* deps - project dependencies * deps - project dependencies
* Ox - Library of things useful for portable bare metal and userland code. Not really that external... * Ox - Library of things useful for portable bare metal and userland code.
* clargs - Command Line Args processing (PG) Not really that external... (PG)
* claw - Reads and writes Metal or Organic Claw with header to indicate which
* event - Qt-like signal system
* fs - file system (PG)
* logconn - connects logging to Bullock (P-)
* mc - Metal Claw serialization, builds on model (PG)
* oc - Organic Claw serialization (wrapper around JsonCpp), builds on model (P-)
* model - Data structure modelling (PG)
* preloader - library for handling preloading of data (PG)
* std - Standard-ish Library with a lot missing and some things added (PG)
* GlUtils - OpenGL helpers (P-) * GlUtils - OpenGL helpers (P-)
* teagba - GBA assembly startup code (mostly pulled from devkitPro under MPL * teagba - GBA assembly startup code (mostly pulled from devkitPro under MPL
2.0), and custom GBA hardware interop code (-G) 2.0), and custom GBA hardware interop code (-G)
Most GBA code is built on PC because it is small and helps to work on both
projects with the same CMake build dir, but GBA code is never linked with any
executables on PC.
## Platform Notes ## Platform Notes
### GBA ### GBA
@ -88,6 +83,7 @@ The GBA has two major resources for learning about its hardware:
* Pointer and reference designators should be bound to the identifier name and * Pointer and reference designators should be bound to the identifier name and
not the type, unless there is not identifier name, in which case it should be not the type, unless there is not identifier name, in which case it should be
bound to the type. bound to the type.
* East const
### Write C++, Not C ### Write C++, Not C
@ -160,479 +156,40 @@ classes in question.
## Project Systems ## Project Systems
Olympic builds on Ox as its standard-ish library.
Please read the [Ox documentation](deps/ox/ox-docs.md).
The Ox way of doing things is the Olympic way of doing things.
### Error Handling ### Error Handling
The GBA build has exceptions disabled. The GBA build has exceptions disabled.
Instead of throwing exceptions, all engine code must return ```ox::Error```s. Instead of throwing exceptions, all engine code should return
[ox::Errors](deps/ox/ox-docs.md#error-handling) for error reporting.
For the sake of consistency, try to stick to ```ox::Error``` in non-engine code For the sake of consistency, try to stick to ```ox::Error``` in non-engine code
as well, but non-engine code is free to use exceptions when they make sense. as well, but non-engine code is free to use exceptions when they make sense.
Nostalgia and Ox both use ```ox::Error``` to report errors. ```ox::Error``` is
a struct that has overloaded operators to behave like an integer error code,
plus some extra fields to enhance debuggability.
If instantiated through the ```OxError(x)``` macro, it will also include the
file and line of the error.
The ```OxError(x)``` macro should only be used for the initial instantiation of
an ```ox::Error```.
In addition to ```ox::Error``` there is also the template ```ox::Result<T>```. Exceptions should generally just use ```OxException```, which is bascially an
```ox::Result``` simply wraps the type T value in a struct that also includes exception form of ```ox::Error```.
error information, which allows the returning of a value and an error without
resorting to output parameters.
If a function returns an ```ox::Error``` or ```ox::Result``` it should be
declared as ```noexcept``` and all exceptions should be translated to an
```ox::Error```.
```ox::Result``` can be used as follows:
```cpp
ox::Result<int> foo(int i) noexcept {
if (i < 10) {
return i + 1; // implicitly calls ox::Result<T>::Result(T)
}
return OxError(1); // implicitly calls ox::Result<T>::Result(ox::Error)
}
int caller1() {
auto v = foo(argc);
if (v.error) {
return 1;
}
std::cout << v.value << '\n';
return 0;
}
int caller2() {
// it is also possible to capture the value and error in their own variables
auto [val, err] = foo(argc);
if (err) {
return 1;
}
std::cout << val << '\n';
return 0;
}
ox::Error caller3(int &i) {
return foo(i).moveTo(i);
}
ox::Error caller4(int &i) {
return foo(i).copyTo(i);
}
int caller5(int i) {
return foo(i).unwrap(); // unwrap will kill the program if there is an error
}
int caller6(int i) {
return foo(i).unwrapThrow(); // unwrap will throw if there is an error
}
int caller7(int i) {
return foo(i).or_value(0); // will return 0 if foo returned an error
}
ox::Result<uint64_t> caller8(int i) {
return foo(i).to<uint64_t>(); // will convert the result of foo to uint64_t
}
```
Lastly, there are a few macros available to help in passing ```ox::Error```s
back up the call stack, ```oxReturnError```, ```oxThrowError```, and
```oxRequire```.
```oxReturnError``` is by far the more helpful of the two.
```oxReturnError``` will return an ```ox::Error``` if it is not 0 and
```oxThrowError``` will throw an ```ox::Error``` if it is not 0.
Because exceptions are disabled for GBA builds and thus cannot be used in the
engine, ```oxThrowError``` is only really useful at the boundary between
engine libraries and Nostalgia Studio.
Since ```ox::Error``` is always nodiscard, you must do something with them.
In rare cases, you may not have anything you can do with them or you may know
the code will never fail in that particular instance.
This should be used sparingly.
```cpp
void studioCode() {
auto [val, err] = foo(1);
oxThrowError(err);
doStuff(val);
}
ox::Error engineCode() noexcept {
auto [val, err] = foo(1);
oxReturnError(err);
doStuff(val);
return {};
}
void anyCode() {
auto [val, err] = foo(1);
std::ignore = err;
doStuff(val);
}
```
Both macros will also take the ```ox::Result``` directly:
```cpp
void studioCode() {
auto valerr = foo(1);
oxThrowError(valerr);
doStuff(valerr.value);
}
ox::Error engineCode() noexcept {
auto valerr = foo(1);
oxReturnError(valerr);
doStuff(valerr.value);
return {};
}
```
Ox also has the ```oxRequire``` macro, which will initialize a value if there is no error, and return if there is.
It aims to somewhat emulate the ```?``` operator in Rust and Swift.
Rust ```?``` operator:
```rust
fn f() -> Result<i32, i32> {
// do stuff
}
fn f2() -> Result<i32, i32> {
let i = f()?;
Ok(i + 4)
}
```
```oxRequire```:
```cpp
ox::Result<int> f() noexcept {
// do stuff
}
ox::Result<int> f2() noexcept {
oxRequire(i, f()); // const auto [out, oxConcat(oxRequire_err_, __LINE__)] = x; oxReturnError(oxConcat(oxRequire_err_, __LINE__))
return i + 4;
}
```
```oxRequire``` is not quite as versatile, but it should still cleanup a lot of otherwise less ideal code.
```oxRequire``` also has variants for throwing the error and for making to value non-const:
* ```oxRequireM``` - oxRequire Mutable
* ```oxRequireT``` - oxRequire Throw
* ```oxRequireMT``` - oxRequire Mutable Throw
The throw variants of ```oxRequire``` are generally legacy code.
```ox::Result::unwrapThrow``` is generally preferred now.
### Logging and Output
Ox provides for logging and debug prints via the ```oxTrace```, ```oxDebug```, and ```oxError``` macros.
Each of these also provides a format variation.
Ox also provide ```oxOut``` and ```oxErr``` for printing to stdout and stderr.
These are intended for permanent messages and always go to stdout and stderr.
Tracing functions do not go to stdout unless the OXTRACE environment variable is set.
They also print with the channel that they are on, along with file and line.
Debug statements go to stdout and go to the logger on the "debug" channel.
Where trace statements are intended to be written with thoughtfulness,
debug statements are intended to be quick and temporary insertions.
Debug statements trigger compilation failures if OX_NODEBUG is enabled when CMake is run,
as it is on Jenkins builds, so ```oxDebug``` statements should never be checked in.
This makes ```oxDebug``` preferable to other forms of logging, as temporary prints should
never be checked in.
```oxError``` always prints.
It includes file and line, and is prefixed with a red "ERROR:".
It should generally be used conservatively.
It shuld be used only when there is an error that is not technically fatal, but
the user almost certainly wants to know about it.
```oxTrace``` and ```oxTracef```:
```cpp
void f(int x, int y) { // x = 9, y = 4
oxTrace("nostalgia.core.sdl.gfx") << "f:" << x << y; // Output: "f: 9 4"
oxTracef("nostalgia.core.sdl.gfx", "f: {}, {}", x, y); // Output: "f: 9, 4"
}
```
```oxDebug``` and ```oxDebugf```:
```cpp
void f(int x, int y) { // x = 9, y = 4
oxDebug() << "f:" << x << y; // Output: "f: 9 4"
oxDebugf("f: {}, {}", x, y); // Output: "f: 9, 4"
}
```
```oxError``` and ```oxErrorf```:
```cpp
void f(int x, int y) { // x = 9, y = 4
oxError() << "f:" << x << y; // Output: "ERROR: (<file>:<line>): f: 9 4"
oxErrorf("f: {}, {}", x, y); // Output: "ERROR: (<file>:<line>): f: 9, 4"
}
```
### File I/O ### File I/O
All engine file I/O should go through nostalgia::core::Context, which should go All engine file I/O should go through Keel, which should go through
through ox::FileSystem. Similarly, all studio file I/O should go thorough ```ox::FileSystem```.
nostalgia::studio::Project, which should go through ox::FileSystem. Similarly, all studio file I/O should go thorough
```nostalgia::studio::Project```, which should go through ```ox::FileSystem```.
ox::FileSystem abstracts away differences between conventional storage devices ```ox::FileSystem``` abstracts away differences between conventional storage
and ROM. devices and ROM.
### Model System Olympic files are generally just [Claw objects](deps/ox/ox-docs.md#serialization).
Ox has a model system that provides a sort of manual reflection mechanism. #### Keel
Models require a model function for the type that you want to model. Keel, as its name implies, is a foundational component of Olympic.
It is also good to provide a type name and type version number, though that is not required. Keel is the asset management system.
The model function takes an instance of the type it is modelling and a template Keel provides for the following needs:
parameter type.
The template parameter type must implement the API used in the models, but it
can do anything with the data provided to it.
Here is an example from the Nostalgia/Core package: * Type conversion
* Asset bundling
```cpp * Asset loading (including flyweighting on PC and preloading to ROM on the GBA)
struct NostalgiaPalette {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
static constexpr auto TypeVersion = 1;
ox::Vector<Color16> colors;
};
struct NostalgiaGraphic {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic";
static constexpr auto TypeVersion = 1;
int8_t bpp = 0;
// rows and columns are really only used by TileSheetEditor
int rows = 1;
int columns = 1;
ox::FileAddress defaultPalette;
NostalgiaPalette pal;
ox::Vector<uint8_t> pixels;
};
template<typename T>
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaPalette> auto *pal) noexcept {
h->template setTypeInfo<NostalgiaPalette>();
// it is also possible to provide the type name and type version as function arguments
//h->setTypeInfo("net.drinkingtea.nostalgia.core.NostalgiaPalette", 1);
oxReturnError(h->field("colors", &pal->colors));
return {};
}
template<typename T>
constexpr ox::Error model(T *h, ox::CommonPtrWith<NostalgiaGraphic> auto *ng) noexcept {
h->template setTypeInfo<NostalgiaGraphic>();
oxReturnError(h->field("bpp", &ng->bpp));
oxReturnError(h->field("rows", &ng->rows));
oxReturnError(h->field("columns", &ng->columns));
oxReturnError(h->field("defaultPalette", &ng->defaultPalette));
oxReturnError(h->field("pal", &ng->pal));
oxReturnError(h->field("pixels", &ng->pixels));
return {};
}
```
The model system also provides for unions:
```cpp
#include <ox/model/types.hpp>
class FileAddress {
template<typename T>
friend constexpr Error model(T*, ox::CommonPtrWith<FileAddress> auto*) noexcept;
public:
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress";
union Data {
static constexpr auto TypeName = "net.drinkingtea.ox.FileAddress.Data";
char *path;
const char *constPath;
uint64_t inode;
};
protected:
FileAddressType m_type = FileAddressType::None;
Data m_data;
};
template<typename T>
constexpr Error model(T *h, ox::CommonPtrWith<FileAddress::Data> auto *obj) noexcept {
h->template setTypeInfo<FileAddress::Data>();
oxReturnError(h->fieldCString("path", &obj->path));
oxReturnError(h->fieldCString("constPath", &obj->path));
oxReturnError(h->field("inode", &obj->inode));
return {};
}
template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<FileAddress> auto *fa) noexcept {
io->template setTypeInfo<FileAddress>();
// cannot read from object in Reflect operation
if constexpr(ox_strcmp(T::opType(), OpType::Reflect) == 0) {
int8_t type = 0;
oxReturnError(io->field("type", &type));
oxReturnError(io->field("data", UnionView(&fa->m_data, 0)));
} else {
auto type = static_cast<int8_t>(fa->m_type);
oxReturnError(io->field("type", &type));
fa->m_type = static_cast<FileAddressType>(type);
oxReturnError(io->field("data", UnionView(&fa->m_data, static_cast<int>(fa->m_type))));
}
return {};
}
```
There are also macros in ```<ox/model/def.hpp>``` for simplifying the declaration of models:
```cpp
oxModelBegin(NostalgiaGraphic)
oxModelField(bpp)
oxModelField(rows)
oxModelField(columns)
oxModelField(defaultPalette)
oxModelField(pal)
oxModelField(pixels)
oxModelEnd()
```
### Serialization
Using the model system, Ox provides for serialization.
Ox has MetalClaw and OrganicClaw as its serialization format options.
MetalClaw is a custom binary format designed for minimal size.
OrganicClaw is a wrapper around JsonCpp, chosen because it technically
implements a superset of JSON.
OrganicClaw requires support for 64 bit integers, whereas normal JSON
technically does not.
These formats do not currently support floats.
There is also a wrapper format called Claw that provides a header at the
beginning of the file and can dynamically switch between the two depending on
what the header says is present.
The Claw header also includes information about the type and type version of
the data.
Claw header: ```M1;net.drinkingtea.nostalgia.core.NostalgiaPalette;1;```
That reads:
* Format is Metal Claw, version 1
* Type ID is net.drinkingtea.nostalgia.core.NostalgiaPalette
* Type version is 1
Except when the data is exported for loading on the GBA, Claw is always used as
a wrapper around the bare formats.
#### Metal Claw Example
##### Read
```cpp
#include <ox/mc/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readMC<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readMC(buff, pal));
return pal;
}
```
##### Write
```cpp
#include <ox/mc/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette const&pal) noexcept {
ox::Buffer buffer(ox::units::MB);
std::size_t sz = 0;
oxReturnError(ox::writeMC(buffer.data(), buffer.size(), pal, &sz));
buffer.resize(sz);
return buffer;
}
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
return ox::writeMC(pal);
}
```
#### Organic Claw Example
##### Read
```cpp
#include <ox/oc/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readOC<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readOC(buff, &pal));
return pal;
}
```
##### Write
```cpp
#include <ox/oc/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette const&pal) noexcept {
ox::Buffer buffer(ox::units::MB);
oxReturnError(ox::writeOC(buffer.data(), buffer.size(), pal));
return buffer;
}
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
return ox::writeOC(pal);
}
```
#### Claw Example
##### Read
```cpp
#include <ox/claw/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readClaw<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readClaw(buff, pal));
return pal;
}
```
##### Write
```cpp
#include <ox/claw/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette(NostalgiaPalette const&pal) noexcept {
return ox::writeClaw(pal);
}
```

View File

@ -143,6 +143,14 @@ ox::Error loadBgTileSheet(
size_t srcTileIdx, size_t srcTileIdx,
size_t tileCnt) noexcept; size_t tileCnt) noexcept;
ox::Error loadBgTileSheet(
Context &ctx,
unsigned cbb,
ox::FileAddress const&tsAddr,
size_t dstTileIdx,
size_t srcTileIdx,
size_t tileCnt) noexcept;
ox::Error loadBgTileSheet( ox::Error loadBgTileSheet(
Context &ctx, Context &ctx,
unsigned cbb, unsigned cbb,

View File

@ -61,21 +61,6 @@ ox::Error loadSpritePalette(
return {}; return {};
} }
ox::Error loadBgPalette(
Context &ctx,
size_t palBank,
ox::FileAddress const&paletteAddr) noexcept {
oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
return loadBgPalette(ctx, palBank, *pal, 0);
}
ox::Error loadSpritePalette(
Context &ctx,
ox::FileAddress const&paletteAddr) noexcept {
oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
return loadSpritePalette(ctx, *pal, 0);
}
static ox::Error loadTileSheetSet( static ox::Error loadTileSheetSet(
Context &ctx, Context &ctx,
ox::Span<uint16_t> tileMapTargetMem, ox::Span<uint16_t> tileMapTargetMem,
@ -156,15 +141,6 @@ ox::Error loadBgTileSheet(
return {}; return {};
} }
ox::Error loadBgTileSheet(
Context &ctx,
unsigned cbb,
ox::FileAddress const&tilesheetAddr,
ox::Optional<unsigned> const&paletteBank) noexcept {
oxRequire(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tilesheetAddr));
return loadBgTileSheet(ctx, cbb, *ts, paletteBank);
}
ox::Error loadBgTileSheet( ox::Error loadBgTileSheet(
Context &ctx, Context &ctx,
unsigned const cbb, unsigned const cbb,
@ -206,14 +182,6 @@ ox::Error loadSpriteTileSheet(
return {}; return {};
} }
ox::Error loadSpriteTileSheet(
Context &ctx,
ox::FileAddress const&tilesheetAddr,
bool loadDefaultPalette) noexcept {
oxRequire(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tilesheetAddr));
return loadSpriteTileSheet(ctx, *ts, loadDefaultPalette);
}
ox::Error loadSpriteTileSheet( ox::Error loadSpriteTileSheet(
Context &ctx, Context &ctx,
TileSheetSet const&set) noexcept { TileSheetSet const&set) noexcept {

View File

@ -2,6 +2,7 @@
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. * Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/ */
#include <keel/media.hpp>
#include <nostalgia/core/gfx.hpp> #include <nostalgia/core/gfx.hpp>
namespace nostalgia::core { namespace nostalgia::core {
@ -17,6 +18,49 @@ int tileRows(Context&) noexcept {
return GbaTileRows; return GbaTileRows;
} }
ox::Error loadBgPalette(
Context &ctx,
size_t palBank,
ox::FileAddress const&paletteAddr) noexcept {
oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
return loadBgPalette(ctx, palBank, *pal, 0);
}
ox::Error loadSpritePalette(
Context &ctx,
ox::FileAddress const&paletteAddr) noexcept {
oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
return loadSpritePalette(ctx, *pal, 0);
}
ox::Error loadBgTileSheet(
Context &ctx,
unsigned cbb,
ox::FileAddress const&tsAddr,
size_t dstTileIdx,
size_t srcTileIdx,
size_t tileCnt) noexcept {
oxRequire(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tsAddr));
return loadBgTileSheet(ctx, cbb, *ts, dstTileIdx, srcTileIdx, tileCnt);
}
ox::Error loadBgTileSheet(
Context &ctx,
unsigned cbb,
ox::FileAddress const&tilesheetAddr,
ox::Optional<unsigned> const&paletteBank) noexcept {
oxRequire(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tilesheetAddr));
return loadBgTileSheet(ctx, cbb, *ts, paletteBank);
}
ox::Error loadSpriteTileSheet(
Context &ctx,
ox::FileAddress const&tilesheetAddr,
bool loadDefaultPalette) noexcept {
oxRequire(ts, readObj<CompactTileSheet>(keelCtx(ctx), tilesheetAddr));
return loadSpriteTileSheet(ctx, *ts, loadDefaultPalette);
}
// map ASCII values to the nostalgia charset // map ASCII values to the nostalgia charset
constexpr ox::Array<char, 128> charMap = { constexpr ox::Array<char, 128> charMap = {
0, 0,

View File

@ -531,26 +531,6 @@ ox::Error loadSpritePalette(
return {}; return {};
} }
ox::Error loadBgPalette(
Context &ctx,
size_t palBank,
ox::FileAddress const&paletteAddr) noexcept {
auto &kctx = keelCtx(ctx.turbineCtx);
oxRequire(palette, readObj<CompactPalette>(kctx, paletteAddr));
renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, *palette);
return {};
}
ox::Error loadSpritePalette(
Context &ctx,
ox::FileAddress const&paletteAddr) noexcept {
auto &kctx = keelCtx(ctx.turbineCtx);
oxRequire(palette, readObj<CompactPalette>(kctx, paletteAddr));
ox::Array<GLfloat, 1024> pal;
renderer::loadPalette(pal, 0, ctx.spriteShader, *palette);
return {};
}
static ox::Result<TileSheetData> buildSetTsd( static ox::Result<TileSheetData> buildSetTsd(
Context &ctx, Context &ctx,
TileSheetSet const&set) noexcept { TileSheetSet const&set) noexcept {
@ -630,16 +610,6 @@ ox::Error loadBgTileSheet(
return {}; return {};
} }
ox::Error loadBgTileSheet(
Context &ctx,
uint_t cbb,
ox::FileAddress const&tilesheetAddr,
ox::Optional<unsigned> const&paletteBank) noexcept {
auto &kctx = keelCtx(ctx.turbineCtx);
oxRequire(ts, readObj<CompactTileSheet>(kctx, tilesheetAddr));
return loadBgTileSheet(ctx, cbb, *ts, paletteBank);
}
ox::Error loadBgTileSheet( ox::Error loadBgTileSheet(
Context &ctx, Context &ctx,
unsigned cbb, unsigned cbb,
@ -662,15 +632,6 @@ ox::Error loadSpriteTileSheet(
return {}; return {};
} }
ox::Error loadSpriteTileSheet(
Context &ctx,
ox::FileAddress const&tilesheetAddr,
bool loadDefaultPalette) noexcept {
auto &kctx = keelCtx(ctx.turbineCtx);
oxRequire(ts, readObj<CompactTileSheet>(kctx, tilesheetAddr));
return loadSpriteTileSheet(ctx, *ts, loadDefaultPalette);
}
ox::Error loadSpriteTileSheet( ox::Error loadSpriteTileSheet(
Context &ctx, Context &ctx,
TileSheetSet const&set) noexcept { TileSheetSet const&set) noexcept {

View File

@ -41,31 +41,10 @@ PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringPa
m_sctx(sctx), m_sctx(sctx),
m_tctx(sctx.tctx), m_tctx(sctx.tctx),
m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), itemPath()).unwrapThrow()) { m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), itemPath()).unwrapThrow()) {
if (keel::ensureValid(m_pal).errCode) {
throw OxException(1, "PaletteEditorImGui: invalid Palette object");
}
undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand); undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
m_pageRename.inputSubmitted.connect(this, &PaletteEditorImGui::renamePage); m_pageRename.inputSubmitted.connect(this, &PaletteEditorImGui::renamePage);
} }
void PaletteEditorImGui::keyStateChanged(turbine::Key key, bool down) {
if (!down || m_pageRename.isOpen()) {
return;
}
if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9) {
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
m_page = ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1), m_pal.pages.size() - 1);
}
} else if (key == turbine::Key::Num_0) {
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
m_selectedColorRow =
ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_pal.pages.size() - 1);
}
}
}
void PaletteEditorImGui::draw(studio::StudioContext&) noexcept { void PaletteEditorImGui::draw(studio::StudioContext&) noexcept {
auto const paneSize = ImGui::GetContentRegionAvail(); auto const paneSize = ImGui::GetContentRegionAvail();
{ {
@ -98,6 +77,19 @@ void PaletteEditorImGui::drawColumn(ox::CStringView txt) noexcept {
ImGui::Text("%s", txt.c_str()); ImGui::Text("%s", txt.c_str());
} }
void PaletteEditorImGui::numShortcuts(size_t &val, size_t const sizeRange) noexcept {
auto const lastElem = sizeRange - 1;
if (ImGui::IsKeyPressed(ImGuiKey_0)) {
val = ox::min<size_t>(9, lastElem);
} else for (auto i = 9u; i < 10; --i) {
auto const key = static_cast<ImGuiKey>(ImGuiKey_1 + i);
if (ImGui::IsKeyPressed(key)) {
val = ox::min<size_t>(i, lastElem);
break;
}
}
}
void PaletteEditorImGui::colorInput(ox::CStringView label, int &v, bool &inputFocused) noexcept { void PaletteEditorImGui::colorInput(ox::CStringView label, int &v, bool &inputFocused) noexcept {
ImGui::InputInt(label.c_str(), &v, 1, 5); ImGui::InputInt(label.c_str(), &v, 1, 5);
inputFocused = inputFocused || ImGui::IsItemFocused(); inputFocused = inputFocused || ImGui::IsItemFocused();
@ -162,22 +154,24 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40); ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40);
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide); ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
for (auto i = 0u; auto const&c : m_pal.pages[m_page].colors) { if (m_page < m_pal.pages.size()) {
ig::IDStackItem const idStackItem(static_cast<int>(i)); for (auto i = 0u; auto const &c: m_pal.pages[m_page].colors) {
ImGui::TableNextRow(); ig::IDStackItem const idStackItem(static_cast<int>(i));
drawColumn(i + 1); ImGui::TableNextRow();
drawColumnLeftAlign(m_pal.colorNames[i]); drawColumn(i + 1);
drawColumn(red16(c)); drawColumnLeftAlign(m_pal.colorNames[i]);
drawColumn(green16(c)); drawColumn(red16(c));
drawColumn(blue16(c)); drawColumn(green16(c));
ImGui::TableNextColumn(); drawColumn(blue16(c));
auto const ic = ImGui::GetColorU32({redf(c), greenf(c), bluef(c), 1}); ImGui::TableNextColumn();
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic); auto const ic = ImGui::GetColorU32({redf(c), greenf(c), bluef(c), 1});
if (ImGui::Selectable( ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
"##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) { if (ImGui::Selectable(
m_selectedColorRow = i; "##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
m_selectedColorRow = i;
}
++i;
} }
++i;
} }
} }
ImGui::EndTable(); ImGui::EndTable();
@ -240,10 +234,8 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
int g = green16(c); int g = green16(c);
int b = blue16(c); int b = blue16(c);
int const a = alpha16(c); int const a = alpha16(c);
auto const&currentName = m_pal.colorNames[m_selectedColorRow]; auto const newName = ig::InputText<50>(
ox::IString<50> name; "Name", m_pal.colorNames[m_selectedColorRow]);
name = currentName;
ImGui::InputText("Name", name.data(), name.cap() + 1);
bool inputFocused = ImGui::IsItemFocused(); bool inputFocused = ImGui::IsItemFocused();
ImGui::Separator(); ImGui::Separator();
colorInput("Red", r, inputFocused); colorInput("Red", r, inputFocused);
@ -253,25 +245,20 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
std::ignore = pushCommand<ApplyColorAllPagesCommand>( std::ignore = pushCommand<ApplyColorAllPagesCommand>(
m_pal, m_page, m_selectedColorRow); m_pal, m_page, m_selectedColorRow);
} }
if (!inputFocused) { if (!inputFocused && !m_pageRename.isOpen()) {
auto const lastColor = largestPage(m_pal) - 1; if (!ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
if (ImGui::IsKeyPressed(ImGuiKey_0, false)) { numShortcuts(m_selectedColorRow, largestPage(m_pal));
m_selectedColorRow = ox::min<size_t>(9, lastColor); } else {
} else for (auto i = 9u; i < 10; --i) { numShortcuts(m_page, m_pal.pages.size());
auto const key = static_cast<ImGuiKey>(ImGuiKey_1 + i);
if (ImGui::IsKeyPressed(key, false)) {
m_selectedColorRow = ox::min<size_t>(i, lastColor);
break;
}
} }
} }
auto const newColor = color16(r, g, b, a); auto const newColor = color16(r, g, b, a);
if (c != newColor) { if (c != newColor) {
std::ignore = pushCommand<UpdateColorCommand>(m_pal, m_page, m_selectedColorRow, newColor); std::ignore = pushCommand<UpdateColorCommand>(m_pal, m_page, m_selectedColorRow, newColor);
} }
if (currentName != name) { if (newName) {
std::ignore = pushCommand<UpdateColorInfoCommand>( std::ignore = pushCommand<UpdateColorInfoCommand>(
m_pal, m_selectedColorRow, ox::String{name}); m_pal, m_selectedColorRow, ox::String{newName});
} }
} }

View File

@ -40,8 +40,6 @@ class PaletteEditorImGui: public studio::Editor {
public: public:
PaletteEditorImGui(studio::StudioContext &sctx, ox::StringParam path); PaletteEditorImGui(studio::StudioContext &sctx, ox::StringParam path);
void keyStateChanged(turbine::Key key, bool down) override;
void draw(studio::StudioContext&) noexcept final; void draw(studio::StudioContext&) noexcept final;
protected: protected:
@ -56,6 +54,8 @@ class PaletteEditorImGui: public studio::Editor {
drawColumn(ox::itoa(i)); drawColumn(ox::itoa(i));
} }
static void numShortcuts(size_t &val, size_t sizeRange) noexcept;
static void colorInput(ox::CStringView label, int &v, bool &inputFocused) noexcept; static void colorInput(ox::CStringView label, int &v, bool &inputFocused) noexcept;
void drawColorsEditor() noexcept; void drawColorsEditor() noexcept;

View File

@ -188,19 +188,21 @@ UpdateColorInfoCommand::UpdateColorInfoCommand(
} }
} }
bool UpdateColorInfoCommand::mergeWith(UndoCommand const&cmd) noexcept { bool UpdateColorInfoCommand::mergeWith(UndoCommand &cmd) noexcept {
if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) { if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColorInfo)) {
return false; return false;
} }
auto ucCmd = static_cast<UpdateColorInfoCommand const*>(&cmd); auto ucCmd = dynamic_cast<UpdateColorInfoCommand const*>(&cmd);
if (m_idx != ucCmd->m_idx) { if (m_idx != ucCmd->m_idx) {
return false; return false;
} }
m_pal.colorNames[m_idx] = std::move(ucCmd->m_pal.colorNames[m_idx]);
setObsolete(m_altColorInfo == m_pal.colorNames[m_idx]);
return true; return true;
} }
int UpdateColorInfoCommand::commandId() const noexcept { int UpdateColorInfoCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::UpdateColor); return static_cast<int>(PaletteEditorCommandId::UpdateColorInfo);
} }
ox::Error UpdateColorInfoCommand::redo() noexcept { ox::Error UpdateColorInfoCommand::redo() noexcept {
@ -232,11 +234,11 @@ UpdateColorCommand::UpdateColorCommand(
} }
} }
bool UpdateColorCommand::mergeWith(UndoCommand const&cmd) noexcept { bool UpdateColorCommand::mergeWith(UndoCommand &cmd) noexcept {
if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) { if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
return false; return false;
} }
auto ucCmd = static_cast<UpdateColorCommand const*>(&cmd); auto ucCmd = dynamic_cast<UpdateColorCommand const*>(&cmd);
if (m_idx != ucCmd->m_idx) { if (m_idx != ucCmd->m_idx) {
return false; return false;
} }

View File

@ -163,7 +163,7 @@ class UpdateColorInfoCommand: public studio::UndoCommand {
~UpdateColorInfoCommand() noexcept override = default; ~UpdateColorInfoCommand() noexcept override = default;
[[nodiscard]] [[nodiscard]]
bool mergeWith(const UndoCommand &cmd) noexcept final; bool mergeWith(UndoCommand &cmd) noexcept final;
[[nodiscard]] [[nodiscard]]
int commandId() const noexcept final; int commandId() const noexcept final;
@ -194,7 +194,7 @@ class UpdateColorCommand: public studio::UndoCommand {
~UpdateColorCommand() noexcept override = default; ~UpdateColorCommand() noexcept override = default;
[[nodiscard]] [[nodiscard]]
bool mergeWith(const UndoCommand &cmd) noexcept final; bool mergeWith(UndoCommand &cmd) noexcept final;
[[nodiscard]] [[nodiscard]]
int commandId() const noexcept final; int commandId() const noexcept final;

View File

@ -158,21 +158,25 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
} else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9) { } else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9) {
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) { if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
auto const idx = ox::min<std::size_t>( auto const idx = ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1), m_model.pal().pages.size() - 1); static_cast<uint32_t>(key - turbine::Key::Num_1),
m_model.pal().pages.size() - 1);
m_model.setPalettePage(idx); m_model.setPalettePage(idx);
} else if (key <= turbine::Key::Num_0 + colorCnt) { } else if (key <= turbine::Key::Num_9) {
auto const idx = ox::min<std::size_t>( auto const idx = ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1); static_cast<uint32_t>(key - turbine::Key::Num_1),
colorCnt - 1);
m_view.setPalIdx(idx); m_view.setPalIdx(idx);
} }
} else if (key == turbine::Key::Num_0) { } else if (key == turbine::Key::Num_0) {
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) { if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
auto const idx = ox::min<std::size_t>( auto const idx = ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_model.pal().pages.size() - 1); static_cast<uint32_t>(key - turbine::Key::Num_1 + 9),
m_model.pal().pages.size() - 1);
m_model.setPalettePage(idx); m_model.setPalettePage(idx);
} else if (colorCnt >= 10) { } else {
auto const idx = ox::min<std::size_t>( auto const idx = ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), colorCnt - 1); static_cast<uint32_t>(key - turbine::Key::Num_1 + 9),
colorCnt - 1);
m_view.setPalIdx(idx); m_view.setPalIdx(idx);
} }
} }

View File

@ -15,7 +15,6 @@ SceneEditorImGui::SceneEditorImGui(studio::StudioContext &ctx, ox::StringView pa
m_ctx(ctx.tctx), m_ctx(ctx.tctx),
m_editor(m_ctx, path), m_editor(m_ctx, path),
m_view(m_ctx, m_editor.scene()) { m_view(m_ctx, m_editor.scene()) {
setRequiresConstantRefresh(false);
} }
void SceneEditorImGui::draw(studio::StudioContext&) noexcept { void SceneEditorImGui::draw(studio::StudioContext&) noexcept {

View File

@ -29,8 +29,8 @@ oxModelBegin(PreloadPtr)
oxModelField(preloadAddr) oxModelField(preloadAddr)
oxModelEnd() oxModelEnd()
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept; ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&addr) noexcept;
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::StringViewCR file) noexcept; ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::StringViewCR path) noexcept;
void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept; void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept;
@ -89,7 +89,7 @@ ox::Result<keel::AssetRef<T>> readObjFile(
assetId = substr(assetId, 7); assetId = substr(assetId, 7);
oxRequire(p, keel::uuidToPath(ctx, assetId)); oxRequire(p, keel::uuidToPath(ctx, assetId));
} else { } else {
auto const [uuid, uuidErr] = getUuid(ctx, assetId); auto const [uuid, uuidErr] = getUuid(ctx, assetId);
if (!uuidErr) { if (!uuidErr) {
uuidStr = uuid.toString(); uuidStr = uuid.toString();
assetId = uuidStr; assetId = uuidStr;

View File

@ -208,15 +208,15 @@ void unloadRom(char*) noexcept {
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::StringViewCR path) noexcept { ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::StringViewCR path) noexcept {
oxRequire(stat, ctx.rom->stat(path)); oxRequire(stat, ctx.rom->stat(path));
oxRequire(buff, static_cast<ox::MemFS*>(ctx.rom.get())->directAccess(path)); oxRequire(buff, static_cast<ox::MemFS&>(*ctx.rom).directAccess(path));
PreloadPtr p; PreloadPtr p;
oxReturnError(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p)); oxReturnError(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset; return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
} }
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept { ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&addr) noexcept {
oxRequire(stat, ctx.rom->stat(file)); oxRequire(stat, ctx.rom->stat(addr));
oxRequire(buff, static_cast<ox::MemFS*>(ctx.rom.get())->directAccess(file)); oxRequire(buff, static_cast<ox::MemFS&>(*ctx.rom).directAccess(addr));
PreloadPtr p; PreloadPtr p;
oxReturnError(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p)); oxReturnError(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset; return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;

View File

@ -77,7 +77,7 @@ void NewMenu::drawNewItemName(studio::StudioContext &sctx) noexcept {
drawWindow(sctx.tctx, &m_open, [this, &sctx] { drawWindow(sctx.tctx, &m_open, [this, &sctx] {
auto const typeIdx = static_cast<std::size_t>(m_selectedType); auto const typeIdx = static_cast<std::size_t>(m_selectedType);
if (typeIdx < m_types.size()) { if (typeIdx < m_types.size()) {
ImGui::InputText("Name", m_itemName.data(), m_itemName.cap()); ig::InputText("Name", m_itemName);
} }
drawLastPageButtons(sctx); drawLastPageButtons(sctx);
}); });

View File

@ -23,7 +23,6 @@ class BaseEditor: public Widget {
bool m_cutEnabled = false; bool m_cutEnabled = false;
bool m_copyEnabled = false; bool m_copyEnabled = false;
bool m_pasteEnabled = false; bool m_pasteEnabled = false;
bool m_requiresConstantRefresh = false;
public: public:
~BaseEditor() override = default; ~BaseEditor() override = default;
@ -52,9 +51,6 @@ class BaseEditor: public Widget {
virtual void onActivated() noexcept; virtual void onActivated() noexcept;
[[nodiscard]]
bool requiresConstantRefresh() const noexcept;
void close() const; void close() const;
/** /**
@ -71,10 +67,10 @@ class BaseEditor: public Widget {
[[nodiscard]] [[nodiscard]]
bool unsavedChanges() const noexcept; bool unsavedChanges() const noexcept;
void setExportable(bool); void setExportable(bool) noexcept;
[[nodiscard]] [[nodiscard]]
bool exportable() const; bool exportable() const noexcept;
void setCutEnabled(bool); void setCutEnabled(bool);

View File

@ -128,9 +128,35 @@ void centerNextWindow(turbine::Context &ctx) noexcept;
bool PushButton(ox::CStringView lbl, ImVec2 const&btnSz = BtnSz) noexcept; bool PushButton(ox::CStringView lbl, ImVec2 const&btnSz = BtnSz) noexcept;
template<typename Str>
struct TextInput {
bool changed{};
Str text;
explicit constexpr operator ox::String() const noexcept { return ox::String{text}; }
constexpr operator ox::CStringView() const noexcept { return text; }
constexpr operator ox::StringView() const noexcept { return text; }
constexpr operator bool() const noexcept { return changed; }
};
template<size_t MaxChars = 50>
TextInput<ox::IString<MaxChars>> InputText(
ox::CStringViewCR label,
ox::StringViewCR currentText,
ImGuiInputTextFlags const flags = 0,
ImGuiInputTextCallback const callback = nullptr,
void *user_data = nullptr) noexcept {
TextInput<ox::IString<MaxChars>> out = {.text = currentText};
out.changed = ImGui::InputText(
label.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
if (out.changed) {
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
}
return out;
}
template<size_t StrCap> template<size_t StrCap>
bool InputText( bool InputText(
ox::CStringView label, ox::CStringViewCR label,
ox::IString<StrCap> &text, ox::IString<StrCap> &text,
ImGuiInputTextFlags const flags = 0, ImGuiInputTextFlags const flags = 0,
ImGuiInputTextCallback const callback = nullptr, ImGuiInputTextCallback const callback = nullptr,

View File

@ -17,13 +17,22 @@ class NoChangesException: public ox::Exception {
}; };
class UndoCommand { class UndoCommand {
private:
bool m_obsolete{};
public: public:
virtual ~UndoCommand() noexcept = default; virtual ~UndoCommand() noexcept = default;
virtual ox::Error redo() noexcept = 0; virtual ox::Error redo() noexcept = 0;
virtual ox::Error undo() noexcept = 0; virtual ox::Error undo() noexcept = 0;
[[nodiscard]] [[nodiscard]]
virtual int commandId() const noexcept = 0; virtual int commandId() const noexcept = 0;
virtual bool mergeWith(UndoCommand const&cmd) noexcept; virtual bool mergeWith(UndoCommand &cmd) noexcept;
constexpr void setObsolete(bool obsolete) noexcept {
m_obsolete = obsolete;
}
[[nodiscard]]
constexpr bool isObsolete() const noexcept {
return m_obsolete;
}
}; };
} }

View File

@ -36,10 +36,6 @@ void BaseEditor::keyStateChanged(turbine::Key, bool) {
void BaseEditor::onActivated() noexcept { void BaseEditor::onActivated() noexcept {
} }
bool BaseEditor::requiresConstantRefresh() const noexcept {
return m_requiresConstantRefresh;
}
void BaseEditor::close() const { void BaseEditor::close() const {
this->closed.emit(itemPath()); this->closed.emit(itemPath());
} }
@ -66,12 +62,12 @@ bool BaseEditor::unsavedChanges() const noexcept {
return m_unsavedChanges; return m_unsavedChanges;
} }
void BaseEditor::setExportable(bool exportable) { void BaseEditor::setExportable(bool exportable) noexcept {
m_exportable = exportable; m_exportable = exportable;
exportableChanged.emit(exportable); exportableChanged.emit(exportable);
} }
bool BaseEditor::exportable() const { bool BaseEditor::exportable() const noexcept {
return m_exportable; return m_exportable;
} }
@ -110,10 +106,6 @@ UndoStack *BaseEditor::undoStack() noexcept {
return nullptr; return nullptr;
} }
void BaseEditor::setRequiresConstantRefresh(bool value) noexcept {
m_requiresConstantRefresh = value;
}
Editor::Editor(ox::StringParam itemPath) noexcept: Editor::Editor(ox::StringParam itemPath) noexcept:
m_itemPath(std::move(itemPath)), m_itemPath(std::move(itemPath)),

View File

@ -3,7 +3,7 @@
namespace studio { namespace studio {
bool UndoCommand::mergeWith(UndoCommand const&) noexcept { bool UndoCommand::mergeWith(UndoCommand&) noexcept {
return false; return false;
} }

View File

@ -17,6 +17,10 @@ ox::Error UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept {
m_stack.emplace_back(std::move(cmd)); m_stack.emplace_back(std::move(cmd));
++m_stackIdx; ++m_stackIdx;
} }
if ((*m_stack.back().unwrap())->isObsolete()) {
m_stack.pop_back();
--m_stackIdx;
}
return {}; return {};
} }