Squashed 'deps/nostalgia/' content from commit 9cb6bd4a
git-subtree-dir: deps/nostalgia git-subtree-split: 9cb6bd4a32e9f39a858f72443ff5c6d40489fe22
This commit is contained in:
		
							
								
								
									
										38
									
								
								src/nostalgia/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/nostalgia/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
 | 
			
		||||
project(nostalgia CXX)
 | 
			
		||||
 | 
			
		||||
#project packages
 | 
			
		||||
 | 
			
		||||
set(NOSTALGIA_BUILD_PLAYER ON CACHE BOOL "Build Player")
 | 
			
		||||
set(NOSTALGIA_BUILD_STUDIO ON CACHE BOOL "Build Studio")
 | 
			
		||||
 | 
			
		||||
if(BUILDCORE_TARGET STREQUAL "gba")
 | 
			
		||||
	set(NOSTALGIA_BUILD_STUDIO OFF)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if(APPLE)
 | 
			
		||||
	set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/nostalgia")
 | 
			
		||||
	set(NOSTALGIA_DIST_BIN              NostalgiaStudio.app/Contents/MacOS)
 | 
			
		||||
	set(NOSTALGIA_DIST_LIB              NostalgiaStudio.app/Contents/Library)
 | 
			
		||||
	set(NOSTALGIA_DIST_MODULE           NostalgiaStudio.app/Contents/Plugins)
 | 
			
		||||
	set(NOSTALGIA_DIST_RESOURCES        NostalgiaStudio.app/Contents/Resources)
 | 
			
		||||
	set(NOSTALGIA_DIST_MAC_APP_CONTENTS NostalgiaStudio.app/Contents)
 | 
			
		||||
else()
 | 
			
		||||
	set(NOSTALGIA_DIST_BIN       bin)
 | 
			
		||||
	set(NOSTALGIA_DIST_LIB       lib)
 | 
			
		||||
	set(NOSTALGIA_DIST_MODULE    lib)
 | 
			
		||||
	set(NOSTALGIA_DIST_RESOURCES share)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_subdirectory(modules)
 | 
			
		||||
 | 
			
		||||
if(${NOSTALGIA_BUILD_PLAYER})
 | 
			
		||||
	add_subdirectory(player)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if(NOT BUILDCORE_TARGET STREQUAL "gba")
 | 
			
		||||
	add_subdirectory(tools)
 | 
			
		||||
	if(${NOSTALGIA_BUILD_STUDIO})
 | 
			
		||||
		add_subdirectory(studio)
 | 
			
		||||
	endif()
 | 
			
		||||
endif()
 | 
			
		||||
							
								
								
									
										52
									
								
								src/nostalgia/modules/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/nostalgia/modules/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
# module dir list
 | 
			
		||||
 | 
			
		||||
add_subdirectory(core)
 | 
			
		||||
add_subdirectory(scene)
 | 
			
		||||
 | 
			
		||||
# module libraries
 | 
			
		||||
 | 
			
		||||
# Keel
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaKeelModules STATIC
 | 
			
		||||
		keelmodules.cpp
 | 
			
		||||
)
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaKeelModules PUBLIC
 | 
			
		||||
		Keel
 | 
			
		||||
		NostalgiaCore-Keel
 | 
			
		||||
		NostalgiaScene-Keel
 | 
			
		||||
)
 | 
			
		||||
install(
 | 
			
		||||
	FILES
 | 
			
		||||
		keelmodules.hpp
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		include/nostalgia/modules
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Studio
 | 
			
		||||
if(NOSTALGIA_BUILD_STUDIO)
 | 
			
		||||
	add_library(
 | 
			
		||||
		NostalgiaStudioModules STATIC
 | 
			
		||||
			studiomodules.cpp
 | 
			
		||||
	)
 | 
			
		||||
	target_link_libraries(
 | 
			
		||||
		NostalgiaStudioModules PUBLIC
 | 
			
		||||
			StudioAppLib
 | 
			
		||||
			NostalgiaCore-Studio-ImGui
 | 
			
		||||
			NostalgiaScene-Studio
 | 
			
		||||
	)
 | 
			
		||||
	install(
 | 
			
		||||
		 FILES
 | 
			
		||||
			studiomodules.hpp
 | 
			
		||||
		 DESTINATION
 | 
			
		||||
			include/nostalgia/modules
 | 
			
		||||
	)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_library(NostalgiaProfile INTERFACE)
 | 
			
		||||
target_compile_definitions(
 | 
			
		||||
	NostalgiaProfile INTERFACE
 | 
			
		||||
		OLYMPIC_PROJECT_NAME="Nostalgia"
 | 
			
		||||
		OLYMPIC_PROJECT_NAMESPACE=nostalgia
 | 
			
		||||
		OLYMPIC_PROJECT_DATADIR=".nostalgia"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										12
									
								
								src/nostalgia/modules/core/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/nostalgia/modules/core/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
add_subdirectory(src)
 | 
			
		||||
 | 
			
		||||
if(NOT BUILDCORE_TARGET STREQUAL "gba")
 | 
			
		||||
	add_subdirectory(test)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	DIRECTORY
 | 
			
		||||
		include/nostalgia
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		include
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										154
									
								
								src/nostalgia/modules/core/include/nostalgia/core/color.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/nostalgia/modules/core/include/nostalgia/core/color.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,154 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
using Color16 = uint16_t;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Nostalgia Core logically uses 16 bit colors, but must translate that to 32
 | 
			
		||||
 * bit colors in some implementations.
 | 
			
		||||
 */
 | 
			
		||||
using Color32 = uint32_t;
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color32 toColor32(Color16 nc) noexcept {
 | 
			
		||||
	const auto r = static_cast<Color32>(((nc & 0b0000000000011111) >> 0) * 8);
 | 
			
		||||
	const auto g = static_cast<Color32>(((nc & 0b0000001111100000) >> 5) * 8);
 | 
			
		||||
	const auto b = static_cast<Color32>(((nc & 0b0111110000000000) >> 10) * 8);
 | 
			
		||||
	const auto a = static_cast<Color32>(255);
 | 
			
		||||
	return r | (g << 8) | (b << 16) | (a << 24);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t red16(Color16 c) noexcept {
 | 
			
		||||
	return c & 0b0000000000011111;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t green16(Color16 c) noexcept {
 | 
			
		||||
	return (c & 0b0000001111100000) >> 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t blue16(Color16 c) noexcept {
 | 
			
		||||
	return (c & 0b0111110000000000) >> 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t alpha16(Color16 c) noexcept {
 | 
			
		||||
	return static_cast<uint8_t>(c >> 15);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t red32(Color16 c) noexcept {
 | 
			
		||||
	return red16(c) * 8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t green32(Color16 c) noexcept {
 | 
			
		||||
	return green16(c) * 8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t blue32(Color16 c) noexcept {
 | 
			
		||||
	return blue16(c) * 8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t alpha32(Color16 c) noexcept {
 | 
			
		||||
	return static_cast<uint8_t>((c >> 15) * 255);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t red32(Color32 c) noexcept {
 | 
			
		||||
	return (c & 0x000000ff) >> 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t green32(Color32 c) noexcept {
 | 
			
		||||
	return (c & 0x0000ff00) >> 8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr uint8_t blue32(Color32 c) noexcept {
 | 
			
		||||
	return (c & 0x00ff0000) >> 16;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color32 color32(uint8_t r, uint8_t g, uint8_t b) noexcept {
 | 
			
		||||
	return static_cast<Color32>(r | (g << 8) | (b << 16));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color32 color32(Color16 c) noexcept {
 | 
			
		||||
	return color32(red32(c), green32(c), blue32(c));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color32 color32(float r, float g, float b) noexcept {
 | 
			
		||||
	return static_cast<Color32>(static_cast<uint8_t>(r * 255) | (static_cast<uint8_t>(g * 255) << 8) | (static_cast<uint8_t>(b * 255) << 16));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr float redf(Color16 c) noexcept {
 | 
			
		||||
	return static_cast<float>(red16(c)) / 31.f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr float greenf(Color16 c) noexcept {
 | 
			
		||||
	return static_cast<float>(green16(c)) / 31.f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr float bluef(Color16 c) noexcept {
 | 
			
		||||
	return static_cast<float>(blue16(c)) / 31.f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr float redf(Color32 c) noexcept {
 | 
			
		||||
	return static_cast<float>(red32(c)) / 255.f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr float greenf(Color32 c) noexcept {
 | 
			
		||||
	return static_cast<float>(green32(c)) / 255.f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr float bluef(Color32 c) noexcept {
 | 
			
		||||
	return static_cast<float>(blue32(c)) / 255.f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color16 color16(int r, int g, int b, int a = 0) noexcept {
 | 
			
		||||
	return static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(r), 31))
 | 
			
		||||
	     | static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(g), 31) << 5)
 | 
			
		||||
	     | static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(b), 31) << 10)
 | 
			
		||||
        | static_cast<Color16>(a << 15);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0) noexcept {
 | 
			
		||||
	return static_cast<Color16>(ox::min<uint8_t>(r, 31))
 | 
			
		||||
	     | static_cast<Color16>(ox::min<uint8_t>(g, 31) << 5)
 | 
			
		||||
	     | static_cast<Color16>(ox::min<uint8_t>(b, 31) << 10)
 | 
			
		||||
        | static_cast<Color16>(a << 15);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static_assert(color16(0, 31, 0) == 992);
 | 
			
		||||
static_assert(color16(16, 31, 0) == 1008);
 | 
			
		||||
static_assert(color16(16, 31, 8) == 9200);
 | 
			
		||||
static_assert(color16(16, 32, 8) == 9200);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/nostalgia/modules/core/include/nostalgia/core/consts.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/nostalgia/modules/core/include/nostalgia/core/consts.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/stringliteral.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
constexpr auto TileWidth = 8;
 | 
			
		||||
constexpr auto TileHeight = 8;
 | 
			
		||||
constexpr auto PixelsPerTile = TileWidth * TileHeight;
 | 
			
		||||
 | 
			
		||||
constexpr ox::StringLiteral FileExt_ng("ng");
 | 
			
		||||
constexpr ox::StringLiteral FileExt_npal("npal");
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/fs/fs.hpp>
 | 
			
		||||
#include <ox/model/desctypes.hpp>
 | 
			
		||||
#include <ox/std/buffer.hpp>
 | 
			
		||||
#include <ox/std/size.hpp>
 | 
			
		||||
 | 
			
		||||
#include <turbine/context.hpp>
 | 
			
		||||
 | 
			
		||||
#include "initparams.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class Context;
 | 
			
		||||
 | 
			
		||||
struct ContextDeleter {
 | 
			
		||||
	void operator()(Context *p) noexcept;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using ContextUPtr = ox::UPtr<Context, ContextDeleter>;
 | 
			
		||||
 | 
			
		||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms = {}) noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								src/nostalgia/modules/core/include/nostalgia/core/core.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/nostalgia/modules/core/include/nostalgia/core/core.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "color.hpp"
 | 
			
		||||
#include "context.hpp"
 | 
			
		||||
#include "gfx.hpp"
 | 
			
		||||
#include "initparams.hpp"
 | 
			
		||||
#include "keelmodule.hpp"
 | 
			
		||||
#include "palette.hpp"
 | 
			
		||||
#include "ptidxconv.hpp"
 | 
			
		||||
#include "tilesheet.hpp"
 | 
			
		||||
							
								
								
									
										159
									
								
								src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/array.hpp>
 | 
			
		||||
#include <ox/std/size.hpp>
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
#include <ox/model/def.hpp>
 | 
			
		||||
 | 
			
		||||
#include "context.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
struct Sprite {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Sprite";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	bool enabled = false;
 | 
			
		||||
	int x = 0;
 | 
			
		||||
	int y = 0;
 | 
			
		||||
	unsigned tileIdx = 0;
 | 
			
		||||
	unsigned spriteShape = 0;
 | 
			
		||||
	unsigned spriteSize = 0;
 | 
			
		||||
	unsigned flipX = 0;
 | 
			
		||||
	unsigned bpp = 0;
 | 
			
		||||
	/**
 | 
			
		||||
	 * Valid priorities: 0-3
 | 
			
		||||
	 */
 | 
			
		||||
	unsigned priority = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(Sprite)
 | 
			
		||||
	oxModelField(idx)
 | 
			
		||||
	oxModelField(x)
 | 
			
		||||
	oxModelField(y)
 | 
			
		||||
	oxModelField(enabled)
 | 
			
		||||
	oxModelField(tileIdx)
 | 
			
		||||
	oxModelField(spriteShape)
 | 
			
		||||
	oxModelField(spriteSize)
 | 
			
		||||
	oxModelField(flipX)
 | 
			
		||||
	oxModelField(bpp)
 | 
			
		||||
	oxModelField(priority)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
struct TileSheetSetEntrySection {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSetEntrySection";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	int32_t begin = 0;
 | 
			
		||||
	int32_t tiles = 0;
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr auto end() const noexcept {
 | 
			
		||||
		return begin + tiles - 1;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetSetEntrySection)
 | 
			
		||||
	oxModelField(begin)
 | 
			
		||||
	oxModelField(size)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
struct TileSheetSetEntry {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSetEntry";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	ox::FileAddress tilesheet;
 | 
			
		||||
	ox::Vector<TileSheetSetEntrySection> sections;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetSetEntry)
 | 
			
		||||
	oxModelField(tilesheet)
 | 
			
		||||
	oxModelField(sections)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
struct TileSheetSet {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSet";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	static constexpr auto Preloadable = true;
 | 
			
		||||
	int bpp = 0;
 | 
			
		||||
	ox::Vector<TileSheetSetEntry> entries;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetSet)
 | 
			
		||||
	oxModelField(bpp)
 | 
			
		||||
	oxModelField(entries)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgPalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpritePalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		unsigned cbb,
 | 
			
		||||
		TileSheetSet const&set) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		unsigned cbb,
 | 
			
		||||
		ox::FileAddress const&tilesheetAddr,
 | 
			
		||||
		bool loadDefaultPalette = false) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpriteTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&tilesheetAddr,
 | 
			
		||||
		bool loadDefaultPalette = false) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpriteTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		TileSheetSet const&set) noexcept;
 | 
			
		||||
 | 
			
		||||
void setBgTile(Context &ctx, uint_t bgIdx, int column, int row, uint8_t tile) noexcept;
 | 
			
		||||
 | 
			
		||||
void clearBg(Context &ctx, uint_t bgIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
uint8_t bgStatus(Context &ctx) noexcept;
 | 
			
		||||
 | 
			
		||||
void setBgStatus(Context &ctx, uint32_t status) noexcept;
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
bool bgStatus(Context &ctx, unsigned bg) noexcept;
 | 
			
		||||
 | 
			
		||||
void setBgStatus(Context &ctx, unsigned bg, bool status) noexcept;
 | 
			
		||||
 | 
			
		||||
void setBgCbb(Context &ctx, unsigned bgIdx, unsigned cbb) noexcept;
 | 
			
		||||
 | 
			
		||||
void setBgPriority(Context &ctx, uint_t bgIdx, uint_t priority) noexcept;
 | 
			
		||||
 | 
			
		||||
void hideSprite(Context &ctx, unsigned) noexcept;
 | 
			
		||||
 | 
			
		||||
void showSprite(Context &ctx, unsigned) noexcept;
 | 
			
		||||
 | 
			
		||||
void setSprite(Context &c, uint_t idx, Sprite const&s) noexcept;
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
uint_t spriteCount(Context &ctx) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Error initConsole(Context &ctx) noexcept;
 | 
			
		||||
 | 
			
		||||
void puts(Context &ctx, int column, int row, ox::CRStringView str) noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core::gl {
 | 
			
		||||
 | 
			
		||||
constexpr ox::CStringView GlslVersion = "#version 330";
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
ox::Size drawSize(int scale = 5) noexcept;
 | 
			
		||||
 | 
			
		||||
void draw(core::Context &ctx, ox::Size const&renderSz) noexcept;
 | 
			
		||||
 | 
			
		||||
void draw(core::Context&, int scale = 5) noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
struct InitParams {
 | 
			
		||||
	bool glInstallDrawer = true;
 | 
			
		||||
	uint_t glSpriteCount = 128;
 | 
			
		||||
	uint_t glBlocksPerSprite = 64;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <keel/module.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
const keel::Module *keelModule() noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/array.hpp>
 | 
			
		||||
#include <ox/std/point.hpp>
 | 
			
		||||
#include <ox/std/size.hpp>
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
#include <ox/std/vector.hpp>
 | 
			
		||||
#include <ox/model/def.hpp>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "color.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
struct NostalgiaPalette {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	ox::Vector<Color16> colors = {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Palette {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	ox::Vector<Color16> colors = {};
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr Color16 color(auto idx) const noexcept {
 | 
			
		||||
		if (idx < colors.size()) [[likely]] {
 | 
			
		||||
			return colors[idx];
 | 
			
		||||
		}
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(NostalgiaPalette)
 | 
			
		||||
	oxModelField(colors)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(Palette)
 | 
			
		||||
	oxModelField(colors)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/point.hpp>
 | 
			
		||||
 | 
			
		||||
#include "consts.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr std::size_t ptToIdx(int x, int y, int c, int scale = 1) noexcept {
 | 
			
		||||
	const auto tileWidth = TileWidth * scale;
 | 
			
		||||
	const auto tileHeight = TileHeight * scale;
 | 
			
		||||
	const auto pixelsPerTile = tileWidth * tileHeight;
 | 
			
		||||
	const auto colLength = static_cast<std::size_t>(pixelsPerTile);
 | 
			
		||||
	const auto rowLength = static_cast<std::size_t>(static_cast<std::size_t>(c / tileWidth) * colLength);
 | 
			
		||||
	const auto colStart = static_cast<std::size_t>(colLength * static_cast<std::size_t>(x / tileWidth));
 | 
			
		||||
	const auto rowStart = static_cast<std::size_t>(rowLength * static_cast<std::size_t>(y / tileHeight));
 | 
			
		||||
	const auto colOffset = static_cast<std::size_t>(x % tileWidth);
 | 
			
		||||
	const auto rowOffset = static_cast<std::size_t>((y % tileHeight) * tileHeight);
 | 
			
		||||
	return static_cast<std::size_t>(colStart + colOffset + rowStart + rowOffset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr std::size_t ptToIdx(const ox::Point &pt, int c, int scale = 1) noexcept {
 | 
			
		||||
	return ptToIdx(pt.x, pt.y, c * TileWidth, scale);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr ox::Point idxToPt(int i, int c, int scale = 1) noexcept {
 | 
			
		||||
	const auto tileWidth = TileWidth * scale;
 | 
			
		||||
	const auto tileHeight = TileHeight * scale;
 | 
			
		||||
	const auto pixelsPerTile = tileWidth * tileHeight;
 | 
			
		||||
	// prevent divide by zeros
 | 
			
		||||
	if (!c) {
 | 
			
		||||
		++c;
 | 
			
		||||
	}
 | 
			
		||||
	const auto t = i / pixelsPerTile; // tile number
 | 
			
		||||
	const auto iti = i % pixelsPerTile; // in tile index
 | 
			
		||||
	const auto tc = t % c; // tile column
 | 
			
		||||
	const auto tr = t / c; // tile row
 | 
			
		||||
	const auto itx = iti % tileWidth; // in tile x
 | 
			
		||||
	const auto ity = iti / tileHeight; // in tile y
 | 
			
		||||
	return {
 | 
			
		||||
		itx + tc * tileWidth,
 | 
			
		||||
		ity + tr * tileHeight,
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static_assert(idxToPt(4, 1) == ox::Point{4, 0});
 | 
			
		||||
static_assert(idxToPt(8, 1) == ox::Point{0, 1});
 | 
			
		||||
static_assert(idxToPt(8, 2) == ox::Point{0, 1});
 | 
			
		||||
static_assert(idxToPt(64, 2) == ox::Point{8, 0});
 | 
			
		||||
static_assert(idxToPt(128, 2) == ox::Point{0, 8});
 | 
			
		||||
static_assert(idxToPt(129, 2) == ox::Point{1, 8});
 | 
			
		||||
static_assert(idxToPt(192, 2) == ox::Point{8, 8});
 | 
			
		||||
static_assert(idxToPt(384, 8) == ox::Point{48, 0});
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
const studio::Module *studioModule() noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										336
									
								
								src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,336 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/fs/fs.hpp>
 | 
			
		||||
#include <ox/std/array.hpp>
 | 
			
		||||
#include <ox/std/point.hpp>
 | 
			
		||||
#include <ox/std/size.hpp>
 | 
			
		||||
#include <ox/std/span.hpp>
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
#include <ox/model/def.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/ptidxconv.hpp>
 | 
			
		||||
 | 
			
		||||
#include "palette.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
// Predecessor to TileSheet, kept for backward compatibility
 | 
			
		||||
struct TileSheetV1 {
 | 
			
		||||
	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;
 | 
			
		||||
	Palette pal;
 | 
			
		||||
	ox::Vector<uint8_t> pixels = {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct TileSheetV2 {
 | 
			
		||||
	using SubSheetIdx = ox::Vector<std::size_t, 4>;
 | 
			
		||||
 | 
			
		||||
	struct SubSheet {
 | 
			
		||||
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet";
 | 
			
		||||
		static constexpr auto TypeVersion = 1;
 | 
			
		||||
		ox::String name;
 | 
			
		||||
		int columns = 0;
 | 
			
		||||
		int rows = 0;
 | 
			
		||||
		ox::Vector<SubSheet> subsheets;
 | 
			
		||||
		ox::Vector<uint8_t> pixels;
 | 
			
		||||
		constexpr SubSheet() noexcept = default;
 | 
			
		||||
		constexpr SubSheet(ox::CRStringView pName, int pColumns, int pRows, int bpp) noexcept:
 | 
			
		||||
			name(pName),
 | 
			
		||||
			columns(pColumns),
 | 
			
		||||
			rows(pRows),
 | 
			
		||||
			pixels(static_cast<size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
 | 
			
		||||
	static constexpr auto TypeVersion = 2;
 | 
			
		||||
	int8_t bpp = 4;
 | 
			
		||||
	ox::FileAddress defaultPalette;
 | 
			
		||||
	SubSheet subsheet{"Root", 1, 1, bpp};
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using SubSheetId = int32_t;
 | 
			
		||||
 | 
			
		||||
struct TileSheet {
 | 
			
		||||
	using SubSheetIdx = ox::Vector<std::size_t, 4>;
 | 
			
		||||
 | 
			
		||||
	struct SubSheet {
 | 
			
		||||
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet";
 | 
			
		||||
		static constexpr auto TypeVersion = 3;
 | 
			
		||||
		SubSheetId id = 0;
 | 
			
		||||
		ox::String name;
 | 
			
		||||
		int columns = 0;
 | 
			
		||||
		int rows = 0;
 | 
			
		||||
		ox::Vector<SubSheet> subsheets;
 | 
			
		||||
		ox::Vector<uint8_t> pixels;
 | 
			
		||||
 | 
			
		||||
		constexpr SubSheet() noexcept = default;
 | 
			
		||||
		constexpr SubSheet(SubSheet const&other) noexcept = default;
 | 
			
		||||
		SubSheet(SubSheet &&other) noexcept;
 | 
			
		||||
		SubSheet(
 | 
			
		||||
				SubSheetId pId,
 | 
			
		||||
				ox::CRStringView pName,
 | 
			
		||||
				int pColumns,
 | 
			
		||||
				int pRows,
 | 
			
		||||
				int bpp) noexcept;
 | 
			
		||||
		SubSheet(
 | 
			
		||||
				SubSheetId pId,
 | 
			
		||||
				ox::CRStringView pName,
 | 
			
		||||
				int pColumns,
 | 
			
		||||
				int pRows,
 | 
			
		||||
				ox::Vector<uint8_t> pPixels) noexcept;
 | 
			
		||||
 | 
			
		||||
		constexpr SubSheet &operator=(const SubSheet &other) noexcept = default;
 | 
			
		||||
 | 
			
		||||
		SubSheet &operator=(SubSheet &&other) noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		std::size_t idx(ox::Point const&pt) const noexcept;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Reads all pixels of this sheet or its children into the given pixel list
 | 
			
		||||
		 * @param pixels
 | 
			
		||||
		 */
 | 
			
		||||
		void readPixelsTo(ox::Vector<uint8_t> *pPixels, int8_t pBpp) const noexcept;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Reads all pixels of this sheet or its children into the given pixel list
 | 
			
		||||
		 * @param pixels
 | 
			
		||||
		 */
 | 
			
		||||
		void readPixelsTo(ox::Vector<uint8_t> *pPixels) const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr std::size_t size() const noexcept {
 | 
			
		||||
			return static_cast<std::size_t>(columns) * static_cast<std::size_t>(rows);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		std::size_t unusedPixels() const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		uint8_t getPixel4Bpp(std::size_t idx) const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		uint8_t getPixel8Bpp(std::size_t idx) const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		uint8_t getPixel(int8_t pBpp, std::size_t idx) const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		uint8_t getPixel4Bpp(const ox::Point &pt) const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		uint8_t getPixel8Bpp(const ox::Point &pt) const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		uint8_t getPixel(int8_t pBpp, const ox::Point &pt) const noexcept;
 | 
			
		||||
 | 
			
		||||
		constexpr auto walkPixels(int8_t pBpp, auto callback) const noexcept {
 | 
			
		||||
			if (pBpp == 4) {
 | 
			
		||||
				const auto pixelCnt = ox::min<std::size_t>(static_cast<std::size_t>(columns * rows * PixelsPerTile) / 2,
 | 
			
		||||
				                                           pixels.size());
 | 
			
		||||
				//oxAssert(pixels.size() == pixelCnt, "Pixel count does not match rows and columns");
 | 
			
		||||
				for (std::size_t i = 0; i < pixelCnt; ++i) {
 | 
			
		||||
					const auto colorIdx1 = static_cast<uint8_t>(pixels[i] & 0xF);
 | 
			
		||||
					const auto colorIdx2 = static_cast<uint8_t>(pixels[i] >> 4);
 | 
			
		||||
					callback(i * 2 + 0, colorIdx1);
 | 
			
		||||
					callback(i * 2 + 1, colorIdx2);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				const auto pixelCnt = ox::min<std::size_t>(
 | 
			
		||||
						static_cast<std::size_t>(columns * rows * PixelsPerTile),
 | 
			
		||||
						pixels.size());
 | 
			
		||||
				for (std::size_t i = 0; i < pixelCnt; ++i) {
 | 
			
		||||
					const auto p = pixels[i];
 | 
			
		||||
					callback(i, p);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void setPixel(int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error setPixelCount(int8_t pBpp, std::size_t cnt) noexcept;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Gets a count of the pixels in this sheet, and not that of its children.
 | 
			
		||||
		 * @param pBpp bits per pixel, need for knowing how to count the pixels
 | 
			
		||||
		 * @return a count of the pixels in this sheet
 | 
			
		||||
		 */
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		unsigned pixelCnt(int8_t pBpp) const noexcept;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Gets the offset in tiles of the desired subsheet.
 | 
			
		||||
		 */
 | 
			
		||||
		ox::Result<unsigned> getTileOffset(
 | 
			
		||||
				ox::SpanView<ox::StringView> const&pNamePath,
 | 
			
		||||
				int8_t pBpp,
 | 
			
		||||
				std::size_t pIt = 0,
 | 
			
		||||
				unsigned pCurrentTotal = 0) const noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Result<SubSheetId> getIdFor(
 | 
			
		||||
				ox::SpanView<ox::StringView> const&pNamePath,
 | 
			
		||||
				std::size_t pIt = 0) const noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Result<ox::StringView> getNameFor(SubSheetId pId) const noexcept;
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
 | 
			
		||||
	static constexpr auto TypeVersion = 3;
 | 
			
		||||
	int8_t bpp = 4;
 | 
			
		||||
	SubSheetId idIt = 0;
 | 
			
		||||
	ox::FileAddress defaultPalette;
 | 
			
		||||
	SubSheet subsheet{0, "Root", 1, 1, bpp};
 | 
			
		||||
 | 
			
		||||
	constexpr TileSheet() noexcept = default;
 | 
			
		||||
	TileSheet(TileSheet const&other) noexcept = default;
 | 
			
		||||
	inline TileSheet(TileSheet &&other) noexcept:
 | 
			
		||||
		bpp(other.bpp),
 | 
			
		||||
		idIt(other.idIt),
 | 
			
		||||
		defaultPalette(std::move(other.defaultPalette)),
 | 
			
		||||
		subsheet(std::move(other.subsheet)) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	TileSheet &operator=(TileSheet const&other) noexcept;
 | 
			
		||||
 | 
			
		||||
	TileSheet &operator=(TileSheet &&other) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	SubSheetIdx validateSubSheetIdx(
 | 
			
		||||
			SubSheetIdx const&pIdx,
 | 
			
		||||
			std::size_t pIdxIt,
 | 
			
		||||
			const SubSheet *pSubsheet) noexcept;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * validateSubSheetIdx takes a SubSheetIdx and moves the index to the
 | 
			
		||||
	 * preceding or parent sheet if the current corresponding sheet does
 | 
			
		||||
	 * not exist.
 | 
			
		||||
	 * @param idx SubSheetIdx to validate and correct
 | 
			
		||||
	 * @return a valid version of idx
 | 
			
		||||
	 */
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	SubSheetIdx validateSubSheetIdx(const SubSheetIdx &idx) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	static SubSheet const&getSubSheet(
 | 
			
		||||
			SubSheetIdx const&idx,
 | 
			
		||||
			std::size_t idxIt,
 | 
			
		||||
			const SubSheet *pSubsheet) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	static SubSheet &getSubSheet(
 | 
			
		||||
			SubSheetIdx const&idx,
 | 
			
		||||
			std::size_t idxIt,
 | 
			
		||||
			SubSheet *pSubsheet) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	const SubSheet &getSubSheet(SubSheetIdx const&idx) const noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	SubSheet &getSubSheet(SubSheetIdx const&idx) noexcept;
 | 
			
		||||
 | 
			
		||||
	ox::Error addSubSheet(SubSheetIdx const&idx) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	static ox::Error rmSubSheet(
 | 
			
		||||
			SubSheetIdx const&idx,
 | 
			
		||||
			std::size_t idxIt,
 | 
			
		||||
			SubSheet *pSubsheet) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	ox::Error rmSubSheet(SubSheetIdx const&idx) noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	uint8_t getPixel4Bpp(
 | 
			
		||||
			ox::Point const&pt,
 | 
			
		||||
			SubSheetIdx const&subsheetIdx) const noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	uint8_t getPixel8Bpp(
 | 
			
		||||
			ox::Point const&pt,
 | 
			
		||||
			SubSheetIdx const&subsheetIdx) const noexcept;
 | 
			
		||||
 | 
			
		||||
	ox::Result<SubSheetId> getIdFor(ox::CRStringView path) const noexcept;
 | 
			
		||||
 | 
			
		||||
	ox::Result<unsigned> getTileOffset(ox::CRStringView pNamePath) const noexcept;
 | 
			
		||||
 | 
			
		||||
	ox::Result<ox::StringView> getNameFor(SubSheetId pId) const noexcept;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	ox::Vector<uint8_t> pixels() const noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using TileSheetV3 = TileSheet;
 | 
			
		||||
 | 
			
		||||
struct CompactTileSheet {
 | 
			
		||||
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactTileSheet";
 | 
			
		||||
	static constexpr auto TypeVersion = 1;
 | 
			
		||||
	int8_t bpp = 0;
 | 
			
		||||
	ox::FileAddress defaultPalette;
 | 
			
		||||
	ox::Vector<uint8_t> pixels = {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetV1)
 | 
			
		||||
	oxModelField(bpp)
 | 
			
		||||
	oxModelField(rows)
 | 
			
		||||
	oxModelField(columns)
 | 
			
		||||
	oxModelField(defaultPalette)
 | 
			
		||||
	oxModelField(pal)
 | 
			
		||||
	oxModelField(pixels)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetV2::SubSheet)
 | 
			
		||||
	oxModelField(name);
 | 
			
		||||
	oxModelField(rows);
 | 
			
		||||
	oxModelField(columns);
 | 
			
		||||
	oxModelField(subsheets)
 | 
			
		||||
	oxModelField(pixels)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetV2)
 | 
			
		||||
	oxModelField(bpp)
 | 
			
		||||
	oxModelField(defaultPalette)
 | 
			
		||||
	oxModelField(subsheet)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetV3::SubSheet)
 | 
			
		||||
	oxModelField(name);
 | 
			
		||||
	oxModelField(rows);
 | 
			
		||||
	oxModelField(columns);
 | 
			
		||||
	oxModelField(subsheets)
 | 
			
		||||
	oxModelField(pixels)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetV3)
 | 
			
		||||
	oxModelField(bpp)
 | 
			
		||||
	oxModelField(idIt)
 | 
			
		||||
	oxModelField(defaultPalette)
 | 
			
		||||
	oxModelField(subsheet)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(CompactTileSheet)
 | 
			
		||||
	oxModelField(bpp)
 | 
			
		||||
	oxModelField(defaultPalette)
 | 
			
		||||
	oxModelField(pixels)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
ox::Vector<uint32_t> resizeTileSheetData(
 | 
			
		||||
		ox::Vector<uint32_t> const&srcPixels,
 | 
			
		||||
		ox::Size const&srcSize,
 | 
			
		||||
		int scale = 2) noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/nostalgia/modules/core/src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/nostalgia/modules/core/src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaCore
 | 
			
		||||
		gfx.cpp
 | 
			
		||||
		tilesheet.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(gba)
 | 
			
		||||
if(NOT BUILDCORE_TARGET STREQUAL "gba")
 | 
			
		||||
	add_subdirectory(opengl)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_include_directories(
 | 
			
		||||
	NostalgiaCore PUBLIC
 | 
			
		||||
		../include
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCore PUBLIC
 | 
			
		||||
		Turbine
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(keel)
 | 
			
		||||
if(NOSTALGIA_BUILD_STUDIO)
 | 
			
		||||
	add_subdirectory(studio)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		NostalgiaCore
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		LIBRARY DESTINATION lib
 | 
			
		||||
		ARCHIVE DESTINATION lib
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										21
									
								
								src/nostalgia/modules/core/src/gba/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/nostalgia/modules/core/src/gba/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaCore-GBA OBJECT
 | 
			
		||||
		context.cpp
 | 
			
		||||
		gfx.cpp
 | 
			
		||||
		panic.cpp
 | 
			
		||||
)
 | 
			
		||||
target_include_directories(
 | 
			
		||||
	NostalgiaCore-GBA PUBLIC
 | 
			
		||||
		../../include
 | 
			
		||||
)
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCore-GBA PUBLIC
 | 
			
		||||
		TeaGBA
 | 
			
		||||
		Keel
 | 
			
		||||
		Turbine
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if(BUILDCORE_TARGET STREQUAL "gba")
 | 
			
		||||
	set_source_files_properties(gfx.cpp PROPERTIES COMPILE_FLAGS -marm)
 | 
			
		||||
	target_link_libraries(NostalgiaCore PUBLIC NostalgiaCore-GBA)
 | 
			
		||||
endif()
 | 
			
		||||
							
								
								
									
										26
									
								
								src/nostalgia/modules/core/src/gba/context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/nostalgia/modules/core/src/gba/context.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
#include "context.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
void ContextDeleter::operator()(Context *p) noexcept {
 | 
			
		||||
	ox::safeDelete(p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Context::Context(turbine::Context &tctx) noexcept: turbineCtx(tctx) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
 | 
			
		||||
 | 
			
		||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms) noexcept {
 | 
			
		||||
	auto ctx = ox::make_unique<Context>(tctx);
 | 
			
		||||
	oxReturnError(initGfx(*ctx, params));
 | 
			
		||||
	return ContextUPtr(std::move(ctx));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/nostalgia/modules/core/src/gba/context.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/nostalgia/modules/core/src/gba/context.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
struct BgCbbData {
 | 
			
		||||
	unsigned bpp = 4;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Context {
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		turbine::Context &turbineCtx;
 | 
			
		||||
		ox::Array<BgCbbData, 4> cbbData;
 | 
			
		||||
 | 
			
		||||
		explicit Context(turbine::Context &tctx) noexcept;
 | 
			
		||||
		Context(Context &other) noexcept = delete;
 | 
			
		||||
		Context(Context const&other) noexcept = delete;
 | 
			
		||||
		Context(Context const&&other) noexcept = delete;
 | 
			
		||||
		virtual ~Context() noexcept = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::MemFS const&rom() const noexcept {
 | 
			
		||||
			return static_cast<ox::MemFS const&>(*turbine::rom(turbineCtx));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										367
									
								
								src/nostalgia/modules/core/src/gba/gfx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										367
									
								
								src/nostalgia/modules/core/src/gba/gfx.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,367 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/fs/fs.hpp>
 | 
			
		||||
#include <ox/mc/mc.hpp>
 | 
			
		||||
#include <ox/std/array.hpp>
 | 
			
		||||
 | 
			
		||||
#include <teagba/addresses.hpp>
 | 
			
		||||
#include <teagba/gfx.hpp>
 | 
			
		||||
#include <teagba/registers.hpp>
 | 
			
		||||
 | 
			
		||||
#include <turbine/turbine.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/color.hpp>
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
#include "context.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
constexpr auto GbaTileColumns = 32;
 | 
			
		||||
constexpr auto GbaTileRows = 32;
 | 
			
		||||
constexpr auto SpriteCount = 128;
 | 
			
		||||
 | 
			
		||||
struct GbaPaletteTarget {
 | 
			
		||||
	static constexpr auto TypeName = Palette::TypeName;
 | 
			
		||||
	static constexpr auto TypeVersion = Palette::TypeVersion;
 | 
			
		||||
	volatile uint16_t *palette = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct GbaTileMapTarget {
 | 
			
		||||
	static constexpr auto TypeName = CompactTileSheet::TypeName;
 | 
			
		||||
	static constexpr auto TypeVersion = CompactTileSheet::TypeVersion;
 | 
			
		||||
	unsigned &bpp;
 | 
			
		||||
	ox::FileAddress defaultPalette;
 | 
			
		||||
	volatile uint16_t *tileMap = nullptr;
 | 
			
		||||
	// the following values are not actually in CompactTileSheet,
 | 
			
		||||
	// and only exist to communicate with the loading process
 | 
			
		||||
	size_t tileWriteIdx = 0;
 | 
			
		||||
	unsigned targetBpp = 0;
 | 
			
		||||
	TileSheetSetEntry const*setEntry = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaPaletteTarget> auto *t) noexcept {
 | 
			
		||||
	oxReturnError(io->template setTypeInfo<Palette>());
 | 
			
		||||
	if (t->palette) {
 | 
			
		||||
		const auto colorHandler = [t](std::size_t i, const Color16 *c) {
 | 
			
		||||
			t->palette[i] = *c;
 | 
			
		||||
			return ox::Error{};
 | 
			
		||||
		};
 | 
			
		||||
		return io->template field<Color16, decltype(colorHandler)>("colors", colorHandler);
 | 
			
		||||
	} else {
 | 
			
		||||
		constexpr auto colorHandler = [](std::size_t, const Color16*) {
 | 
			
		||||
			return ox::Error{};
 | 
			
		||||
		};
 | 
			
		||||
		return io->template field<Color16, decltype(colorHandler)>("colors", colorHandler);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
static bool loadPixel(TileSheetSetEntry const&setEntry, size_t §ionIdx, int tileIdx) noexcept {
 | 
			
		||||
	if (setEntry.sections.size() <= sectionIdx) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	auto §ion = setEntry.sections[sectionIdx];
 | 
			
		||||
	if (tileIdx < section.begin) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	if (tileIdx > section.end()) {
 | 
			
		||||
		if (sectionIdx >= setEntry.sections.size()) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		++sectionIdx;
 | 
			
		||||
		return tileIdx > section.begin && tileIdx <= section.end();
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t) noexcept {
 | 
			
		||||
	oxReturnError(io->template setTypeInfo<CompactTileSheet>());
 | 
			
		||||
	oxReturnError(io->field("bpp", &t->bpp));
 | 
			
		||||
	oxReturnError(io->field("defaultPalette", &t->defaultPalette));
 | 
			
		||||
	if (t->targetBpp == 0) {
 | 
			
		||||
		t->targetBpp = t->bpp;
 | 
			
		||||
	}
 | 
			
		||||
	if (t->targetBpp != t->bpp && t->bpp == 8) {
 | 
			
		||||
		return OxError(1, "Cannot load an 8 BPP tilesheet into a 4 BPP CBB");
 | 
			
		||||
	}
 | 
			
		||||
	ox::Error out{};
 | 
			
		||||
	if (t->setEntry) {
 | 
			
		||||
		// The following code is atrocious, but it works.
 | 
			
		||||
		// It might be possible to clean it up a little, but it probably
 | 
			
		||||
		// cannot be seriously optimized without preloading TileSheets.
 | 
			
		||||
		size_t sectionIdx = 0;
 | 
			
		||||
		if (t->targetBpp == t->bpp) {
 | 
			
		||||
			uint16_t intermediate = 0;
 | 
			
		||||
			size_t const fourBpp = t->bpp == 4;
 | 
			
		||||
			const auto handleTileMap = [t, &intermediate, §ionIdx, fourBpp]
 | 
			
		||||
					(std::size_t i, uint8_t const*tile) {
 | 
			
		||||
				auto const tileIdx = static_cast<int>((i * (2 * fourBpp)) / PixelsPerTile);
 | 
			
		||||
				if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) {
 | 
			
		||||
					return ox::Error{};
 | 
			
		||||
				}
 | 
			
		||||
				if (i & 1) { // i is odd
 | 
			
		||||
					intermediate |= static_cast<uint16_t>(*tile) << 8;
 | 
			
		||||
					t->tileMap[t->tileWriteIdx] = intermediate;
 | 
			
		||||
					++t->tileWriteIdx;
 | 
			
		||||
				} else { // i is even
 | 
			
		||||
					intermediate = *tile & 0x00ff;
 | 
			
		||||
				}
 | 
			
		||||
				return ox::Error{};
 | 
			
		||||
			};
 | 
			
		||||
			out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
 | 
			
		||||
		} else if (t->targetBpp > t->bpp) { // 4 -> 8 bits
 | 
			
		||||
			const auto handleTileMap = [t, §ionIdx](std::size_t i, uint8_t const*tile) {
 | 
			
		||||
				auto constexpr BytesPerTile4Bpp = 32;
 | 
			
		||||
				auto const tileIdx = static_cast<int>(i / BytesPerTile4Bpp);
 | 
			
		||||
				if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) {
 | 
			
		||||
					return ox::Error{};
 | 
			
		||||
				}
 | 
			
		||||
				uint16_t const px1 = *tile & 0xf;
 | 
			
		||||
				uint16_t const px2 = *tile >> 4;
 | 
			
		||||
				t->tileMap[t->tileWriteIdx] = static_cast<uint16_t>(px1 | (px2 << 8));
 | 
			
		||||
				++t->tileWriteIdx;
 | 
			
		||||
				return ox::Error{};
 | 
			
		||||
			};
 | 
			
		||||
			out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		uint16_t intermediate = 0;
 | 
			
		||||
		const auto handleTileMap = [t, &intermediate](std::size_t i, const uint8_t*tile) {
 | 
			
		||||
			if (i & 1) { // i is odd
 | 
			
		||||
				intermediate |= static_cast<uint16_t>(*tile) << 8;
 | 
			
		||||
				t->tileMap[i / 2] = intermediate;
 | 
			
		||||
			} else { // i is even
 | 
			
		||||
				intermediate = *tile & 0x00ff;
 | 
			
		||||
			}
 | 
			
		||||
			return ox::Error{};
 | 
			
		||||
		};
 | 
			
		||||
		out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error initGfx(Context&, InitParams const&) noexcept {
 | 
			
		||||
	for (auto bgCtl = ®_BG0CTL; bgCtl <= ®_BG3CTL; bgCtl += 2) {
 | 
			
		||||
		teagba::bgSetSbb(*bgCtl, 28);
 | 
			
		||||
	}
 | 
			
		||||
	for (uint16_t i = 0; i < SpriteCount; ++i) {
 | 
			
		||||
		auto &sa = teagba::spriteAttr(i);
 | 
			
		||||
		sa.idx = i;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgPalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	auto &rom = ctx.rom();
 | 
			
		||||
	GbaPaletteTarget const palTarget{.palette = MEM_BG_PALETTE};
 | 
			
		||||
	oxRequire(palStat, rom.stat(paletteAddr));
 | 
			
		||||
	oxRequire(pal, rom.directAccess(paletteAddr));
 | 
			
		||||
	oxReturnError(ox::readMC(pal, static_cast<std::size_t>(palStat.size), &palTarget));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpritePalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	auto &rom = ctx.rom();
 | 
			
		||||
	GbaPaletteTarget const palTarget{.palette = MEM_SPRITE_PALETTE};
 | 
			
		||||
	oxRequire(palStat, rom.stat(paletteAddr));
 | 
			
		||||
	oxRequire(pal, rom.directAccess(paletteAddr));
 | 
			
		||||
	oxReturnError(ox::readMC(pal, static_cast<std::size_t>(palStat.size), &palTarget));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ox::Error loadTileSheetSet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		uint16_t *tileMapTargetMem,
 | 
			
		||||
		TileSheetSet const&set) noexcept {
 | 
			
		||||
	auto &rom = ctx.rom();
 | 
			
		||||
	size_t tileWriteIdx = 0;
 | 
			
		||||
	for (auto const&entry : set.entries) {
 | 
			
		||||
		oxRequire(tsStat, rom.stat(entry.tilesheet));
 | 
			
		||||
		oxRequire(ts, rom.directAccess(entry.tilesheet));
 | 
			
		||||
		unsigned tilesheetBpp{};
 | 
			
		||||
		GbaTileMapTarget target{
 | 
			
		||||
				.bpp = tilesheetBpp,
 | 
			
		||||
				.defaultPalette = {},
 | 
			
		||||
				.tileMap = tileMapTargetMem + tileWriteIdx,
 | 
			
		||||
				.targetBpp = static_cast<unsigned>(set.bpp),
 | 
			
		||||
				.setEntry = &entry,
 | 
			
		||||
		};
 | 
			
		||||
		oxReturnError(ox::readMC(ts, static_cast<std::size_t>(tsStat.size), &target));
 | 
			
		||||
		tileWriteIdx += target.tileWriteIdx;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		unsigned cbb,
 | 
			
		||||
		ox::FileAddress const&tilesheetAddr,
 | 
			
		||||
		bool loadDefaultPalette) noexcept {
 | 
			
		||||
	auto &rom = ctx.rom();
 | 
			
		||||
	oxRequire(tsStat, rom.stat(tilesheetAddr));
 | 
			
		||||
	oxRequire(ts, rom.directAccess(tilesheetAddr));
 | 
			
		||||
	GbaTileMapTarget target{
 | 
			
		||||
		.bpp = ctx.cbbData[cbb].bpp,
 | 
			
		||||
		.defaultPalette = {},
 | 
			
		||||
		.tileMap = MEM_BG_TILES[cbb].data(),
 | 
			
		||||
	};
 | 
			
		||||
	oxReturnError(ox::readMC(ts, static_cast<std::size_t>(tsStat.size), &target));
 | 
			
		||||
	// update bpp of all bgs with the updated cbb
 | 
			
		||||
	const auto bpp = ctx.cbbData[cbb].bpp;
 | 
			
		||||
	teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) {
 | 
			
		||||
		if (teagba::bgCbb(bgCtl) == cbb) {
 | 
			
		||||
			teagba::bgSetBpp(bgCtl, bpp);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	if (loadDefaultPalette && target.defaultPalette) {
 | 
			
		||||
		oxReturnError(loadBgPalette(ctx, target.defaultPalette));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		unsigned cbb,
 | 
			
		||||
		TileSheetSet const&set) noexcept {
 | 
			
		||||
	auto const bpp = static_cast<unsigned>(set.bpp);
 | 
			
		||||
	oxReturnError(loadTileSheetSet(ctx, MEM_BG_TILES[cbb].data(), set));
 | 
			
		||||
	// update bpp of all bgs with the updated cbb
 | 
			
		||||
	ctx.cbbData[cbb].bpp = bpp;
 | 
			
		||||
	teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) {
 | 
			
		||||
		if (teagba::bgCbb(bgCtl) == cbb) {
 | 
			
		||||
			teagba::bgSetBpp(bgCtl, bpp);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void setSpritesBpp(unsigned const bpp) noexcept {
 | 
			
		||||
	auto const eightBpp = static_cast<uint16_t >(bpp == 8);
 | 
			
		||||
	for (auto i = 0u; i < SpriteCount; ++i) {
 | 
			
		||||
		auto &sa = teagba::spriteAttr(i);
 | 
			
		||||
		auto &a = sa.attr0;
 | 
			
		||||
		a |= static_cast<uint16_t>((a & ~static_cast<uint16_t>(1u << 13)) | (eightBpp << 13));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpriteTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&tilesheetAddr,
 | 
			
		||||
		bool loadDefaultPalette) noexcept {
 | 
			
		||||
	auto &rom = ctx.rom();
 | 
			
		||||
	oxRequire(tsStat, rom.stat(tilesheetAddr));
 | 
			
		||||
	oxRequire(ts, rom.directAccess(tilesheetAddr));
 | 
			
		||||
	unsigned bpp{};
 | 
			
		||||
	GbaTileMapTarget target{
 | 
			
		||||
		.bpp = bpp,
 | 
			
		||||
		.defaultPalette = {},
 | 
			
		||||
		.tileMap = MEM_SPRITE_TILES,
 | 
			
		||||
	};
 | 
			
		||||
	oxReturnError(ox::readMC(ts, static_cast<std::size_t>(tsStat.size), &target));
 | 
			
		||||
	if (loadDefaultPalette && target.defaultPalette) {
 | 
			
		||||
		oxReturnError(loadSpritePalette(ctx, target.defaultPalette));
 | 
			
		||||
	}
 | 
			
		||||
	setSpritesBpp(bpp);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpriteTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		TileSheetSet const&set) noexcept {
 | 
			
		||||
	auto const bpp = static_cast<unsigned>(set.bpp);
 | 
			
		||||
	oxReturnError(loadTileSheetSet(ctx, MEM_SPRITE_TILES, set));
 | 
			
		||||
	setSpritesBpp(bpp);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgTile(Context&, uint_t bgIdx, int column, int row, uint8_t tile) noexcept {
 | 
			
		||||
	const auto tileIdx = static_cast<std::size_t>(row * GbaTileColumns + column);
 | 
			
		||||
	MEM_BG_MAP[bgIdx][tileIdx] = tile;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void clearBg(Context&, uint_t bgIdx) noexcept {
 | 
			
		||||
	memset(MEM_BG_MAP[bgIdx].data(), 0, GbaTileRows * GbaTileColumns);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t bgStatus(Context&) noexcept {
 | 
			
		||||
	return (REG_DISPCTL >> 8u) & 0b1111u;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgStatus(Context&, uint32_t status) noexcept {
 | 
			
		||||
	constexpr auto BgStatus = 8;
 | 
			
		||||
	REG_DISPCTL = (REG_DISPCTL & ~0b111100000000u) | status << BgStatus;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool bgStatus(Context&, unsigned bg) noexcept {
 | 
			
		||||
	return (REG_DISPCTL >> (8 + bg)) & 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgStatus(Context&, unsigned bg, bool status) noexcept {
 | 
			
		||||
	constexpr auto Bg0Status = 8;
 | 
			
		||||
	const auto mask = static_cast<uint32_t>(status) << (Bg0Status + bg);
 | 
			
		||||
	REG_DISPCTL = REG_DISPCTL | ((REG_DISPCTL & ~mask) | mask);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgCbb(Context &ctx, unsigned bgIdx, unsigned cbb) noexcept {
 | 
			
		||||
	auto &bgCtl = regBgCtl(bgIdx);
 | 
			
		||||
	const auto &cbbData = ctx.cbbData[cbb];
 | 
			
		||||
	teagba::bgSetBpp(bgCtl, cbbData.bpp);
 | 
			
		||||
	teagba::bgSetCbb(bgCtl, cbb);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgPriority(Context&, uint_t bgIdx, uint_t priority) noexcept {
 | 
			
		||||
	auto &bgCtl = regBgCtl(bgIdx);
 | 
			
		||||
	bgCtl = (bgCtl & 0b1111'1111'1111'1100u) | (priority & 0b11);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void hideSprite(Context&, unsigned idx) noexcept {
 | 
			
		||||
	//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
 | 
			
		||||
	teagba::addSpriteUpdate({
 | 
			
		||||
		.attr0 = uint16_t{0b11 << 8},
 | 
			
		||||
		.idx = static_cast<uint16_t>(idx),
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void showSprite(Context&, unsigned idx) noexcept {
 | 
			
		||||
	//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
 | 
			
		||||
	teagba::addSpriteUpdate({
 | 
			
		||||
		.attr0 = 0,
 | 
			
		||||
		.idx = static_cast<uint16_t>(idx),
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setSprite(Context&, uint_t idx, Sprite const&s) noexcept {
 | 
			
		||||
	//oxAssert(g_spriteUpdates < config::GbaSpriteBufferLen, "Sprite update buffer overflow");
 | 
			
		||||
	uint16_t const eightBpp = s.bpp == 8;
 | 
			
		||||
	teagba::addSpriteUpdate({
 | 
			
		||||
		.attr0 = static_cast<uint16_t>(
 | 
			
		||||
			  (static_cast<uint16_t>(s.y & ox::onMask<uint8_t>(0b111'1111)))
 | 
			
		||||
			| (static_cast<uint16_t>(1) << 10) // enable alpha
 | 
			
		||||
			| (static_cast<uint16_t>(eightBpp) << 13)
 | 
			
		||||
			| (static_cast<uint16_t>(s.spriteShape) << 14)),
 | 
			
		||||
		.attr1 = static_cast<uint16_t>(
 | 
			
		||||
			  (static_cast<uint16_t>(s.x) & ox::onMask<uint8_t>(8))
 | 
			
		||||
			| (static_cast<uint16_t>(s.flipX) << 12)
 | 
			
		||||
			| (static_cast<uint16_t>(s.spriteSize) << 14)),
 | 
			
		||||
		.attr2 = static_cast<uint16_t>(
 | 
			
		||||
			// double tileIdx if 8 bpp
 | 
			
		||||
			  (static_cast<uint16_t>((s.tileIdx * (1 + eightBpp)) & ox::onMask<uint16_t>(8)))
 | 
			
		||||
			| (static_cast<uint16_t>(s.priority & 0b11) << 10)),
 | 
			
		||||
		.idx = static_cast<uint16_t>(idx),
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint_t spriteCount(Context&) noexcept {
 | 
			
		||||
	return SpriteCount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/nostalgia/modules/core/src/gba/gfx.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/nostalgia/modules/core/src/gba/gfx.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								src/nostalgia/modules/core/src/gba/panic.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/nostalgia/modules/core/src/gba/panic.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <ox/std/def.hpp>
 | 
			
		||||
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
#include <turbine/turbine.hpp>
 | 
			
		||||
 | 
			
		||||
#include <teagba/addresses.hpp>
 | 
			
		||||
#include <teagba/bios.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/core.hpp>
 | 
			
		||||
 | 
			
		||||
#include "gfx.hpp"
 | 
			
		||||
 | 
			
		||||
#define HEAP_BEGIN (reinterpret_cast<char*>(MEM_EWRAM_BEGIN))
 | 
			
		||||
#define HEAP_SIZE ((MEM_EWRAM_END - MEM_EWRAM_BEGIN) / 2)
 | 
			
		||||
#define HEAP_END  (reinterpret_cast<char*>(MEM_EWRAM_BEGIN + HEAP_SIZE))
 | 
			
		||||
 | 
			
		||||
namespace ox {
 | 
			
		||||
 | 
			
		||||
using namespace nostalgia::core;
 | 
			
		||||
 | 
			
		||||
void panic(const char *file, int line, const char *panicMsg, ox::Error const&err) noexcept {
 | 
			
		||||
	// reset heap to make sure we have enough memory to allocate context data
 | 
			
		||||
	ox::heapmgr::initHeap(HEAP_BEGIN, HEAP_END);
 | 
			
		||||
	auto tctx = turbine::init(keel::loadRomFs("").unwrap(), "Nostalgia").unwrap();
 | 
			
		||||
	auto ctx = init(*tctx).unwrap();
 | 
			
		||||
	oxIgnoreError(initGfx(*ctx, {}));
 | 
			
		||||
	oxIgnoreError(initConsole(*ctx));
 | 
			
		||||
	setBgStatus(*ctx, 0, true);
 | 
			
		||||
	clearBg(*ctx, 0);
 | 
			
		||||
	ox::BString<23> serr = "Error code: ";
 | 
			
		||||
	serr += static_cast<int64_t>(err);
 | 
			
		||||
	puts(*ctx, 32 + 1,  1, "SADNESS...");
 | 
			
		||||
	puts(*ctx, 32 + 1,  4, "UNEXPECTED STATE:");
 | 
			
		||||
	puts(*ctx, 32 + 2,  6, panicMsg);
 | 
			
		||||
	if (err) {
 | 
			
		||||
		puts(*ctx, 32 + 2,  8, serr);
 | 
			
		||||
	}
 | 
			
		||||
	puts(*ctx, 32 + 1, 15, "PLEASE RESTART THE SYSTEM");
 | 
			
		||||
	// print to terminal if in mGBA
 | 
			
		||||
	oxErrf("\033[31;1;1mPANIC:\033[0m [{}:{}]: {}\n", file, line, panicMsg);
 | 
			
		||||
	if (err.msg) {
 | 
			
		||||
		oxErrf("\tError Message:\t{}\n", err.msg);
 | 
			
		||||
	}
 | 
			
		||||
	oxErrf("\tError Code:\t{}\n", static_cast<ErrorCode>(err));
 | 
			
		||||
	if (err.file != nullptr) {
 | 
			
		||||
		oxErrf("\tError Location:\t{}:{}\n", err.file, err.line);
 | 
			
		||||
	}
 | 
			
		||||
	// disable all interrupt handling and IntrWait on no interrupts
 | 
			
		||||
	REG_IE = 0;
 | 
			
		||||
	teagba::intrwait(0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										165
									
								
								src/nostalgia/modules/core/src/gfx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/nostalgia/modules/core/src/gfx.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
// map ASCII values to the nostalgia charset
 | 
			
		||||
constexpr ox::Array<char, 128> charMap = {
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,
 | 
			
		||||
	0,  // space
 | 
			
		||||
	38, // !
 | 
			
		||||
	0,  // "
 | 
			
		||||
	49, // #
 | 
			
		||||
	0,  // $
 | 
			
		||||
	0,  // %
 | 
			
		||||
	0,  // &
 | 
			
		||||
	0,  // '
 | 
			
		||||
	42, // (
 | 
			
		||||
	43, // )
 | 
			
		||||
	0,  // *
 | 
			
		||||
	0,  // +
 | 
			
		||||
	37, // ,
 | 
			
		||||
	0,  // -
 | 
			
		||||
	39, // .
 | 
			
		||||
	0,  // /
 | 
			
		||||
	27, // 0
 | 
			
		||||
	28, // 1
 | 
			
		||||
	29, // 2
 | 
			
		||||
	30, // 3
 | 
			
		||||
	31, // 4
 | 
			
		||||
	32, // 5
 | 
			
		||||
	33, // 6
 | 
			
		||||
	34, // 7
 | 
			
		||||
	35, // 8
 | 
			
		||||
	36, // 9
 | 
			
		||||
	40, // :
 | 
			
		||||
	0,  // ;
 | 
			
		||||
	0,  // <
 | 
			
		||||
	41, // =
 | 
			
		||||
	0,  // >
 | 
			
		||||
	0,  // ?
 | 
			
		||||
	0,  // @
 | 
			
		||||
	1,  // A
 | 
			
		||||
	2,  // B
 | 
			
		||||
	3,  // C
 | 
			
		||||
	4,  // D
 | 
			
		||||
	5,  // E
 | 
			
		||||
	6,  // F
 | 
			
		||||
	7,  // G
 | 
			
		||||
	8,  // H
 | 
			
		||||
	9,  // I
 | 
			
		||||
	10, // J
 | 
			
		||||
	11, // K
 | 
			
		||||
	12, // L
 | 
			
		||||
	13, // M
 | 
			
		||||
	14, // N
 | 
			
		||||
	15, // O
 | 
			
		||||
	16, // P
 | 
			
		||||
	17, // Q
 | 
			
		||||
	18, // R
 | 
			
		||||
	19, // S
 | 
			
		||||
	20, // T
 | 
			
		||||
	21, // U
 | 
			
		||||
	22, // V
 | 
			
		||||
	23, // W
 | 
			
		||||
	24, // X
 | 
			
		||||
	25, // Y
 | 
			
		||||
	26, // Z
 | 
			
		||||
	44, // [
 | 
			
		||||
	0,  // backslash
 | 
			
		||||
	45, // ]
 | 
			
		||||
	0,  // ^
 | 
			
		||||
	0,  // _
 | 
			
		||||
	0,  // `
 | 
			
		||||
	1,  // a
 | 
			
		||||
	2,  // b
 | 
			
		||||
	3,  // c
 | 
			
		||||
	4,  // d
 | 
			
		||||
	5,  // e
 | 
			
		||||
	6,  // f
 | 
			
		||||
	7,  // g
 | 
			
		||||
	8,  // h
 | 
			
		||||
	9,  // i
 | 
			
		||||
	10, // j
 | 
			
		||||
	11, // k
 | 
			
		||||
	12, // l
 | 
			
		||||
	13, // m
 | 
			
		||||
	14, // n
 | 
			
		||||
	15, // o
 | 
			
		||||
	16, // p
 | 
			
		||||
	17, // q
 | 
			
		||||
	18, // r
 | 
			
		||||
	19, // s
 | 
			
		||||
	20, // t
 | 
			
		||||
	21, // u
 | 
			
		||||
	22, // v
 | 
			
		||||
	23, // w
 | 
			
		||||
	24, // x
 | 
			
		||||
	25, // y
 | 
			
		||||
	26, // z
 | 
			
		||||
	46, // {
 | 
			
		||||
	51, // |
 | 
			
		||||
	48, // }
 | 
			
		||||
	50, // ~
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ox::Error initConsole(Context &ctx) noexcept {
 | 
			
		||||
	constexpr ox::FileAddress TilesheetAddr = ox::StringLiteral("/TileSheets/Charset.ng");
 | 
			
		||||
	constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Charset.npal");
 | 
			
		||||
	setBgStatus(ctx, 0b0001);
 | 
			
		||||
	setBgCbb(ctx, 0, 0);
 | 
			
		||||
	oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr));
 | 
			
		||||
	return loadBgPalette(ctx, PaletteAddr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void puts(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		int const column,
 | 
			
		||||
		int const row,
 | 
			
		||||
		ox::CRStringView str) noexcept {
 | 
			
		||||
	auto const col = static_cast<uint_t>(column);
 | 
			
		||||
	for (auto i = 0u; i < str.bytes(); ++i) {
 | 
			
		||||
		setBgTile(
 | 
			
		||||
				ctx,
 | 
			
		||||
				0,
 | 
			
		||||
				static_cast<int>(col + i),
 | 
			
		||||
				row,
 | 
			
		||||
				static_cast<uint8_t>(charMap[static_cast<uint8_t>(str[i])]));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/nostalgia/modules/core/src/keel/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/nostalgia/modules/core/src/keel/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaCore-Keel
 | 
			
		||||
		keelmodule.cpp
 | 
			
		||||
		typeconv.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCore-Keel PUBLIC
 | 
			
		||||
		Keel
 | 
			
		||||
		NostalgiaCore
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		NostalgiaCore-Keel
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		LIBRARY DESTINATION lib
 | 
			
		||||
		ARCHIVE DESTINATION lib
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										76
									
								
								src/nostalgia/modules/core/src/keel/keelmodule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/nostalgia/modules/core/src/keel/keelmodule.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/model/model.hpp>
 | 
			
		||||
 | 
			
		||||
#include <keel/asset.hpp>
 | 
			
		||||
#include <keel/module.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/palette.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
#include "typeconv.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class KeelModule: public keel::Module {
 | 
			
		||||
	private:
 | 
			
		||||
		NostalgiaPaletteToPaletteConverter m_nostalgiaPaletteToPaletteConverter;
 | 
			
		||||
		TileSheetV1ToTileSheetV2Converter m_tileSheetV1ToTileSheetV2Converter;
 | 
			
		||||
		TileSheetV2ToTileSheetV3Converter m_tileSheetV2ToTileSheetConverter;
 | 
			
		||||
		TileSheetToCompactTileSheetConverter m_tileSheetToCompactTileSheetConverter;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::String id() const noexcept override {
 | 
			
		||||
			return ox::String("net.drinkingtea.nostalgia.core");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vector<keel::TypeDescGenerator> types() const noexcept final {
 | 
			
		||||
			return {
 | 
			
		||||
				keel::generateTypeDesc<TileSheetV1>,
 | 
			
		||||
				keel::generateTypeDesc<TileSheetV2>,
 | 
			
		||||
				keel::generateTypeDesc<TileSheet>,
 | 
			
		||||
				keel::generateTypeDesc<CompactTileSheet>,
 | 
			
		||||
				keel::generateTypeDesc<Palette>,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vector<const keel::BaseConverter*> converters() const noexcept final {
 | 
			
		||||
			return {
 | 
			
		||||
				&m_nostalgiaPaletteToPaletteConverter,
 | 
			
		||||
				&m_tileSheetV1ToTileSheetV2Converter,
 | 
			
		||||
				&m_tileSheetV2ToTileSheetConverter,
 | 
			
		||||
				&m_tileSheetToCompactTileSheetConverter,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vector<keel::PackTransform> packTransforms() const noexcept final {
 | 
			
		||||
			return {
 | 
			
		||||
				// convert tilesheets to CompactTileSheets
 | 
			
		||||
				[](keel::Context &ctx, ox::Buffer &buff) -> ox::Error {
 | 
			
		||||
					oxRequire(hdr, keel::readAssetHeader(buff));
 | 
			
		||||
					const auto typeId = ox::buildTypeId(
 | 
			
		||||
							hdr.clawHdr.typeName, hdr.clawHdr.typeVersion, hdr.clawHdr.typeParams);
 | 
			
		||||
					if (typeId == ox::buildTypeId<TileSheetV1>() ||
 | 
			
		||||
						 typeId == ox::buildTypeId<TileSheetV2>() ||
 | 
			
		||||
						 typeId == ox::buildTypeId<TileSheet>()) {
 | 
			
		||||
						oxReturnError(keel::convertBuffToBuff<core::CompactTileSheet>(
 | 
			
		||||
								ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
 | 
			
		||||
					}
 | 
			
		||||
					return {};
 | 
			
		||||
				},
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const KeelModule mod;
 | 
			
		||||
const keel::Module *keelModule() noexcept {
 | 
			
		||||
	return &mod;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								src/nostalgia/modules/core/src/keel/typeconv.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/nostalgia/modules/core/src/keel/typeconv.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "typeconv.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
ox::Error NostalgiaPaletteToPaletteConverter::convert(
 | 
			
		||||
		keel::Context&,
 | 
			
		||||
		NostalgiaPalette &src,
 | 
			
		||||
		Palette &dst) const noexcept {
 | 
			
		||||
	dst.colors = std::move(src.colors);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetV1ToTileSheetV2Converter::convert(
 | 
			
		||||
		keel::Context&,
 | 
			
		||||
		TileSheetV1 &src,
 | 
			
		||||
		TileSheetV2 &dst) const noexcept {
 | 
			
		||||
	dst.bpp              = src.bpp;
 | 
			
		||||
	dst.defaultPalette   = std::move(src.defaultPalette);
 | 
			
		||||
	dst.subsheet.name    = "Root";
 | 
			
		||||
	dst.subsheet.rows    = src.rows;
 | 
			
		||||
	dst.subsheet.columns = src.columns;
 | 
			
		||||
	dst.subsheet.pixels  = std::move(src.pixels);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetV2ToTileSheetV3Converter::convertSubsheet(
 | 
			
		||||
		TileSheetV2::SubSheet &src,
 | 
			
		||||
		TileSheetV3::SubSheet &dst,
 | 
			
		||||
		SubSheetId &idIt) noexcept {
 | 
			
		||||
	dst.id      = idIt;
 | 
			
		||||
	dst.name    = std::move(src.name);
 | 
			
		||||
	dst.columns = src.columns;
 | 
			
		||||
	dst.rows    = src.rows;
 | 
			
		||||
	dst.pixels  = std::move(src.pixels);
 | 
			
		||||
	++idIt;
 | 
			
		||||
	dst.subsheets.resize(src.subsheets.size());
 | 
			
		||||
	for (auto i = 0u; i < src.subsheets.size(); ++i) {
 | 
			
		||||
		convertSubsheet(src.subsheets[i], dst.subsheets[i], idIt);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetV2ToTileSheetV3Converter::convert(
 | 
			
		||||
		keel::Context&,
 | 
			
		||||
		TileSheetV2 &src,
 | 
			
		||||
		TileSheetV3 &dst) const noexcept {
 | 
			
		||||
	dst.bpp            = src.bpp;
 | 
			
		||||
	dst.defaultPalette = std::move(src.defaultPalette);
 | 
			
		||||
	convertSubsheet(src.subsheet, dst.subsheet, dst.idIt);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetToCompactTileSheetConverter::convert(
 | 
			
		||||
		keel::Context&,
 | 
			
		||||
		TileSheet &src,
 | 
			
		||||
		CompactTileSheet &dst) const noexcept {
 | 
			
		||||
	dst.bpp            = src.bpp;
 | 
			
		||||
	dst.defaultPalette = std::move(src.defaultPalette);
 | 
			
		||||
	dst.pixels         = src.pixels();
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/nostalgia/modules/core/src/keel/typeconv.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/nostalgia/modules/core/src/keel/typeconv.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/def.hpp>
 | 
			
		||||
 | 
			
		||||
#include <keel/typeconv.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
#include <nostalgia/core/palette.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
// Type converters
 | 
			
		||||
 | 
			
		||||
class NostalgiaPaletteToPaletteConverter: public keel::Converter<NostalgiaPalette, Palette> {
 | 
			
		||||
	ox::Error convert(keel::Context&, NostalgiaPalette &src, Palette &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TileSheetV1ToTileSheetV2Converter: public keel::Converter<TileSheetV1, TileSheetV2> {
 | 
			
		||||
	ox::Error convert(keel::Context&, TileSheetV1 &src, TileSheetV2 &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TileSheetV2ToTileSheetV3Converter: public keel::Converter<TileSheetV2, TileSheetV3> {
 | 
			
		||||
	static void convertSubsheet(
 | 
			
		||||
			TileSheetV2::SubSheet &src,
 | 
			
		||||
			TileSheetV3::SubSheet &dst,
 | 
			
		||||
			SubSheetId &idIt) noexcept;
 | 
			
		||||
	ox::Error convert(keel::Context&, TileSheetV2 &src, TileSheetV3 &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TileSheetToCompactTileSheetConverter: public keel::Converter<TileSheet, CompactTileSheet> {
 | 
			
		||||
		ox::Error convert(keel::Context&, TileSheet &src, CompactTileSheet &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/nostalgia/modules/core/src/opengl/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/nostalgia/modules/core/src/opengl/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
target_sources(
 | 
			
		||||
	NostalgiaCore PRIVATE
 | 
			
		||||
		context.cpp
 | 
			
		||||
		gfx.cpp
 | 
			
		||||
)
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCore PUBLIC
 | 
			
		||||
		GlUtils
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										32
									
								
								src/nostalgia/modules/core/src/opengl/context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/nostalgia/modules/core/src/opengl/context.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "context.hpp"
 | 
			
		||||
#include "gfx.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
void ContextDeleter::operator()(Context *p) noexcept {
 | 
			
		||||
	ox::safeDelete(p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Context::Context(turbine::Context &tctx, InitParams const¶ms) noexcept:
 | 
			
		||||
	turbineCtx(tctx),
 | 
			
		||||
	spriteBlocks(params.glSpriteCount, params.glBlocksPerSprite),
 | 
			
		||||
	drawer(*this),
 | 
			
		||||
	spriteCount(params.glSpriteCount),
 | 
			
		||||
	blocksPerSprite(params.glBlocksPerSprite) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Context::~Context() noexcept {
 | 
			
		||||
	shutdownGfx(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms) noexcept {
 | 
			
		||||
	auto ctx = ox::make_unique<Context>(tctx, params);
 | 
			
		||||
	oxReturnError(initGfx(*ctx, params));
 | 
			
		||||
	return ContextUPtr(ctx.release());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/nostalgia/modules/core/src/opengl/context.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/nostalgia/modules/core/src/opengl/context.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
 | 
			
		||||
#include "gfx.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class Context {
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		turbine::Context &turbineCtx;
 | 
			
		||||
		glutils::GLProgram bgShader;
 | 
			
		||||
		glutils::GLProgram spriteShader;
 | 
			
		||||
		ox::Array<renderer::CBB, 4> cbbs;
 | 
			
		||||
		renderer::SpriteBlockset spriteBlocks;
 | 
			
		||||
		ox::Array<Sprite, 128> spriteStates;
 | 
			
		||||
		ox::Array<renderer::Background, 4> backgrounds;
 | 
			
		||||
		renderer::Drawer drawer;
 | 
			
		||||
		uint_t spriteCount = 0;
 | 
			
		||||
		uint_t blocksPerSprite = 0;
 | 
			
		||||
		explicit Context(turbine::Context &tctx, InitParams const¶ms) noexcept;
 | 
			
		||||
		Context(Context const&) = delete;
 | 
			
		||||
		Context(Context&&) = delete;
 | 
			
		||||
		Context &operator=(Context const&) = delete;
 | 
			
		||||
		Context &operator=(Context&&) = delete;
 | 
			
		||||
		~Context() noexcept;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										694
									
								
								src/nostalgia/modules/core/src/opengl/gfx.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										694
									
								
								src/nostalgia/modules/core/src/opengl/gfx.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,694 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/std/array.hpp>
 | 
			
		||||
#include <ox/std/fmt.hpp>
 | 
			
		||||
#include <ox/std/vec.hpp>
 | 
			
		||||
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
#include "context.hpp"
 | 
			
		||||
#include "gfx.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
namespace renderer {
 | 
			
		||||
 | 
			
		||||
constexpr auto Scale = 1;
 | 
			
		||||
constexpr auto PriorityScale = 0.01f;
 | 
			
		||||
 | 
			
		||||
Drawer::Drawer(Context &ctx) noexcept: m_ctx(ctx) {}
 | 
			
		||||
 | 
			
		||||
void Drawer::draw(turbine::Context &tctx) noexcept {
 | 
			
		||||
	core::gl::draw(m_ctx, turbine::getScreenSize(tctx));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr ox::CStringView bgvshadTmpl = R"glsl(
 | 
			
		||||
	{}
 | 
			
		||||
	in vec2 vTexCoord;
 | 
			
		||||
	in vec3 vPosition;
 | 
			
		||||
	in float vTileIdx;
 | 
			
		||||
	out vec2 fTexCoord;
 | 
			
		||||
	uniform float vXScale;
 | 
			
		||||
	uniform float vTileHeight;
 | 
			
		||||
	uniform float vBgIdx;
 | 
			
		||||
	void main() {
 | 
			
		||||
		float xScaleInvert = 1.0 - vXScale;
 | 
			
		||||
		gl_Position = vec4(
 | 
			
		||||
				vPosition.x * vXScale - xScaleInvert,
 | 
			
		||||
				vPosition.y,
 | 
			
		||||
				vPosition.z - 0.001 * vBgIdx,
 | 
			
		||||
				1.0);
 | 
			
		||||
		fTexCoord = vec2(
 | 
			
		||||
			vTexCoord.x,
 | 
			
		||||
			vTexCoord.y * vTileHeight + vTileIdx * vTileHeight);
 | 
			
		||||
	})glsl";
 | 
			
		||||
 | 
			
		||||
constexpr ox::CStringView bgfshadTmpl = R"glsl(
 | 
			
		||||
	{}
 | 
			
		||||
	out vec4 outColor;
 | 
			
		||||
	in vec2 fTexCoord;
 | 
			
		||||
	uniform sampler2D image;
 | 
			
		||||
	uniform vec2 fSrcImgSz;
 | 
			
		||||
	uniform vec4 fPalette[256];
 | 
			
		||||
	void main() {
 | 
			
		||||
		outColor = fPalette[int(texture(image, fTexCoord).rgb.r * 256)];
 | 
			
		||||
		//outColor = vec4(0.0, 0.7, 1.0, 1.0);
 | 
			
		||||
		if (outColor.a == 0) {
 | 
			
		||||
			discard;
 | 
			
		||||
		}
 | 
			
		||||
	})glsl";
 | 
			
		||||
 | 
			
		||||
constexpr ox::CStringView spritevshadTmpl = R"glsl(
 | 
			
		||||
	{}
 | 
			
		||||
	in float vEnabled;
 | 
			
		||||
	in vec3 vPosition;
 | 
			
		||||
	in vec2 vTexCoord;
 | 
			
		||||
	out vec2 fTexCoord;
 | 
			
		||||
	uniform float vXScale;
 | 
			
		||||
	uniform float vTileHeight;
 | 
			
		||||
	void main() {
 | 
			
		||||
		float xScaleInvert = 1.0 - vXScale;
 | 
			
		||||
		gl_Position = vec4(
 | 
			
		||||
				vPosition.x * vXScale - xScaleInvert,
 | 
			
		||||
				vPosition.y,
 | 
			
		||||
				// offset to ensure sprites draw on top of BGs by default
 | 
			
		||||
				vPosition.z - 0.004,
 | 
			
		||||
				1.0) * vEnabled;
 | 
			
		||||
		fTexCoord = vTexCoord * vec2(1, vTileHeight);
 | 
			
		||||
	})glsl";
 | 
			
		||||
 | 
			
		||||
constexpr ox::CStringView spritefshadTmpl = R"glsl(
 | 
			
		||||
	{}
 | 
			
		||||
	out vec4 outColor;
 | 
			
		||||
	in vec2 fTexCoord;
 | 
			
		||||
	uniform sampler2D image;
 | 
			
		||||
	uniform vec2 fSrcImgSz;
 | 
			
		||||
	uniform vec4 fPalette[256];
 | 
			
		||||
	void main() {
 | 
			
		||||
		outColor = fPalette[int(texture(image, fTexCoord).rgb.r * 256)];
 | 
			
		||||
		//outColor = vec4(0.0, 0.7, 1.0, 1.0);
 | 
			
		||||
		if (outColor.a == 0) {
 | 
			
		||||
			discard;
 | 
			
		||||
		}
 | 
			
		||||
	})glsl";
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
static constexpr auto bgVertexRow(uint_t x, uint_t y) noexcept {
 | 
			
		||||
	return y * TileRows + x;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void setSpriteBufferObject(
 | 
			
		||||
		uint_t vi,
 | 
			
		||||
		float enabled,
 | 
			
		||||
		float x,
 | 
			
		||||
		float y,
 | 
			
		||||
		uint_t textureRow,
 | 
			
		||||
		uint_t flipX,
 | 
			
		||||
		uint_t priority,
 | 
			
		||||
		float *vbo,
 | 
			
		||||
		GLuint *ebo) noexcept {
 | 
			
		||||
	// don't worry, this memcpy gets optimized to something much more ideal
 | 
			
		||||
	constexpr float xmod = 0.1f;
 | 
			
		||||
	constexpr float ymod = 0.1f;
 | 
			
		||||
	x *= xmod;
 | 
			
		||||
	y *= -ymod;
 | 
			
		||||
	x -= 1.f;
 | 
			
		||||
	y += 1.f - ymod;
 | 
			
		||||
	auto const prif = static_cast<float>(priority) * PriorityScale;
 | 
			
		||||
	auto const textureRowf = static_cast<float>(textureRow);
 | 
			
		||||
	float const L = flipX ? 1 : 0;
 | 
			
		||||
	float const R = flipX ? 0 : 1;
 | 
			
		||||
	ox::Array<float, SpriteVertexVboLength> const vertices {
 | 
			
		||||
	// vEnabled| vPosition               | vTexCoord
 | 
			
		||||
	    enabled,        x,        y, prif, L, textureRowf + 1, // bottom left
 | 
			
		||||
	    enabled, x + xmod,        y, prif, R, textureRowf + 1, // bottom right
 | 
			
		||||
	    enabled, x + xmod, y + ymod, prif, R, textureRowf + 0, // top right
 | 
			
		||||
	    enabled,        x, y + ymod, prif, L, textureRowf + 0, // top left
 | 
			
		||||
	};
 | 
			
		||||
	memcpy(vbo, vertices.data(), sizeof(vertices));
 | 
			
		||||
	ox::Array<GLuint, SpriteVertexEboLength> const elms {
 | 
			
		||||
			vi + 0, vi + 1, vi + 2,
 | 
			
		||||
			vi + 2, vi + 3, vi + 0,
 | 
			
		||||
	};
 | 
			
		||||
	memcpy(ebo, elms.data(), sizeof(elms));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void setTileBufferObject(
 | 
			
		||||
		uint_t vi,
 | 
			
		||||
		float x,
 | 
			
		||||
		float y,
 | 
			
		||||
		float textureTileIdx,
 | 
			
		||||
		float priority,
 | 
			
		||||
		float *vbo,
 | 
			
		||||
		GLuint *ebo) noexcept {
 | 
			
		||||
	// don't worry, this memcpy gets optimized to something much more ideal
 | 
			
		||||
	constexpr float ymod = 0.1f;
 | 
			
		||||
	constexpr float xmod = 0.1f;
 | 
			
		||||
	x *= xmod;
 | 
			
		||||
	y *= -ymod;
 | 
			
		||||
	x -= 1.0f;
 | 
			
		||||
	y += 1.0f - ymod;
 | 
			
		||||
	auto const prif = priority * PriorityScale;
 | 
			
		||||
	ox::Array<float, BgVertexVboLength> const vertices {
 | 
			
		||||
			       x,        y, prif, 0, 1, textureTileIdx, // bottom left
 | 
			
		||||
			x + xmod,        y, prif, 1, 1, textureTileIdx, // bottom right
 | 
			
		||||
			x + xmod, y + ymod, prif, 1, 0, textureTileIdx, // top right
 | 
			
		||||
			       x, y + ymod, prif, 0, 0, textureTileIdx, // top left
 | 
			
		||||
	};
 | 
			
		||||
	memcpy(vbo, vertices.data(), sizeof(vertices));
 | 
			
		||||
	ox::Array<GLuint, BgVertexEboLength> const elms {
 | 
			
		||||
			vi + 0, vi + 1, vi + 2,
 | 
			
		||||
			vi + 2, vi + 3, vi + 0,
 | 
			
		||||
	};
 | 
			
		||||
	memcpy(ebo, elms.data(), sizeof(elms));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void initSpriteBufferObjects(Context &ctx, glutils::BufferSet &bs) noexcept {
 | 
			
		||||
	for (auto i = 0u; i < ctx.spriteCount; ++i) {
 | 
			
		||||
		auto vbo = &bs.vertices[i * static_cast<std::size_t>(SpriteVertexVboLength)];
 | 
			
		||||
		auto ebo = &bs.elements[i * static_cast<std::size_t>(SpriteVertexEboLength)];
 | 
			
		||||
		setSpriteBufferObject(i * static_cast<uint_t>(SpriteVertexVboRows) * ctx.blocksPerSprite, 0, 0, 0, 0, false, 0, vbo, ebo);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void initBackgroundBufferObjects(glutils::BufferSet &bs) noexcept {
 | 
			
		||||
	for (auto x = 0u; x < TileColumns; ++x) {
 | 
			
		||||
		for (auto y = 0u; y < TileRows; ++y) {
 | 
			
		||||
			const auto i = bgVertexRow(x, y);
 | 
			
		||||
			auto vbo = &bs.vertices[i * static_cast<std::size_t>(BgVertexVboLength)];
 | 
			
		||||
			auto ebo = &bs.elements[i * static_cast<std::size_t>(BgVertexEboLength)];
 | 
			
		||||
			setTileBufferObject(
 | 
			
		||||
				static_cast<uint_t>(i * BgVertexVboRows),
 | 
			
		||||
				static_cast<float>(x),
 | 
			
		||||
				static_cast<float>(y),
 | 
			
		||||
				0,
 | 
			
		||||
				0,
 | 
			
		||||
				vbo,
 | 
			
		||||
				ebo);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void initSpritesBufferset(Context &ctx) noexcept {
 | 
			
		||||
	auto const shader = ctx.spriteShader.id;
 | 
			
		||||
	auto &bs = ctx.spriteBlocks;
 | 
			
		||||
	// vao
 | 
			
		||||
	bs.vao = glutils::generateVertexArrayObject();
 | 
			
		||||
	glBindVertexArray(bs.vao);
 | 
			
		||||
	// vbo & ebo
 | 
			
		||||
	bs.vbo = glutils::generateBuffer();
 | 
			
		||||
	bs.ebo = glutils::generateBuffer();
 | 
			
		||||
	initSpriteBufferObjects(ctx, bs);
 | 
			
		||||
	glutils::sendVbo(bs);
 | 
			
		||||
	glutils::sendEbo(bs);
 | 
			
		||||
	// vbo layout
 | 
			
		||||
	// in float vEnabled;
 | 
			
		||||
	auto const enabledAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vEnabled"));
 | 
			
		||||
	glEnableVertexAttribArray(enabledAttr);
 | 
			
		||||
	glVertexAttribPointer(enabledAttr, 1, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float), nullptr);
 | 
			
		||||
	// in vec3 vPosition;
 | 
			
		||||
	auto const posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
 | 
			
		||||
	glEnableVertexAttribArray(posAttr);
 | 
			
		||||
	glVertexAttribPointer(posAttr, 3, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
 | 
			
		||||
	                      std::bit_cast<void*>(uintptr_t{1 * sizeof(float)}));
 | 
			
		||||
	// in vec2 vTexCoord;
 | 
			
		||||
	auto const texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
 | 
			
		||||
	glEnableVertexAttribArray(texCoordAttr);
 | 
			
		||||
	glVertexAttribPointer(texCoordAttr, 2, GL_FLOAT, GL_FALSE, SpriteVertexVboRowLength * sizeof(float),
 | 
			
		||||
	                      std::bit_cast<void*>(uintptr_t{4 * sizeof(float)}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void initBackgroundBufferset(
 | 
			
		||||
		GLuint shader,
 | 
			
		||||
		glutils::BufferSet &bs) noexcept {
 | 
			
		||||
	// vao
 | 
			
		||||
	bs.vao = glutils::generateVertexArrayObject();
 | 
			
		||||
	glBindVertexArray(bs.vao);
 | 
			
		||||
	// vbo & ebo
 | 
			
		||||
	bs.vbo = glutils::generateBuffer();
 | 
			
		||||
	bs.ebo = glutils::generateBuffer();
 | 
			
		||||
	initBackgroundBufferObjects(bs);
 | 
			
		||||
	glutils::sendVbo(bs);
 | 
			
		||||
	glutils::sendEbo(bs);
 | 
			
		||||
	// vbo layout
 | 
			
		||||
	auto const posAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vPosition"));
 | 
			
		||||
	glEnableVertexAttribArray(posAttr);
 | 
			
		||||
	glVertexAttribPointer(posAttr, 3, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float), nullptr);
 | 
			
		||||
	auto const texCoordAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTexCoord"));
 | 
			
		||||
	glEnableVertexAttribArray(texCoordAttr);
 | 
			
		||||
	glVertexAttribPointer(
 | 
			
		||||
			texCoordAttr, 2, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float),
 | 
			
		||||
			std::bit_cast<void*>(uintptr_t{3 * sizeof(float)}));
 | 
			
		||||
	auto const heightMultAttr = static_cast<GLuint>(glGetAttribLocation(shader, "vTileIdx"));
 | 
			
		||||
	glEnableVertexAttribArray(heightMultAttr);
 | 
			
		||||
	glVertexAttribPointer(
 | 
			
		||||
			heightMultAttr, 1, GL_FLOAT, GL_FALSE, BgVertexVboRowLength * sizeof(float),
 | 
			
		||||
			std::bit_cast<void*>(uintptr_t{5 * sizeof(float)}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static glutils::GLTexture createTexture(
 | 
			
		||||
		GLsizei w,
 | 
			
		||||
		GLsizei h,
 | 
			
		||||
		const void *pixels) noexcept {
 | 
			
		||||
	GLuint texId = 0;
 | 
			
		||||
	glGenTextures(1, &texId);
 | 
			
		||||
	glutils::GLTexture tex(texId);
 | 
			
		||||
	tex.width = w;
 | 
			
		||||
	tex.height = h;
 | 
			
		||||
	glActiveTexture(GL_TEXTURE0);
 | 
			
		||||
	glBindTexture(GL_TEXTURE_2D, tex.id);
 | 
			
		||||
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
 | 
			
		||||
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 | 
			
		||||
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 | 
			
		||||
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 | 
			
		||||
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 | 
			
		||||
	return tex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void drawBackground(CBB &cbb) noexcept {
 | 
			
		||||
	glBindVertexArray(cbb.vao);
 | 
			
		||||
	if (cbb.updated) {
 | 
			
		||||
		cbb.updated = false;
 | 
			
		||||
		glutils::sendVbo(cbb);
 | 
			
		||||
	}
 | 
			
		||||
	glBindTexture(GL_TEXTURE_2D, cbb.tex);
 | 
			
		||||
	glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(cbb.elements.size()), GL_UNSIGNED_INT, nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void drawBackgrounds(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::Size const&renderSz) noexcept {
 | 
			
		||||
	// load background shader and its uniforms
 | 
			
		||||
	glUseProgram(ctx.bgShader);
 | 
			
		||||
	const auto uniformSrcImgSz = glGetUniformLocation(ctx.bgShader, "fSrcImgSz");
 | 
			
		||||
	const auto uniformXScale = static_cast<GLint>(glGetUniformLocation(ctx.bgShader, "vXScale"));
 | 
			
		||||
	const auto uniformTileHeight = static_cast<GLint>(glGetUniformLocation(ctx.bgShader, "vTileHeight"));
 | 
			
		||||
	const auto uniformBgIdx = static_cast<GLint>(glGetUniformLocation(ctx.bgShader, "vBgIdx"));
 | 
			
		||||
	const auto [wi, hi] = renderSz;
 | 
			
		||||
	const auto wf = static_cast<float>(wi);
 | 
			
		||||
	const auto hf = static_cast<float>(hi);
 | 
			
		||||
	glUniform1f(uniformXScale, hf / wf);
 | 
			
		||||
	auto bgIdx = 0.f;
 | 
			
		||||
	for (const auto &bg : ctx.backgrounds) {
 | 
			
		||||
		if (bg.enabled) {
 | 
			
		||||
			auto &cbb = ctx.cbbs[bg.cbbIdx];
 | 
			
		||||
			const auto tileRows = cbb.tex.height / (TileHeight * Scale);
 | 
			
		||||
			glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
 | 
			
		||||
			glUniform2f(
 | 
			
		||||
					uniformSrcImgSz,
 | 
			
		||||
					static_cast<float>(cbb.tex.width),
 | 
			
		||||
					static_cast<float>(cbb.tex.height));
 | 
			
		||||
			glUniform1f(uniformBgIdx, bgIdx);
 | 
			
		||||
			drawBackground(cbb);
 | 
			
		||||
			++bgIdx;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void drawSprites(Context &ctx, ox::Size const&renderSz) noexcept {
 | 
			
		||||
	glUseProgram(ctx.spriteShader);
 | 
			
		||||
	auto &sb = ctx.spriteBlocks;
 | 
			
		||||
	const auto uniformXScale = glGetUniformLocation(ctx.bgShader, "vXScale");
 | 
			
		||||
	const auto uniformTileHeight = glGetUniformLocation(ctx.spriteShader, "vTileHeight");
 | 
			
		||||
	const auto [wi, hi] = renderSz;
 | 
			
		||||
	const auto wf = static_cast<float>(wi);
 | 
			
		||||
	const auto hf = static_cast<float>(hi);
 | 
			
		||||
	glUniform1f(uniformXScale, hf / wf);
 | 
			
		||||
	// update vbo
 | 
			
		||||
	glBindVertexArray(sb.vao);
 | 
			
		||||
	if (sb.updated) {
 | 
			
		||||
		sb.updated = false;
 | 
			
		||||
		glutils::sendVbo(sb);
 | 
			
		||||
	}
 | 
			
		||||
	// set vTileHeight uniform
 | 
			
		||||
	const auto tileRows = sb.tex.height / (TileHeight * Scale);
 | 
			
		||||
	glUniform1f(uniformTileHeight, 1.0f / static_cast<float>(tileRows));
 | 
			
		||||
	// draw
 | 
			
		||||
	glBindTexture(GL_TEXTURE_2D, sb.tex);
 | 
			
		||||
	glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(sb.elements.size()), GL_UNSIGNED_INT, nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void loadPalette(
 | 
			
		||||
		GLuint shaderPgrm,
 | 
			
		||||
		Palette const&pal) noexcept {
 | 
			
		||||
	static constexpr std::size_t ColorCnt = 256;
 | 
			
		||||
	ox::Array<GLfloat, ColorCnt * 4> palette{};
 | 
			
		||||
	for (auto i = 0u; const auto c : pal.colors) {
 | 
			
		||||
		palette[i++] = redf(c);
 | 
			
		||||
		palette[i++] = greenf(c);
 | 
			
		||||
		palette[i++] = bluef(c);
 | 
			
		||||
		palette[i++] = 255;
 | 
			
		||||
	}
 | 
			
		||||
	// make first color transparent
 | 
			
		||||
	palette[3] = 0;
 | 
			
		||||
	glUseProgram(shaderPgrm);
 | 
			
		||||
	const auto uniformPalette = static_cast<GLint>(glGetUniformLocation(shaderPgrm, "fPalette"));
 | 
			
		||||
	glUniform4fv(uniformPalette, ColorCnt, palette.data());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void setSprite(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		uint_t const idx,
 | 
			
		||||
		Sprite const&s) noexcept {
 | 
			
		||||
	// Tonc Table 8.4
 | 
			
		||||
	static constexpr ox::Array<ox::Vec<uint_t>, 12> dimensions{
 | 
			
		||||
			// col 0
 | 
			
		||||
			{1, 1}, // 0, 0
 | 
			
		||||
			{2, 2}, // 0, 1
 | 
			
		||||
			{4, 4}, // 0, 2
 | 
			
		||||
			{8, 8}, // 0, 3
 | 
			
		||||
			// col 1
 | 
			
		||||
			{2, 1}, // 1, 0
 | 
			
		||||
			{4, 1}, // 1, 1
 | 
			
		||||
			{4, 2}, // 1, 2
 | 
			
		||||
			{8, 4}, // 1, 3
 | 
			
		||||
			// col 2
 | 
			
		||||
			{1, 1}, // 2, 0
 | 
			
		||||
			{1, 4}, // 2, 1
 | 
			
		||||
			{2, 4}, // 2, 2
 | 
			
		||||
			{4, 8}, // 2, 3
 | 
			
		||||
	};
 | 
			
		||||
	oxAssert(idx < ctx.spriteStates.size(), "overflow");
 | 
			
		||||
	auto const dim = dimensions[(s.spriteShape << 2) | s.spriteSize];
 | 
			
		||||
	auto const uX = static_cast<int>(s.x) % 255;
 | 
			
		||||
	auto const uY = static_cast<int>(s.y + 8) % 255 - 8;
 | 
			
		||||
	oxAssert(1 < ctx.spriteBlocks.vertices.size(), "vbo overflow");
 | 
			
		||||
	oxAssert(1 < ctx.spriteBlocks.elements.size(), "ebo overflow");
 | 
			
		||||
	const auto spriteVboSz = ctx.blocksPerSprite * renderer::SpriteVertexVboLength;
 | 
			
		||||
	const auto spriteEboSz = ctx.blocksPerSprite * renderer::SpriteVertexEboLength;
 | 
			
		||||
	auto const vboBase = spriteVboSz * idx;
 | 
			
		||||
	auto const eboBase = spriteEboSz * idx;
 | 
			
		||||
	auto i = 0u;
 | 
			
		||||
	const auto set = [&](int xIt, int yIt, bool enabled) {
 | 
			
		||||
		auto const fX = static_cast<float>(uX + xIt * 8) / 8;
 | 
			
		||||
		auto const fY = static_cast<float>(uY + yIt * 8) / 8;
 | 
			
		||||
		auto const vboIdx = vboBase + renderer::SpriteVertexVboLength * i;
 | 
			
		||||
		auto const eboIdx = eboBase + renderer::SpriteVertexEboLength * i;
 | 
			
		||||
		oxAssert(vboIdx < ctx.spriteBlocks.vertices.size(), "vbo overflow");
 | 
			
		||||
		oxAssert(eboIdx < ctx.spriteBlocks.elements.size(), "ebo overflow");
 | 
			
		||||
		auto const vbo = &ctx.spriteBlocks.vertices[vboIdx];
 | 
			
		||||
		auto const ebo = &ctx.spriteBlocks.elements[eboIdx];
 | 
			
		||||
		renderer::setSpriteBufferObject(
 | 
			
		||||
				static_cast<uint_t>(vboIdx),
 | 
			
		||||
				enabled,
 | 
			
		||||
				fX,
 | 
			
		||||
				fY,
 | 
			
		||||
				s.tileIdx + i,
 | 
			
		||||
				s.flipX,
 | 
			
		||||
				s.priority & 0b11,
 | 
			
		||||
				vbo,
 | 
			
		||||
				ebo);
 | 
			
		||||
		++i;
 | 
			
		||||
	};
 | 
			
		||||
	if (!s.flipX) {
 | 
			
		||||
		for (auto yIt = 0; yIt < static_cast<int>(dim.y); ++yIt) {
 | 
			
		||||
			for (auto xIt = 0u; xIt < dim.x; ++xIt) {
 | 
			
		||||
				set(static_cast<int>(xIt), static_cast<int>(yIt), s.enabled);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for (auto yIt = 0u; yIt < dim.y; ++yIt) {
 | 
			
		||||
			for (auto xIt = dim.x - 1; xIt < ~0u; --xIt) {
 | 
			
		||||
				set(static_cast<int>(xIt), static_cast<int>(yIt), s.enabled);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// clear remaining blocks in the sprite
 | 
			
		||||
	for (; i < ctx.blocksPerSprite; ++i) {
 | 
			
		||||
		set(0, 0, false);
 | 
			
		||||
	}
 | 
			
		||||
	ctx.spriteBlocks.updated = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error initGfx(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		InitParams const&initParams) noexcept {
 | 
			
		||||
	const auto bgVshad = ox::sfmt(renderer::bgvshadTmpl, gl::GlslVersion);
 | 
			
		||||
	const auto bgFshad = ox::sfmt(renderer::bgfshadTmpl, gl::GlslVersion);
 | 
			
		||||
	const auto spriteVshad = ox::sfmt(renderer::spritevshadTmpl, gl::GlslVersion);
 | 
			
		||||
	const auto spriteFshad = ox::sfmt(renderer::spritefshadTmpl, gl::GlslVersion);
 | 
			
		||||
	oxReturnError(glutils::buildShaderProgram(bgVshad, bgFshad).moveTo(ctx.bgShader));
 | 
			
		||||
	oxReturnError(
 | 
			
		||||
			glutils::buildShaderProgram(spriteVshad, spriteFshad).moveTo(ctx.spriteShader));
 | 
			
		||||
	for (auto &cbb : ctx.cbbs) {
 | 
			
		||||
		initBackgroundBufferset(ctx.bgShader, cbb);
 | 
			
		||||
	}
 | 
			
		||||
	renderer::initSpritesBufferset(ctx);
 | 
			
		||||
	if (initParams.glInstallDrawer) {
 | 
			
		||||
		turbine::gl::addDrawer(ctx.turbineCtx, &ctx.drawer);
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void shutdownGfx(Context &ctx) noexcept {
 | 
			
		||||
	turbine::gl::removeDrawer(ctx.turbineCtx, &ctx.drawer);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TileSheetData {
 | 
			
		||||
	ox::Vector<uint32_t> pixels;
 | 
			
		||||
	int width = 0;
 | 
			
		||||
	int height = 0;
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr ox::Size size() const noexcept {
 | 
			
		||||
		return {width, height};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static ox::Result<TileSheetData> normalizeTileSheet(
 | 
			
		||||
		CompactTileSheet const&tilesheet) noexcept {
 | 
			
		||||
	const uint_t bytesPerTile = tilesheet.bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
 | 
			
		||||
	const auto tiles = tilesheet.pixels.size() / bytesPerTile;
 | 
			
		||||
	constexpr int width = 8;
 | 
			
		||||
	const int height = 8 * static_cast<int>(tiles);
 | 
			
		||||
	ox::Vector<uint32_t> pixels;
 | 
			
		||||
	if (bytesPerTile == 64) { // 8 BPP
 | 
			
		||||
		pixels.resize(tilesheet.pixels.size());
 | 
			
		||||
		for (std::size_t i = 0; i < tilesheet.pixels.size(); ++i) {
 | 
			
		||||
			pixels[i] = tilesheet.pixels[i];
 | 
			
		||||
		}
 | 
			
		||||
	} else { // 4 BPP
 | 
			
		||||
		pixels.resize(tilesheet.pixels.size() * 2);
 | 
			
		||||
		for (std::size_t i = 0; i < tilesheet.pixels.size(); ++i) {
 | 
			
		||||
			pixels[i * 2 + 0] = tilesheet.pixels[i] & 0xF;
 | 
			
		||||
			pixels[i * 2 + 1] = tilesheet.pixels[i] >> 4;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return TileSheetData{std::move(pixels), width, height};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgPalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	auto &kctx = keelCtx(ctx.turbineCtx);
 | 
			
		||||
	oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
 | 
			
		||||
	renderer::loadPalette(ctx.bgShader, *palette);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpritePalette(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&paletteAddr) noexcept {
 | 
			
		||||
	auto &kctx = keelCtx(ctx.turbineCtx);
 | 
			
		||||
	oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
 | 
			
		||||
	renderer::loadPalette(ctx.spriteShader, *palette);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ox::Result<TileSheetData> buildSetTsd(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		TileSheetSet const&set) noexcept {
 | 
			
		||||
	auto &kctx = keelCtx(ctx.turbineCtx);
 | 
			
		||||
	TileSheetData setTsd;
 | 
			
		||||
	setTsd.width = TileWidth;
 | 
			
		||||
	for (auto const&entry : set.entries) {
 | 
			
		||||
		oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, entry.tilesheet));
 | 
			
		||||
		oxRequire(tsd, normalizeTileSheet(*tilesheet));
 | 
			
		||||
		for (auto const&s : entry.sections) {
 | 
			
		||||
			auto const size = s.tiles * PixelsPerTile;
 | 
			
		||||
			for (auto i = 0; i < size; ++i) {
 | 
			
		||||
				auto const srcIdx = static_cast<size_t>(i) + static_cast<size_t>(s.begin * PixelsPerTile);
 | 
			
		||||
				setTsd.pixels.push_back(tsd.pixels[srcIdx]);
 | 
			
		||||
			}
 | 
			
		||||
			setTsd.height += TileHeight * s.tiles;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return setTsd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		uint_t cbb,
 | 
			
		||||
		ox::FileAddress const&tilesheetAddr,
 | 
			
		||||
		bool loadDefaultPalette) noexcept {
 | 
			
		||||
	auto &kctx = keelCtx(ctx.turbineCtx);
 | 
			
		||||
	oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
 | 
			
		||||
	oxRequire(tsd, normalizeTileSheet(*tilesheet));
 | 
			
		||||
	oxTracef("nostalgia.core.gfx.gl", "loadBgTexture: { cbbIdx: {}, w: {}, h: {} }", cbb, tsd.width, tsd.height);
 | 
			
		||||
	ctx.cbbs[cbb].tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data());
 | 
			
		||||
	if (loadDefaultPalette) {
 | 
			
		||||
		oxReturnError(loadBgPalette(ctx, tilesheet->defaultPalette));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadBgTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		unsigned cbb,
 | 
			
		||||
		TileSheetSet const&set) noexcept {
 | 
			
		||||
	oxRequire(setTsd, buildSetTsd(ctx, set));
 | 
			
		||||
	ctx.cbbs[cbb].tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data());
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpriteTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		ox::FileAddress const&tilesheetAddr,
 | 
			
		||||
		bool loadDefaultPalette) noexcept {
 | 
			
		||||
	auto &kctx = keelCtx(ctx.turbineCtx);
 | 
			
		||||
	oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
 | 
			
		||||
	oxRequire(tsd, normalizeTileSheet(*tilesheet));
 | 
			
		||||
	oxTracef("nostalgia.core.gfx.gl", "loadSpriteTexture: { w: {}, h: {} }", tsd.width, tsd.height);
 | 
			
		||||
	ctx.spriteBlocks.tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data());
 | 
			
		||||
	if (loadDefaultPalette) {
 | 
			
		||||
		oxReturnError(loadSpritePalette(ctx, tilesheet->defaultPalette));
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error loadSpriteTileSheet(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		TileSheetSet const&set) noexcept {
 | 
			
		||||
	oxRequire(setTsd, buildSetTsd(ctx, set));
 | 
			
		||||
	ctx.spriteBlocks.tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data());
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgTile(
 | 
			
		||||
		Context &ctx,
 | 
			
		||||
		uint_t bgIdx,
 | 
			
		||||
		int column,
 | 
			
		||||
		int row,
 | 
			
		||||
		uint8_t tile) noexcept {
 | 
			
		||||
	oxTracef(
 | 
			
		||||
			"nostalgia.core.gfx.setBgTile",
 | 
			
		||||
			"bgIdx: {}, column: {}, row: {}, tile: {}",
 | 
			
		||||
			bgIdx, column, row, tile);
 | 
			
		||||
	const auto z = static_cast<uint_t>(bgIdx);
 | 
			
		||||
	const auto y = static_cast<uint_t>(row);
 | 
			
		||||
	const auto x = static_cast<uint_t>(column);
 | 
			
		||||
	const auto i = renderer::bgVertexRow(x, y);
 | 
			
		||||
	auto &cbb = ctx.cbbs[z];
 | 
			
		||||
	const auto vbo = &cbb.vertices[i * renderer::BgVertexVboLength];
 | 
			
		||||
	const auto ebo = &cbb.elements[i * renderer::BgVertexEboLength];
 | 
			
		||||
	auto &bg = ctx.backgrounds[bgIdx];
 | 
			
		||||
	renderer::setTileBufferObject(
 | 
			
		||||
			static_cast<uint_t>(i * renderer::BgVertexVboRows),
 | 
			
		||||
			static_cast<float>(x),
 | 
			
		||||
			static_cast<float>(y),
 | 
			
		||||
			static_cast<float>(tile),
 | 
			
		||||
			bg.priority,
 | 
			
		||||
			vbo,
 | 
			
		||||
			ebo);
 | 
			
		||||
	cbb.updated = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void clearBg(Context &ctx, uint_t bgIdx) noexcept {
 | 
			
		||||
	auto &cbb = ctx.cbbs[static_cast<std::size_t>(bgIdx)];
 | 
			
		||||
	initBackgroundBufferObjects(cbb);
 | 
			
		||||
	cbb.updated = true;
 | 
			
		||||
	auto &bg = ctx.backgrounds[static_cast<std::size_t>(bgIdx)];
 | 
			
		||||
	bg.priority = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t bgStatus(Context &ctx) noexcept {
 | 
			
		||||
	uint8_t out = 0;
 | 
			
		||||
	for (uint_t i = 0; i < ctx.cbbs.size(); ++i) {
 | 
			
		||||
		out |= static_cast<uint8_t>(static_cast<uint_t>(ctx.backgrounds[i].enabled) << i);
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgStatus(Context &ctx, uint32_t status) noexcept {
 | 
			
		||||
	for (uint_t i = 0; i < ctx.cbbs.size(); ++i) {
 | 
			
		||||
		ctx.backgrounds[i].enabled = (status >> i) & 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool bgStatus(Context &ctx, uint_t bg) noexcept {
 | 
			
		||||
	return ctx.backgrounds[bg].enabled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgStatus(Context &ctx, uint_t bg, bool status) noexcept {
 | 
			
		||||
	ctx.backgrounds[bg].enabled = status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgCbb(Context &ctx, uint_t bgIdx, uint_t cbbIdx) noexcept {
 | 
			
		||||
	auto &bg = ctx.backgrounds[bgIdx];
 | 
			
		||||
	bg.cbbIdx = cbbIdx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setBgPriority(Context &ctx, uint_t bgIdx, uint_t priority) noexcept {
 | 
			
		||||
	auto &bg = ctx.backgrounds[bgIdx];
 | 
			
		||||
	bg.priority = static_cast<float>(priority & 0b11);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void hideSprite(Context &ctx, uint_t idx) noexcept {
 | 
			
		||||
	auto &s = ctx.spriteStates[idx];
 | 
			
		||||
	s.enabled = false;
 | 
			
		||||
	renderer::setSprite(ctx, idx, s);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void showSprite(Context &ctx, uint_t idx) noexcept {
 | 
			
		||||
	auto &s = ctx.spriteStates[idx];
 | 
			
		||||
	s.enabled = true;
 | 
			
		||||
	renderer::setSprite(ctx, idx, s);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void setSprite(Context &ctx, uint_t idx, Sprite const&sprite) noexcept {
 | 
			
		||||
	auto &s = ctx.spriteStates[idx];
 | 
			
		||||
	s = sprite;
 | 
			
		||||
	renderer::setSprite(ctx, idx, s);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint_t spriteCount(Context &ctx) noexcept {
 | 
			
		||||
	return ctx.spriteCount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace gl {
 | 
			
		||||
 | 
			
		||||
ox::Size drawSize(int scale) noexcept {
 | 
			
		||||
	return {240 * scale, 160 * scale};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void draw(core::Context &ctx, ox::Size const&renderSz) noexcept {
 | 
			
		||||
	glViewport(0, 0, renderSz.width, renderSz.height);
 | 
			
		||||
	glEnable(GL_DEPTH_TEST);
 | 
			
		||||
	glEnable(GL_BLEND);
 | 
			
		||||
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 | 
			
		||||
	glClearColor(0, 0, 0, 1);
 | 
			
		||||
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | 
			
		||||
	renderer::drawBackgrounds(ctx, renderSz);
 | 
			
		||||
	if (ctx.spriteBlocks.tex) {
 | 
			
		||||
		renderer::drawSprites(ctx, renderSz);
 | 
			
		||||
	}
 | 
			
		||||
	glDisable(GL_DEPTH_TEST);
 | 
			
		||||
	glDisable(GL_BLEND);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void draw(core::Context &ctx, int scale) noexcept {
 | 
			
		||||
	draw(ctx, drawSize(scale));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								src/nostalgia/modules/core/src/opengl/gfx.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/nostalgia/modules/core/src/opengl/gfx.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
 | 
			
		||||
#include <turbine/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core::renderer {
 | 
			
		||||
 | 
			
		||||
constexpr uint64_t TileRows = 128;
 | 
			
		||||
constexpr uint64_t TileColumns = 128;
 | 
			
		||||
constexpr uint64_t TileCount = TileRows * TileColumns;
 | 
			
		||||
constexpr uint64_t BgVertexVboRows = 4;
 | 
			
		||||
constexpr uint64_t BgVertexVboRowLength = 6;
 | 
			
		||||
constexpr uint64_t BgVertexVboLength = BgVertexVboRows * BgVertexVboRowLength;
 | 
			
		||||
constexpr uint64_t BgVertexEboLength = 6;
 | 
			
		||||
constexpr uint64_t SpriteVertexVboRows = 4;
 | 
			
		||||
constexpr uint64_t SpriteVertexVboRowLength = 6;
 | 
			
		||||
constexpr uint64_t SpriteVertexVboLength = SpriteVertexVboRows * SpriteVertexVboRowLength;
 | 
			
		||||
constexpr uint64_t SpriteVertexEboLength = 6;
 | 
			
		||||
 | 
			
		||||
struct CBB: public glutils::BufferSet {
 | 
			
		||||
	bool updated = false;
 | 
			
		||||
	constexpr CBB() noexcept {
 | 
			
		||||
		vertices.resize(TileCount * BgVertexVboLength);
 | 
			
		||||
		elements.resize(TileCount * BgVertexEboLength);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SpriteBlockset: public glutils::BufferSet {
 | 
			
		||||
	bool updated = false;
 | 
			
		||||
	constexpr SpriteBlockset(uint64_t spriteCount, uint64_t blocksPerSprite) noexcept {
 | 
			
		||||
		vertices.resize(spriteCount * SpriteVertexVboLength * blocksPerSprite);
 | 
			
		||||
		elements.resize(spriteCount * SpriteVertexEboLength * blocksPerSprite);
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Background {
 | 
			
		||||
	float priority = 0;
 | 
			
		||||
	bool enabled = false;
 | 
			
		||||
	unsigned cbbIdx = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Drawer: public turbine::gl::Drawer {
 | 
			
		||||
	private:
 | 
			
		||||
		Context &m_ctx;
 | 
			
		||||
	public:
 | 
			
		||||
		explicit Drawer(Context &ctx) noexcept;
 | 
			
		||||
		void draw(turbine::Context&) noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
 | 
			
		||||
void shutdownGfx(Context &ctx) noexcept;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/nostalgia/modules/core/src/studio/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/nostalgia/modules/core/src/studio/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
add_library(NostalgiaCore-Studio)
 | 
			
		||||
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaCore-Studio-ImGui
 | 
			
		||||
		studiomodule.cpp
 | 
			
		||||
		tilesheeteditor/tilesheeteditor-imgui.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCore-Studio PUBLIC
 | 
			
		||||
		NostalgiaCore
 | 
			
		||||
		Studio
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCore-Studio-ImGui PUBLIC
 | 
			
		||||
		NostalgiaCore-Studio
 | 
			
		||||
		Studio
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		NostalgiaCore-Studio-ImGui
 | 
			
		||||
		NostalgiaCore-Studio
 | 
			
		||||
	LIBRARY DESTINATION
 | 
			
		||||
		${NOSTALGIA_DIST_MODULE}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(paletteeditor)
 | 
			
		||||
add_subdirectory(tilesheeteditor)
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
target_sources(
 | 
			
		||||
	NostalgiaCore-Studio PRIVATE
 | 
			
		||||
		paletteeditor.cpp
 | 
			
		||||
		paletteeditor-imgui.cpp
 | 
			
		||||
)
 | 
			
		||||
@@ -0,0 +1,137 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
 | 
			
		||||
#include <ox/std/memory.hpp>
 | 
			
		||||
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
#include "paletteeditor.hpp"
 | 
			
		||||
#include "paletteeditor-imgui.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
PaletteEditorImGui::PaletteEditorImGui(turbine::Context &ctx, ox::CRStringView path):
 | 
			
		||||
	Editor(path),
 | 
			
		||||
	m_ctx(ctx),
 | 
			
		||||
	m_pal(*keel::readObj<Palette>(keelCtx(m_ctx), ox::FileAddress(itemPath())).unwrapThrow()) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteEditorImGui::draw(turbine::Context&) noexcept {
 | 
			
		||||
	static constexpr auto flags = ImGuiTableFlags_RowBg;
 | 
			
		||||
	const auto paneSize = ImGui::GetContentRegionAvail();
 | 
			
		||||
	ImGui::BeginChild("Colors", ImVec2(paneSize.x - 208, paneSize.y), true);
 | 
			
		||||
	{
 | 
			
		||||
		const auto colorsSz = ImGui::GetContentRegionAvail();
 | 
			
		||||
		static constexpr auto toolbarHeight = 40;
 | 
			
		||||
		{
 | 
			
		||||
			const auto sz = ImVec2(70, 24);
 | 
			
		||||
			if (ImGui::Button("Add", sz)) {
 | 
			
		||||
				const auto colorSz = static_cast<int>(m_pal.colors.size());
 | 
			
		||||
				constexpr Color16 c = 0;
 | 
			
		||||
				undoStack()->push(ox::make_unique<AddColorCommand>(&m_pal, c, colorSz));
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			ImGui::BeginDisabled(m_selectedRow >= m_pal.colors.size());
 | 
			
		||||
			{
 | 
			
		||||
				if (ImGui::Button("Remove", sz)) {
 | 
			
		||||
					undoStack()->push(
 | 
			
		||||
							ox::make_unique<RemoveColorCommand>(
 | 
			
		||||
								&m_pal,
 | 
			
		||||
								m_pal.colors[static_cast<std::size_t>(m_selectedRow)],
 | 
			
		||||
								static_cast<int>(m_selectedRow)));
 | 
			
		||||
					m_selectedRow = ox::min(m_pal.colors.size() - 1, m_selectedRow);
 | 
			
		||||
				}
 | 
			
		||||
				ImGui::SameLine();
 | 
			
		||||
				ImGui::BeginDisabled(m_selectedRow <= 0);
 | 
			
		||||
				{
 | 
			
		||||
					if (ImGui::Button("Move Up", sz)) {
 | 
			
		||||
						undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_selectedRow, -1));
 | 
			
		||||
						--m_selectedRow;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				ImGui::EndDisabled();
 | 
			
		||||
				ImGui::SameLine();
 | 
			
		||||
				ImGui::BeginDisabled(m_selectedRow >= m_pal.colors.size() - 1);
 | 
			
		||||
				{
 | 
			
		||||
					if (ImGui::Button("Move Down", sz)) {
 | 
			
		||||
						undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_selectedRow, 1));
 | 
			
		||||
						++m_selectedRow;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				ImGui::EndDisabled();
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::EndDisabled();
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::BeginTable("Colors", 5, flags, ImVec2(colorsSz.x, colorsSz.y - (toolbarHeight + 5)));
 | 
			
		||||
		{
 | 
			
		||||
			ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25);
 | 
			
		||||
			ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
			ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
			ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
			ImGui::TableSetupColumn("Color Preview", ImGuiTableColumnFlags_NoHide);
 | 
			
		||||
			ImGui::TableHeadersRow();
 | 
			
		||||
			constexpr auto colVal = [] (unsigned num) {
 | 
			
		||||
				ox::Array<char, 4> numStr;
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				ox_itoa(num, numStr.data());
 | 
			
		||||
				ImGui::SetCursorPosX(
 | 
			
		||||
						ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(numStr.data()).x);
 | 
			
		||||
				ImGui::Text("%s", numStr.data());
 | 
			
		||||
			};
 | 
			
		||||
			for (auto i = 0u; const auto c : m_pal.colors) {
 | 
			
		||||
				ImGui::PushID(static_cast<int>(i));
 | 
			
		||||
				ImGui::TableNextRow();
 | 
			
		||||
				// Color No.
 | 
			
		||||
				colVal(i);
 | 
			
		||||
				// Red
 | 
			
		||||
				colVal(red16(c));
 | 
			
		||||
				// Green
 | 
			
		||||
				colVal(green16(c));
 | 
			
		||||
				// Blue
 | 
			
		||||
				colVal(blue16(c));
 | 
			
		||||
				// ColorPreview
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				const auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
 | 
			
		||||
				ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
 | 
			
		||||
				if (ImGui::Selectable("##ColorRow", i == m_selectedRow, ImGuiSelectableFlags_SpanAllColumns)) {
 | 
			
		||||
					m_selectedRow = i;
 | 
			
		||||
				}
 | 
			
		||||
				ImGui::PopID();
 | 
			
		||||
				++i;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndTable();
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::EndChild();
 | 
			
		||||
	if (m_selectedRow < m_pal.colors.size()) {
 | 
			
		||||
		ImGui::SameLine();
 | 
			
		||||
		ImGui::BeginChild("ColorEditor", ImVec2(200, paneSize.y), true);
 | 
			
		||||
		{
 | 
			
		||||
			const auto c = m_pal.colors[m_selectedRow];
 | 
			
		||||
			int r = red16(c);
 | 
			
		||||
			int g = green16(c);
 | 
			
		||||
			int b = blue16(c);
 | 
			
		||||
			int const a = alpha16(c);
 | 
			
		||||
			ImGui::InputInt("Red", &r, 1, 5);
 | 
			
		||||
			ImGui::InputInt("Green", &g, 1, 5);
 | 
			
		||||
			ImGui::InputInt("Blue", &b, 1, 5);
 | 
			
		||||
			const auto newColor = color16(r, g, b, a);
 | 
			
		||||
			if (c != newColor) {
 | 
			
		||||
				undoStack()->push(ox::make_unique<UpdateColorCommand>(&m_pal, static_cast<int>(m_selectedRow), c, newColor));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndChild();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error PaletteEditorImGui::saveItem() noexcept {
 | 
			
		||||
	const auto sctx = applicationData<studio::StudioContext>(m_ctx);
 | 
			
		||||
	return sctx->project->writeObj(itemPath(), m_pal);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/core/palette.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class PaletteEditorImGui: public studio::Editor {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		turbine::Context &m_ctx;
 | 
			
		||||
		Palette m_pal;
 | 
			
		||||
		std::size_t m_selectedRow = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		PaletteEditorImGui(turbine::Context &ctx, ox::CRStringView path);
 | 
			
		||||
 | 
			
		||||
		void draw(turbine::Context&) noexcept final;
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		ox::Error saveItem() noexcept final;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,105 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "paletteeditor.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
AddColorCommand::AddColorCommand(Palette *pal, Color16 color, int idx) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_color = color;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int AddColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::AddColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddColorCommand::redo() noexcept {
 | 
			
		||||
	m_pal->colors.insert(static_cast<std::size_t>(m_idx), m_color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddColorCommand::undo() noexcept {
 | 
			
		||||
	oxIgnoreError(m_pal->colors.erase(static_cast<std::size_t>(m_idx)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RemoveColorCommand::RemoveColorCommand(Palette *pal, Color16 color, int idx) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_color = color;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int RemoveColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::RemoveColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RemoveColorCommand::redo() noexcept {
 | 
			
		||||
	oxIgnoreError(m_pal->colors.erase(static_cast<std::size_t>(m_idx)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RemoveColorCommand::undo() noexcept {
 | 
			
		||||
m_pal->colors.insert(static_cast<std::size_t>(m_idx), m_color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
UpdateColorCommand::UpdateColorCommand(Palette *pal, int idx, Color16 oldColor, Color16 newColor) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
	m_oldColor = oldColor;
 | 
			
		||||
	m_newColor = newColor;
 | 
			
		||||
	//setObsolete(m_oldColor == m_newColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool UpdateColorCommand::mergeWith(const UndoCommand *cmd) noexcept {
 | 
			
		||||
	if (cmd->commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	auto ucCmd = static_cast<const UpdateColorCommand*>(cmd);
 | 
			
		||||
	if (m_idx != ucCmd->m_idx) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	m_newColor = ucCmd->m_newColor;
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
int UpdateColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::UpdateColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateColorCommand::redo() noexcept {
 | 
			
		||||
	m_pal->colors[static_cast<std::size_t>(m_idx)] = m_newColor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateColorCommand::undo() noexcept {
 | 
			
		||||
	m_pal->colors[static_cast<std::size_t>(m_idx)] = m_oldColor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
MoveColorCommand::MoveColorCommand(Palette *pal, std::size_t idx, int offset) noexcept {
 | 
			
		||||
	m_pal = pal;
 | 
			
		||||
	m_idx = idx;
 | 
			
		||||
	m_offset = offset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int MoveColorCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(PaletteEditorCommandId::MoveColor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MoveColorCommand::redo() noexcept {
 | 
			
		||||
	moveColor(static_cast<int>(m_idx), m_offset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MoveColorCommand::undo() noexcept {
 | 
			
		||||
	moveColor(static_cast<int>(m_idx) + m_offset, -m_offset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MoveColorCommand::moveColor(int idx, int offset) noexcept {
 | 
			
		||||
	const auto c = m_pal->colors[static_cast<std::size_t>(idx)];
 | 
			
		||||
	oxIgnoreError(m_pal->colors.erase(static_cast<std::size_t>(idx)));
 | 
			
		||||
	m_pal->colors.insert(static_cast<std::size_t>(idx + offset), c);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,110 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/color.hpp>
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/core/palette.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
enum class PaletteEditorCommandId {
 | 
			
		||||
	AddColor,
 | 
			
		||||
	RemoveColor,
 | 
			
		||||
	UpdateColor,
 | 
			
		||||
	MoveColor,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AddColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		Color16 m_color = 0;
 | 
			
		||||
		int m_idx = -1;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		AddColorCommand(Palette *pal, Color16 color, int idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		~AddColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept override;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept override;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RemoveColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		Color16 m_color = 0;
 | 
			
		||||
		int m_idx = -1;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		RemoveColorCommand(Palette *pal, Color16 color, int idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		~RemoveColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept override;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept override;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class UpdateColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		Color16 m_oldColor = 0;
 | 
			
		||||
		Color16 m_newColor = 0;
 | 
			
		||||
		int m_idx = -1;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		UpdateColorCommand(Palette *pal, int idx, Color16 oldColor, Color16 newColor) noexcept;
 | 
			
		||||
 | 
			
		||||
		~UpdateColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		bool mergeWith(const UndoCommand *cmd) noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MoveColorCommand: public studio::UndoCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		Palette *m_pal = nullptr;
 | 
			
		||||
		std::size_t m_idx = 0;
 | 
			
		||||
		int m_offset = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		MoveColorCommand(Palette *pal, std::size_t idx, int offset) noexcept;
 | 
			
		||||
 | 
			
		||||
		~MoveColorCommand() noexcept override = default;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept override;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		void redo() noexcept override;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept override;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void moveColor(int idx, int offset) noexcept;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/nostalgia/modules/core/src/studio/studiomodule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/nostalgia/modules/core/src/studio/studiomodule.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/std/memory.hpp>
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include "paletteeditor/paletteeditor-imgui.hpp"
 | 
			
		||||
#include "tilesheeteditor/tilesheeteditor-imgui.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class StudioModule: public studio::Module {
 | 
			
		||||
	ox::Vector<studio::EditorMaker> editors(turbine::Context &ctx) const noexcept final {
 | 
			
		||||
		return {
 | 
			
		||||
			studio::editorMaker<TileSheetEditorImGui>(ctx, FileExt_ng),
 | 
			
		||||
			studio::editorMaker<PaletteEditorImGui>(ctx, FileExt_npal),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(turbine::Context&) const noexcept final {
 | 
			
		||||
		ox::Vector<ox::UniquePtr<studio::ItemMaker>> out;
 | 
			
		||||
		out.emplace_back(ox::make<studio::ItemMakerT<core::TileSheet>>("Tile Sheet", "TileSheets", FileExt_ng));
 | 
			
		||||
		out.emplace_back(ox::make<studio::ItemMakerT<core::Palette>>("Palette", "Palettes", FileExt_npal));
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static StudioModule mod;
 | 
			
		||||
const studio::Module *studioModule() noexcept {
 | 
			
		||||
	return &mod;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
target_sources(
 | 
			
		||||
	NostalgiaCore-Studio PRIVATE
 | 
			
		||||
		tilesheeteditorview.cpp
 | 
			
		||||
		tilesheeteditormodel.cpp
 | 
			
		||||
		tilesheetpixelgrid.cpp
 | 
			
		||||
		tilesheetpixels.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCore-Studio-ImGui PUBLIC
 | 
			
		||||
		lodepng
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(commands)
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
target_sources(
 | 
			
		||||
	NostalgiaCore-Studio PRIVATE
 | 
			
		||||
		addsubsheetcommand.cpp
 | 
			
		||||
		cutpastecommand.cpp
 | 
			
		||||
		deletetilescommand.cpp
 | 
			
		||||
		drawcommand.cpp
 | 
			
		||||
		inserttilescommand.cpp
 | 
			
		||||
		palettechangecommand.cpp
 | 
			
		||||
		rmsubsheetcommand.cpp
 | 
			
		||||
		updatesubsheetcommand.cpp
 | 
			
		||||
)
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "addsubsheetcommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
AddSubSheetCommand::AddSubSheetCommand(
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		TileSheet::SubSheetIdx parentIdx) noexcept:
 | 
			
		||||
		m_img(img), m_parentIdx(std::move(parentIdx)) {
 | 
			
		||||
	auto &parent = m_img.getSubSheet(m_parentIdx);
 | 
			
		||||
	if (!parent.subsheets.empty()) {
 | 
			
		||||
		auto idx = m_parentIdx;
 | 
			
		||||
		idx.emplace_back(parent.subsheets.size());
 | 
			
		||||
		m_addedSheets.push_back(idx);
 | 
			
		||||
	} else {
 | 
			
		||||
		auto idx = m_parentIdx;
 | 
			
		||||
		idx.emplace_back(0u);
 | 
			
		||||
		m_addedSheets.push_back(idx);
 | 
			
		||||
		*idx.back().value = 1;
 | 
			
		||||
		m_addedSheets.push_back(idx);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddSubSheetCommand::redo() noexcept {
 | 
			
		||||
	auto &parent = m_img.getSubSheet(m_parentIdx);
 | 
			
		||||
	if (m_addedSheets.size() < 2) {
 | 
			
		||||
		auto i = parent.subsheets.size();
 | 
			
		||||
		parent.subsheets.emplace_back(m_img.idIt++, ox::sfmt("Subsheet {}", i), 1, 1, m_img.bpp);
 | 
			
		||||
	} else {
 | 
			
		||||
		parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 0", parent.columns, parent.rows, std::move(parent.pixels));
 | 
			
		||||
		parent.rows = 0;
 | 
			
		||||
		parent.columns = 0;
 | 
			
		||||
		parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 1", 1, 1, m_img.bpp);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AddSubSheetCommand::undo() noexcept {
 | 
			
		||||
	auto &parent = m_img.getSubSheet(m_parentIdx);
 | 
			
		||||
	if (parent.subsheets.size() == 2) {
 | 
			
		||||
		auto s = parent.subsheets[0];
 | 
			
		||||
		parent.rows = s.rows;
 | 
			
		||||
		parent.columns = s.columns;
 | 
			
		||||
		parent.pixels = std::move(s.pixels);
 | 
			
		||||
		parent.subsheets.clear();
 | 
			
		||||
	} else {
 | 
			
		||||
		for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
 | 
			
		||||
			oxLogError(m_img.rmSubSheet(*idx));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int AddSubSheetCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(CommandId::AddSubSheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&AddSubSheetCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_parentIdx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class AddSubSheetCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_parentIdx;
 | 
			
		||||
		ox::Vector<TileSheet::SubSheetIdx, 2> m_addedSheets;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		AddSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx parentIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,39 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <studio/undostack.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
// Command IDs to use with QUndoCommand::id()
 | 
			
		||||
enum class CommandId {
 | 
			
		||||
	Draw = 1,
 | 
			
		||||
	AddSubSheet = 2,
 | 
			
		||||
	RmSubSheet = 3,
 | 
			
		||||
	DeleteTile = 4,
 | 
			
		||||
	InsertTile = 4,
 | 
			
		||||
	UpdateSubSheet = 5,
 | 
			
		||||
	Cut = 6,
 | 
			
		||||
	Paste = 7,
 | 
			
		||||
	PaletteChange = 8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr bool operator==(CommandId c, int i) noexcept {
 | 
			
		||||
	return static_cast<int>(c) == i;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr bool operator==(int i, CommandId c) noexcept {
 | 
			
		||||
	return static_cast<int>(c) == i;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TileSheetCommand: public studio::UndoCommand {
 | 
			
		||||
	public:
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		virtual TileSheet::SubSheetIdx const&subsheetIdx() const noexcept = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,66 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "cutpastecommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
TileSheetClipboard::Pixel::Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept {
 | 
			
		||||
	colorIdx = pColorIdx;
 | 
			
		||||
	pt = pPt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void TileSheetClipboard::addPixel(const ox::Point &pt, uint16_t colorIdx) noexcept {
 | 
			
		||||
	m_pixels.emplace_back(colorIdx, pt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ox::Vector<TileSheetClipboard::Pixel> &TileSheetClipboard::pixels() const noexcept {
 | 
			
		||||
	return m_pixels;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CutPasteCommand::CutPasteCommand(
 | 
			
		||||
		CommandId commandId,
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		TileSheet::SubSheetIdx subSheetIdx,
 | 
			
		||||
		const ox::Point &dstStart,
 | 
			
		||||
		const ox::Point &dstEnd,
 | 
			
		||||
		const TileSheetClipboard &cb) noexcept:
 | 
			
		||||
		m_commandId(commandId),
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_subSheetIdx(std::move(subSheetIdx)) {
 | 
			
		||||
	const auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	for (const auto &p : cb.pixels()) {
 | 
			
		||||
		const auto dstPt = p.pt + dstStart;
 | 
			
		||||
		if (dstPt.x <= dstEnd.x && dstPt.y <= dstEnd.y) {
 | 
			
		||||
			const auto idx = subsheet.idx(dstPt);
 | 
			
		||||
			m_changes.emplace_back(static_cast<uint32_t>(idx), p.colorIdx, subsheet.getPixel(m_img.bpp, idx));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CutPasteCommand::redo() noexcept {
 | 
			
		||||
	auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.newPalIdx));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CutPasteCommand::undo() noexcept {
 | 
			
		||||
	auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int CutPasteCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(m_commandId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&CutPasteCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_subSheetIdx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <turbine/clipboard.hpp>
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class TileSheetClipboard: public turbine::ClipboardObject<TileSheetClipboard> {
 | 
			
		||||
	public:
 | 
			
		||||
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
 | 
			
		||||
		static constexpr auto TypeVersion = 1;
 | 
			
		||||
 | 
			
		||||
		oxModelFriend(TileSheetClipboard);
 | 
			
		||||
 | 
			
		||||
		struct Pixel {
 | 
			
		||||
			static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard.Pixel";
 | 
			
		||||
			static constexpr auto TypeVersion = 1;
 | 
			
		||||
			uint16_t colorIdx = 0;
 | 
			
		||||
			ox::Point pt;
 | 
			
		||||
			Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept;
 | 
			
		||||
		};
 | 
			
		||||
	protected:
 | 
			
		||||
		ox::Vector<Pixel> m_pixels;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		void addPixel(ox::Point const&pt, uint16_t colorIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vector<Pixel> const&pixels() const noexcept;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetClipboard::Pixel)
 | 
			
		||||
	oxModelField(colorIdx)
 | 
			
		||||
	oxModelField(pt)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileSheetClipboard)
 | 
			
		||||
	oxModelFieldRename(pixels, m_pixels)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
class CutPasteCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		struct Change {
 | 
			
		||||
			uint32_t idx = 0;
 | 
			
		||||
			uint16_t newPalIdx = 0;
 | 
			
		||||
			uint16_t oldPalIdx = 0;
 | 
			
		||||
			constexpr Change(uint32_t pIdx, uint16_t pNewPalIdx, uint16_t pOldPalIdx) noexcept {
 | 
			
		||||
				idx = pIdx;
 | 
			
		||||
				newPalIdx = pNewPalIdx;
 | 
			
		||||
				oldPalIdx = pOldPalIdx;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
		CommandId m_commandId;
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_subSheetIdx;
 | 
			
		||||
		ox::Vector<Change> m_changes;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		CutPasteCommand(
 | 
			
		||||
				CommandId commandId,
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				TileSheet::SubSheetIdx subSheetIdx,
 | 
			
		||||
				ox::Point const&dstStart,
 | 
			
		||||
				ox::Point const&dstEnd,
 | 
			
		||||
				TileSheetClipboard const&cb) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "deletetilescommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
core::DeleteTilesCommand::DeleteTilesCommand(
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		TileSheet::SubSheetIdx idx,
 | 
			
		||||
		std::size_t tileIdx,
 | 
			
		||||
		std::size_t tileCnt) noexcept:
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_idx(std::move(idx)) {
 | 
			
		||||
	const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
 | 
			
		||||
	m_deletePos = tileIdx * bytesPerTile;
 | 
			
		||||
	m_deleteSz = tileCnt * bytesPerTile;
 | 
			
		||||
	m_deletedPixels.resize(m_deleteSz);
 | 
			
		||||
	// copy pixels to be erased
 | 
			
		||||
	{
 | 
			
		||||
		auto &s = m_img.getSubSheet(m_idx);
 | 
			
		||||
		auto &p = s.pixels;
 | 
			
		||||
		auto dst = m_deletedPixels.data();
 | 
			
		||||
		auto src = p.data() + m_deletePos;
 | 
			
		||||
		const auto sz = m_deleteSz * sizeof(decltype(p[0]));
 | 
			
		||||
		ox_memcpy(dst, src, sz);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void core::DeleteTilesCommand::redo() noexcept {
 | 
			
		||||
	auto &s = m_img.getSubSheet(m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	auto srcPos = m_deletePos + m_deleteSz;
 | 
			
		||||
	const auto src = p.data() + srcPos;
 | 
			
		||||
	const auto dst1 = p.data() + m_deletePos;
 | 
			
		||||
	const auto dst2 = p.data() + (p.size() - m_deleteSz);
 | 
			
		||||
	ox_memmove(dst1, src, p.size() - srcPos);
 | 
			
		||||
	ox_memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DeleteTilesCommand::undo() noexcept {
 | 
			
		||||
	auto &s = m_img.getSubSheet(m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	const auto src = p.data() + m_deletePos;
 | 
			
		||||
	const auto dst1 = p.data() + m_deletePos + m_deleteSz;
 | 
			
		||||
	const auto dst2 = src;
 | 
			
		||||
	const auto sz = p.size() - m_deletePos - m_deleteSz;
 | 
			
		||||
	ox_memmove(dst1, src, sz);
 | 
			
		||||
	ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int DeleteTilesCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(CommandId::DeleteTile);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&DeleteTilesCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class DeleteTilesCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_idx;
 | 
			
		||||
		std::size_t m_deletePos = 0;
 | 
			
		||||
		std::size_t m_deleteSz = 0;
 | 
			
		||||
		ox::Vector<uint8_t> m_deletedPixels = {};
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		DeleteTilesCommand(
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				TileSheet::SubSheetIdx idx,
 | 
			
		||||
				std::size_t tileIdx,
 | 
			
		||||
				std::size_t tileCnt) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,81 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "drawcommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
DrawCommand::DrawCommand(
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		TileSheet::SubSheetIdx subSheetIdx,
 | 
			
		||||
		std::size_t idx,
 | 
			
		||||
		int palIdx) noexcept:
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_subSheetIdx(std::move(subSheetIdx)),
 | 
			
		||||
		m_palIdx(palIdx) {
 | 
			
		||||
	auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DrawCommand::DrawCommand(
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		TileSheet::SubSheetIdx subSheetIdx,
 | 
			
		||||
		const ox::Vector<std::size_t> &idxList,
 | 
			
		||||
		int palIdx) noexcept:
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_subSheetIdx(std::move(subSheetIdx)),
 | 
			
		||||
		m_palIdx(palIdx) {
 | 
			
		||||
	auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	for (const auto idx : idxList) {
 | 
			
		||||
		m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DrawCommand::append(std::size_t idx) noexcept {
 | 
			
		||||
	auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	if (m_changes.back().value->idx != idx && subsheet.getPixel(m_img.bpp, idx) != m_palIdx) {
 | 
			
		||||
		// duplicate entries are bad
 | 
			
		||||
		auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
 | 
			
		||||
			return c.idx == idx;
 | 
			
		||||
		});
 | 
			
		||||
		if (existing == m_changes.cend()) {
 | 
			
		||||
			m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
 | 
			
		||||
			subsheet.setPixel(m_img.bpp, idx, static_cast<uint8_t>(m_palIdx));
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DrawCommand::append(const ox::Vector<std::size_t> &idxList) noexcept {
 | 
			
		||||
	auto out = false;
 | 
			
		||||
	for (auto idx : idxList) {
 | 
			
		||||
		out = append(idx) || out;
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DrawCommand::redo() noexcept {
 | 
			
		||||
	auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(m_palIdx));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DrawCommand::undo() noexcept {
 | 
			
		||||
	auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
 | 
			
		||||
	for (const auto &c : m_changes) {
 | 
			
		||||
		subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int DrawCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(CommandId::Draw);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&DrawCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_subSheetIdx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class DrawCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		struct Change {
 | 
			
		||||
			uint32_t idx = 0;
 | 
			
		||||
			uint16_t oldPalIdx = 0;
 | 
			
		||||
			constexpr Change(uint32_t pIdx, uint16_t pOldPalIdx) noexcept {
 | 
			
		||||
				idx = pIdx;
 | 
			
		||||
				oldPalIdx = pOldPalIdx;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_subSheetIdx;
 | 
			
		||||
		ox::Vector<Change, 8> m_changes;
 | 
			
		||||
		int m_palIdx = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		DrawCommand(
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				TileSheet::SubSheetIdx subSheetIdx,
 | 
			
		||||
				std::size_t idx,
 | 
			
		||||
				int palIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		DrawCommand(
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				TileSheet::SubSheetIdx subSheetIdx,
 | 
			
		||||
				const ox::Vector<std::size_t> &idxList,
 | 
			
		||||
				int palIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		bool append(std::size_t idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		bool append(const ox::Vector<std::size_t> &idxList) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "inserttilescommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
core::InsertTilesCommand::InsertTilesCommand(
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		TileSheet::SubSheetIdx idx,
 | 
			
		||||
		std::size_t tileIdx,
 | 
			
		||||
		std::size_t tileCnt) noexcept:
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_idx(std::move(idx)) {
 | 
			
		||||
	const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
 | 
			
		||||
	m_insertPos = tileIdx * bytesPerTile;
 | 
			
		||||
	m_insertCnt = tileCnt * bytesPerTile;
 | 
			
		||||
	m_deletedPixels.resize(m_insertCnt);
 | 
			
		||||
	// copy pixels to be erased
 | 
			
		||||
	{
 | 
			
		||||
		auto &s = m_img.getSubSheet(m_idx);
 | 
			
		||||
		auto &p = s.pixels;
 | 
			
		||||
		auto dst = m_deletedPixels.data();
 | 
			
		||||
		auto src = p.data() + p.size() - m_insertCnt;
 | 
			
		||||
		const auto sz = m_insertCnt * sizeof(decltype(p[0]));
 | 
			
		||||
		ox_memcpy(dst, src, sz);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void InsertTilesCommand::redo() noexcept {
 | 
			
		||||
	auto &s = m_img.getSubSheet(m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	auto dstPos = m_insertPos + m_insertCnt;
 | 
			
		||||
	const auto dst = p.data() + dstPos;
 | 
			
		||||
	const auto src = p.data() + m_insertPos;
 | 
			
		||||
	ox_memmove(dst, src, p.size() - dstPos);
 | 
			
		||||
	ox_memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void InsertTilesCommand::undo() noexcept {
 | 
			
		||||
	auto &s = m_img.getSubSheet(m_idx);
 | 
			
		||||
	auto &p = s.pixels;
 | 
			
		||||
	const auto srcIdx = m_insertPos + m_insertCnt;
 | 
			
		||||
	const auto src = p.data() + srcIdx;
 | 
			
		||||
	const auto dst1 = p.data() + m_insertPos;
 | 
			
		||||
	const auto dst2 = p.data() + p.size() - m_insertCnt;
 | 
			
		||||
	const auto sz = p.size() - srcIdx;
 | 
			
		||||
	ox_memmove(dst1, src, sz);
 | 
			
		||||
	ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int InsertTilesCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(CommandId::InsertTile);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&InsertTilesCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class InsertTilesCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_idx;
 | 
			
		||||
		std::size_t m_insertPos = 0;
 | 
			
		||||
		std::size_t m_insertCnt = 0;
 | 
			
		||||
		ox::Vector<uint8_t> m_deletedPixels = {};
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		InsertTilesCommand(
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				TileSheet::SubSheetIdx idx,
 | 
			
		||||
				std::size_t tileIdx,
 | 
			
		||||
				std::size_t tileCnt) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "palettechangecommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
core::PaletteChangeCommand::PaletteChangeCommand(
 | 
			
		||||
		TileSheet::SubSheetIdx idx,
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		ox::CRStringView newPalette) noexcept:
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_idx(std::move(idx)),
 | 
			
		||||
		m_oldPalette(m_img.defaultPalette),
 | 
			
		||||
		m_newPalette(ox::FileAddress(ox::sfmt<ox::BString<43>>("uuid://{}", newPalette))) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteChangeCommand::redo() noexcept {
 | 
			
		||||
	m_img.defaultPalette = m_newPalette;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PaletteChangeCommand::undo() noexcept {
 | 
			
		||||
	m_img.defaultPalette = m_oldPalette;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int PaletteChangeCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(CommandId::PaletteChange);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&PaletteChangeCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class PaletteChangeCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_idx;
 | 
			
		||||
		ox::FileAddress m_oldPalette;
 | 
			
		||||
		ox::FileAddress m_newPalette;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		PaletteChangeCommand(
 | 
			
		||||
				TileSheet::SubSheetIdx idx,
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				ox::CRStringView newPalette) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "rmsubsheetcommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
core::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept:
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_idx(std::move(idx)),
 | 
			
		||||
		m_parentIdx(m_idx) {
 | 
			
		||||
	m_parentIdx.pop_back();
 | 
			
		||||
	auto &parent = m_img.getSubSheet(m_parentIdx);
 | 
			
		||||
	m_sheet = parent.subsheets[*m_idx.back().value];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RmSubSheetCommand::redo() noexcept {
 | 
			
		||||
	auto &parent = m_img.getSubSheet(m_parentIdx);
 | 
			
		||||
	oxLogError(parent.subsheets.erase(*m_idx.back().value).error);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RmSubSheetCommand::undo() noexcept {
 | 
			
		||||
	auto &parent = m_img.getSubSheet(m_parentIdx);
 | 
			
		||||
	auto i = *m_idx.back().value;
 | 
			
		||||
	parent.subsheets.insert(i, m_sheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int RmSubSheetCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(CommandId::RmSubSheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&RmSubSheetCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class RmSubSheetCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_idx;
 | 
			
		||||
		TileSheet::SubSheetIdx m_parentIdx;
 | 
			
		||||
		TileSheet::SubSheet m_sheet;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "updatesubsheetcommand.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
core::UpdateSubSheetCommand::UpdateSubSheetCommand(
 | 
			
		||||
		TileSheet &img,
 | 
			
		||||
		TileSheet::SubSheetIdx idx,
 | 
			
		||||
		ox::String name,
 | 
			
		||||
		int cols,
 | 
			
		||||
		int rows) noexcept:
 | 
			
		||||
		m_img(img),
 | 
			
		||||
		m_idx(std::move(idx)),
 | 
			
		||||
		m_sheet(m_img.getSubSheet(m_idx)),
 | 
			
		||||
		m_newName(std::move(name)),
 | 
			
		||||
		m_newCols(cols),
 | 
			
		||||
		m_newRows(rows) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateSubSheetCommand::redo() noexcept {
 | 
			
		||||
	auto &sheet = m_img.getSubSheet(m_idx);
 | 
			
		||||
	sheet.name = m_newName;
 | 
			
		||||
	sheet.columns = m_newCols;
 | 
			
		||||
	sheet.rows = m_newRows;
 | 
			
		||||
	oxLogError(sheet.setPixelCount(m_img.bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void UpdateSubSheetCommand::undo() noexcept {
 | 
			
		||||
	auto &sheet = m_img.getSubSheet(m_idx);
 | 
			
		||||
	sheet = m_sheet;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int UpdateSubSheetCommand::commandId() const noexcept {
 | 
			
		||||
	return static_cast<int>(CommandId::UpdateSubSheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx const&UpdateSubSheetCommand::subsheetIdx() const noexcept {
 | 
			
		||||
	return m_idx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "commands.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class UpdateSubSheetCommand: public TileSheetCommand {
 | 
			
		||||
	private:
 | 
			
		||||
		TileSheet &m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_idx;
 | 
			
		||||
		TileSheet::SubSheet m_sheet;
 | 
			
		||||
		ox::String m_newName;
 | 
			
		||||
		int m_newCols = 0;
 | 
			
		||||
		int m_newRows = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		UpdateSubSheetCommand(
 | 
			
		||||
				TileSheet &img,
 | 
			
		||||
				TileSheet::SubSheetIdx idx,
 | 
			
		||||
				ox::String name,
 | 
			
		||||
				int cols,
 | 
			
		||||
				int rows) noexcept;
 | 
			
		||||
 | 
			
		||||
		void redo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		void undo() noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		int commandId() const noexcept final;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,512 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
#include <lodepng.h>
 | 
			
		||||
 | 
			
		||||
#include <ox/std/point.hpp>
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include "tilesheeteditor-imgui.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
static ox::Vector<uint32_t> normalizePixelSizes(
 | 
			
		||||
		ox::Vector<uint8_t> const&inPixels,
 | 
			
		||||
		int const bpp) noexcept {
 | 
			
		||||
	uint_t const bytesPerTile = bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
 | 
			
		||||
	ox::Vector<uint32_t> outPixels;
 | 
			
		||||
	if (bytesPerTile == 64) { // 8 BPP
 | 
			
		||||
		outPixels.resize(inPixels.size());
 | 
			
		||||
		for (std::size_t i = 0; i < inPixels.size(); ++i) {
 | 
			
		||||
			outPixels[i] = inPixels[i];
 | 
			
		||||
		}
 | 
			
		||||
	} else { // 4 BPP
 | 
			
		||||
		outPixels.resize(inPixels.size() * 2);
 | 
			
		||||
		for (std::size_t i = 0; i < inPixels.size(); ++i) {
 | 
			
		||||
			outPixels[i * 2 + 0] = inPixels[i] & 0xF;
 | 
			
		||||
			outPixels[i * 2 + 1] = inPixels[i] >> 4;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return outPixels;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ox::Vector<uint32_t> normalizePixelArrangement(
 | 
			
		||||
		ox::Vector<uint32_t> const&inPixels,
 | 
			
		||||
		int cols,
 | 
			
		||||
		int scale) {
 | 
			
		||||
	auto const scalePt = ox::Point{scale, scale};
 | 
			
		||||
	auto const width = cols * TileWidth;
 | 
			
		||||
	auto const height = static_cast<int>(inPixels.size()) / width;
 | 
			
		||||
	auto const dstWidth = width * scale;
 | 
			
		||||
	ox::Vector<uint32_t> outPixels(static_cast<size_t>((width * scale) * (height * scale)));
 | 
			
		||||
	for (std::size_t dstIdx = 0; dstIdx < outPixels.size(); ++dstIdx) {
 | 
			
		||||
		auto const dstPt = ox::Point{
 | 
			
		||||
			static_cast<int>(dstIdx) % dstWidth,
 | 
			
		||||
			static_cast<int>(dstIdx) / dstWidth};
 | 
			
		||||
		auto const srcPt = dstPt / scalePt;
 | 
			
		||||
		auto const srcIdx = ptToIdx(srcPt, cols);
 | 
			
		||||
		outPixels[dstIdx] = inPixels[srcIdx];
 | 
			
		||||
	}
 | 
			
		||||
	return outPixels;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static ox::Error toPngFile(
 | 
			
		||||
		ox::CStringView const&path,
 | 
			
		||||
		ox::Vector<uint32_t> &&pixels,
 | 
			
		||||
		Palette const&pal,
 | 
			
		||||
		unsigned width,
 | 
			
		||||
		unsigned height) noexcept {
 | 
			
		||||
	for (auto &c : pixels) {
 | 
			
		||||
		c = color32(pal.color(c)) | static_cast<Color32>(0XFF << 24);
 | 
			
		||||
	}
 | 
			
		||||
	constexpr auto fmt = LCT_RGBA;
 | 
			
		||||
	return OxError(static_cast<ox::ErrorCode>(
 | 
			
		||||
			lodepng_encode_file(
 | 
			
		||||
					path.c_str(),
 | 
			
		||||
					reinterpret_cast<uint8_t const*>(pixels.data()),
 | 
			
		||||
					width,
 | 
			
		||||
					height,
 | 
			
		||||
					fmt,
 | 
			
		||||
					8)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheetEditorImGui::TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringView path):
 | 
			
		||||
		Editor(path),
 | 
			
		||||
		m_ctx(ctx),
 | 
			
		||||
		m_view(m_ctx, path, *undoStack()),
 | 
			
		||||
		m_model(m_view.model()) {
 | 
			
		||||
	oxIgnoreError(setPaletteSelection());
 | 
			
		||||
	// connect signal/slots
 | 
			
		||||
	undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges);
 | 
			
		||||
	m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
 | 
			
		||||
	m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
 | 
			
		||||
	m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::exportFile() {
 | 
			
		||||
	m_exportMenu.show();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::cut() {
 | 
			
		||||
	m_model.cut();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::copy() {
 | 
			
		||||
	m_model.copy();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::paste() {
 | 
			
		||||
	m_model.paste();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
 | 
			
		||||
	if (!down) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	if (key == turbine::Key::Escape) {
 | 
			
		||||
		m_subsheetEditor.close();
 | 
			
		||||
		m_exportMenu.close();
 | 
			
		||||
	}
 | 
			
		||||
	auto pal = m_model.pal();
 | 
			
		||||
	if (pal) {
 | 
			
		||||
		const auto colorCnt = pal->colors.size();
 | 
			
		||||
		if (key == turbine::Key::Alpha_D) {
 | 
			
		||||
			m_tool = Tool::Draw;
 | 
			
		||||
			setCopyEnabled(false);
 | 
			
		||||
			setCutEnabled(false);
 | 
			
		||||
			setPasteEnabled(false);
 | 
			
		||||
			m_model.clearSelection();
 | 
			
		||||
		} else if (key == turbine::Key::Alpha_S) {
 | 
			
		||||
			m_tool = Tool::Select;
 | 
			
		||||
			setCopyEnabled(true);
 | 
			
		||||
			setCutEnabled(true);
 | 
			
		||||
			setPasteEnabled(true);
 | 
			
		||||
		} else if (key == turbine::Key::Alpha_F) {
 | 
			
		||||
			m_tool = Tool::Fill;
 | 
			
		||||
			setCopyEnabled(false);
 | 
			
		||||
			setCutEnabled(false);
 | 
			
		||||
			setPasteEnabled(false);
 | 
			
		||||
			m_model.clearSelection();
 | 
			
		||||
		} else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9 && key <= turbine::Key::Num_0 + colorCnt) {
 | 
			
		||||
			auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
 | 
			
		||||
			m_view.setPalIdx(idx);
 | 
			
		||||
		} else if (key == turbine::Key::Num_0 && colorCnt >= 10) {
 | 
			
		||||
			auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), colorCnt - 1);
 | 
			
		||||
			m_view.setPalIdx(idx);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::draw(turbine::Context&) noexcept {
 | 
			
		||||
	const auto paneSize = ImGui::GetContentRegionAvail();
 | 
			
		||||
	const auto tileSheetParentSize = ImVec2(paneSize.x - m_palViewWidth, paneSize.y);
 | 
			
		||||
	const auto fbSize = ox::Vec2(tileSheetParentSize.x - 16, tileSheetParentSize.y - 16);
 | 
			
		||||
	ImGui::BeginChild("TileSheetView", tileSheetParentSize, true);
 | 
			
		||||
	{
 | 
			
		||||
		drawTileSheet(fbSize);
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::EndChild();
 | 
			
		||||
	ImGui::SameLine();
 | 
			
		||||
	ImGui::BeginChild("Controls", ImVec2(m_palViewWidth - 8, paneSize.y), true);
 | 
			
		||||
	{
 | 
			
		||||
		const auto controlsSize = ImGui::GetContentRegionAvail();
 | 
			
		||||
		ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
 | 
			
		||||
		{
 | 
			
		||||
			const auto btnSz = ImVec2(45, 14);
 | 
			
		||||
			if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
 | 
			
		||||
				m_tool = Tool::Select;
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
 | 
			
		||||
				m_tool = Tool::Draw;
 | 
			
		||||
				m_model.clearSelection();
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
 | 
			
		||||
				m_tool = Tool::Fill;
 | 
			
		||||
				m_model.clearSelection();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndChild();
 | 
			
		||||
		const auto ySize = controlsSize.y - 38;
 | 
			
		||||
		// draw palette/color picker
 | 
			
		||||
		ImGui::BeginChild("Palette", ImVec2(m_palViewWidth - 24, ySize / 2.f), true);
 | 
			
		||||
		{
 | 
			
		||||
			drawPaletteSelector();
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndChild();
 | 
			
		||||
		ImGui::BeginChild("SubSheets", ImVec2(m_palViewWidth - 24, ySize / 2.f), true);
 | 
			
		||||
		{
 | 
			
		||||
			static constexpr auto btnHeight = 18;
 | 
			
		||||
			const auto btnSize = ImVec2(18, btnHeight);
 | 
			
		||||
			if (ImGui::Button("+", btnSize)) {
 | 
			
		||||
				auto insertOnIdx = m_model.activeSubSheetIdx();
 | 
			
		||||
				const auto &parent = *m_model.activeSubSheet();
 | 
			
		||||
				m_model.addSubsheet(insertOnIdx);
 | 
			
		||||
				insertOnIdx.emplace_back(parent.subsheets.size() - 1);
 | 
			
		||||
				m_model.setActiveSubsheet(insertOnIdx);
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Button("-", btnSize)) {
 | 
			
		||||
				const auto &activeSubsheetIdx = m_model.activeSubSheetIdx();
 | 
			
		||||
				if (activeSubsheetIdx.size() > 0) {
 | 
			
		||||
					m_model.rmSubsheet(activeSubsheetIdx);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Button("Edit", ImVec2(51, btnHeight))) {
 | 
			
		||||
				showSubsheetEditor();
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::SameLine();
 | 
			
		||||
			if (ImGui::Button("Export", ImVec2(51, btnHeight))) {
 | 
			
		||||
				m_exportMenu.show();
 | 
			
		||||
			}
 | 
			
		||||
			TileSheet::SubSheetIdx path;
 | 
			
		||||
			static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
 | 
			
		||||
			if (ImGui::BeginTable("Subsheets", 3, flags)) {
 | 
			
		||||
				ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide);
 | 
			
		||||
				ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
				ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50);
 | 
			
		||||
				ImGui::TableHeadersRow();
 | 
			
		||||
				drawSubsheetSelector(&m_view.img().subsheet, &path);
 | 
			
		||||
				ImGui::EndTable();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndChild();
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::EndChild();
 | 
			
		||||
	m_subsheetEditor.draw();
 | 
			
		||||
	m_exportMenu.draw();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) {
 | 
			
		||||
	ImGui::TableNextRow(0, 5);
 | 
			
		||||
	using Str = ox::BasicString<100>;
 | 
			
		||||
	auto pathStr = ox::join<Str>("##", *path).value;
 | 
			
		||||
	auto lbl = ox::sfmt<Str>("{}##{}", subsheet->name, pathStr);
 | 
			
		||||
	const auto rowSelected = *path == m_model.activeSubSheetIdx();
 | 
			
		||||
	const auto flags = ImGuiTreeNodeFlags_SpanFullWidth
 | 
			
		||||
	                 | ImGuiTreeNodeFlags_OpenOnArrow
 | 
			
		||||
	                 | ImGuiTreeNodeFlags_DefaultOpen
 | 
			
		||||
	                 | (subsheet->subsheets.empty() ? ImGuiTreeNodeFlags_Leaf : 0)
 | 
			
		||||
	                 | (rowSelected ? ImGuiTreeNodeFlags_Selected : 0);
 | 
			
		||||
	ImGui::TableNextColumn();
 | 
			
		||||
	const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags);
 | 
			
		||||
	ImGui::SameLine();
 | 
			
		||||
	if (ImGui::IsItemClicked()) {
 | 
			
		||||
		m_model.setActiveSubsheet(*path);
 | 
			
		||||
	}
 | 
			
		||||
	if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
 | 
			
		||||
		showSubsheetEditor();
 | 
			
		||||
	}
 | 
			
		||||
	if (subsheet->subsheets.empty()) {
 | 
			
		||||
		ImGui::TableNextColumn();
 | 
			
		||||
		ImGui::Text("%d", subsheet->columns);
 | 
			
		||||
		ImGui::TableNextColumn();
 | 
			
		||||
		ImGui::Text("%d", subsheet->rows);
 | 
			
		||||
	} else {
 | 
			
		||||
		ImGui::TableNextColumn();
 | 
			
		||||
		ImGui::Text("--");
 | 
			
		||||
		ImGui::TableNextColumn();
 | 
			
		||||
		ImGui::Text("--");
 | 
			
		||||
	}
 | 
			
		||||
	if (open) {
 | 
			
		||||
		for (auto i = 0ul; auto &child : subsheet->subsheets) {
 | 
			
		||||
			path->push_back(i);
 | 
			
		||||
			ImGui::PushID(static_cast<int>(i));
 | 
			
		||||
			drawSubsheetSelector(&child, path);
 | 
			
		||||
			ImGui::PopID();
 | 
			
		||||
			path->pop_back();
 | 
			
		||||
			++i;
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::TreePop();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
ox::Vec2 TileSheetEditorImGui::clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept {
 | 
			
		||||
	clickPos.x -= winPos.x + 10;
 | 
			
		||||
	clickPos.y -= winPos.y + 10;
 | 
			
		||||
	return clickPos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorImGui::saveItem() noexcept {
 | 
			
		||||
	return m_model.saveFile();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::showSubsheetEditor() noexcept {
 | 
			
		||||
	const auto sheet = m_model.activeSubSheet();
 | 
			
		||||
	if (!sheet->subsheets.empty()) {
 | 
			
		||||
		m_subsheetEditor.show(sheet->name, -1, -1);
 | 
			
		||||
	} else {
 | 
			
		||||
		m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int scale) noexcept {
 | 
			
		||||
	oxRequire(path, studio::saveFile({{"PNG", "png"}}));
 | 
			
		||||
	// subsheet to png
 | 
			
		||||
	auto const&img = m_model.img();
 | 
			
		||||
	auto const&s = *m_model.activeSubSheet();
 | 
			
		||||
	auto const&pal = m_model.pal();
 | 
			
		||||
	auto const width = s.columns * TileWidth;
 | 
			
		||||
	auto const height = s.rows * TileHeight;
 | 
			
		||||
	auto pixels = normalizePixelSizes(s.pixels, img.bpp);
 | 
			
		||||
	pixels = normalizePixelArrangement(pixels, s.columns, scale);
 | 
			
		||||
	auto const err = toPngFile(
 | 
			
		||||
			path,
 | 
			
		||||
			std::move(pixels),
 | 
			
		||||
			*pal,
 | 
			
		||||
			static_cast<unsigned>(width * scale),
 | 
			
		||||
			static_cast<unsigned>(height * scale));
 | 
			
		||||
	if (err) {
 | 
			
		||||
		oxErrorf("Tilesheet export failed: {}", toStr(err));
 | 
			
		||||
	}
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
 | 
			
		||||
	const auto winPos = ImGui::GetWindowPos();
 | 
			
		||||
	const auto fbSizei = ox::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
 | 
			
		||||
	if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
 | 
			
		||||
		glutils::resizeInitFrameBuffer(m_framebuffer, fbSizei.width, fbSizei.height);
 | 
			
		||||
		m_view.resizeView(fbSize);
 | 
			
		||||
	} else if (m_view.updated()) {
 | 
			
		||||
		m_view.ackUpdate();
 | 
			
		||||
	}
 | 
			
		||||
	glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
 | 
			
		||||
	// clear screen and draw
 | 
			
		||||
	glViewport(0, 0, fbSizei.width, fbSizei.height);
 | 
			
		||||
	m_view.draw();
 | 
			
		||||
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 | 
			
		||||
	ImGui::Image(
 | 
			
		||||
		std::bit_cast<ImTextureID>(uintptr_t{m_framebuffer.color.id}),
 | 
			
		||||
		static_cast<ImVec2>(fbSize),
 | 
			
		||||
		ImVec2(0, 1),
 | 
			
		||||
		ImVec2(1, 0));
 | 
			
		||||
	// handle input, this must come after drawing
 | 
			
		||||
	const auto &io = ImGui::GetIO();
 | 
			
		||||
	const auto mousePos = ox::Vec2(io.MousePos);
 | 
			
		||||
	if (ImGui::IsItemHovered()) {
 | 
			
		||||
		const auto wheel = io.MouseWheel;
 | 
			
		||||
		const auto wheelh = io.MouseWheelH;
 | 
			
		||||
		if (wheel != 0) {
 | 
			
		||||
			const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
 | 
			
		||||
			                     io.KeySuper : turbine::buttonDown(m_ctx, turbine::Key::Mod_Ctrl);
 | 
			
		||||
			m_view.scrollV(fbSize, wheel, zoomMod);
 | 
			
		||||
		}
 | 
			
		||||
		if (wheelh != 0) {
 | 
			
		||||
			m_view.scrollH(fbSize, wheelh);
 | 
			
		||||
		}
 | 
			
		||||
		if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
 | 
			
		||||
			m_prevMouseDownPos = mousePos;
 | 
			
		||||
			switch (m_tool) {
 | 
			
		||||
				case Tool::Draw:
 | 
			
		||||
					m_view.clickDraw(fbSize, clickPos(winPos, mousePos));
 | 
			
		||||
					break;
 | 
			
		||||
				case Tool::Fill:
 | 
			
		||||
					m_view.clickFill(fbSize, clickPos(winPos, mousePos));
 | 
			
		||||
					break;
 | 
			
		||||
				case Tool::Select:
 | 
			
		||||
					m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
 | 
			
		||||
					break;
 | 
			
		||||
				case Tool::None:
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) {
 | 
			
		||||
		const auto popupPos = ox::Vec2(ImGui::GetWindowPos());
 | 
			
		||||
		if (ImGui::MenuItem("Insert Tile")) {
 | 
			
		||||
			m_view.insertTile(fbSize, clickPos(winPos, popupPos));
 | 
			
		||||
		}
 | 
			
		||||
		if (ImGui::MenuItem("Delete Tile")) {
 | 
			
		||||
			m_view.deleteTile(fbSize, clickPos(winPos, popupPos));
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndPopup();
 | 
			
		||||
	}
 | 
			
		||||
	if (io.MouseReleased[0]) {
 | 
			
		||||
		m_prevMouseDownPos = {-1, -1};
 | 
			
		||||
		m_view.releaseMouseButton();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::drawPaletteSelector() noexcept {
 | 
			
		||||
	auto &sctx = *applicationData<studio::StudioContext>(m_ctx);
 | 
			
		||||
	const auto &files = sctx.project->fileList(core::FileExt_npal);
 | 
			
		||||
	const auto first = m_selectedPaletteIdx < files.size() ?
 | 
			
		||||
		files[m_selectedPaletteIdx].c_str() : "";
 | 
			
		||||
	if (ImGui::BeginCombo("Palette", first, 0)) {
 | 
			
		||||
		for (auto n = 0u; n < files.size(); n++) {
 | 
			
		||||
			const auto selected = (m_selectedPaletteIdx == n);
 | 
			
		||||
			if (ImGui::Selectable(files[n].c_str(), selected) && m_selectedPaletteIdx != n) {
 | 
			
		||||
				m_selectedPaletteIdx = n;
 | 
			
		||||
				oxLogError(m_model.setPalette(files[n]));
 | 
			
		||||
			}
 | 
			
		||||
			if (selected) {
 | 
			
		||||
				ImGui::SetItemDefaultFocus();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndCombo();
 | 
			
		||||
	}
 | 
			
		||||
	// header
 | 
			
		||||
	if (ImGui::BeginTable("PaletteTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
 | 
			
		||||
		ImGui::TableSetupColumn("No.", 0, 0.45f);
 | 
			
		||||
		ImGui::TableSetupColumn("", 0, 0.22f);
 | 
			
		||||
		ImGui::TableSetupColumn("Color16", 0, 3);
 | 
			
		||||
		ImGui::TableHeadersRow();
 | 
			
		||||
		if (auto pal = m_view.pal()) {
 | 
			
		||||
			for (auto i = 0u; auto c: pal->colors) {
 | 
			
		||||
				ImGui::PushID(static_cast<int>(i));
 | 
			
		||||
				// Column: color idx
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				const auto label = ox::BString<8>() + (i + 1);
 | 
			
		||||
				const auto rowSelected = i == m_view.palIdx();
 | 
			
		||||
				if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
 | 
			
		||||
					m_view.setPalIdx(i);
 | 
			
		||||
				}
 | 
			
		||||
				// Column: color RGB
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
 | 
			
		||||
				ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
 | 
			
		||||
				ImGui::TableNextColumn();
 | 
			
		||||
				ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
 | 
			
		||||
				ImGui::TableNextRow();
 | 
			
		||||
				ImGui::PopID();
 | 
			
		||||
				++i;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndTable();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorImGui::updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept {
 | 
			
		||||
	return m_model.updateSubsheet(m_model.activeSubSheetIdx(), name, cols, rows);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
 | 
			
		||||
	const auto &palPath = m_model.palPath();
 | 
			
		||||
	auto &sctx = *applicationData<studio::StudioContext>(m_ctx);
 | 
			
		||||
	const auto &palList = sctx.project->fileList(core::FileExt_npal);
 | 
			
		||||
	for (std::size_t i = 0; const auto &pal : palList) {
 | 
			
		||||
		if (palPath == pal) {
 | 
			
		||||
			m_selectedPaletteIdx = i;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
		++i;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorImGui::markUnsavedChanges(const studio::UndoCommand*) noexcept {
 | 
			
		||||
	setUnsavedChanges(true);
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::SubSheetEditor::draw() noexcept {
 | 
			
		||||
	constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
 | 
			
		||||
	constexpr auto popupName = "Edit Subsheet";
 | 
			
		||||
	if (!m_show) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::OpenPopup(popupName);
 | 
			
		||||
	const auto modSize = m_cols > 0;
 | 
			
		||||
	const auto popupHeight = modSize ? 125.f : 80.f;
 | 
			
		||||
	ImGui::SetNextWindowSize(ImVec2(235, popupHeight));
 | 
			
		||||
	if (ImGui::BeginPopupModal(popupName, &m_show, modalFlags)) {
 | 
			
		||||
		ImGui::InputText("Name", m_name.data(), m_name.cap());
 | 
			
		||||
		if (modSize) {
 | 
			
		||||
			ImGui::InputInt("Columns", &m_cols);
 | 
			
		||||
			ImGui::InputInt("Rows", &m_rows);
 | 
			
		||||
		}
 | 
			
		||||
		if (ImGui::Button("OK", ImVec2{50, 20})) {
 | 
			
		||||
			ImGui::CloseCurrentPopup();
 | 
			
		||||
			m_show = false;
 | 
			
		||||
			inputSubmitted.emit(m_name, m_cols, m_rows);
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::SameLine();
 | 
			
		||||
		if (ImGui::Button("Cancel", ImVec2{50, 20})) {
 | 
			
		||||
			ImGui::CloseCurrentPopup();
 | 
			
		||||
			m_show = false;
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndPopup();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::SubSheetEditor::close() noexcept {
 | 
			
		||||
	m_show = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::ExportMenu::draw() noexcept {
 | 
			
		||||
	constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
 | 
			
		||||
	constexpr auto popupName = "Export Tile Sheet";
 | 
			
		||||
	if (!m_show) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	ImGui::OpenPopup(popupName);
 | 
			
		||||
	constexpr auto popupHeight = 80.f;
 | 
			
		||||
	ImGui::SetNextWindowSize(ImVec2(235, popupHeight));
 | 
			
		||||
	if (ImGui::BeginPopupModal(popupName, &m_show, modalFlags)) {
 | 
			
		||||
		ImGui::InputInt("Scale", &m_scale);
 | 
			
		||||
		m_scale = ox::clamp(m_scale, 1, 20);
 | 
			
		||||
		if (ImGui::Button("OK", ImVec2{50, 20})) {
 | 
			
		||||
			ImGui::CloseCurrentPopup();
 | 
			
		||||
			m_show = false;
 | 
			
		||||
			inputSubmitted.emit(m_scale);
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::SameLine();
 | 
			
		||||
		if (ImGui::Button("Cancel", ImVec2{50, 20})) {
 | 
			
		||||
			ImGui::CloseCurrentPopup();
 | 
			
		||||
			m_show = false;
 | 
			
		||||
		}
 | 
			
		||||
		ImGui::EndPopup();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorImGui::ExportMenu::close() noexcept {
 | 
			
		||||
	m_show = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,113 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/model/def.hpp>
 | 
			
		||||
#include <ox/std/vec.hpp>
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
#include <studio/editor.hpp>
 | 
			
		||||
 | 
			
		||||
#include "tilesheetpixelgrid.hpp"
 | 
			
		||||
#include "tilesheetpixels.hpp"
 | 
			
		||||
#include "tilesheeteditorview.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
enum class Tool {
 | 
			
		||||
	None,
 | 
			
		||||
	Draw,
 | 
			
		||||
	Fill,
 | 
			
		||||
	Select,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class TileSheetEditorImGui: public studio::Editor {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		class SubSheetEditor {
 | 
			
		||||
				ox::BString<100> m_name;
 | 
			
		||||
				int m_cols = 0;
 | 
			
		||||
				int m_rows = 0;
 | 
			
		||||
				bool m_show = false;
 | 
			
		||||
			public:
 | 
			
		||||
				ox::Signal<ox::Error(ox::StringView const&name, int cols, int rows)> inputSubmitted;
 | 
			
		||||
				constexpr void show(ox::StringView const&name, int cols, int rows) noexcept {
 | 
			
		||||
					m_show = true;
 | 
			
		||||
					m_name = name;
 | 
			
		||||
					m_cols = cols;
 | 
			
		||||
					m_rows = rows;
 | 
			
		||||
				}
 | 
			
		||||
				void draw() noexcept;
 | 
			
		||||
				void close() noexcept;
 | 
			
		||||
		};
 | 
			
		||||
		class ExportMenu {
 | 
			
		||||
			int m_scale = 0;
 | 
			
		||||
			bool m_show = false;
 | 
			
		||||
			public:
 | 
			
		||||
				ox::Signal<ox::Error(int scale)> inputSubmitted;
 | 
			
		||||
				constexpr void show() noexcept {
 | 
			
		||||
					m_show = true;
 | 
			
		||||
					m_scale = 5;
 | 
			
		||||
				}
 | 
			
		||||
				void draw() noexcept;
 | 
			
		||||
				void close() noexcept;
 | 
			
		||||
		};
 | 
			
		||||
		std::size_t m_selectedPaletteIdx = 0;
 | 
			
		||||
		turbine::Context &m_ctx;
 | 
			
		||||
		ox::Vector<ox::String> m_paletteList;
 | 
			
		||||
		SubSheetEditor m_subsheetEditor;
 | 
			
		||||
		ExportMenu m_exportMenu;
 | 
			
		||||
		glutils::FrameBuffer m_framebuffer;
 | 
			
		||||
		TileSheetEditorView m_view;
 | 
			
		||||
		TileSheetEditorModel &m_model;
 | 
			
		||||
		float m_palViewWidth = 300;
 | 
			
		||||
		ox::Vec2 m_prevMouseDownPos;
 | 
			
		||||
		Tool m_tool = Tool::Draw;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		TileSheetEditorImGui(turbine::Context &ctx, ox::CRStringView path);
 | 
			
		||||
 | 
			
		||||
		~TileSheetEditorImGui() override = default;
 | 
			
		||||
 | 
			
		||||
		void exportFile() override;
 | 
			
		||||
 | 
			
		||||
		void cut() override;
 | 
			
		||||
 | 
			
		||||
		void copy() override;
 | 
			
		||||
 | 
			
		||||
		void paste() override;
 | 
			
		||||
 | 
			
		||||
		void keyStateChanged(turbine::Key key, bool down) override;
 | 
			
		||||
 | 
			
		||||
		void draw(turbine::Context&) noexcept override;
 | 
			
		||||
 | 
			
		||||
		void drawSubsheetSelector(TileSheet::SubSheet*, TileSheet::SubSheetIdx *path);
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		static ox::Vec2 clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept;
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		ox::Error saveItem() noexcept override;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void showSubsheetEditor() noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error exportSubhseetToPng(int scale) noexcept;
 | 
			
		||||
 | 
			
		||||
		void drawTileSheet(ox::Vec2 const&fbSize) noexcept;
 | 
			
		||||
 | 
			
		||||
		void drawPaletteSelector() noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error setPaletteSelection() noexcept;
 | 
			
		||||
 | 
			
		||||
	// slots
 | 
			
		||||
	private:
 | 
			
		||||
		ox::Error markUnsavedChanges(const studio::UndoCommand*) noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,290 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <ox/claw/read.hpp>
 | 
			
		||||
#include <ox/std/buffer.hpp>
 | 
			
		||||
#include <ox/std/memory.hpp>
 | 
			
		||||
 | 
			
		||||
#include <turbine/clipboard.hpp>
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/ptidxconv.hpp>
 | 
			
		||||
 | 
			
		||||
#include "commands/commands.hpp"
 | 
			
		||||
#include "commands/addsubsheetcommand.hpp"
 | 
			
		||||
#include "commands/cutpastecommand.hpp"
 | 
			
		||||
#include "commands/deletetilescommand.hpp"
 | 
			
		||||
#include "commands/drawcommand.hpp"
 | 
			
		||||
#include "commands/inserttilescommand.hpp"
 | 
			
		||||
#include "commands/palettechangecommand.hpp"
 | 
			
		||||
#include "commands/rmsubsheetcommand.hpp"
 | 
			
		||||
#include "commands/updatesubsheetcommand.hpp"
 | 
			
		||||
#include "tilesheeteditormodel.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
const Palette TileSheetEditorModel::s_defaultPalette = {
 | 
			
		||||
	.colors = ox::Vector<Color16>(128),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TileSheetEditorModel::TileSheetEditorModel(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack):
 | 
			
		||||
	m_ctx(ctx),
 | 
			
		||||
	m_path(path),
 | 
			
		||||
	m_img(*readObj<TileSheet>(keelCtx(m_ctx), m_path).unwrapThrow()),
 | 
			
		||||
	// ignore failure to load palette
 | 
			
		||||
	m_pal(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).value),
 | 
			
		||||
	m_undoStack(undoStack) {
 | 
			
		||||
	m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
 | 
			
		||||
	m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::cut() {
 | 
			
		||||
	TileSheetClipboard blankCb;
 | 
			
		||||
	auto cb = ox::make_unique<TileSheetClipboard>();
 | 
			
		||||
	const auto s = activeSubSheet();
 | 
			
		||||
	for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
 | 
			
		||||
		for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
 | 
			
		||||
			auto pt = ox::Point(x, y);
 | 
			
		||||
			const auto idx = s->idx(pt);
 | 
			
		||||
			const auto c = s->getPixel(m_img.bpp, idx);
 | 
			
		||||
			pt.x -= m_selectionBounds.x;
 | 
			
		||||
			pt.y -= m_selectionBounds.y;
 | 
			
		||||
			cb->addPixel(pt, c);
 | 
			
		||||
			blankCb.addPixel(pt, 0);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
 | 
			
		||||
	const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
 | 
			
		||||
	turbine::setClipboardObject(m_ctx, std::move(cb));
 | 
			
		||||
	pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::copy() {
 | 
			
		||||
	auto cb = ox::make_unique<TileSheetClipboard>();
 | 
			
		||||
	for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
 | 
			
		||||
		for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
 | 
			
		||||
			auto pt = ox::Point(x, y);
 | 
			
		||||
			const auto s = activeSubSheet();
 | 
			
		||||
			const auto idx = s->idx(pt);
 | 
			
		||||
			const auto c = s->getPixel(m_img.bpp, idx);
 | 
			
		||||
			pt.x -= m_selectionBounds.x;
 | 
			
		||||
			pt.y -= m_selectionBounds.y;
 | 
			
		||||
			cb->addPixel(pt, c);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	turbine::setClipboardObject(m_ctx, std::move(cb));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::paste() {
 | 
			
		||||
	auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_ctx);
 | 
			
		||||
	if (err) {
 | 
			
		||||
		oxLogError(err);
 | 
			
		||||
		oxErrf("Could not read clipboard: {}", toStr(err));
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const auto s = activeSubSheet();
 | 
			
		||||
	const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
 | 
			
		||||
	const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
 | 
			
		||||
	pushCommand(ox::make<CutPasteCommand>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::StringView TileSheetEditorModel::palPath() const noexcept {
 | 
			
		||||
	auto [path, err] = m_img.defaultPalette.getPath();
 | 
			
		||||
	if (err) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	constexpr ox::StringView uuidPrefix = "uuid://";
 | 
			
		||||
	if (ox::beginsWith(path, uuidPrefix)) {
 | 
			
		||||
		auto uuid = ox::StringView(path.data() + uuidPrefix.bytes(), path.bytes() - uuidPrefix.bytes());
 | 
			
		||||
		auto out = keelCtx(m_ctx).uuidToPath.at(uuid);
 | 
			
		||||
		if (out.error) {
 | 
			
		||||
			return {};
 | 
			
		||||
		}
 | 
			
		||||
		return *out.value;
 | 
			
		||||
	} else {
 | 
			
		||||
		return path;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorModel::setPalette(ox::StringView path) noexcept {
 | 
			
		||||
	oxRequire(uuid, keelCtx(m_ctx).pathToUuid.at(path));
 | 
			
		||||
	pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString()));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept {
 | 
			
		||||
	const auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
 | 
			
		||||
	if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const auto idx = activeSubSheet.idx(pt);
 | 
			
		||||
	if (m_ongoingDrawCommand) {
 | 
			
		||||
		m_updated = m_updated || m_ongoingDrawCommand->append(idx);
 | 
			
		||||
	} else if (activeSubSheet.getPixel(m_img.bpp, idx) != palIdx) {
 | 
			
		||||
		pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::endDrawCommand() noexcept {
 | 
			
		||||
	m_ongoingDrawCommand = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
 | 
			
		||||
	pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
 | 
			
		||||
	pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
 | 
			
		||||
	pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
 | 
			
		||||
	pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorModel::updateSubsheet(TileSheet::SubSheetIdx const&idx, const ox::StringView &name, int cols, int rows) noexcept {
 | 
			
		||||
	pushCommand(ox::make<UpdateSubSheetCommand>(m_img, idx, ox::String(name), cols, rows));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
 | 
			
		||||
	m_activeSubsSheetIdx = idx;
 | 
			
		||||
	this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
 | 
			
		||||
	const auto &s = m_img.getSubSheet(m_activeSubsSheetIdx);
 | 
			
		||||
	// build idx list
 | 
			
		||||
	ox::Array<bool, PixelsPerTile> updateMap = {};
 | 
			
		||||
	const auto oldColor = s.getPixel(m_img.bpp, pt);
 | 
			
		||||
	if (pt.x >= s.columns * TileWidth || pt.y >= s.rows * TileHeight) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	getFillPixels(updateMap.data(), pt, oldColor);
 | 
			
		||||
	ox::Vector<std::size_t> idxList;
 | 
			
		||||
	auto i = s.idx(pt) / PixelsPerTile * PixelsPerTile;
 | 
			
		||||
	for (auto u : updateMap) {
 | 
			
		||||
		if (u) {
 | 
			
		||||
			idxList.emplace_back(i);
 | 
			
		||||
		}
 | 
			
		||||
		++i;
 | 
			
		||||
	}
 | 
			
		||||
	// do updates to sheet
 | 
			
		||||
	if (m_ongoingDrawCommand) {
 | 
			
		||||
		m_updated = m_updated || m_ongoingDrawCommand->append(idxList);
 | 
			
		||||
	} else if (s.getPixel(m_img.bpp, pt) != palIdx) {
 | 
			
		||||
		pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
 | 
			
		||||
	if (!m_selectionOngoing) {
 | 
			
		||||
		m_selectionOrigin = pt;
 | 
			
		||||
		m_selectionOngoing = true;
 | 
			
		||||
		m_selectionBounds = {pt, pt};
 | 
			
		||||
		m_updated = true;
 | 
			
		||||
	} else if (m_selectionBounds.pt2() != pt) {
 | 
			
		||||
		m_selectionBounds = {m_selectionOrigin, pt};
 | 
			
		||||
		m_updated = true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::completeSelection() noexcept {
 | 
			
		||||
	m_selectionOngoing = false;
 | 
			
		||||
	auto s = activeSubSheet();
 | 
			
		||||
	auto pt = m_selectionBounds.pt2();
 | 
			
		||||
	pt.x = ox::min(s->columns * TileWidth, pt.x);
 | 
			
		||||
	pt.y = ox::min(s->rows * TileHeight, pt.y);
 | 
			
		||||
	m_selectionBounds.setPt2(pt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::clearSelection() noexcept {
 | 
			
		||||
	m_updated = true;
 | 
			
		||||
	m_selectionOrigin = {-1, -1};
 | 
			
		||||
	m_selectionBounds = {{-1, -1}, {-1, -1}};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TileSheetEditorModel::updated() const noexcept {
 | 
			
		||||
	return m_updated;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept {
 | 
			
		||||
	m_updated = true;
 | 
			
		||||
	const auto cmdId = cmd->commandId();
 | 
			
		||||
	if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
 | 
			
		||||
		oxReturnError(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).moveTo(m_pal));
 | 
			
		||||
		paletteChanged.emit();
 | 
			
		||||
	}
 | 
			
		||||
	auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
 | 
			
		||||
	auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());
 | 
			
		||||
	if (idx != m_activeSubsSheetIdx) {
 | 
			
		||||
		setActiveSubsheet(idx);
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorModel::markUpdated() noexcept {
 | 
			
		||||
	m_updated = true;
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::ackUpdate() noexcept {
 | 
			
		||||
	m_updated = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorModel::saveFile() noexcept {
 | 
			
		||||
	const auto sctx = applicationData<studio::StudioContext>(m_ctx);
 | 
			
		||||
	return sctx->project->writeObj(m_path, m_img);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
 | 
			
		||||
	const auto s = activeSubSheet();
 | 
			
		||||
	const auto pt = idxToPt(static_cast<int>(idx), s->columns);
 | 
			
		||||
	return m_selectionBounds.contains(pt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
 | 
			
		||||
	const auto &activeSubSheet = *this->activeSubSheet();
 | 
			
		||||
	const auto tileIdx = [activeSubSheet](const ox::Point &pt) noexcept {
 | 
			
		||||
		return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile;
 | 
			
		||||
	};
 | 
			
		||||
	// get points
 | 
			
		||||
	const auto leftPt = pt + ox::Point(-1, 0);
 | 
			
		||||
	const auto rightPt = pt + ox::Point(1, 0);
 | 
			
		||||
	const auto topPt = pt + ox::Point(0, -1);
 | 
			
		||||
	const auto bottomPt = pt + ox::Point(0, 1);
 | 
			
		||||
	// calculate indices
 | 
			
		||||
	const auto idx = ptToIdx(pt, activeSubSheet.columns);
 | 
			
		||||
	const auto leftIdx = ptToIdx(leftPt, activeSubSheet.columns);
 | 
			
		||||
	const auto rightIdx = ptToIdx(rightPt, activeSubSheet.columns);
 | 
			
		||||
	const auto topIdx = ptToIdx(topPt, activeSubSheet.columns);
 | 
			
		||||
	const auto bottomIdx = ptToIdx(bottomPt, activeSubSheet.columns);
 | 
			
		||||
	const auto tile = tileIdx(pt);
 | 
			
		||||
	// mark pixels to update
 | 
			
		||||
	pixels[idx % PixelsPerTile] = true;
 | 
			
		||||
	if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && activeSubSheet.getPixel(m_img.bpp, leftIdx) == oldColor) {
 | 
			
		||||
		getFillPixels(pixels, leftPt, oldColor);
 | 
			
		||||
	}
 | 
			
		||||
	if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && activeSubSheet.getPixel(m_img.bpp, rightIdx) == oldColor) {
 | 
			
		||||
		getFillPixels(pixels, rightPt, oldColor);
 | 
			
		||||
	}
 | 
			
		||||
	if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && activeSubSheet.getPixel(m_img.bpp, topIdx) == oldColor) {
 | 
			
		||||
		getFillPixels(pixels, topPt, oldColor);
 | 
			
		||||
	}
 | 
			
		||||
	if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && activeSubSheet.getPixel(m_img.bpp, bottomIdx) == oldColor) {
 | 
			
		||||
		getFillPixels(pixels, bottomPt, oldColor);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
 | 
			
		||||
	m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd));
 | 
			
		||||
	m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
 | 
			
		||||
	m_updated = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,148 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/bounds.hpp>
 | 
			
		||||
#include <ox/std/point.hpp>
 | 
			
		||||
#include <ox/std/trace.hpp>
 | 
			
		||||
#include <ox/std/string.hpp>
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class TileSheetEditorModel: public ox::SignalHandler {
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		ox::Signal<ox::Error(const TileSheet::SubSheetIdx&)> activeSubsheetChanged;
 | 
			
		||||
		ox::Signal<ox::Error()> paletteChanged;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		static const Palette s_defaultPalette;
 | 
			
		||||
		turbine::Context &m_ctx;
 | 
			
		||||
		ox::String m_path;
 | 
			
		||||
		TileSheet m_img;
 | 
			
		||||
		TileSheet::SubSheetIdx m_activeSubsSheetIdx;
 | 
			
		||||
		keel::AssetRef<Palette> m_pal;
 | 
			
		||||
		studio::UndoStack &m_undoStack;
 | 
			
		||||
		class DrawCommand *m_ongoingDrawCommand = nullptr;
 | 
			
		||||
		bool m_updated = false;
 | 
			
		||||
		bool m_selectionOngoing = false;
 | 
			
		||||
		ox::Point m_selectionOrigin = {-1, -1};
 | 
			
		||||
		ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		TileSheetEditorModel(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack);
 | 
			
		||||
 | 
			
		||||
		~TileSheetEditorModel() override = default;
 | 
			
		||||
 | 
			
		||||
		void cut();
 | 
			
		||||
 | 
			
		||||
		void copy();
 | 
			
		||||
 | 
			
		||||
		void paste();
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr const TileSheet &img() const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr TileSheet &img() noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr const Palette *pal() const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::StringView palPath() const noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error setPalette(ox::StringView path) noexcept;
 | 
			
		||||
 | 
			
		||||
		void drawCommand(const ox::Point &pt, std::size_t palIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void endDrawCommand() noexcept;
 | 
			
		||||
 | 
			
		||||
		void addSubsheet(const TileSheet::SubSheetIdx &parentIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void insertTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
 | 
			
		||||
 | 
			
		||||
		void deleteTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::StringView &name, int cols, int rows) noexcept;
 | 
			
		||||
 | 
			
		||||
		void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		const TileSheet::SubSheet *activeSubSheet() const noexcept {
 | 
			
		||||
			auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
 | 
			
		||||
			return &activeSubSheet;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		TileSheet::SubSheet *activeSubSheet() noexcept {
 | 
			
		||||
			auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
 | 
			
		||||
			return &activeSubSheet;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr TileSheet::SubSheetIdx const&activeSubSheetIdx() const noexcept {
 | 
			
		||||
			return m_activeSubsSheetIdx;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void fill(ox::Point const&pt, int palIdx) noexcept;
 | 
			
		||||
 | 
			
		||||
		void select(ox::Point const&pt) noexcept;
 | 
			
		||||
 | 
			
		||||
		void completeSelection() noexcept;
 | 
			
		||||
 | 
			
		||||
		void clearSelection() noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		bool updated() const noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error markUpdated() noexcept;
 | 
			
		||||
 | 
			
		||||
		void ackUpdate() noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error saveFile() noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr studio::UndoStack *undoStack() noexcept;
 | 
			
		||||
 | 
			
		||||
		bool pixelSelected(std::size_t idx) const noexcept;
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		void getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void pushCommand(studio::UndoCommand *cmd) noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr const TileSheet &TileSheetEditorModel::img() const noexcept {
 | 
			
		||||
	return m_img;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr TileSheet &TileSheetEditorModel::img() noexcept {
 | 
			
		||||
	return m_img;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr const Palette *TileSheetEditorModel::pal() const noexcept {
 | 
			
		||||
	if (m_pal) {
 | 
			
		||||
		return m_pal.get();
 | 
			
		||||
	}
 | 
			
		||||
	return &s_defaultPalette;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr studio::UndoStack *TileSheetEditorModel::undoStack() noexcept {
 | 
			
		||||
	return &m_undoStack;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,133 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/std/point.hpp>
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/consts.hpp>
 | 
			
		||||
 | 
			
		||||
#include "tilesheeteditorview.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
TileSheetEditorView::TileSheetEditorView(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack):
 | 
			
		||||
	m_model(ctx, path, undoStack),
 | 
			
		||||
	m_pixelsDrawer(&m_model) {
 | 
			
		||||
	// build shaders
 | 
			
		||||
	oxThrowError(m_pixelsDrawer.buildShader());
 | 
			
		||||
	oxThrowError(m_pixelGridDrawer.buildShader());
 | 
			
		||||
	m_model.activeSubsheetChanged.connect(this, &TileSheetEditorView::setActiveSubsheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::draw() noexcept {
 | 
			
		||||
	constexpr Color32 bgColor = 0x717d7e;
 | 
			
		||||
	glClearColor(redf(bgColor), greenf(bgColor), bluef(bgColor), 1.f);
 | 
			
		||||
	glClear(GL_COLOR_BUFFER_BIT);
 | 
			
		||||
	m_pixelsDrawer.draw(updated(), m_scrollOffset);
 | 
			
		||||
	m_pixelGridDrawer.draw(updated(), m_scrollOffset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept {
 | 
			
		||||
	const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz);
 | 
			
		||||
	const ImVec2 sheetSize(pixelSize.x * static_cast<float>(m_model.activeSubSheet()->columns) * TileWidth,
 | 
			
		||||
	                       pixelSize.y * static_cast<float>(m_model.activeSubSheet()->rows) * TileHeight);
 | 
			
		||||
	if (zoomMod) {
 | 
			
		||||
		m_pixelSizeMod = ox::clamp(m_pixelSizeMod + wheel * 0.02f, 0.55f, 2.f);
 | 
			
		||||
		m_pixelsDrawer.setPixelSizeMod(m_pixelSizeMod);
 | 
			
		||||
		m_pixelGridDrawer.setPixelSizeMod(m_pixelSizeMod);
 | 
			
		||||
		m_updated = true;
 | 
			
		||||
	} else {
 | 
			
		||||
		m_scrollOffset.y -= wheel * 0.1f;
 | 
			
		||||
	}
 | 
			
		||||
	// adjust scroll offset in both cases because the image can be zoomed
 | 
			
		||||
	// or scrolled off screen
 | 
			
		||||
	m_scrollOffset.y = ox::clamp(m_scrollOffset.y, 0.f, sheetSize.y / 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::scrollH(ox::Vec2 const&paneSz, float wheelh) noexcept {
 | 
			
		||||
	const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz);
 | 
			
		||||
	const ImVec2 sheetSize(pixelSize.x * static_cast<float>(m_model.activeSubSheet()->columns) * TileWidth,
 | 
			
		||||
	                       pixelSize.y * static_cast<float>(m_model.activeSubSheet()->rows) * TileHeight);
 | 
			
		||||
	m_scrollOffset.x += wheelh * 0.1f;
 | 
			
		||||
	m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::insertTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
 | 
			
		||||
	const auto pt = clickPoint(paneSize, clickPos);
 | 
			
		||||
	const auto s = m_model.activeSubSheet();
 | 
			
		||||
	const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile;
 | 
			
		||||
	m_model.insertTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::deleteTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
 | 
			
		||||
	const auto pt = clickPoint(paneSize, clickPos);
 | 
			
		||||
	const auto s = m_model.activeSubSheet();
 | 
			
		||||
	const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile;
 | 
			
		||||
	m_model.deleteTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::clickDraw(ox::Vec2 const &paneSize, ox::Vec2 const&clickPos) noexcept {
 | 
			
		||||
	const auto pt = clickPoint(paneSize, clickPos);
 | 
			
		||||
	m_model.drawCommand(pt, m_palIdx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
 | 
			
		||||
	const auto pt = clickPoint(paneSize, clickPos);
 | 
			
		||||
	m_model.select(pt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
 | 
			
		||||
	const auto pt = clickPoint(paneSize, clickPos);
 | 
			
		||||
	m_model.fill(pt, static_cast<int>(m_palIdx));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::releaseMouseButton() noexcept {
 | 
			
		||||
	m_model.endDrawCommand();
 | 
			
		||||
	m_model.completeSelection();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept {
 | 
			
		||||
	m_viewSize = sz;
 | 
			
		||||
	initView();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TileSheetEditorView::updated() const noexcept {
 | 
			
		||||
	return m_updated || m_model.updated();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorView::markUpdated() noexcept {
 | 
			
		||||
	m_updated = true;
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::ackUpdate() noexcept {
 | 
			
		||||
	m_updated = false;
 | 
			
		||||
	m_pixelsDrawer.update(m_viewSize);
 | 
			
		||||
	m_pixelGridDrawer.update(m_viewSize, *m_model.activeSubSheet());
 | 
			
		||||
	m_model.ackUpdate();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetEditorView::initView() noexcept {
 | 
			
		||||
	m_pixelsDrawer.initBufferSet(m_viewSize);
 | 
			
		||||
	m_pixelGridDrawer.initBufferSet(m_viewSize, *m_model.activeSubSheet());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Point TileSheetEditorView::clickPoint(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) const noexcept {
 | 
			
		||||
	auto [x, y] = clickPos;
 | 
			
		||||
	const auto pixDrawSz = m_pixelsDrawer.pixelSize(paneSize);
 | 
			
		||||
	x /= paneSize.x;
 | 
			
		||||
	y /= paneSize.y;
 | 
			
		||||
	x += -m_scrollOffset.x / 2;
 | 
			
		||||
	y += m_scrollOffset.y / 2;
 | 
			
		||||
	x /= pixDrawSz.x;
 | 
			
		||||
	y /= pixDrawSz.y;
 | 
			
		||||
	return {static_cast<int>(x * 2), static_cast<int>(y * 2)};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetEditorView::setActiveSubsheet(TileSheet::SubSheetIdx const&) noexcept {
 | 
			
		||||
	initView();
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,133 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/vec.hpp>
 | 
			
		||||
#include <ox/model/def.hpp>
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
#include "tilesheeteditormodel.hpp"
 | 
			
		||||
#include "tilesheetpixelgrid.hpp"
 | 
			
		||||
#include "tilesheetpixels.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
enum class TileSheetTool: int {
 | 
			
		||||
	Select,
 | 
			
		||||
	Draw,
 | 
			
		||||
	Fill,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr auto toString(TileSheetTool t) noexcept {
 | 
			
		||||
	switch (t) {
 | 
			
		||||
		case TileSheetTool::Select:
 | 
			
		||||
			return "Select";
 | 
			
		||||
		case TileSheetTool::Draw:
 | 
			
		||||
			return "Draw";
 | 
			
		||||
		case TileSheetTool::Fill:
 | 
			
		||||
			return "Fill";
 | 
			
		||||
	}
 | 
			
		||||
	return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TileSheetEditorView: public ox::SignalHandler {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		TileSheetEditorModel m_model;
 | 
			
		||||
		TileSheetGrid m_pixelGridDrawer;
 | 
			
		||||
		TileSheetPixels m_pixelsDrawer;
 | 
			
		||||
		ox::Vec2 m_viewSize;
 | 
			
		||||
		float m_pixelSizeMod = 1;
 | 
			
		||||
		bool m_updated = false;
 | 
			
		||||
		ox::Vec2 m_scrollOffset;
 | 
			
		||||
		std::size_t m_palIdx = 0;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		TileSheetEditorView(turbine::Context &ctx, ox::StringView path, studio::UndoStack &undoStack);
 | 
			
		||||
 | 
			
		||||
		~TileSheetEditorView() override = default;
 | 
			
		||||
 | 
			
		||||
		void draw() noexcept;
 | 
			
		||||
 | 
			
		||||
		void insertTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
 | 
			
		||||
 | 
			
		||||
		void deleteTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
 | 
			
		||||
 | 
			
		||||
		void clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
 | 
			
		||||
 | 
			
		||||
		void clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
 | 
			
		||||
 | 
			
		||||
		void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
 | 
			
		||||
 | 
			
		||||
		void releaseMouseButton() noexcept;
 | 
			
		||||
 | 
			
		||||
		void scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept;
 | 
			
		||||
 | 
			
		||||
		void scrollH(ox::Vec2 const&paneSz, float wheel) noexcept;
 | 
			
		||||
 | 
			
		||||
		void resizeView(ox::Vec2 const&sz) noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr TileSheet const&img() const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr TileSheet &img() noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr const Palette *pal() const noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr auto &model() noexcept {
 | 
			
		||||
			return m_model;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr auto &model() const noexcept {
 | 
			
		||||
			return m_model;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		constexpr auto setPalIdx(auto palIdx) noexcept {
 | 
			
		||||
			m_palIdx = palIdx;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr auto palIdx() const noexcept {
 | 
			
		||||
			return m_palIdx;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		bool updated() const noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error markUpdated() noexcept;
 | 
			
		||||
 | 
			
		||||
		void ackUpdate() noexcept;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void initView() noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Point clickPoint(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) const noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
constexpr TileSheet const&TileSheetEditorView::img() const noexcept {
 | 
			
		||||
	return m_model.img();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr TileSheet &TileSheetEditorView::img() noexcept {
 | 
			
		||||
	return m_model.img();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr const Palette *TileSheetEditorView::pal() const noexcept {
 | 
			
		||||
	return m_model.pal();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,120 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/claw/write.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/consts.hpp>
 | 
			
		||||
 | 
			
		||||
#include "tilesheetpixelgrid.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
void TileSheetGrid::setPixelSizeMod(float sm) noexcept {
 | 
			
		||||
	m_pixelSizeMod = sm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetGrid::buildShader() noexcept {
 | 
			
		||||
	const auto pixelLineVshad = ox::sfmt(VShad, gl::GlslVersion);
 | 
			
		||||
	const auto pixelLineFshad = ox::sfmt(FShad, gl::GlslVersion);
 | 
			
		||||
	const auto pixelLineGshad = ox::sfmt(GShad, gl::GlslVersion);
 | 
			
		||||
	return glutils::buildShaderProgram(pixelLineVshad, pixelLineFshad, pixelLineGshad).moveTo(m_shader);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetGrid::draw(bool update, ox::Vec2 const&scroll) noexcept {
 | 
			
		||||
	glLineWidth(3 * m_pixelSizeMod * 0.5f);
 | 
			
		||||
	glUseProgram(m_shader);
 | 
			
		||||
	glBindVertexArray(m_bufferSet.vao);
 | 
			
		||||
	if (update) {
 | 
			
		||||
		glutils::sendVbo(m_bufferSet);
 | 
			
		||||
	}
 | 
			
		||||
	const auto uniformScroll = glGetUniformLocation(m_shader, "gScroll");
 | 
			
		||||
	glUniform2f(uniformScroll, scroll.x, scroll.y);
 | 
			
		||||
	glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(m_bufferSet.vertices.size() / VertexVboRowLength));
 | 
			
		||||
	glBindVertexArray(0);
 | 
			
		||||
	glUseProgram(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetGrid::initBufferSet(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept {
 | 
			
		||||
	// vao
 | 
			
		||||
	m_bufferSet.vao = glutils::generateVertexArrayObject();
 | 
			
		||||
	glBindVertexArray(m_bufferSet.vao);
 | 
			
		||||
	// vbo
 | 
			
		||||
	m_bufferSet.vbo = glutils::generateBuffer();
 | 
			
		||||
	setBufferObjects(paneSize, subsheet);
 | 
			
		||||
	glutils::sendVbo(m_bufferSet);
 | 
			
		||||
	// vbo layout
 | 
			
		||||
	const auto pt1Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt1"));
 | 
			
		||||
	glEnableVertexAttribArray(pt1Attr);
 | 
			
		||||
	glVertexAttribPointer(pt1Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float), nullptr);
 | 
			
		||||
	const auto pt2Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt2"));
 | 
			
		||||
	glEnableVertexAttribArray(pt2Attr);
 | 
			
		||||
	glVertexAttribPointer(pt2Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
 | 
			
		||||
	                      std::bit_cast<void*>(uintptr_t{2 * sizeof(float)}));
 | 
			
		||||
	const auto colorAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vColor"));
 | 
			
		||||
	glEnableVertexAttribArray(colorAttr);
 | 
			
		||||
	glVertexAttribPointer(colorAttr, 3, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
 | 
			
		||||
	                      std::bit_cast<void*>(uintptr_t{4 * sizeof(float)}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetGrid::update(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept {
 | 
			
		||||
	glBindVertexArray(m_bufferSet.vao);
 | 
			
		||||
	setBufferObjects(paneSize, subsheet);
 | 
			
		||||
	glutils::sendVbo(m_bufferSet);
 | 
			
		||||
	glutils::sendEbo(m_bufferSet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetGrid::setBufferObject(ox::Point pt1, ox::Point pt2, Color32 c, float *vbo, ox::Vec2 const&pixSize) noexcept {
 | 
			
		||||
	const auto x1 = static_cast<float>(pt1.x) * pixSize.x - 1.f;
 | 
			
		||||
	const auto y1 = 1.f - static_cast<float>(pt1.y) * pixSize.y;
 | 
			
		||||
	const auto x2 = static_cast<float>(pt2.x) * pixSize.x - 1.f;
 | 
			
		||||
	const auto y2 = 1.f - static_cast<float>(pt2.y) * pixSize.y;
 | 
			
		||||
	// don't worry, this memcpy gets optimized to something much more ideal
 | 
			
		||||
	const ox::Array<float, VertexVboLength> vertices = {x1, y1, x2, y2, redf(c), greenf(c), bluef(c)};
 | 
			
		||||
	memcpy(vbo, vertices.data(), sizeof(vertices));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetGrid::setBufferObjects(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept {
 | 
			
		||||
	const auto pixSize = pixelSize(paneSize);
 | 
			
		||||
	const auto set = [&](std::size_t i, ox::Point pt1, ox::Point pt2, Color32 c) {
 | 
			
		||||
		const auto vbo = &m_bufferSet.vertices[i * VertexVboLength];
 | 
			
		||||
		setBufferObject(pt1, pt2, c, vbo, pixSize);
 | 
			
		||||
	};
 | 
			
		||||
	// set buffer length
 | 
			
		||||
	const auto width = subsheet.columns * TileWidth;
 | 
			
		||||
	const auto height = subsheet.rows * TileHeight;
 | 
			
		||||
	const auto tileCnt = static_cast<unsigned>(subsheet.columns + subsheet.rows);
 | 
			
		||||
	const auto pixelCnt = static_cast<unsigned>(width + height);
 | 
			
		||||
	m_bufferSet.vertices.resize(static_cast<std::size_t>(tileCnt + pixelCnt + 4) * VertexVboLength);
 | 
			
		||||
	// set buffer
 | 
			
		||||
	std::size_t i = 0;
 | 
			
		||||
	// pixel outlines
 | 
			
		||||
	constexpr auto pixOutlineColor = color32(0.4431f, 0.4901f, 0.4941f);
 | 
			
		||||
	for (auto x = 0; x < subsheet.columns * TileWidth + 1; ++x) {
 | 
			
		||||
		set(i, {x, 0}, {x, subsheet.rows * TileHeight}, pixOutlineColor);
 | 
			
		||||
		++i;
 | 
			
		||||
	}
 | 
			
		||||
	for (auto y = 0; y < subsheet.rows * TileHeight + 1; ++y) {
 | 
			
		||||
		set(i, {0, y}, {subsheet.columns * TileWidth, y}, pixOutlineColor);
 | 
			
		||||
		++i;
 | 
			
		||||
	}
 | 
			
		||||
	// tile outlines
 | 
			
		||||
	constexpr auto tileOutlineColor = color32(0.f, 0.f, 0.f);
 | 
			
		||||
	for (auto x = 0; x < subsheet.columns * TileWidth + 1; x += TileWidth) {
 | 
			
		||||
		set(i, {x, 0}, {x, subsheet.rows * TileHeight}, tileOutlineColor);
 | 
			
		||||
		++i;
 | 
			
		||||
	}
 | 
			
		||||
	for (auto y = 0; y < subsheet.rows * TileHeight + 1; y += TileHeight) {
 | 
			
		||||
		set(i, {0, y}, {subsheet.columns * TileWidth, y}, tileOutlineColor);
 | 
			
		||||
		++i;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Vec2 TileSheetGrid::pixelSize(ox::Vec2 const&paneSize) const noexcept {
 | 
			
		||||
	const auto [sw, sh] = paneSize;
 | 
			
		||||
	constexpr float ymod = 0.35f / 10.0f;
 | 
			
		||||
	const auto xmod = ymod * sh / sw;
 | 
			
		||||
	return {xmod * m_pixelSizeMod, ymod * m_pixelSizeMod};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,86 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class TileSheetGrid {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		static constexpr auto VertexVboRows = 1;
 | 
			
		||||
		static constexpr auto VertexVboRowLength = 7;
 | 
			
		||||
		static constexpr auto VertexVboLength = VertexVboRows * VertexVboRowLength;
 | 
			
		||||
 | 
			
		||||
		static constexpr auto VShad = R"glsl(
 | 
			
		||||
		{}
 | 
			
		||||
		in vec2 vPt1;
 | 
			
		||||
		in vec2 vPt2;
 | 
			
		||||
		in vec3 vColor;
 | 
			
		||||
		out vec2 gPt2;
 | 
			
		||||
		out vec3 gColor;
 | 
			
		||||
		void main() {
 | 
			
		||||
			gColor = vColor;
 | 
			
		||||
			gl_Position = vec4(vPt1, 0.0, 1.0);
 | 
			
		||||
			gPt2 = vPt2;
 | 
			
		||||
		})glsl";
 | 
			
		||||
 | 
			
		||||
		static constexpr auto FShad = R"glsl(
 | 
			
		||||
		{}
 | 
			
		||||
		in vec3 fColor;
 | 
			
		||||
		out vec4 outColor;
 | 
			
		||||
		void main() {
 | 
			
		||||
			outColor = vec4(fColor, 1);
 | 
			
		||||
			//outColor = vec4(0.4431, 0.4901, 0.4941, 1.0);
 | 
			
		||||
		})glsl";
 | 
			
		||||
 | 
			
		||||
		static constexpr auto GShad = R"glsl(
 | 
			
		||||
		{}
 | 
			
		||||
		layout(points) in;
 | 
			
		||||
		layout(line_strip, max_vertices = 2) out;
 | 
			
		||||
		in vec3 gColor[];
 | 
			
		||||
		in vec2 gPt2[];
 | 
			
		||||
		out vec3 fColor;
 | 
			
		||||
		uniform vec2 gScroll;
 | 
			
		||||
		void main() {
 | 
			
		||||
			fColor = gColor[0];
 | 
			
		||||
			gl_Position = gl_in[0].gl_Position + vec4(gScroll, 0, 0);
 | 
			
		||||
			EmitVertex();
 | 
			
		||||
			gl_Position = vec4(gPt2[0] + gScroll, 0, 1);
 | 
			
		||||
			EmitVertex();
 | 
			
		||||
			EndPrimitive();
 | 
			
		||||
		})glsl";
 | 
			
		||||
 | 
			
		||||
		glutils::GLProgram m_shader;
 | 
			
		||||
		glutils::BufferSet m_bufferSet;
 | 
			
		||||
		float m_pixelSizeMod = 1;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		void setPixelSizeMod(float sm) noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error buildShader() noexcept;
 | 
			
		||||
 | 
			
		||||
		void draw(bool update, ox::Vec2 const&scroll) noexcept;
 | 
			
		||||
 | 
			
		||||
		void initBufferSet(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept;
 | 
			
		||||
 | 
			
		||||
		void update(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		static void setBufferObject(ox::Point pt1, ox::Point pt2, Color32 c, float *vbo, ox::Vec2 const&pixSize) noexcept;
 | 
			
		||||
 | 
			
		||||
		void setBufferObjects(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vec2 pixelSize(ox::Vec2 const&paneSize) const noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,128 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/consts.hpp>
 | 
			
		||||
#include <nostalgia/core/ptidxconv.hpp>
 | 
			
		||||
#include "tilesheeteditormodel.hpp"
 | 
			
		||||
#include "tilesheetpixels.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
TileSheetPixels::TileSheetPixels(TileSheetEditorModel *model) noexcept: m_model(model) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetPixels::setPixelSizeMod(float sm) noexcept {
 | 
			
		||||
	m_pixelSizeMod = sm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheetPixels::buildShader() noexcept {
 | 
			
		||||
	const auto Vshad = ox::sfmt(VShad, gl::GlslVersion);
 | 
			
		||||
	const auto Fshad = ox::sfmt(FShad, gl::GlslVersion);
 | 
			
		||||
	return glutils::buildShaderProgram(Vshad, Fshad).moveTo(m_shader);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetPixels::draw(bool update, ox::Vec2 const&scroll) noexcept {
 | 
			
		||||
	glUseProgram(m_shader);
 | 
			
		||||
	glBindVertexArray(m_bufferSet.vao);
 | 
			
		||||
	if (update) {
 | 
			
		||||
		glutils::sendVbo(m_bufferSet);
 | 
			
		||||
	}
 | 
			
		||||
	const auto uniformScroll = glGetUniformLocation(m_shader, "vScroll");
 | 
			
		||||
	glUniform2f(uniformScroll, scroll.x, scroll.y);
 | 
			
		||||
	glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_bufferSet.elements.size()), GL_UNSIGNED_INT, nullptr);
 | 
			
		||||
	glBindVertexArray(0);
 | 
			
		||||
	glUseProgram(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetPixels::initBufferSet(ox::Vec2 const&paneSize) noexcept {
 | 
			
		||||
	m_bufferSet.vao = glutils::generateVertexArrayObject();
 | 
			
		||||
	m_bufferSet.vbo = glutils::generateBuffer();
 | 
			
		||||
	m_bufferSet.ebo = glutils::generateBuffer();
 | 
			
		||||
	update(paneSize);
 | 
			
		||||
	// vbo layout
 | 
			
		||||
	const auto posAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPosition"));
 | 
			
		||||
	glEnableVertexAttribArray(posAttr);
 | 
			
		||||
	glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float), nullptr);
 | 
			
		||||
	const auto colorAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vColor"));
 | 
			
		||||
	glEnableVertexAttribArray(colorAttr);
 | 
			
		||||
	glVertexAttribPointer(colorAttr, 3, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
 | 
			
		||||
	                      std::bit_cast<void*>(uintptr_t{2 * sizeof(float)}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetPixels::update(ox::Vec2 const&paneSize) noexcept {
 | 
			
		||||
	glBindVertexArray(m_bufferSet.vao);
 | 
			
		||||
	setBufferObjects(paneSize);
 | 
			
		||||
	glutils::sendVbo(m_bufferSet);
 | 
			
		||||
	glutils::sendEbo(m_bufferSet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Vec2 TileSheetPixels::pixelSize(ox::Vec2 const&paneSize) const noexcept {
 | 
			
		||||
	const auto [sw, sh] = paneSize;
 | 
			
		||||
	constexpr float ymod = 0.35f / 10.0f;
 | 
			
		||||
	const auto xmod = ymod * sh / sw;
 | 
			
		||||
	return {xmod * m_pixelSizeMod, ymod * m_pixelSizeMod};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetPixels::setPixelBufferObject(
 | 
			
		||||
		ox::Vec2 const&paneSize,
 | 
			
		||||
		unsigned vertexRow,
 | 
			
		||||
		float x, float y,
 | 
			
		||||
		Color16 color,
 | 
			
		||||
		float *vbo,
 | 
			
		||||
		GLuint *ebo) const noexcept {
 | 
			
		||||
	const auto [xmod, ymod] = pixelSize(paneSize);
 | 
			
		||||
	x *= xmod;
 | 
			
		||||
	y *= -ymod;
 | 
			
		||||
	x -= 1.0f;
 | 
			
		||||
	y += 1.0f - ymod;
 | 
			
		||||
	const auto r = redf(color), g = greenf(color), b = bluef(color);
 | 
			
		||||
	// don't worry, these memcpys gets optimized to something much more ideal
 | 
			
		||||
	const ox::Array<float, VertexVboLength> vertices = {
 | 
			
		||||
			       x,        y, r, g, b, // bottom left
 | 
			
		||||
			x + xmod,        y, r, g, b, // bottom right
 | 
			
		||||
			x + xmod, y + ymod, r, g, b, // top right
 | 
			
		||||
			       x, y + ymod, r, g, b, // top left
 | 
			
		||||
	};
 | 
			
		||||
	memcpy(vbo, vertices.data(), sizeof(vertices));
 | 
			
		||||
	const ox::Array<GLuint, VertexEboLength> elms = {
 | 
			
		||||
			vertexRow + 0, vertexRow + 1, vertexRow + 2,
 | 
			
		||||
			vertexRow + 2, vertexRow + 3, vertexRow + 0,
 | 
			
		||||
	};
 | 
			
		||||
	memcpy(ebo, elms.data(), sizeof(elms));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept {
 | 
			
		||||
	// set buffer lengths
 | 
			
		||||
	const auto subSheet = m_model->activeSubSheet();
 | 
			
		||||
	const auto pal = m_model->pal();
 | 
			
		||||
	const auto width = subSheet->columns * TileWidth;
 | 
			
		||||
	const auto height = subSheet->rows * TileHeight;
 | 
			
		||||
	const auto pixels = static_cast<unsigned>(width * height);
 | 
			
		||||
	m_bufferSet.vertices.resize(pixels * VertexVboLength);
 | 
			
		||||
	m_bufferSet.elements.resize(pixels * VertexEboLength);
 | 
			
		||||
	// set pixels
 | 
			
		||||
	subSheet->walkPixels(m_model->img().bpp, [&](std::size_t i, uint8_t p) {
 | 
			
		||||
		auto color = pal->color(p);
 | 
			
		||||
		const auto pt = idxToPt(static_cast<int>(i), subSheet->columns);
 | 
			
		||||
		const auto fx = static_cast<float>(pt.x);
 | 
			
		||||
		const auto fy = static_cast<float>(pt.y);
 | 
			
		||||
		const auto vbo = &m_bufferSet.vertices[i * VertexVboLength];
 | 
			
		||||
		const auto ebo = &m_bufferSet.elements[i * VertexEboLength];
 | 
			
		||||
		if (i * VertexVboLength + VertexVboLength > m_bufferSet.vertices.size()) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if (i * VertexEboLength + VertexEboLength > m_bufferSet.elements.size()) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		if (m_model->pixelSelected(i)) {
 | 
			
		||||
			const auto r = red16(color) / 2;
 | 
			
		||||
			const auto g = (green16(color) + 20) / 2;
 | 
			
		||||
			const auto b = (blue16(color) + 31) / 2;
 | 
			
		||||
			color = color16(r, g, b);
 | 
			
		||||
		}
 | 
			
		||||
		setPixelBufferObject(paneSize, static_cast<unsigned>(i * VertexVboRows), fx, fy, color, vbo, ebo);
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,71 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/std/vec.hpp>
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/color.hpp>
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
class TileSheetPixels {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		static constexpr auto VertexVboRows = 4;
 | 
			
		||||
		static constexpr auto VertexVboRowLength = 5;
 | 
			
		||||
		static constexpr auto VertexVboLength = VertexVboRows * VertexVboRowLength;
 | 
			
		||||
		static constexpr auto VertexEboLength = 6;
 | 
			
		||||
		static constexpr auto VShad = R"(
 | 
			
		||||
		{}
 | 
			
		||||
		in vec2 vPosition;
 | 
			
		||||
		in vec3 vColor;
 | 
			
		||||
		out vec3 fColor;
 | 
			
		||||
		uniform vec2 vScroll;
 | 
			
		||||
		void main() {
 | 
			
		||||
			gl_Position = vec4(vPosition + vScroll, 0.0, 1.0);
 | 
			
		||||
			fColor = vColor;
 | 
			
		||||
		})";
 | 
			
		||||
		static constexpr auto FShad = R"(
 | 
			
		||||
		{}
 | 
			
		||||
		in vec3 fColor;
 | 
			
		||||
		out vec4 outColor;
 | 
			
		||||
		void main() {
 | 
			
		||||
			//outColor = vec4(0.0, 0.7, 1.0, 1.0);
 | 
			
		||||
			outColor = vec4(fColor, 1.0);
 | 
			
		||||
		})";
 | 
			
		||||
 | 
			
		||||
		float m_pixelSizeMod = 1;
 | 
			
		||||
		glutils::GLProgram m_shader;
 | 
			
		||||
		glutils::BufferSet m_bufferSet;
 | 
			
		||||
		const class TileSheetEditorModel *m_model = nullptr;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		explicit TileSheetPixels(class TileSheetEditorModel *model) noexcept;
 | 
			
		||||
 | 
			
		||||
		void setPixelSizeMod(float sm) noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error buildShader() noexcept;
 | 
			
		||||
 | 
			
		||||
		void draw(bool update, ox::Vec2 const&scroll) noexcept;
 | 
			
		||||
 | 
			
		||||
		void initBufferSet(ox::Vec2 const&paneSize) noexcept;
 | 
			
		||||
 | 
			
		||||
		void update(ox::Vec2 const&paneSize) noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vec2 pixelSize(ox::Vec2 const&paneSize) const noexcept;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void setPixelBufferObject(ox::Vec2 const&paneS, unsigned vertexRow, float x, float y, Color16 color, float *vbo, GLuint *ebo) const noexcept;
 | 
			
		||||
 | 
			
		||||
		void setBufferObjects(ox::Vec2 const&paneS) noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										380
									
								
								src/nostalgia/modules/core/src/tilesheet.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								src/nostalgia/modules/core/src/tilesheet.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,380 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/std/size.hpp>
 | 
			
		||||
#include <ox/std/vector.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/ptidxconv.hpp>
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::core {
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheet::SubSheet(SubSheet &&other) noexcept:
 | 
			
		||||
	id       (other.id),
 | 
			
		||||
	name     (std::move(other.name)),
 | 
			
		||||
	columns  (other.columns),
 | 
			
		||||
	rows     (other.rows),
 | 
			
		||||
	subsheets(std::move(other.subsheets)),
 | 
			
		||||
	pixels   (std::move(other.pixels)) {
 | 
			
		||||
	other.name = "";
 | 
			
		||||
	other.columns = {};
 | 
			
		||||
	other.rows = {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheet::SubSheet(
 | 
			
		||||
		SubSheetId pId,
 | 
			
		||||
		ox::CRStringView pName,
 | 
			
		||||
		int pColumns,
 | 
			
		||||
		int pRows,
 | 
			
		||||
		int bpp) noexcept:
 | 
			
		||||
	id(pId),
 | 
			
		||||
	name(pName),
 | 
			
		||||
	columns(pColumns),
 | 
			
		||||
	rows(pRows),
 | 
			
		||||
	pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheet::SubSheet(
 | 
			
		||||
		SubSheetId pId,
 | 
			
		||||
		ox::CRStringView pName,
 | 
			
		||||
		int pColumns,
 | 
			
		||||
		int pRows,
 | 
			
		||||
		ox::Vector<uint8_t> pPixels) noexcept:
 | 
			
		||||
	id(pId),
 | 
			
		||||
	name(pName),
 | 
			
		||||
	columns(pColumns),
 | 
			
		||||
	rows(pRows),
 | 
			
		||||
	pixels(std::move(pPixels)) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheet &TileSheet::SubSheet::operator=(TileSheet::SubSheet &&other) noexcept {
 | 
			
		||||
	name      = std::move(other.name);
 | 
			
		||||
	columns   = other.columns;
 | 
			
		||||
	rows      = other.rows;
 | 
			
		||||
	subsheets = std::move(other.subsheets);
 | 
			
		||||
	pixels    = std::move(other.pixels);
 | 
			
		||||
	return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t TileSheet::SubSheet::idx(ox::Point const&pt) const noexcept {
 | 
			
		||||
	return ptToIdx(pt, columns);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheet::SubSheet::readPixelsTo(ox::Vector<uint8_t> *pPixels, int8_t pBpp) const noexcept {
 | 
			
		||||
	if (!subsheets.empty()) {
 | 
			
		||||
		for (auto &s: subsheets) {
 | 
			
		||||
			s.readPixelsTo(pPixels);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if (pBpp == 4) {
 | 
			
		||||
			for (auto p: this->pixels) {
 | 
			
		||||
				pPixels->emplace_back(static_cast<uint8_t>(p & 0b1111));
 | 
			
		||||
				pPixels->emplace_back(static_cast<uint8_t>(p >> 4));
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			for (auto p: this->pixels) {
 | 
			
		||||
				pPixels->emplace_back(p);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheet::SubSheet::readPixelsTo(ox::Vector<uint8_t> *pPixels) const noexcept {
 | 
			
		||||
	if (!subsheets.empty()) {
 | 
			
		||||
		for (auto &s: subsheets) {
 | 
			
		||||
			s.readPixelsTo(pPixels);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for (auto p : this->pixels) {
 | 
			
		||||
			pPixels->emplace_back(p);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t TileSheet::SubSheet::unusedPixels() const noexcept {
 | 
			
		||||
	std::size_t childrenSize = 0;
 | 
			
		||||
	for (auto &c : subsheets) {
 | 
			
		||||
		childrenSize += c.size();
 | 
			
		||||
	}
 | 
			
		||||
	return size() - childrenSize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::SubSheet::getPixel4Bpp(std::size_t idx) const noexcept {
 | 
			
		||||
	if (idx & 1) {
 | 
			
		||||
		return this->pixels[idx / 2] >> 4;
 | 
			
		||||
	} else {
 | 
			
		||||
		return this->pixels[idx / 2] & 0b0000'1111;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::SubSheet::getPixel8Bpp(std::size_t idx) const noexcept {
 | 
			
		||||
	return this->pixels[idx];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::SubSheet::getPixel(int8_t pBpp, std::size_t idx) const noexcept {
 | 
			
		||||
	if (pBpp == 4) {
 | 
			
		||||
		return getPixel4Bpp(idx);
 | 
			
		||||
	} else {
 | 
			
		||||
		return getPixel8Bpp(idx);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::SubSheet::getPixel4Bpp(ox::Point const&pt) const noexcept {
 | 
			
		||||
	const auto idx = ptToIdx(pt, columns);
 | 
			
		||||
	return getPixel4Bpp(idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::SubSheet::getPixel8Bpp(ox::Point const&pt) const noexcept {
 | 
			
		||||
	const auto idx = ptToIdx(pt, columns);
 | 
			
		||||
	return getPixel8Bpp(idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::SubSheet::getPixel(int8_t pBpp, ox::Point const&pt) const noexcept {
 | 
			
		||||
	const auto idx = ptToIdx(pt, columns);
 | 
			
		||||
	return getPixel(pBpp, idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheet::SubSheet::setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
 | 
			
		||||
	auto &pixel = this->pixels[static_cast<std::size_t>(idx / 2)];
 | 
			
		||||
	if (pBpp == 4) {
 | 
			
		||||
		if (idx & 1) {
 | 
			
		||||
			pixel = static_cast<uint8_t>((pixel & 0b0000'1111) | (palIdx << 4));
 | 
			
		||||
		} else {
 | 
			
		||||
			pixel = (pixel & 0b1111'0000) | (palIdx);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		pixel = palIdx;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TileSheet::SubSheet::setPixel(int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
 | 
			
		||||
	const auto idx = ptToIdx(pt, columns);
 | 
			
		||||
	setPixel(pBpp, idx, palIdx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheet::SubSheet::setPixelCount(int8_t pBpp, std::size_t cnt) noexcept {
 | 
			
		||||
	switch (pBpp) {
 | 
			
		||||
		case 4:
 | 
			
		||||
			pixels.resize(cnt / 2);
 | 
			
		||||
			return OxError(0);
 | 
			
		||||
		case 8:
 | 
			
		||||
			pixels.resize(cnt);
 | 
			
		||||
			return OxError(0);
 | 
			
		||||
		default:
 | 
			
		||||
			return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
unsigned TileSheet::SubSheet::pixelCnt(int8_t pBpp) const noexcept {
 | 
			
		||||
	const auto pixelsSize = static_cast<unsigned>(pixels.size());
 | 
			
		||||
	return pBpp == 4 ? pixelsSize * 2 : pixelsSize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<unsigned> TileSheet::SubSheet::getTileOffset(
 | 
			
		||||
		ox::SpanView<ox::StringView> const&pNamePath,
 | 
			
		||||
		int8_t pBpp,
 | 
			
		||||
		std::size_t pIt,
 | 
			
		||||
		unsigned pCurrentTotal) const noexcept {
 | 
			
		||||
	// pIt == pNamePath.size() - 1 && 
 | 
			
		||||
	if (name != pNamePath[pIt]) {
 | 
			
		||||
		return OxError(2, "Wrong branch");
 | 
			
		||||
	}
 | 
			
		||||
	if (pIt == pNamePath.size() - 1) {
 | 
			
		||||
		return pCurrentTotal;
 | 
			
		||||
	}
 | 
			
		||||
	for (auto &sub : subsheets) {
 | 
			
		||||
		auto [offset, err] = sub.getTileOffset(
 | 
			
		||||
				pNamePath, pBpp, pIt + 1, pCurrentTotal);
 | 
			
		||||
		if (!err) {
 | 
			
		||||
			return offset;
 | 
			
		||||
		}
 | 
			
		||||
		pCurrentTotal += sub.pixelCnt(pBpp) / PixelsPerTile;
 | 
			
		||||
	}
 | 
			
		||||
	return OxError(1, "SubSheet not found");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<SubSheetId> TileSheet::SubSheet::getIdFor(
 | 
			
		||||
		ox::SpanView<ox::StringView> const&pNamePath,
 | 
			
		||||
		std::size_t pIt) const noexcept {
 | 
			
		||||
	for (auto &sub : subsheets) {
 | 
			
		||||
		if (sub.name == pNamePath[pIt]) {
 | 
			
		||||
			if (pIt == pNamePath.size()) {
 | 
			
		||||
				return id;
 | 
			
		||||
			}
 | 
			
		||||
			return getIdFor(pNamePath, pIt + 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return OxError(1, "SubSheet not found");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<ox::StringView> TileSheet::SubSheet::getNameFor(SubSheetId pId) const noexcept {
 | 
			
		||||
	if (id == pId) {
 | 
			
		||||
		return ox::StringView(name);
 | 
			
		||||
	}
 | 
			
		||||
	for (const auto &sub : subsheets) {
 | 
			
		||||
		const auto [name, err] = sub.getNameFor(pId);
 | 
			
		||||
		if (!err) {
 | 
			
		||||
			return name;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return OxError(1, "SubSheet not found");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TileSheet &TileSheet::operator=(TileSheet const&other) noexcept {
 | 
			
		||||
	if (this != &other) {
 | 
			
		||||
		bpp = other.bpp;
 | 
			
		||||
		idIt = other.idIt;
 | 
			
		||||
		defaultPalette = other.defaultPalette;
 | 
			
		||||
		subsheet = other.subsheet;
 | 
			
		||||
	}
 | 
			
		||||
	return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet &TileSheet::operator=(TileSheet &&other) noexcept {
 | 
			
		||||
	bpp = other.bpp;
 | 
			
		||||
	idIt = other.idIt;
 | 
			
		||||
	defaultPalette = std::move(other.defaultPalette);
 | 
			
		||||
	subsheet = std::move(other.subsheet);
 | 
			
		||||
	return *this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx TileSheet::validateSubSheetIdx(
 | 
			
		||||
		const SubSheetIdx &pIdx,
 | 
			
		||||
		std::size_t pIdxIt,
 | 
			
		||||
		const SubSheet *pSubsheet) noexcept {
 | 
			
		||||
	if (pIdxIt == pIdx.size()) {
 | 
			
		||||
		return pIdx;
 | 
			
		||||
	}
 | 
			
		||||
	const auto currentIdx = pIdx[pIdxIt];
 | 
			
		||||
	if (pSubsheet->subsheets.size() <= currentIdx) {
 | 
			
		||||
		auto out = pIdx;
 | 
			
		||||
		if (!pSubsheet->subsheets.empty()) {
 | 
			
		||||
			*out.back().value = pSubsheet->subsheets.size() - 1;
 | 
			
		||||
		} else {
 | 
			
		||||
			out.pop_back();
 | 
			
		||||
		}
 | 
			
		||||
		return out;
 | 
			
		||||
	}
 | 
			
		||||
	return validateSubSheetIdx(pIdx, pIdxIt + 1, &pSubsheet->subsheets[pIdx[pIdxIt]]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheetIdx TileSheet::validateSubSheetIdx(const SubSheetIdx &idx) noexcept {
 | 
			
		||||
	return validateSubSheetIdx(idx, 0, &subsheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TileSheet::SubSheet &TileSheet::getSubSheet(
 | 
			
		||||
		TileSheet::SubSheetIdx const&idx,
 | 
			
		||||
		std::size_t idxIt,
 | 
			
		||||
		SubSheet const*pSubsheet) noexcept {
 | 
			
		||||
	if (idxIt == idx.size()) {
 | 
			
		||||
		return *pSubsheet;
 | 
			
		||||
	}
 | 
			
		||||
	const auto currentIdx = idx[idxIt];
 | 
			
		||||
	if (pSubsheet->subsheets.size() < currentIdx) {
 | 
			
		||||
		return *pSubsheet;
 | 
			
		||||
	}
 | 
			
		||||
	return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[currentIdx]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheet &TileSheet::getSubSheet(
 | 
			
		||||
		TileSheet::SubSheetIdx const&idx,
 | 
			
		||||
		std::size_t idxIt,
 | 
			
		||||
		TileSheet::SubSheet *pSubsheet) noexcept {
 | 
			
		||||
	if (idxIt == idx.size()) {
 | 
			
		||||
		return *pSubsheet;
 | 
			
		||||
	}
 | 
			
		||||
	return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TileSheet::SubSheet &TileSheet::getSubSheet(TileSheet::SubSheetIdx const&idx) const noexcept {
 | 
			
		||||
	return getSubSheet(idx, 0, &subsheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TileSheet::SubSheet &TileSheet::getSubSheet(TileSheet::SubSheetIdx const&idx) noexcept {
 | 
			
		||||
	return getSubSheet(idx, 0, &subsheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheet::addSubSheet(TileSheet::SubSheetIdx const&idx) noexcept {
 | 
			
		||||
	auto &parent = getSubSheet(idx);
 | 
			
		||||
	if (parent.subsheets.size() < 2) {
 | 
			
		||||
		parent.subsheets.emplace_back(idIt++, ox::sfmt("Subsheet {}", parent.subsheets.size()), 1, 1, bpp);
 | 
			
		||||
	} else {
 | 
			
		||||
		parent.subsheets.emplace_back(idIt++, "Subsheet 0", parent.columns, parent.rows, bpp);
 | 
			
		||||
		parent.subsheets.emplace_back(idIt++, "Subsheet 1", 1, 1, bpp);
 | 
			
		||||
	}
 | 
			
		||||
	return OxError(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheet::rmSubSheet(
 | 
			
		||||
		SubSheetIdx const&idx,
 | 
			
		||||
		std::size_t idxIt,
 | 
			
		||||
		SubSheet *pSubsheet) noexcept {
 | 
			
		||||
	if (idxIt == idx.size() - 1) {
 | 
			
		||||
		return pSubsheet->subsheets.erase(idx[idxIt]).error;
 | 
			
		||||
	}
 | 
			
		||||
	return rmSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error TileSheet::rmSubSheet(TileSheet::SubSheetIdx const&idx) noexcept {
 | 
			
		||||
	return rmSubSheet(idx, 0, &subsheet);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::getPixel4Bpp(
 | 
			
		||||
		ox::Point const&pt,
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx) const noexcept {
 | 
			
		||||
	oxAssert(bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
 | 
			
		||||
	auto &s = this->getSubSheet(subsheetIdx);
 | 
			
		||||
	const auto idx = ptToIdx(pt, s.columns);
 | 
			
		||||
	return s.getPixel4Bpp(idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t TileSheet::getPixel8Bpp(
 | 
			
		||||
		ox::Point const&pt,
 | 
			
		||||
		TileSheet::SubSheetIdx const&subsheetIdx) const noexcept {
 | 
			
		||||
	oxAssert(bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
 | 
			
		||||
	auto &s = this->getSubSheet(subsheetIdx);
 | 
			
		||||
	const auto idx = ptToIdx(pt, s.columns);
 | 
			
		||||
	return s.getPixel8Bpp(idx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<SubSheetId> TileSheet::getIdFor(ox::CRStringView path) const noexcept {
 | 
			
		||||
	return subsheet.getIdFor(ox::split<8>(path, '.'));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<unsigned> TileSheet::getTileOffset(ox::CRStringView pNamePath) const noexcept {
 | 
			
		||||
	return subsheet.getTileOffset(ox::split<8>(pNamePath, '.'), bpp);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Result<ox::StringView> TileSheet::getNameFor(SubSheetId pId) const noexcept {
 | 
			
		||||
	return subsheet.getNameFor(pId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Vector<uint8_t> TileSheet::pixels() const noexcept {
 | 
			
		||||
	ox::Vector<uint8_t> out;
 | 
			
		||||
	subsheet.readPixelsTo(&out);
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ox::Vector<uint32_t> resizeTileSheetData(
 | 
			
		||||
		ox::Vector<uint32_t> const&srcPixels,
 | 
			
		||||
		ox::Size const&srcSize,
 | 
			
		||||
		int scale) noexcept {
 | 
			
		||||
	ox::Vector<uint32_t> dst;
 | 
			
		||||
	auto dstWidth = srcSize.width * scale;
 | 
			
		||||
	auto dstHeight = srcSize.height * scale;
 | 
			
		||||
	const auto pixelCnt = dstWidth * dstHeight;
 | 
			
		||||
	dst.resize(static_cast<std::size_t>(pixelCnt));
 | 
			
		||||
	for (auto i = 0; i < pixelCnt; ++i) {
 | 
			
		||||
		const auto dstPt = idxToPt(i, 1, scale);
 | 
			
		||||
		const auto srcPt = dstPt / ox::Point{scale, scale};
 | 
			
		||||
		const auto srcIdx = ptToIdx(srcPt, 1);
 | 
			
		||||
		const auto srcPixel = srcPixels[srcIdx];
 | 
			
		||||
		dst[static_cast<std::size_t>(i)] = srcPixel;
 | 
			
		||||
	}
 | 
			
		||||
	return dst;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/nostalgia/modules/core/test/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/nostalgia/modules/core/test/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
add_executable(
 | 
			
		||||
	NostalgiaCoreTest
 | 
			
		||||
		tests.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaCoreTest
 | 
			
		||||
		NostalgiaCore
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_test("[NostalgiaCore] readWriteTileSheet" ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/NostalgiaCoreTest readWriteTileSheet)
 | 
			
		||||
							
								
								
									
										38
									
								
								src/nostalgia/modules/core/test/tests.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/nostalgia/modules/core/test/tests.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#undef NDEBUG
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <ox/std/error.hpp>
 | 
			
		||||
#include <ox/mc/mc.hpp>
 | 
			
		||||
#include <nostalgia/core/core.hpp>
 | 
			
		||||
 | 
			
		||||
using namespace nostalgia;
 | 
			
		||||
 | 
			
		||||
static std::map<ox::StringView, ox::Error(*)()> tests = {
 | 
			
		||||
	{
 | 
			
		||||
		"readWriteTileSheet",
 | 
			
		||||
		[]() -> ox::Error {
 | 
			
		||||
			core::TileSheet in;
 | 
			
		||||
			oxRequire(buff, ox::writeMC(in));
 | 
			
		||||
			oxRequire(out, ox::readMC<core::TileSheet>(buff));
 | 
			
		||||
			oxAssert(in.subsheet.name == out.subsheet.name, "subsheet.name serialization broken");
 | 
			
		||||
			return {};
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int main(int argc, const char **args) {
 | 
			
		||||
	int retval = -1;
 | 
			
		||||
	if (argc > 0) {
 | 
			
		||||
		auto const testName = ox::StringView(args[1]);
 | 
			
		||||
		if (tests.find(testName) != tests.end()) {
 | 
			
		||||
			retval = static_cast<int>(tests[testName]());
 | 
			
		||||
		} else {
 | 
			
		||||
			retval = 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/nostalgia/modules/keelmodules.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/nostalgia/modules/keelmodules.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/keelmodule.hpp>
 | 
			
		||||
#include <nostalgia/scene/keelmodule.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia {
 | 
			
		||||
 | 
			
		||||
static bool modulesRegistered = false;
 | 
			
		||||
void registerKeelModules() noexcept {
 | 
			
		||||
	if (modulesRegistered) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	modulesRegistered = true;
 | 
			
		||||
	keel::registerModule(core::keelModule());
 | 
			
		||||
	keel::registerModule(scene::keelModule());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/nostalgia/modules/keelmodules.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/nostalgia/modules/keelmodules.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
namespace nostalgia {
 | 
			
		||||
 | 
			
		||||
void registerKeelModules() noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/nostalgia/modules/scene/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/nostalgia/modules/scene/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
add_subdirectory(src)
 | 
			
		||||
 | 
			
		||||
target_include_directories(
 | 
			
		||||
	NostalgiaScene PUBLIC
 | 
			
		||||
		include
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	DIRECTORY
 | 
			
		||||
		include/nostalgia
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		include
 | 
			
		||||
)
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <keel/module.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
const keel::Module *keelModule() noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
 | 
			
		||||
#include "scenestatic.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
class Scene {
 | 
			
		||||
	private:
 | 
			
		||||
		SceneStatic const&m_sceneStatic;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		explicit Scene(SceneStatic const&sceneStatic) noexcept;
 | 
			
		||||
 | 
			
		||||
		ox::Error setupDisplay(core::Context &ctx) const noexcept;
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		void setupLayer(core::Context&, ox::Vector<uint16_t> const&layer, unsigned layerNo) const noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,228 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <ox/fs/fs.hpp>
 | 
			
		||||
#include <ox/std/error.hpp>
 | 
			
		||||
#include <ox/std/size.hpp>
 | 
			
		||||
#include <ox/std/types.hpp>
 | 
			
		||||
#include <ox/std/vector.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/tilesheet.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
struct SpriteDoc {
 | 
			
		||||
 | 
			
		||||
	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SpriteDoc";
 | 
			
		||||
	constexpr static auto TypeVersion = 1;
 | 
			
		||||
	constexpr static auto Preloadable = true;
 | 
			
		||||
 | 
			
		||||
	ox::String tilesheetPath;
 | 
			
		||||
	ox::Vector<core::SubSheetId> subsheetId;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct TileDoc {
 | 
			
		||||
 | 
			
		||||
	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.TileDoc";
 | 
			
		||||
	constexpr static auto TypeVersion = 1;
 | 
			
		||||
	constexpr static auto Preloadable = true;
 | 
			
		||||
 | 
			
		||||
	core::SubSheetId subsheetId = -1;
 | 
			
		||||
	ox::String subsheetPath;
 | 
			
		||||
	uint8_t type = 0;
 | 
			
		||||
	ox::Array<uint8_t, 4> layerAttachments;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr ox::Result<core::SubSheetId> getSubsheetId(core::TileSheet const&ts) const noexcept {
 | 
			
		||||
		// prefer the already present ID
 | 
			
		||||
		if (subsheetId > -1) {
 | 
			
		||||
			return subsheetId;
 | 
			
		||||
		}
 | 
			
		||||
		return ts.getIdFor(subsheetPath);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr ox::Result<ox::StringView> getSubsheetPath(
 | 
			
		||||
			core::TileSheet const&ts) const noexcept {
 | 
			
		||||
		// prefer the already present path
 | 
			
		||||
		if (!subsheetPath.len()) {
 | 
			
		||||
			return ts.getNameFor(subsheetId);
 | 
			
		||||
		}
 | 
			
		||||
		return ox::StringView(subsheetPath);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(TileDoc)
 | 
			
		||||
	oxModelFieldRename(subsheet_id, subsheetId)
 | 
			
		||||
	oxModelFieldRename(subsheet_path, subsheetPath)
 | 
			
		||||
	oxModelField(type)
 | 
			
		||||
	oxModelFieldRename(layer_attachments, layerAttachments)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
struct SceneDoc {
 | 
			
		||||
 | 
			
		||||
	using TileMapRow = ox::Vector<TileDoc>;
 | 
			
		||||
	using TileMapLayer = ox::Vector<TileMapRow>;
 | 
			
		||||
	using TileMap = ox::Vector<TileMapLayer>;
 | 
			
		||||
 | 
			
		||||
	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneDoc";
 | 
			
		||||
	constexpr static auto TypeVersion = 1;
 | 
			
		||||
	constexpr static auto Preloadable = true;
 | 
			
		||||
 | 
			
		||||
	ox::String tilesheet; // path
 | 
			
		||||
	ox::Vector<ox::String> palettes; // paths
 | 
			
		||||
	TileMap tiles;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr ox::Size size(std::size_t layerIdx) const noexcept {
 | 
			
		||||
		const auto &layer = this->tiles[layerIdx];
 | 
			
		||||
		const auto rowCnt = static_cast<int>(layer.size());
 | 
			
		||||
		if (!rowCnt) {
 | 
			
		||||
			return {};
 | 
			
		||||
		}
 | 
			
		||||
		auto colCnt = layer[0].size();
 | 
			
		||||
		// find shortest row (they should all be the same, but you know this data
 | 
			
		||||
		// could come from a file)
 | 
			
		||||
		for (auto const&row : layer) {
 | 
			
		||||
			colCnt = ox::min(colCnt, row.size());
 | 
			
		||||
		}
 | 
			
		||||
		return {static_cast<int>(colCnt), rowCnt};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(SceneDoc)
 | 
			
		||||
	oxModelField(tilesheet)
 | 
			
		||||
	oxModelField(palettes)
 | 
			
		||||
	oxModelField(tiles)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
constexpr void setTopEdge(uint8_t &layerAttachments, unsigned val) noexcept {
 | 
			
		||||
	const auto val8 = static_cast<uint8_t>(val);
 | 
			
		||||
	layerAttachments = (layerAttachments & 0b11111100) | val8;
 | 
			
		||||
}
 | 
			
		||||
constexpr void setBottomEdge(uint8_t &layerAttachments, unsigned val) noexcept {
 | 
			
		||||
	const auto val8 = static_cast<uint8_t>(val);
 | 
			
		||||
	layerAttachments = (layerAttachments & 0b11110011) | static_cast<uint8_t>(val8 << 2);
 | 
			
		||||
}
 | 
			
		||||
constexpr void setLeftEdge(uint8_t &layerAttachments, unsigned val) noexcept {
 | 
			
		||||
	const auto val8 = static_cast<uint8_t>(val);
 | 
			
		||||
	layerAttachments = (layerAttachments & 0b11001111) | static_cast<uint8_t>(val8 << 4);
 | 
			
		||||
}
 | 
			
		||||
constexpr void setRightEdge(uint8_t &layerAttachments, unsigned val) noexcept {
 | 
			
		||||
	const auto val8 = static_cast<uint8_t>(val);
 | 
			
		||||
	layerAttachments = (layerAttachments & 0b00111111) | static_cast<uint8_t>(val8 << 6);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr unsigned topEdge(uint8_t layerAttachments) noexcept {
 | 
			
		||||
	return layerAttachments & 0b11;
 | 
			
		||||
}
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr unsigned bottomEdge(uint8_t layerAttachments) noexcept {
 | 
			
		||||
	return (layerAttachments >> 2) & 0b11;
 | 
			
		||||
}
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr unsigned leftEdge(uint8_t layerAttachments) noexcept {
 | 
			
		||||
	return (layerAttachments >> 4) & 0b11;
 | 
			
		||||
}
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept {
 | 
			
		||||
	return (layerAttachments >> 6) & 0b11;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct SceneStatic {
 | 
			
		||||
 | 
			
		||||
	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneStatic";
 | 
			
		||||
	constexpr static auto TypeVersion = 1;
 | 
			
		||||
	constexpr static auto Preloadable = true;
 | 
			
		||||
 | 
			
		||||
	struct Tile {
 | 
			
		||||
		uint16_t &tileMapIdx;
 | 
			
		||||
		uint8_t &tileType;
 | 
			
		||||
		uint8_t &layerAttachments;
 | 
			
		||||
		constexpr Tile(uint16_t &pTileMapIdx, uint8_t &pTileType, uint8_t &pLayerAttachments) noexcept:
 | 
			
		||||
			tileMapIdx(pTileMapIdx),
 | 
			
		||||
			tileType(pTileType),
 | 
			
		||||
			layerAttachments(pLayerAttachments) {
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	struct Layer {
 | 
			
		||||
		uint16_t &columns;
 | 
			
		||||
		uint16_t &rows;
 | 
			
		||||
		ox::Vector<uint16_t> &tileMapIdx;
 | 
			
		||||
		ox::Vector<uint8_t> &tileType;
 | 
			
		||||
		ox::Vector<uint8_t> &layerAttachments;
 | 
			
		||||
		constexpr Layer(
 | 
			
		||||
				uint16_t &pColumns,
 | 
			
		||||
				uint16_t &pRows,
 | 
			
		||||
				ox::Vector<uint16_t> &pTileMapIdx,
 | 
			
		||||
				ox::Vector<uint8_t> &pTileType,
 | 
			
		||||
				ox::Vector<uint8_t> &pLayerAttachments) noexcept:
 | 
			
		||||
			columns(pColumns),
 | 
			
		||||
			rows(pRows),
 | 
			
		||||
			tileMapIdx(pTileMapIdx),
 | 
			
		||||
			tileType(pTileType),
 | 
			
		||||
			layerAttachments(pLayerAttachments) {
 | 
			
		||||
		}
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		constexpr Tile tile(std::size_t i) noexcept {
 | 
			
		||||
			return {tileMapIdx[i], tileType[i], layerAttachments[i]};
 | 
			
		||||
		}
 | 
			
		||||
		constexpr auto setDimensions(ox::Size dim) noexcept {
 | 
			
		||||
			columns = static_cast<uint16_t>(dim.width);
 | 
			
		||||
			rows = static_cast<uint16_t>(dim.height);
 | 
			
		||||
			const auto tileCnt = static_cast<unsigned>(columns * rows);
 | 
			
		||||
			tileMapIdx.resize(tileCnt);
 | 
			
		||||
			tileType.resize(tileCnt);
 | 
			
		||||
			layerAttachments.resize(tileCnt);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ox::FileAddress tilesheet;
 | 
			
		||||
	ox::Vector<ox::FileAddress> palettes;
 | 
			
		||||
	// tile layer data
 | 
			
		||||
	ox::Vector<uint16_t> columns;
 | 
			
		||||
	ox::Vector<uint16_t> rows;
 | 
			
		||||
	ox::Vector<ox::Vector<uint16_t>> tileMapIdx;
 | 
			
		||||
	ox::Vector<ox::Vector<uint8_t>>  tileType;
 | 
			
		||||
	ox::Vector<ox::Vector<uint8_t>>  layerAttachments;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]]
 | 
			
		||||
	constexpr Layer layer(std::size_t i) noexcept {
 | 
			
		||||
		return {
 | 
			
		||||
			columns[i],
 | 
			
		||||
			rows[i],
 | 
			
		||||
			tileMapIdx[i],
 | 
			
		||||
			tileType[i],
 | 
			
		||||
			layerAttachments[i],
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	constexpr auto setLayerCnt(std::size_t layerCnt) noexcept {
 | 
			
		||||
		this->layerAttachments.resize(layerCnt);
 | 
			
		||||
		this->columns.resize(layerCnt);
 | 
			
		||||
		this->rows.resize(layerCnt);
 | 
			
		||||
		this->tileMapIdx.resize(layerCnt);
 | 
			
		||||
		this->tileType.resize(layerCnt);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
oxModelBegin(SceneStatic)
 | 
			
		||||
	oxModelField(tilesheet)
 | 
			
		||||
	oxModelField(palettes)
 | 
			
		||||
	oxModelField(columns)
 | 
			
		||||
	oxModelField(rows)
 | 
			
		||||
	oxModelField(tileMapIdx)
 | 
			
		||||
	oxModelField(tileType)
 | 
			
		||||
	oxModelField(layerAttachments)
 | 
			
		||||
oxModelEnd()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
const studio::Module *studioModule() noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/nostalgia/modules/scene/src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/nostalgia/modules/scene/src/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaScene
 | 
			
		||||
		scene.cpp
 | 
			
		||||
		scenestatic.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_include_directories(
 | 
			
		||||
	NostalgiaScene PUBLIC
 | 
			
		||||
		../include
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaScene PUBLIC
 | 
			
		||||
		NostalgiaCore
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(keel)
 | 
			
		||||
if(NOSTALGIA_BUILD_STUDIO)
 | 
			
		||||
	add_subdirectory(studio)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		NostalgiaScene
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		LIBRARY DESTINATION lib
 | 
			
		||||
		ARCHIVE DESTINATION lib
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										17
									
								
								src/nostalgia/modules/scene/src/keel/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/nostalgia/modules/scene/src/keel/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaScene-Keel
 | 
			
		||||
		keelmodule.cpp
 | 
			
		||||
		typeconv.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaScene-Keel PUBLIC
 | 
			
		||||
		Keel
 | 
			
		||||
		NostalgiaScene
 | 
			
		||||
)
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		NostalgiaScene-Keel
 | 
			
		||||
	LIBRARY DESTINATION
 | 
			
		||||
		${NOSTALGIA_DIST_MODULE}
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										52
									
								
								src/nostalgia/modules/scene/src/keel/keelmodule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/nostalgia/modules/scene/src/keel/keelmodule.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <keel/module.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/scene/scenestatic.hpp>
 | 
			
		||||
 | 
			
		||||
#include "typeconv.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
class SceneModule: public keel::Module {
 | 
			
		||||
	private:
 | 
			
		||||
		SceneDocToSceneStaticConverter m_sceneDocToSceneStaticConverter;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::String id() const noexcept override {
 | 
			
		||||
			return ox::String("net.drinkingtea.nostalgia.scene");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vector<keel::TypeDescGenerator> types() const noexcept override {
 | 
			
		||||
			return {
 | 
			
		||||
				keel::generateTypeDesc<SceneDoc>,
 | 
			
		||||
				keel::generateTypeDesc<SceneStatic>,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vector<const keel::BaseConverter*> converters() const noexcept override {
 | 
			
		||||
			return {
 | 
			
		||||
				&m_sceneDocToSceneStaticConverter,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		ox::Vector<keel::PackTransform> packTransforms() const noexcept override {
 | 
			
		||||
			return {
 | 
			
		||||
				keel::transformRule<SceneDoc, SceneStatic>,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const SceneModule mod;
 | 
			
		||||
const keel::Module *keelModule() noexcept {
 | 
			
		||||
	return &mod;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								src/nostalgia/modules/scene/src/keel/typeconv.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/nostalgia/modules/scene/src/keel/typeconv.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include "typeconv.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
constexpr unsigned adjustLayerAttachment(unsigned layer, unsigned attachment) noexcept {
 | 
			
		||||
	if (attachment == 0) {
 | 
			
		||||
		return layer;
 | 
			
		||||
	} else {
 | 
			
		||||
		return attachment - 1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
constexpr void setLayerAttachments(unsigned layer, TileDoc const&srcTile, SceneStatic::Tile &dstTile) noexcept {
 | 
			
		||||
	setTopEdge(
 | 
			
		||||
			dstTile.layerAttachments,
 | 
			
		||||
			adjustLayerAttachment(layer, srcTile.layerAttachments[0]));
 | 
			
		||||
	setBottomEdge(
 | 
			
		||||
			dstTile.layerAttachments,
 | 
			
		||||
			adjustLayerAttachment(layer, srcTile.layerAttachments[1]));
 | 
			
		||||
	setLeftEdge(
 | 
			
		||||
			dstTile.layerAttachments,
 | 
			
		||||
			adjustLayerAttachment(layer, srcTile.layerAttachments[2]));
 | 
			
		||||
	setRightEdge(
 | 
			
		||||
			dstTile.layerAttachments,
 | 
			
		||||
			adjustLayerAttachment(layer, srcTile.layerAttachments[3]));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error SceneDocToSceneStaticConverter::convert(
 | 
			
		||||
		keel::Context &ctx,
 | 
			
		||||
		SceneDoc &src,
 | 
			
		||||
		SceneStatic &dst) const noexcept {
 | 
			
		||||
	oxRequire(ts, keel::readObj<core::TileSheet>(ctx, src.tilesheet));
 | 
			
		||||
	const auto layerCnt = src.tiles.size();
 | 
			
		||||
	dst.setLayerCnt(layerCnt);
 | 
			
		||||
	dst.tilesheet = ox::FileAddress(src.tilesheet);
 | 
			
		||||
	dst.palettes.reserve(src.palettes.size());
 | 
			
		||||
	for (const auto &pal : src.palettes) {
 | 
			
		||||
		dst.palettes.emplace_back(pal);
 | 
			
		||||
	}
 | 
			
		||||
	for (auto layerIdx = 0u; const auto &layer : src.tiles) {
 | 
			
		||||
		const auto layerDim = src.size(layerIdx);
 | 
			
		||||
		auto dstLayer = dst.layer(layerIdx);
 | 
			
		||||
		dstLayer.setDimensions(layerDim);
 | 
			
		||||
		for (auto tileIdx = 0u; const auto &row : layer) {
 | 
			
		||||
			for (const auto &srcTile : row) {
 | 
			
		||||
				auto dstTile = dstLayer.tile(tileIdx);
 | 
			
		||||
				dstTile.tileType = srcTile.type;
 | 
			
		||||
				oxRequire(path, srcTile.getSubsheetPath(*ts));
 | 
			
		||||
				oxRequire(mapIdx, ts->getTileOffset(path));
 | 
			
		||||
				dstTile.tileMapIdx = static_cast<uint16_t>(mapIdx);
 | 
			
		||||
				setLayerAttachments(layerIdx, srcTile, dstTile);
 | 
			
		||||
				++tileIdx;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		++layerIdx;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								src/nostalgia/modules/scene/src/keel/typeconv.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/nostalgia/modules/scene/src/keel/typeconv.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <keel/typeconv.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/scene/scenestatic.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
class SceneDocToSceneStaticConverter: public keel::Converter<SceneDoc, SceneStatic> {
 | 
			
		||||
	ox::Error convert(keel::Context&, SceneDoc &src, SceneStatic &dst) const noexcept final;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/nostalgia/modules/scene/src/scene.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/nostalgia/modules/scene/src/scene.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/scene/scene.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
Scene::Scene(SceneStatic const&sceneStatic) noexcept:
 | 
			
		||||
	m_sceneStatic(sceneStatic) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error Scene::setupDisplay(core::Context &ctx) const noexcept {
 | 
			
		||||
	if (m_sceneStatic.palettes.empty()) {
 | 
			
		||||
		return OxError(1, "Scene has no palettes");
 | 
			
		||||
	}
 | 
			
		||||
	auto const&palette = m_sceneStatic.palettes[0];
 | 
			
		||||
	oxReturnError(core::loadBgTileSheet(ctx, 0, m_sceneStatic.tilesheet));
 | 
			
		||||
	oxReturnError(core::loadBgPalette(ctx, palette));
 | 
			
		||||
	// disable all backgrounds
 | 
			
		||||
	core::setBgStatus(ctx, 0);
 | 
			
		||||
	for (auto layerNo = 0u; auto const&layer : m_sceneStatic.tileMapIdx) {
 | 
			
		||||
		setupLayer(ctx, layer, layerNo);
 | 
			
		||||
		++layerNo;
 | 
			
		||||
	}
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Scene::setupLayer(
 | 
			
		||||
		core::Context &ctx,
 | 
			
		||||
		ox::Vector<uint16_t> const&layer,
 | 
			
		||||
		unsigned layerNo) const noexcept {
 | 
			
		||||
	core::setBgStatus(ctx, layerNo, true);
 | 
			
		||||
	core::setBgCbb(ctx, layerNo, 0);
 | 
			
		||||
	auto x = 0;
 | 
			
		||||
	auto y = 0;
 | 
			
		||||
	const auto width = m_sceneStatic.rows[layerNo];
 | 
			
		||||
	for (auto const&tile : layer) {
 | 
			
		||||
		const auto tile8 = static_cast<uint8_t>(tile);
 | 
			
		||||
		core::setBgTile(ctx, layerNo, x, y, tile8);
 | 
			
		||||
		core::setBgTile(ctx, layerNo, x + 1, y, tile8 + 1);
 | 
			
		||||
		core::setBgTile(ctx, layerNo, x, y + 1, tile8 + 2);
 | 
			
		||||
		core::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3);
 | 
			
		||||
		x += 2;
 | 
			
		||||
		if (x >= width * 2) {
 | 
			
		||||
			x = 0;
 | 
			
		||||
			y += 2;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/nostalgia/modules/scene/src/scenestatic.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/nostalgia/modules/scene/src/scenestatic.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/scene/scenestatic.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/nostalgia/modules/scene/src/studio/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/nostalgia/modules/scene/src/studio/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
add_library(
 | 
			
		||||
	NostalgiaScene-Studio
 | 
			
		||||
		studiomodule.cpp
 | 
			
		||||
		sceneeditor-imgui.cpp
 | 
			
		||||
		sceneeditor.cpp
 | 
			
		||||
		sceneeditorview.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaScene-Studio PUBLIC
 | 
			
		||||
		NostalgiaScene
 | 
			
		||||
		Studio
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		NostalgiaScene-Studio
 | 
			
		||||
	LIBRARY DESTINATION
 | 
			
		||||
		${NOSTALGIA_DIST_MODULE}
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										57
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include "sceneeditor-imgui.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
SceneEditorImGui::SceneEditorImGui(turbine::Context &ctx, ox::StringView path):
 | 
			
		||||
	Editor(path),
 | 
			
		||||
	m_ctx(ctx),
 | 
			
		||||
	m_editor(m_ctx, path),
 | 
			
		||||
	m_view(m_ctx, m_editor.scene()) {
 | 
			
		||||
	setRequiresConstantRefresh(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SceneEditorImGui::draw(turbine::Context&) noexcept {
 | 
			
		||||
	auto const paneSize = ImGui::GetContentRegionAvail();
 | 
			
		||||
	m_view.draw(ox::Size{static_cast<int>(paneSize.x), static_cast<int>(paneSize.y)});
 | 
			
		||||
	auto &fb = m_view.framebuffer();
 | 
			
		||||
	auto const srcH = static_cast<float>(fb.height) / static_cast<float>(fb.width);
 | 
			
		||||
	auto const dstH = paneSize.y / paneSize.x;
 | 
			
		||||
	float xScale{}, yScale{};
 | 
			
		||||
	if (dstH > srcH) {
 | 
			
		||||
		// crop off width
 | 
			
		||||
		xScale = srcH / dstH;
 | 
			
		||||
		yScale = 1;
 | 
			
		||||
	} else {
 | 
			
		||||
		auto const srcW = static_cast<float>(fb.width) / static_cast<float>(fb.height);
 | 
			
		||||
		auto const dstW = (paneSize.x / paneSize.y);
 | 
			
		||||
		xScale = 1;
 | 
			
		||||
		yScale = srcW / dstW;
 | 
			
		||||
	}
 | 
			
		||||
	uintptr_t const buffId = fb.color.id;
 | 
			
		||||
	ImGui::Image(
 | 
			
		||||
		std::bit_cast<void*>(buffId),
 | 
			
		||||
		paneSize,
 | 
			
		||||
		ImVec2(0, 1),
 | 
			
		||||
		ImVec2(xScale, 1 - yScale));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SceneEditorImGui::onActivated() noexcept {
 | 
			
		||||
	oxLogError(m_view.setupScene());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error SceneEditorImGui::saveItem() noexcept {
 | 
			
		||||
	const auto sctx = applicationData<studio::StudioContext>(m_ctx);
 | 
			
		||||
	oxReturnError(sctx->project->writeObj(itemPath(), m_editor.scene()));
 | 
			
		||||
	oxReturnError(keelCtx(m_ctx).assetManager.setAsset(itemPath(), m_editor.scene()));
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include <turbine/context.hpp>
 | 
			
		||||
 | 
			
		||||
#include "sceneeditor.hpp"
 | 
			
		||||
#include "sceneeditorview.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
class SceneEditorImGui: public studio::Editor {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		turbine::Context &m_ctx;
 | 
			
		||||
		SceneEditor m_editor;
 | 
			
		||||
		SceneEditorView m_view;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		SceneEditorImGui(turbine::Context &ctx, ox::StringView path);
 | 
			
		||||
 | 
			
		||||
		void draw(turbine::Context&) noexcept final;
 | 
			
		||||
 | 
			
		||||
		void onActivated() noexcept override;
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		ox::Error saveItem() noexcept final;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <keel/keel.hpp>
 | 
			
		||||
 | 
			
		||||
#include "sceneeditor.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
SceneEditor::SceneEditor(turbine::Context &ctx, ox::CRStringView path):
 | 
			
		||||
	m_ctx(ctx),
 | 
			
		||||
	m_scene(*keel::readObj<SceneStatic>(keelCtx(m_ctx), path).unwrapThrow()) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditor.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <turbine/context.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/scene/scene.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
class SceneEditor {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		turbine::Context &m_ctx;
 | 
			
		||||
		ox::String m_itemName;
 | 
			
		||||
		ox::String m_itemPath;
 | 
			
		||||
		SceneStatic m_scene;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		SceneEditor(turbine::Context &ctx, ox::CRStringView path);
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		SceneStatic const&scene() const noexcept {
 | 
			
		||||
			return m_scene;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	protected:
 | 
			
		||||
		ox::Error saveItem() noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditorview.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditorview.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
 | 
			
		||||
#include "sceneeditorview.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
SceneEditorView::SceneEditorView(turbine::Context &tctx, SceneStatic const&sceneStatic):
 | 
			
		||||
	m_cctx(core::init(tctx, {.glInstallDrawer = false}).unwrapThrow()),
 | 
			
		||||
	m_sceneStatic(sceneStatic),
 | 
			
		||||
	m_scene(m_sceneStatic) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error SceneEditorView::setupScene() noexcept {
 | 
			
		||||
	glutils::resizeInitFrameBuffer(m_frameBuffer, core::gl::drawSize(m_scale));
 | 
			
		||||
	return m_scene.setupDisplay(*m_cctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SceneEditorView::draw(ox::Size const&targetSz) noexcept {
 | 
			
		||||
	auto const scaleSz = targetSz / core::gl::drawSize(1);
 | 
			
		||||
	if (m_scaleSz != scaleSz) [[unlikely]] {
 | 
			
		||||
		m_scale = ox::max(1, ox::max(scaleSz.width, scaleSz.height));
 | 
			
		||||
		glutils::resizeInitFrameBuffer(m_frameBuffer, core::gl::drawSize(m_scale));
 | 
			
		||||
	}
 | 
			
		||||
	glutils::FrameBufferBind const frameBufferBind(m_frameBuffer);
 | 
			
		||||
	core::gl::draw(*m_cctx, m_scale);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
glutils::FrameBuffer const&SceneEditorView::framebuffer() const noexcept {
 | 
			
		||||
	return m_frameBuffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditorview.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/nostalgia/modules/scene/src/studio/sceneeditorview.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <glutils/glutils.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/context.hpp>
 | 
			
		||||
#include <nostalgia/core/gfx.hpp>
 | 
			
		||||
#include <nostalgia/scene/scene.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
class SceneEditorView {
 | 
			
		||||
 | 
			
		||||
	private:
 | 
			
		||||
		core::ContextUPtr m_cctx;
 | 
			
		||||
		SceneStatic const&m_sceneStatic;
 | 
			
		||||
		Scene m_scene;
 | 
			
		||||
		glutils::FrameBuffer m_frameBuffer;
 | 
			
		||||
		int m_scale = 1;
 | 
			
		||||
		ox::Size m_scaleSz = core::gl::drawSize(m_scale);
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		SceneEditorView(turbine::Context &ctx, SceneStatic const&sceneStatic);
 | 
			
		||||
 | 
			
		||||
		ox::Error setupScene() noexcept;
 | 
			
		||||
 | 
			
		||||
		void draw(ox::Size const&targetSz) noexcept;
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		glutils::FrameBuffer const&framebuffer() const noexcept;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/nostalgia/modules/scene/src/studio/studiomodule.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/nostalgia/modules/scene/src/studio/studiomodule.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <studio/studio.hpp>
 | 
			
		||||
 | 
			
		||||
#include "sceneeditor-imgui.hpp"
 | 
			
		||||
 | 
			
		||||
namespace nostalgia::scene {
 | 
			
		||||
 | 
			
		||||
constexpr ox::StringLiteral FileExt_nscn("nscn");
 | 
			
		||||
 | 
			
		||||
class StudioModule: public studio::Module {
 | 
			
		||||
	public:
 | 
			
		||||
		ox::Vector<studio::EditorMaker> editors(turbine::Context &ctx) const noexcept override {
 | 
			
		||||
			return {
 | 
			
		||||
				 studio::editorMaker<SceneEditorImGui>(ctx, FileExt_nscn),
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
		ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(turbine::Context&) const noexcept override {
 | 
			
		||||
			ox::Vector<ox::UPtr<studio::ItemMaker>> out;
 | 
			
		||||
			return out;
 | 
			
		||||
		}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static StudioModule const mod;
 | 
			
		||||
const studio::Module *studioModule() noexcept {
 | 
			
		||||
	return &mod;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								src/nostalgia/modules/studiomodules.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/nostalgia/modules/studiomodules.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <studioapp/studioapp.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/studiomodule.hpp>
 | 
			
		||||
#include <nostalgia/scene/studiomodule.hpp>
 | 
			
		||||
 | 
			
		||||
namespace nostalgia {
 | 
			
		||||
 | 
			
		||||
static bool modulesRegistered = false;
 | 
			
		||||
void registerStudioModules() noexcept {
 | 
			
		||||
	if (modulesRegistered) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	modulesRegistered = true;
 | 
			
		||||
	studio::registerModule(core::studioModule());
 | 
			
		||||
	studio::registerModule(scene::studioModule());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								src/nostalgia/modules/studiomodules.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/nostalgia/modules/studiomodules.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
namespace nostalgia {
 | 
			
		||||
 | 
			
		||||
void registerStudioModules() noexcept;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/nostalgia/player/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/nostalgia/player/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
add_executable(
 | 
			
		||||
	nostalgia WIN32
 | 
			
		||||
		app.cpp
 | 
			
		||||
		main.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# enable LTO
 | 
			
		||||
if(NOT WIN32)
 | 
			
		||||
	set_property(TARGET nostalgia PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if(COMMAND OBJCOPY_FILE)
 | 
			
		||||
	set_target_properties(nostalgia
 | 
			
		||||
		PROPERTIES
 | 
			
		||||
			LINK_FLAGS ${LINKER_FLAGS}
 | 
			
		||||
			COMPILER_FLAGS "-mthumb -mthumb-interwork"
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	OBJCOPY_FILE(nostalgia)
 | 
			
		||||
	#PADBIN_FILE(nostalgia)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	nostalgia
 | 
			
		||||
		NostalgiaKeelModules
 | 
			
		||||
		OxLogConn
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		nostalgia
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		bin
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										160
									
								
								src/nostalgia/player/app.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/nostalgia/player/app.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
#include <turbine/turbine.hpp>
 | 
			
		||||
 | 
			
		||||
#include <nostalgia/core/core.hpp>
 | 
			
		||||
#include <nostalgia/scene/scene.hpp>
 | 
			
		||||
 | 
			
		||||
using namespace nostalgia;
 | 
			
		||||
 | 
			
		||||
static int spriteX{};
 | 
			
		||||
static int spriteY{};
 | 
			
		||||
static bool s_paused = false;
 | 
			
		||||
 | 
			
		||||
[[maybe_unused]]
 | 
			
		||||
static int testUpdateHandler(turbine::Context &tctx) noexcept {
 | 
			
		||||
	auto &cctx = *turbine::applicationData<core::Context>(tctx);
 | 
			
		||||
	constexpr auto sleepTime = 16;
 | 
			
		||||
	if (s_paused) {
 | 
			
		||||
		return sleepTime;
 | 
			
		||||
	}
 | 
			
		||||
	int xmod = 0;
 | 
			
		||||
	int ymod = 0;
 | 
			
		||||
	if (buttonDown(tctx, turbine::Alpha_D) || buttonDown(tctx, turbine::GamePad_Right)) {
 | 
			
		||||
		xmod = 2;
 | 
			
		||||
	} else if (buttonDown(tctx, turbine::Alpha_A) || buttonDown(tctx, turbine::GamePad_Left)) {
 | 
			
		||||
		xmod = -2;
 | 
			
		||||
	}
 | 
			
		||||
	if (buttonDown(tctx, turbine::Alpha_S) || buttonDown(tctx, turbine::GamePad_Down)) {
 | 
			
		||||
		ymod = 2;
 | 
			
		||||
	} else if (buttonDown(tctx, turbine::Alpha_W) || buttonDown(tctx, turbine::GamePad_Up)) {
 | 
			
		||||
		ymod = -2;
 | 
			
		||||
	}
 | 
			
		||||
	if (!xmod && !ymod) {
 | 
			
		||||
		spriteX += 1;
 | 
			
		||||
	}
 | 
			
		||||
	spriteX += xmod;
 | 
			
		||||
	spriteY += ymod;
 | 
			
		||||
	constexpr ox::StringView sprites = "nostalgia";
 | 
			
		||||
	for (unsigned i = 0; i < sprites.len(); ++i) {
 | 
			
		||||
		auto const c = static_cast<unsigned>(sprites[i] - ('a' - 1));
 | 
			
		||||
		core::setSprite(cctx, i, {
 | 
			
		||||
			.enabled = true,
 | 
			
		||||
			.x = spriteX + 8 * (static_cast<int>(i) + 1),
 | 
			
		||||
			.y = spriteY,
 | 
			
		||||
			.tileIdx = c,
 | 
			
		||||
			.priority = 1,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	return sleepTime;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[maybe_unused]]
 | 
			
		||||
static void testKeyEventHandler(turbine::Context &tctx, turbine::Key key, bool down) noexcept {
 | 
			
		||||
	if (down) {
 | 
			
		||||
		if (key == turbine::Key::Alpha_Q) {
 | 
			
		||||
			turbine::requestShutdown(tctx);
 | 
			
		||||
		} else if (key == turbine::Key::Alpha_P) {
 | 
			
		||||
			s_paused = !s_paused;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[maybe_unused]]
 | 
			
		||||
static ox::Error runTest(turbine::Context &tctx) {
 | 
			
		||||
	constexpr ox::FileAddress TileSheetAddr = ox::StringLiteral("/TileSheets/Charset.ng");
 | 
			
		||||
	constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Chester.npal");
 | 
			
		||||
	oxRequireM(cctx, core::init(tctx));
 | 
			
		||||
	turbine::setApplicationData(tctx, cctx.get());
 | 
			
		||||
	oxRequire(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
 | 
			
		||||
	oxReturnError(core::loadSpriteTileSheet(*cctx, TileSheetAddr));
 | 
			
		||||
	oxReturnError(core::loadSpritePalette(*cctx, PaletteAddr));
 | 
			
		||||
	oxReturnError(core::initConsole(*cctx));
 | 
			
		||||
	core::puts(*cctx, 10, 9, "DOPENESS!!!");
 | 
			
		||||
	turbine::setUpdateHandler(tctx, testUpdateHandler);
 | 
			
		||||
	turbine::setKeyEventHandler(tctx, testKeyEventHandler);
 | 
			
		||||
	return turbine::run(tctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[maybe_unused]]
 | 
			
		||||
static ox::Error runTileSheetSetTest(turbine::Context &tctx) {
 | 
			
		||||
	// this should make the screen display 'ABCDB'
 | 
			
		||||
	constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Charset.npal");
 | 
			
		||||
	oxRequireM(cctx, core::init(tctx));
 | 
			
		||||
	turbine::setApplicationData(tctx, cctx.get());
 | 
			
		||||
	oxRequire(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
 | 
			
		||||
	core::TileSheetSet const set{
 | 
			
		||||
		.bpp = 4,
 | 
			
		||||
		.entries = {
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/Chester.ng"), .sections{{.begin = 0, .tiles = 1}} },
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"),      .sections{{.begin = 0, .tiles = 2}} },
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/CD.ng"),      .sections{{.begin = 0, .tiles = 2}} },
 | 
			
		||||
			{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"),      .sections{{.begin = 1, .tiles = 1}} },
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
	oxReturnError(core::loadBgTileSheet(*cctx, 0, set));
 | 
			
		||||
	oxReturnError(core::loadSpriteTileSheet(*cctx, set));
 | 
			
		||||
	oxReturnError(core::loadBgPalette(*cctx, PaletteAddr));
 | 
			
		||||
	oxReturnError(core::loadSpritePalette(*cctx, PaletteAddr));
 | 
			
		||||
	core::setBgStatus(*cctx, 0, true);
 | 
			
		||||
	core::setBgTile(*cctx, 0, 10, 9, 1);
 | 
			
		||||
	core::setBgTile(*cctx, 0, 11, 9, 2);
 | 
			
		||||
	core::setBgTile(*cctx, 0, 13, 9, 4);
 | 
			
		||||
	core::setSprite(*cctx, 16, {
 | 
			
		||||
		.enabled = true,
 | 
			
		||||
		.x = 12 * 8,
 | 
			
		||||
		.y = 9 * 8,
 | 
			
		||||
		.tileIdx = 3,
 | 
			
		||||
		.bpp = static_cast<unsigned>(set.bpp),
 | 
			
		||||
	});
 | 
			
		||||
	core::setSprite(*cctx, 17, {
 | 
			
		||||
		.enabled = true,
 | 
			
		||||
		.x = 14 * 8,
 | 
			
		||||
		.y = 9 * 8,
 | 
			
		||||
		.tileIdx = 5,
 | 
			
		||||
		.bpp = static_cast<unsigned>(set.bpp),
 | 
			
		||||
	});
 | 
			
		||||
	turbine::setKeyEventHandler(tctx, testKeyEventHandler);
 | 
			
		||||
	return turbine::run(tctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static int sceneUpdateHandler(turbine::Context&) noexcept {
 | 
			
		||||
	constexpr auto sleepTime = 16;
 | 
			
		||||
	if (s_paused) {
 | 
			
		||||
		return sleepTime;
 | 
			
		||||
	}
 | 
			
		||||
	// do stuff
 | 
			
		||||
	return sleepTime;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void sceneKeyEventHandler(turbine::Context &tctx, turbine::Key key, bool down) noexcept {
 | 
			
		||||
	if (down) {
 | 
			
		||||
		if (key == turbine::Key::Alpha_Q) {
 | 
			
		||||
			turbine::requestShutdown(tctx);
 | 
			
		||||
		} else if (key == turbine::Key::Alpha_P) {
 | 
			
		||||
			s_paused = !s_paused;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[[maybe_unused]]
 | 
			
		||||
static ox::Error runScene(turbine::Context &tctx) {
 | 
			
		||||
	constexpr ox::FileAddress SceneAddr = ox::StringLiteral("/Scenes/Chester.nscn");
 | 
			
		||||
	oxRequireM(cctx, core::init(tctx));
 | 
			
		||||
	oxRequire(scn, keel::readObj<scene::SceneStatic>(keelCtx(tctx), SceneAddr));
 | 
			
		||||
	turbine::setUpdateHandler(tctx, sceneUpdateHandler);
 | 
			
		||||
	turbine::setKeyEventHandler(tctx, sceneKeyEventHandler);
 | 
			
		||||
	scene::Scene const scene(*scn);
 | 
			
		||||
	oxReturnError(scene.setupDisplay(*cctx));
 | 
			
		||||
	return turbine::run(tctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ox::Error run(ox::UniquePtr<ox::FileSystem> &&fs) noexcept {
 | 
			
		||||
	oxRequireM(tctx, turbine::init(std::move(fs), "Nostalgia"));
 | 
			
		||||
	return runTileSheetSetTest(*tctx);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/nostalgia/player/app.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/nostalgia/player/app.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/std/memory.hpp>
 | 
			
		||||
#include <ox/fs/fs.hpp>
 | 
			
		||||
 | 
			
		||||
typename ox::Error run(ox::UniquePtr<ox::FileSystem> &&fs) noexcept;
 | 
			
		||||
							
								
								
									
										43
									
								
								src/nostalgia/player/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/nostalgia/player/main.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <ox/logconn/def.hpp>
 | 
			
		||||
#include <ox/logconn/logconn.hpp>
 | 
			
		||||
 | 
			
		||||
#include <keel/media.hpp>
 | 
			
		||||
 | 
			
		||||
#include "../modules/keelmodules.hpp"
 | 
			
		||||
 | 
			
		||||
#include "app.hpp"
 | 
			
		||||
 | 
			
		||||
static ox::Error run(int argc, const char **argv) noexcept {
 | 
			
		||||
#ifndef OX_BARE_METAL
 | 
			
		||||
	// GBA doesn't need the modules and calling this doubles the size of the
 | 
			
		||||
	// binary.
 | 
			
		||||
	nostalgia::registerKeelModules();
 | 
			
		||||
#endif
 | 
			
		||||
	if (argc < 2) {
 | 
			
		||||
		return OxError(1, "Please provide path to project directory or OxFS file.");
 | 
			
		||||
	}
 | 
			
		||||
	const auto path = argv[1];
 | 
			
		||||
	oxRequireM(fs, keel::loadRomFs(path));
 | 
			
		||||
	return run(std::move(fs));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
int WinMain() {
 | 
			
		||||
	auto const argc = __argc;
 | 
			
		||||
	auto const argv = const_cast<const char**>(__argv);
 | 
			
		||||
#else
 | 
			
		||||
int main(int argc, const char **argv) {
 | 
			
		||||
#endif
 | 
			
		||||
	OX_INIT_DEBUG_LOGGER(loggerConn, "Nostalgia Player")
 | 
			
		||||
	ox::Error err;
 | 
			
		||||
	err = run(argc, argv);
 | 
			
		||||
	oxAssert(err, "Something went wrong...");
 | 
			
		||||
	if (err) {
 | 
			
		||||
		oxErrf("Failure: {}\n", toStr(err));
 | 
			
		||||
	}
 | 
			
		||||
	return static_cast<int>(err);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/nostalgia/studio/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/nostalgia/studio/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
add_executable(NostalgiaStudio WIN32 MACOSX_BUNDLE)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(
 | 
			
		||||
	NostalgiaStudio
 | 
			
		||||
		NostalgiaProfile
 | 
			
		||||
		NostalgiaStudioModules
 | 
			
		||||
		NostalgiaKeelModules
 | 
			
		||||
		StudioAppLib
 | 
			
		||||
		OlympicApplib
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	TARGETS
 | 
			
		||||
		NostalgiaStudio
 | 
			
		||||
	RUNTIME DESTINATION
 | 
			
		||||
		${NOSTALGIA_DIST_BIN}
 | 
			
		||||
	BUNDLE DESTINATION .
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT WIN32)
 | 
			
		||||
	# enable LTO
 | 
			
		||||
	set_property(TARGET NostalgiaStudio PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if(APPLE)
 | 
			
		||||
	set_target_properties(NostalgiaStudio PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
install(
 | 
			
		||||
	FILES
 | 
			
		||||
		ns.icns
 | 
			
		||||
	DESTINATION
 | 
			
		||||
		${NOSTALGIA_DIST_RESOURCES}/icons
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										35
									
								
								src/nostalgia/studio/Info.plist
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/nostalgia/studio/Info.plist
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 | 
			
		||||
<plist version="1.0">
 | 
			
		||||
<dict>
 | 
			
		||||
	<key>CFBundleExecutable</key>
 | 
			
		||||
	<string>NostalgiaStudio</string>
 | 
			
		||||
 | 
			
		||||
	<key>CFBundleGetInfoString</key>
 | 
			
		||||
	<string>Nostalgia Studio</string>
 | 
			
		||||
 | 
			
		||||
	<key>CFBundleIconFile</key>
 | 
			
		||||
	<string>icons/ns.icns</string>
 | 
			
		||||
 | 
			
		||||
	<key>CFBundleIdentifier</key>
 | 
			
		||||
	<string>net.drinkingtea.nostalgia.studio</string>
 | 
			
		||||
 | 
			
		||||
	<key>CFBundlePackageType</key>
 | 
			
		||||
	<string>APPL</string>
 | 
			
		||||
 | 
			
		||||
	<key>CFBundleVersion</key>
 | 
			
		||||
	<string>0.0.0</string>
 | 
			
		||||
 | 
			
		||||
	<key>LSMinimumSystemVersion</key>
 | 
			
		||||
	<string>12.0.0</string>
 | 
			
		||||
 | 
			
		||||
	<!-- HiDPI -->
 | 
			
		||||
	<key>NSPrincipalClass</key>
 | 
			
		||||
	<string>NSApplication</string>
 | 
			
		||||
	<key>NSHighResolutionCapable</key>
 | 
			
		||||
	<string>True</string>
 | 
			
		||||
 | 
			
		||||
	<key>NSHumanReadableCopyright</key>
 | 
			
		||||
	<string>Copyright (c) 2016-2023 Gary Talent <gary@drinkingtea.net></string>
 | 
			
		||||
</dict>
 | 
			
		||||
</plist>
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user