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:
parent
34b7779397
commit
9e11019b87
482
deps/ox/ox-docs.md
vendored
Normal file
482
deps/ox/ox-docs.md
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
```
|
@ -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);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
@ -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¤tName = 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});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
namespace studio {
|
namespace studio {
|
||||||
|
|
||||||
bool UndoCommand::mergeWith(UndoCommand const&) noexcept {
|
bool UndoCommand::mergeWith(UndoCommand&) noexcept {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user