Compare commits

..

47 Commits

Author SHA1 Message Date
gary 5eed819aa3 [nostalgia/studio] Set version to d2026.05.0
Build / build (push) Successful in 1m12s
2026-05-31 17:03:07 -05:00
gary 380c58936d [nostalgia] Update release notes
Build / build (push) Successful in 1m13s
2026-05-31 17:03:02 -05:00
gary 03bd191664 [studio] Fix PassThroughFS::mkdir to report correctly if a trailing / is given
Build / build (push) Successful in 1m9s
2026-05-31 16:54:52 -05:00
gary 62fe7ea67c [studio] Fix MSVC build
Build / build (push) Successful in 1m21s
2026-05-31 07:21:10 -05:00
gary a64e6d3f6e [studio] Remove debug line
Build / build (push) Successful in 1m15s
2026-05-31 07:06:14 -05:00
gary 80ae9bdedf [studio] Style consistency
Build / build (push) Has been cancelled
2026-05-31 06:53:34 -05:00
gary c18908a70c [studio,keel] Make keel-pack command a Studio Module subcommand
Build / build (push) Failing after 26s
2026-05-31 06:37:01 -05:00
gary 737f8f58f9 [nostalgia] Improve release notes consistency for d2026.05.0
Build / build (push) Successful in 1m14s
2026-05-31 02:57:14 -05:00
gary 5b6ea9468b [studio] Cleanup navigation code
Build / build (push) Successful in 1m14s
2026-05-31 02:41:03 -05:00
gary 7128b7c83a [nostalgia] Update release notes
Build / build (push) Successful in 1m19s
2026-05-31 02:02:40 -05:00
gary d560b7deaf [studio] Make navigation code more consistent
Build / build (push) Successful in 1m15s
2026-05-31 01:59:53 -05:00
gary d4d66b5fea [nostalgia] Add StringParam to .lldbinit
Build / build (push) Successful in 1m10s
2026-05-31 01:26:31 -05:00
gary 1948a1e701 [studio] Fix Back/Forward navigation actions 2026-05-31 01:01:32 -05:00
gary 8e52dc64d8 [ox/std] Make Result::reoriginate non-const 2026-05-31 00:43:11 -05:00
gary 5c9b0be867 [studio] Add subcommands output on invalid subcommand dispatch
Build / build (push) Successful in 1m31s
2026-05-29 14:00:27 -05:00
gary e2395411bd [nostalgia] Set next version to 2026.05.0 in release notes
Build / build (push) Successful in 1m21s
2026-05-28 22:17:42 -05:00
gary afb1bd23a3 [studio,turbine] Add pre-draw font loading
Build / build (push) Successful in 1m18s
2026-05-27 20:39:05 -05:00
gary eb3364ce8e [studio] Add include guard to font resource file 2026-05-26 22:32:42 -05:00
gary 8899fc106f [applib,studio/applib] Move app ID to runtime variable in olympic namespace
Build / build (push) Successful in 1m23s
2026-05-25 15:04:42 -05:00
gary 4a3d27fde8 [studio/applib] Remove default Studio OLYMPIC_APP_ID
Build / build (push) Successful in 1m25s
2026-05-25 14:53:51 -05:00
gary 51099f493b [nostalgia] Move OLYMPIC_APP_ID to Nostalgia Studio
Build / build (push) Failing after 10m38s
2026-05-25 14:42:36 -05:00
gary fa9f3f8edf Merge commit 'f5e4ccf53b1d3c97904590815fc892ddd0ce91bb' 2026-05-25 14:15:21 -05:00
gary 89fedc13b9 [studio] Add handling for DPI changes
Build / build (push) Successful in 1m22s
2026-05-25 14:08:50 -05:00
gary 7b209583f5 [turbine/gba] Fix build
Build / build (push) Successful in 1m24s
2026-05-25 13:04:41 -05:00
gary 8fd09f6fff [studio] Fix window icon for Wayland
Build / build (push) Successful in 1m13s
2026-05-23 16:18:59 -05:00
gary 261c620cec [ox/oc] Cleanup 2026-05-23 14:19:10 -05:00
gary f949d22b8e [nostalgia/gfx/studio] Fix warning
Build / build (push) Successful in 1m16s
2026-05-23 01:13:02 -05:00
gary 75039f6056 [studio] Make g_dpiScale static
Build / build (push) Failing after 1m8s
2026-05-23 01:00:20 -05:00
gary 61e1a7aa72 [studio,nostalgia/gfx/studio] Cleanup
Build / build (push) Failing after 1m4s
2026-05-22 23:41:22 -05:00
gary d2fe752846 [nostalgia] Update release notes
Build / build (push) Successful in 1m17s
2026-05-22 23:30:53 -05:00
gary 406a53fd3a [studio] Fix DPI scaling for New and New Project popups
Build / build (push) Successful in 1m16s
2026-05-22 23:25:23 -05:00
gary 7d144eb18a [studio,turbine] Fix DPI scaling for Mac
Build / build (push) Successful in 1m20s
2026-05-22 23:00:50 -05:00
gary ac9bbefa82 [studio,nostalgia] Add HiDPI support
Build / build (push) Successful in 1m36s
2026-05-22 22:46:45 -05:00
gary ef1c108b58 [ox/std] Address GCC finding a new way to be retarded
Build / build (push) Successful in 1m12s
2026-05-19 23:19:50 -05:00
gary 6a054cd970 [nostalgia] Enable Wayland support for GLFW
Build / build (push) Successful in 1m39s
2026-05-19 22:47:02 -05:00
gary 9070e6e109 [ox/std] Fix compiler warning
Build / build (push) Successful in 1m18s
2026-05-17 20:33:58 -05:00
gary 6cc6e9e7ed [studio] Rename caseInsensitiveEquals to caseInsensitiveStrCmp
Build / build (push) Successful in 1m9s
2026-05-17 15:56:14 -05:00
gary de859bef77 [ox/std] Rename caseInsensitiveEquals to caseInsensitiveStrCmp 2026-05-17 15:55:35 -05:00
gary d1a3538e9a [ox/oc] Fix writeOC Writer_c variant to add null terminator
Build / build (push) Successful in 1m10s
2026-05-17 15:49:00 -05:00
gary d10a71f06d [studio] Add missing include
Build / build (push) Failing after 1m6s
2026-05-17 15:36:57 -05:00
gary 9593e7eef9 [ox] Add writeOC Writer_c variant 2026-05-17 15:31:56 -05:00
gary 2f9b9c0842 [studio] Fix change-format to only write data portion once
Build / build (push) Successful in 1m15s
2026-05-17 15:22:11 -05:00
gary dce09b564c [ox/std] Fix Result::originate to return value
Build / build (push) Successful in 1m12s
2026-05-17 15:19:13 -05:00
gary 9f485d9496 [studio] Cleanup
Build / build (push) Successful in 1m9s
2026-05-17 15:07:10 -05:00
gary 71a20c00a0 Merge commit 'd84c4980b2ec51a5f774229e715c361ec4096546' 2026-01-25 23:53:51 -06:00
gary 8ea158f14d Merge commit '5fc3d23a9783e7c424d9cdb0dc50f3ba3abb427b' 2025-11-23 01:17:29 -06:00
gary e1d0c59d1c [ox/std] Ignore broken GCC warning 2025-09-20 13:57:45 -05:00
63 changed files with 494 additions and 280 deletions
+1
View File
@@ -1 +1,2 @@
type summary add --summary-string "${var.m_buff.m_items}" ox::String type summary add --summary-string "${var.m_buff.m_items}" ox::String
type summary add --summary-string "${var.m_value.m_buff.m_items}" ox::StringParam
+2 -2
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.19) cmake_minimum_required(VERSION 3.25)
set(CMAKE_POLICY_DEFAULT_CMP0110 NEW) # requires CMake 3.19 set(CMAKE_POLICY_DEFAULT_CMP0110 NEW) # requires CMake 3.19
if(BUILDCORE_TARGET STREQUAL "gba") if(BUILDCORE_TARGET STREQUAL "gba")
@@ -52,7 +52,7 @@ if(NOT BUILDCORE_TARGET STREQUAL "gba")
set(GLFW_BUILD_TESTS OFF) set(GLFW_BUILD_TESTS OFF)
set(GLFW_BUILD_DOCS OFF) set(GLFW_BUILD_DOCS OFF)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(GLFW_BUILD_WAYLAND OFF) set(GLFW_BUILD_WAYLAND ON)
endif() endif()
add_subdirectory(deps/glfw) add_subdirectory(deps/glfw)
add_subdirectory(deps/glutils) add_subdirectory(deps/glutils)
+2 -7
View File
@@ -27,8 +27,8 @@ git-push-ox:
git subtree push --prefix=deps/oxlib ox-master master git subtree push --prefix=deps/oxlib ox-master master
.PHONY: pkg-gba .PHONY: pkg-gba
pkg-gba: build-pack build-gba-player pkg-gba: build-studio build-gba-player
${BC_CMD_ENVRUN} ${BC_PY3} ./util/scripts/pkg-gba.py sample_project ${BC_VAR_PROJECT_NAME_CAP} ${BC_CMD_ENVRUN} ${BC_PY3} ./util/scripts/pkg-gba.py ${PROJECT_STUDIO} sample_project ${BC_VAR_PROJECT_NAME_CAP}
.PHONY: pkg-mac .PHONY: pkg-mac
pkg-mac: install pkg-mac: install
@@ -47,11 +47,6 @@ build-gba-player:
build-player: build-player:
${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} ${BC_VAR_PROJECT_NAME_CAP} ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} ${BC_VAR_PROJECT_NAME_CAP}
.PHONY: build-pack
build-pack:
cmake --build ./build/${BC_VAR_CURRENT_BUILD} --target ${BC_VAR_PROJECT_NAME}-pack
.PHONY: run .PHONY: run
run: build-player run: build-player
${PROJECT_PLAYER} sample_project ${PROJECT_PLAYER} sample_project
+1 -2
View File
@@ -129,8 +129,7 @@ Error writeClaw(
if (fmt == ClawFormat::Metal) { if (fmt == ClawFormat::Metal) {
OX_RETURN_ERROR(writeMC(writer, obj)); OX_RETURN_ERROR(writeMC(writer, obj));
} else if (fmt == ClawFormat::Organic) { } else if (fmt == ClawFormat::Organic) {
OX_REQUIRE(data, writeOC(obj)); OX_RETURN_ERROR(writeOC(writer, obj));
OX_RETURN_ERROR(writer.write(data.data(), data.size()));
} }
#else #else
if (fmt != ClawFormat::Metal) { if (fmt != ClawFormat::Metal) {
+3 -1
View File
@@ -28,8 +28,10 @@ String PassThroughFS::basePath() const noexcept {
} }
Error PassThroughFS::mkdir(StringViewCR path, bool recursive) noexcept { Error PassThroughFS::mkdir(StringViewCR path, bool recursive) noexcept {
auto const cleanPath =
endsWith(path, '/') ? substr(path, 0, path.size() - 1) : path;
bool success = false; bool success = false;
const auto p = m_path / stripSlash(path); auto p = cleanPath.size() ? m_path / stripSlash(cleanPath) : m_path;
const auto u8p = p.u8string(); const auto u8p = p.u8string();
oxTrace("ox.fs.PassThroughFS.mkdir", std::bit_cast<const char*>(u8p.c_str())); oxTrace("ox.fs.PassThroughFS.mkdir", std::bit_cast<const char*>(u8p.c_str()));
if (recursive) { if (recursive) {
+1 -1
View File
@@ -282,7 +282,7 @@ Error readOC(BufferView const buff, auto &val) noexcept {
try { try {
Json::Value doc; Json::Value doc;
Json::CharReaderBuilder parserBuilder; Json::CharReaderBuilder parserBuilder;
auto parser = UPtr<Json::CharReader>(parserBuilder.newCharReader()); auto const parser = UPtr<Json::CharReader>(parserBuilder.newCharReader());
OX_ALLOW_UNSAFE_BUFFERS_BEGIN OX_ALLOW_UNSAFE_BUFFERS_BEGIN
if (!parser->parse(buff.data(), buff.data() + buff.size(), &doc, nullptr)) { if (!parser->parse(buff.data(), buff.data() + buff.size(), &doc, nullptr)) {
OX_ALLOW_UNSAFE_BUFFERS_END OX_ALLOW_UNSAFE_BUFFERS_END
+13 -2
View File
@@ -28,6 +28,7 @@ namespace ox {
class OrganicClawWriter { class OrganicClawWriter {
friend Error writeOC(Writer_c auto &writer, auto const &val) noexcept;
friend Result<Buffer> writeOC(const auto &val) noexcept; friend Result<Buffer> writeOC(const auto &val) noexcept;
friend Result<String> writeOCString(const auto &val) noexcept; friend Result<String> writeOCString(const auto &val) noexcept;
@@ -254,9 +255,19 @@ Error OrganicClawWriter::field(CString key, UnionView<U, force> val) noexcept {
return {}; return {};
} }
Error writeOC(Writer_c auto &writer, auto const &val) noexcept {
OrganicClawWriter ocWriter;
ModelHandlerInterface handler(ocWriter);
OX_RETURN_ERROR(model(&handler, &val));
Json::StreamWriterBuilder const jsonBuilder;
auto const str = Json::writeString(jsonBuilder, ocWriter.m_json);
OX_RETURN_ERROR(writer.write(str.data(), str.size()));
return writer.put('\0');
}
Result<Buffer> writeOC(auto const &val) noexcept { Result<Buffer> writeOC(auto const &val) noexcept {
OrganicClawWriter writer; OrganicClawWriter writer;
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler(writer); ModelHandlerInterface handler(writer);
OX_RETURN_ERROR(model(&handler, &val)); OX_RETURN_ERROR(model(&handler, &val));
Json::StreamWriterBuilder const jsonBuilder; Json::StreamWriterBuilder const jsonBuilder;
auto const str = Json::writeString(jsonBuilder, writer.m_json); auto const str = Json::writeString(jsonBuilder, writer.m_json);
@@ -270,7 +281,7 @@ Result<Buffer> writeOC(auto const &val) noexcept {
Result<String> writeOCString(auto const &val) noexcept { Result<String> writeOCString(auto const &val) noexcept {
OrganicClawWriter writer; OrganicClawWriter writer;
ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler(writer); ModelHandlerInterface handler(writer);
OX_RETURN_ERROR(model(&handler, &val)); OX_RETURN_ERROR(model(&handler, &val));
Json::StreamWriterBuilder const jsonBuilder; Json::StreamWriterBuilder const jsonBuilder;
auto const str = Json::writeString(jsonBuilder, writer.m_json); auto const str = Json::writeString(jsonBuilder, writer.m_json);
+3 -3
View File
@@ -348,15 +348,15 @@ struct [[nodiscard]] Result {
constexpr Result reoriginate( constexpr Result reoriginate(
ErrorCode const pErrCode, ErrorCode const pErrCode,
CString const pMsg = nullptr, CString const pMsg = nullptr,
std::source_location const &pSrc = std::source_location::current()) const && noexcept { std::source_location const &pSrc = std::source_location::current()) && noexcept {
if (error) { if (error) {
return {std::move(value), Error{pErrCode, pMsg, pSrc}}; return {std::move(value), Error{pErrCode, pMsg, pSrc}};
} }
return Error{}; return {std::move(value)};
} }
constexpr Result reoriginate( constexpr Result reoriginate(
std::source_location const &pSrc = std::source_location::current()) const && noexcept { std::source_location const &pSrc = std::source_location::current()) && noexcept {
return {std::move(value), Error{error.errCode, error.msg, pSrc}}; return {std::move(value), Error{error.errCode, error.msg, pSrc}};
} }
+2 -2
View File
@@ -38,11 +38,11 @@ constexpr StringView substr(StringViewCR str, std::size_t const start, std::size
[[nodiscard]] [[nodiscard]]
constexpr char toUpper(char const c) noexcept { constexpr char toUpper(char const c) noexcept {
return c & 0b1101'1111; return c & static_cast<char>(0b1101'1111);
} }
[[nodiscard]] [[nodiscard]]
constexpr int caseInsensitiveEquals(ox::StringViewCR a, ox::StringViewCR b) noexcept { constexpr int caseInsensitiveStrCmp(StringViewCR a, StringViewCR b) noexcept {
auto const sz = ox::min(a.size(), b.size()); auto const sz = ox::min(a.size(), b.size());
for (size_t i{}; i < sz; ++i) { for (size_t i{}; i < sz; ++i) {
auto const ac = toUpper(a[i]); auto const ac = toUpper(a[i]);
+14
View File
@@ -54,10 +54,21 @@ struct VectorAllocator {
// this totally idiotic redundant check (&& count <= Size) is required to address a bug in devkitARM, // this totally idiotic redundant check (&& count <= Size) is required to address a bug in devkitARM,
// try removing it later // try removing it later
if (!std::is_constant_evaluated()) { if (!std::is_constant_evaluated()) {
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow="
#endif
if (cap <= m_data.size() && count <= m_data.size()) { if (cap <= m_data.size() && count <= m_data.size()) {
for (auto i = 0u; i < count; ++i) { for (auto i = 0u; i < count; ++i) {
auto const srcItem = std::launder(reinterpret_cast<T*>(&src->m_data[i])); auto const srcItem = std::launder(reinterpret_cast<T*>(&src->m_data[i]));
#if defined(__GNUC__) && __GNUC__ >= 12
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow="
#endif
new (&m_data[i]) T(std::move(*srcItem)); new (&m_data[i]) T(std::move(*srcItem));
#if defined(__GNUC__) && __GNUC__ >= 12
#pragma GCC diagnostic pop
#endif
} }
if (count) { if (count) {
*items = std::launder(reinterpret_cast<T*>(m_data.data())); *items = std::launder(reinterpret_cast<T*>(m_data.data()));
@@ -65,6 +76,9 @@ struct VectorAllocator {
*items = reinterpret_cast<T*>(m_data.data()); *items = reinterpret_cast<T*>(m_data.data());
} }
} }
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
} }
} }
+4 -1
View File
@@ -1,6 +1,9 @@
# NEXT # d2026.05.0
* Add HiDPI support
* Add Get Info file dialog option in project explorer * Add Get Info file dialog option in project explorer
* Fix Navigate Back to not require a double Forward when going all the way back
* Fix issue with config data not being saved correctly on first run
# d2025.07.0 # d2025.07.0
-1
View File
@@ -41,7 +41,6 @@ if(${NOSTALGIA_BUILD_PLAYER})
endif() endif()
if(NOT BUILDCORE_TARGET STREQUAL "gba") if(NOT BUILDCORE_TARGET STREQUAL "gba")
add_subdirectory(tools)
if(${NOSTALGIA_BUILD_STUDIO_APP}) if(${NOSTALGIA_BUILD_STUDIO_APP})
add_subdirectory(studio) add_subdirectory(studio)
endif() endif()
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -4,7 +4,7 @@
#pragma once #pragma once
#include <studio/studio.hpp> #include <studio/undocommand.hpp>
#include <nostalgia/gfx/palette.hpp> #include <nostalgia/gfx/palette.hpp>
@@ -45,11 +45,11 @@ OX_MODEL_BEGIN(PageDragDrop)
OX_MODEL_FIELD(page) OX_MODEL_FIELD(page)
OX_MODEL_END() OX_MODEL_END()
void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept { void PaletteEditorImGui::PageRenameDialog::draw() noexcept {
if (!m_show) { if (!m_show) {
return; return;
} }
if (ig::BeginPopup(tctx, "Rename Page", m_show)) { if (ig::BeginPopup("Rename Page", m_show)) {
if (ImGui::IsWindowAppearing()) { if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere(); ImGui::SetKeyboardFocusHere();
} }
@@ -71,26 +71,26 @@ void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept
PaletteEditorImGui::PaletteEditorImGui(studio::Context &sctx, ox::StringParam path): PaletteEditorImGui::PaletteEditorImGui(studio::Context &sctx, ox::StringParam path):
Editor(sctx, std::move(path)), Editor(sctx, std::move(path)),
m_sctx(sctx), m_sctx(sctx),
m_tctx(sctx.tctx),
m_pal(m_sctx.project->loadObj<Palette>(itemPath()).unwrapThrow()) { m_pal(m_sctx.project->loadObj<Palette>(itemPath()).unwrapThrow()) {
undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand); undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
m_pageRenameDlg.inputSubmitted.connect(this, &PaletteEditorImGui::renamePage); m_pageRenameDlg.inputSubmitted.connect(this, &PaletteEditorImGui::renamePage);
} }
void PaletteEditorImGui::draw(studio::Context&) noexcept { void PaletteEditorImGui::draw(studio::Context&) noexcept {
auto const scale = ig::dpiScale();
auto const paneSize = ImGui::GetContentRegionAvail(); auto const paneSize = ImGui::GetContentRegionAvail();
{ {
ImGui::BeginChild("Pages", {280, paneSize.y}, true); ImGui::BeginChild("Pages", {scale * 280, paneSize.y}, true);
drawPagesEditor(); drawPagesEditor();
ImGui::EndChild(); ImGui::EndChild();
} }
ImGui::SameLine(); ImGui::SameLine();
{ {
ImGui::BeginChild("Colors", {-1, paneSize.y}, true); ImGui::BeginChild("Colors", {scale * -1, paneSize.y}, true);
drawColorsEditor(); drawColorsEditor();
ImGui::EndChild(); ImGui::EndChild();
} }
m_pageRenameDlg.draw(m_tctx); m_pageRenameDlg.draw();
} }
ox::Error PaletteEditorImGui::saveItem() noexcept { ox::Error PaletteEditorImGui::saveItem() noexcept {
@@ -165,7 +165,7 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
static constexpr auto toolbarHeight = 40; static constexpr auto toolbarHeight = 40;
{ {
auto constexpr sz = ImVec2{70, 24}; auto constexpr sz = ImVec2{70, 24};
if (ImGui::Button("Add", sz)) { if (ig::PushButton("Add", sz)) {
auto const colorSz = colorCnt(m_pal, m_page); auto const colorSz = colorCnt(m_pal, m_page);
constexpr Color16 c = 0; constexpr Color16 c = 0;
std::ignore = pushCommand<AddColorCommand>(m_pal, c, colorSz); std::ignore = pushCommand<AddColorCommand>(m_pal, c, colorSz);
@@ -173,7 +173,7 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page)); ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page));
{ {
if (ImGui::Button("Remove", sz)) { if (ig::PushButton("Remove", sz)) {
std::ignore = pushCommand<RemoveColorCommand>(m_pal, m_selectedColorRow); std::ignore = pushCommand<RemoveColorCommand>(m_pal, m_selectedColorRow);
m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow); m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow);
colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page); colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page);
@@ -183,17 +183,18 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
} }
auto const tblWidth = (colorsSz.x - static_cast<float>(colorEditorWidth) - 8.f) auto const tblWidth = (colorsSz.x - static_cast<float>(colorEditorWidth) - 8.f)
* static_cast<float>(colorEditor); * static_cast<float>(colorEditor);
auto const scale = ig::dpiScale();
ImGui::BeginTable( ImGui::BeginTable(
"Colors", "Colors",
6, 6,
tableFlags, tableFlags,
{tblWidth, colorsSz.y - (toolbarHeight + 5)}); {tblWidth * scale, colorsSz.y - (toolbarHeight + 5) * scale});
{ {
ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25); ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25 * scale);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100 * scale);
ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40); ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40 * scale);
ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40); ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40 * scale);
ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40); ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40 * scale);
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide); ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
if (m_page < m_pal.pages.size()) { if (m_page < m_pal.pages.size()) {
@@ -236,10 +237,11 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
} }
void PaletteEditorImGui::drawPagesEditor() noexcept { void PaletteEditorImGui::drawPagesEditor() noexcept {
auto const scale = ig::dpiScale();
constexpr auto tableFlags = ImGuiTableFlags_RowBg; constexpr auto tableFlags = ImGuiTableFlags_RowBg;
auto const paneSz = ImGui::GetContentRegionAvail(); auto const paneSz = ImGui::GetContentRegionAvail();
constexpr auto toolbarHeight = 40; constexpr auto toolbarHeight = 40;
auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f, 24}; auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f * scale, 24 * scale};
if (ImGui::Button("Add", btnSz)) { if (ImGui::Button("Add", btnSz)) {
if (m_pal.pages.empty()) { if (m_pal.pages.empty()) {
std::ignore = pushCommand<AddPageCommand>(m_pal); std::ignore = pushCommand<AddPageCommand>(m_pal);
@@ -265,7 +267,7 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
"PageSelect", "PageSelect",
2, 2,
tableFlags, tableFlags,
{paneSz.x, paneSz.y - (toolbarHeight + 5)}); {paneSz.x, paneSz.y - (toolbarHeight + 5) * scale});
{ {
ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60); ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 200); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 200);
@@ -29,10 +29,9 @@ class PaletteEditorImGui: public studio::Editor {
} }
[[nodiscard]] [[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; } constexpr bool isOpen() const noexcept { return m_show; }
void draw(turbine::Context &tctx) noexcept; void draw() noexcept;
} m_pageRenameDlg; } m_pageRenameDlg;
studio::Context &m_sctx; studio::Context &m_sctx;
turbine::Context &m_tctx;
Palette m_pal; Palette m_pal;
size_t m_selectedColorRow = 0; size_t m_selectedColorRow = 0;
size_t m_page = 0; size_t m_page = 0;
@@ -23,7 +23,7 @@ struct SubSheetRef {
TileSheet::SubSheetIdx subsheet{}; TileSheet::SubSheetIdx subsheet{};
}; };
OX_MODEL_BEGIN(SubSheetRef) static OX_MODEL_BEGIN(SubSheetRef)
OX_MODEL_FIELD(subsheet) OX_MODEL_FIELD(subsheet)
OX_MODEL_END() OX_MODEL_END()
@@ -33,7 +33,7 @@ struct TileSheetEditorConfig {
TileSheet::SubSheetIdx activeSubsheet{}; TileSheet::SubSheetIdx activeSubsheet{};
}; };
OX_MODEL_BEGIN(TileSheetEditorConfig) static OX_MODEL_BEGIN(TileSheetEditorConfig)
OX_MODEL_FIELD_RENAME(activeSubsheet, active_subsheet) OX_MODEL_FIELD_RENAME(activeSubsheet, active_subsheet)
OX_MODEL_END() OX_MODEL_END()
@@ -149,27 +149,30 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) { if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) {
if (ImGui::IsKeyPressed(ImGuiKey_A)) { if (ImGui::IsKeyPressed(ImGuiKey_A)) {
auto const &img = m_model.activeSubSheet(); auto const &img = m_model.activeSubSheet();
m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}}); m_model.setSelection({
{},
{img.columns * TileWidth - 1, img.rows * TileHeight - 1}});
} else if (ImGui::IsKeyPressed(ImGuiKey_G)) { } else if (ImGui::IsKeyPressed(ImGuiKey_G)) {
m_model.clearSelection(); m_model.clearSelection();
} }
} }
} }
auto const scale = ig::dpiScale();
auto const paneSize = ImGui::GetContentRegionAvail(); auto const paneSize = ImGui::GetContentRegionAvail();
auto const tileSheetParentSize = ImVec2{paneSize.x - s_palViewWidth, paneSize.y}; auto const tileSheetParentSize = ImVec2{paneSize.x - s_palViewWidth * scale, paneSize.y};
auto const fbSize = ox::Vec2{tileSheetParentSize.x - 16, tileSheetParentSize.y - 16}; auto const fbSize = ox::Vec2{tileSheetParentSize.x - 16 * scale, tileSheetParentSize.y - 16 * scale};
ImGui::BeginChild("TileSheetView", tileSheetParentSize, true); ImGui::BeginChild("TileSheetView", tileSheetParentSize, true);
{ {
drawTileSheet(fbSize); drawTileSheet(fbSize);
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true); ImGui::BeginChild("Controls", {(s_palViewWidth - 8) * scale, paneSize.y}, true);
{ {
auto const controlsSize = ImGui::GetContentRegionAvail(); auto const controlsSize = ImGui::GetContentRegionAvail();
ImGui::BeginChild("ToolBox", {0, 32}, true); ImGui::BeginChild("ToolBox", {0, 32 * scale}, true);
{ {
auto const btnSz = ImVec2{45, 14}; auto const btnSz = ImVec2{45 * scale, 14 * scale};
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) { if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
m_tool = TileSheetTool::Select; m_tool = TileSheetTool::Select;
} }
@@ -193,7 +196,7 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
//ig::ComboBox("##Operations", ox::Array<ox::CStringView, 1>{"Operations"}, i); //ig::ComboBox("##Operations", ox::Array<ox::CStringView, 1>{"Operations"}, i);
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::BeginChild("OperationsBox", {0, 35}, ImGuiWindowFlags_NoTitleBar); ImGui::BeginChild("OperationsBox", {0, 35 * scale}, ImGuiWindowFlags_NoTitleBar);
{ {
if (ImGui::BeginCombo("##Operations", "Operations", 0)) { if (ImGui::BeginCombo("##Operations", "Operations", 0)) {
if (ImGui::Selectable("Flip X", false)) { if (ImGui::Selectable("Flip X", false)) {
@@ -214,17 +217,17 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
} }
} }
ImGui::EndChild(); ImGui::EndChild();
auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21); auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21) * scale;
// draw palette/color picker // draw palette/color picker
ImGui::BeginChild("Palette", {s_palViewWidth - 24, ySize / 2.f}, true); ImGui::BeginChild("Palette", {(s_palViewWidth - 24) * scale, ySize / 2.f}, true);
{ {
drawPaletteMenu(); drawPaletteMenu();
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::BeginChild("SubSheets", {s_palViewWidth - 24, ySize / 2.f}, true); ImGui::BeginChild("SubSheets", {(s_palViewWidth - 24) * scale, ySize / 2.f}, true);
{ {
static constexpr auto btnHeight = ig::BtnSz.y; auto constexpr btnHeight = ig::BtnSz.y;
auto constexpr btnSize = ImVec2{btnHeight, btnHeight}; auto const btnSize = ImVec2{btnHeight, btnHeight};
if (ig::PushButton("+", btnSize)) { if (ig::PushButton("+", btnSize)) {
auto insertOnIdx = m_model.activeSubSheetIdx(); auto insertOnIdx = m_model.activeSubSheetIdx();
auto const &parent = m_model.activeSubSheet(); auto const &parent = m_model.activeSubSheet();
@@ -247,16 +250,16 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
if (ig::PushButton("Export")) { if (ig::PushButton("Export")) {
m_exportMenu.show(); m_exportMenu.show();
} }
TileSheet::SubSheetIdx path;
static constexpr auto flags = static constexpr auto flags =
ImGuiTableFlags_RowBg | ImGuiTableFlags_RowBg |
ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBody |
ImGuiTableFlags_ScrollY; ImGuiTableFlags_ScrollY;
if (ImGui::BeginTable("Subsheets", 4, flags)) { if (ImGui::BeginTable("Subsheets", 4, flags)) {
TileSheet::SubSheetIdx path;
ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide); ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide);
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 25); ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 25 * scale);
ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50); ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50 * scale);
ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50); ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50 * scale);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
drawSubsheetSelector(m_view.img().subsheet, path); drawSubsheetSelector(m_view.img().subsheet, path);
ImGui::EndTable(); ImGui::EndTable();
@@ -265,8 +268,8 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
ImGui::EndChild(); ImGui::EndChild();
} }
ImGui::EndChild(); ImGui::EndChild();
m_subsheetEditor.draw(m_tctx); m_subsheetEditor.draw();
m_exportMenu.draw(m_tctx); m_exportMenu.draw();
if (auto pal = m_palPicker.draw(m_sctx)) { if (auto pal = m_palPicker.draw(m_sctx)) {
if (*pal != m_model.palPath()) { if (*pal != m_model.palPath()) {
oxLogError(m_model.setPalette(*pal)); oxLogError(m_model.setPalette(*pal));
@@ -445,10 +448,11 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const &fbSize) noexcept {
void TileSheetEditorImGui::drawPaletteMenu() noexcept { void TileSheetEditorImGui::drawPaletteMenu() noexcept {
ig::IDStackItem const idStackItem{"PaletteMenu"}; ig::IDStackItem const idStackItem{"PaletteMenu"};
auto constexpr comboWidthSub = 62; auto const scale = ig::dpiScale();
auto const comboWidthSub = 62 * scale;
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub);
auto constexpr palTags = ImGuiInputTextFlags_ReadOnly; auto constexpr palFlags = ImGuiInputTextFlags_ReadOnly;
if (ig::InputTextWithHint("##Palette", "Path to Palette", m_model.palPath(), palTags)) { if (ig::InputTextWithHint("##Palette", "Path to Palette", m_model.palPath(), palFlags)) {
oxLogError(m_model.setPalette(m_model.palPath())); oxLogError(m_model.setPalette(m_model.palPath()));
} }
m_palPathFocused = ImGui::IsItemFocused(); m_palPathFocused = ImGui::IsItemFocused();
@@ -556,7 +560,7 @@ void TileSheetEditorImGui::SubSheetEditor::show(ox::StringViewCR name, int const
m_rows = rows; m_rows = rows;
} }
void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept { void TileSheetEditorImGui::SubSheetEditor::draw() noexcept {
constexpr auto popupName = "Edit Subsheet"; constexpr auto popupName = "Edit Subsheet";
if (!m_show) { if (!m_show) {
return; return;
@@ -565,13 +569,13 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept
auto constexpr popupWidth = 235.f; auto constexpr popupWidth = 235.f;
auto const popupHeight = modSize ? 130.f : 85.f; auto const popupHeight = modSize ? 130.f : 85.f;
auto const popupSz = ImVec2{popupWidth, popupHeight}; auto const popupSz = ImVec2{popupWidth, popupHeight};
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) { if (ig::BeginPopup(popupName, m_show, popupSz)) {
ig::InputText("Name", m_name); ig::InputText("Name", m_name);
if (modSize) { if (modSize) {
ImGui::InputInt("Columns", &m_cols); ImGui::InputInt("Columns", &m_cols);
ImGui::InputInt("Rows", &m_rows); ImGui::InputInt("Rows", &m_rows);
} }
if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) { if (ig::PopupControlsOkCancel(m_show) == ig::PopupResponse::OK) {
inputSubmitted.emit(m_name, m_cols, m_rows); inputSubmitted.emit(m_name, m_cols, m_rows);
} }
ImGui::EndPopup(); ImGui::EndPopup();
@@ -588,18 +592,18 @@ void TileSheetEditorImGui::ExportMenu::show() noexcept {
m_scale = 5; m_scale = 5;
} }
void TileSheetEditorImGui::ExportMenu::draw(turbine::Context &tctx) noexcept { void TileSheetEditorImGui::ExportMenu::draw() noexcept {
constexpr auto popupName = "Export Tile Sheet"; constexpr auto popupName = "Export Tile Sheet";
if (!m_show) { if (!m_show) {
return; return;
} }
constexpr auto popupWidth = 235.f; auto constexpr popupWidth = 235.f;
constexpr auto popupHeight = 85.f; auto constexpr popupHeight = 85.f;
constexpr auto popupSz = ImVec2{popupWidth, popupHeight}; auto constexpr popupSz = ImVec2{popupWidth, popupHeight};
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) { if (ig::BeginPopup(popupName, m_show, popupSz)) {
ImGui::InputInt("Scale", &m_scale); ImGui::InputInt("Scale", &m_scale);
m_scale = ox::clamp(m_scale, 1, 135); m_scale = ox::clamp(m_scale, 1, 135);
if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) { if (ig::PopupControlsOkCancel(m_show) == ig::PopupResponse::OK) {
inputSubmitted.emit(m_scale); inputSubmitted.emit(m_scale);
} }
ImGui::EndPopup(); ImGui::EndPopup();
@@ -27,7 +27,7 @@ class TileSheetEditorImGui: public studio::Editor {
public: public:
ox::Signal<ox::Error(ox::StringViewCR name, int cols, int rows)> inputSubmitted; ox::Signal<ox::Error(ox::StringViewCR name, int cols, int rows)> inputSubmitted;
void show(ox::StringViewCR name, int cols, int rows) noexcept; void show(ox::StringViewCR name, int cols, int rows) noexcept;
void draw(turbine::Context &tctx) noexcept; void draw() noexcept;
void close() noexcept; void close() noexcept;
[[nodiscard]] [[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; } constexpr bool isOpen() const noexcept { return m_show; }
@@ -39,7 +39,7 @@ class TileSheetEditorImGui: public studio::Editor {
public: public:
ox::Signal<ox::Error(int scale)> inputSubmitted; ox::Signal<ox::Error(int scale)> inputSubmitted;
void show() noexcept; void show() noexcept;
void draw(turbine::Context &tctx) noexcept; void draw() noexcept;
void close() noexcept; void close() noexcept;
[[nodiscard]] [[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; } constexpr bool isOpen() const noexcept { return m_show; }
+2 -1
View File
@@ -15,7 +15,8 @@ target_link_libraries(
target_compile_definitions( target_compile_definitions(
NostalgiaStudio PUBLIC NostalgiaStudio PUBLIC
OLYMPIC_APP_VERSION="dev build" OLYMPIC_APP_VERSION="d2026.05.0"
OLYMPIC_APP_ID="net.drinkingtea.nostalgia.NostalgiaStudio"
) )
install( install(
+1 -1
View File
@@ -18,7 +18,7 @@
<string>APPL</string> <string>APPL</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>dev build</string> <string>d2026.05.0</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0.0</string> <string>12.0.0</string>
-21
View File
@@ -1,21 +0,0 @@
add_executable(nostalgia-pack)
target_link_libraries(
nostalgia-pack
KeelPack-AppLib
NostalgiaKeelModules
NostalgiaProfile
OlympicApplib
)
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT WIN32)
# enable LTO
set_property(TARGET nostalgia-pack PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
install(
TARGETS
nostalgia-pack
RUNTIME DESTINATION
bin
)
+8 -2
View File
@@ -9,6 +9,10 @@
#define OLYMPIC_PROJECT_NAME "OlympicProject" #define OLYMPIC_PROJECT_NAME "OlympicProject"
#endif #endif
#ifndef OLYMPIC_APP_ID
#define OLYMPIC_APP_ID "AppID"
#endif
#ifndef OLYMPIC_APP_NAME #ifndef OLYMPIC_APP_NAME
#define OLYMPIC_APP_NAME "App" #define OLYMPIC_APP_NAME "App"
#endif #endif
@@ -38,7 +42,9 @@
#endif #endif
namespace olympic { namespace olympic {
ox::String appVersion{OLYMPIC_APP_VERSION}; ox::StringLiteral appVersion{OLYMPIC_APP_VERSION};
ox::StringLiteral appId{OLYMPIC_APP_ID};
ox::StringLiteral projectName{OLYMPIC_PROJECT_NAME};
} }
ox::Error run( ox::Error run(
@@ -72,7 +78,7 @@ int main(int const argc, char const **argv) {
OLYMPIC_PROJECT_DATADIR, OLYMPIC_PROJECT_DATADIR,
{argv, static_cast<size_t>(argc)}); {argv, static_cast<size_t>(argc)});
oxAssert(err, "Something went wrong..."); oxAssert(err, "Something went wrong...");
if (err) { if (ox::defines::Debug && err) {
oxErrf("Failure: {}\n", toStr(err)); oxErrf("Failure: {}\n", toStr(err));
} }
return static_cast<int>(err); return static_cast<int>(err);
@@ -0,0 +1,18 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/error.hpp>
#include <ox/std/span.hpp>
#include <ox/std/stringview.hpp>
namespace keel {
ox::Error pack(
ox::StringViewCR projectDir,
ox::StringViewCR projectDataDir,
ox::SpanView<ox::CString> argv) noexcept;
}
+5 -10
View File
@@ -41,20 +41,15 @@ install(
if(TURBINE_BUILD_TYPE STREQUAL "Native") if(TURBINE_BUILD_TYPE STREQUAL "Native")
add_library( add_library(
KeelPack-AppLib KeelPack-PackLib
pack-applib.cpp pack-packlib.cpp
) )
target_include_directories( target_include_directories(
KeelPack-AppLib PUBLIC KeelPack-PackLib PUBLIC
../include ../include
) )
target_compile_definitions(
KeelPack-AppLib PUBLIC
OLYMPIC_LOAD_KEEL_MODULES=1
OLYMPIC_APP_NAME="Keel Pack"
)
target_link_libraries( target_link_libraries(
KeelPack-AppLib KeelPack-PackLib
Keel Keel
OxClArgs OxClArgs
OxClaw OxClaw
@@ -62,7 +57,7 @@ if(TURBINE_BUILD_TYPE STREQUAL "Native")
) )
install( install(
TARGETS TARGETS
KeelPack-AppLib KeelPack-PackLib
DESTINATION DESTINATION
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
@@ -92,22 +92,20 @@ static ox::Error pack(
return {}; return {};
} }
ox::Error run( namespace keel {
[[maybe_unused]] ox::StringView const project,
[[maybe_unused]] ox::StringView const appName, ox::Error pack(
ox::StringView const projectDataDir, ox::StringViewCR projectDir,
ox::StringViewCR projectDataDir,
ox::SpanView<ox::CString> const argv) noexcept { ox::SpanView<ox::CString> const argv) noexcept {
ox::ClArgs const args(argv); ox::ClArgs const args(argv);
auto const argSrc = args.getString("src", "");
auto const argRomBin = args.getString("rom-bin", ""); auto const argRomBin = args.getString("rom-bin", "");
auto const argManifest = args.getString("manifest", ""); auto const argManifest = args.getString("manifest", "");
if (argSrc == "") {
oxErr("\033[31;1;1merror:\033[0m must specify a source directory\n");
return ox::Error(1, "must specify a source directory");
}
if (argRomBin == "") { if (argRomBin == "") {
oxErr("\033[31;1;1merror:\033[0m must specify a path for ROM file\n"); oxErr("\033[31;1;1merror:\033[0m must specify a path for ROM file\n");
return ox::Error(1, "must specify a path for preload file"); return ox::Error(1, "must specify a path for preload file");
} }
return pack(argSrc, argRomBin, argManifest, projectDataDir); return ::pack(projectDir, argRomBin, argManifest, projectDataDir);
} }
}
+34 -12
View File
@@ -15,10 +15,16 @@
#include <studioapp/studioapp.hpp> #include <studioapp/studioapp.hpp>
#include "subcommands/change-format/change-format.hpp" #include "subcommands/change-format/change-format.hpp"
#include "subcommands/pack/pack.hpp"
#include "configfile.hpp" #include "configfile.hpp"
#include "font.hpp"
#include "studioui.hpp" #include "studioui.hpp"
namespace olympic {
extern ox::StringLiteral appId;
}
namespace studio { namespace studio {
static ox::Error convertStudioConfigV1ToStudioConfigV2( static ox::Error convertStudioConfigV1ToStudioConfigV2(
@@ -41,10 +47,8 @@ static struct: Module {
ox::Vector<Command> commands() const final { ox::Vector<Command> commands() const final {
return { return {
{ { "change-format", cmdChangeFormat, },
"change-format", { "pack", cmdPack, },
cmdChangeFormat,
}
}; };
} }
} constexpr mod; } constexpr mod;
@@ -69,6 +73,10 @@ class StudioUIDrawer: public turbine::gl::Drawer {
explicit StudioUIDrawer(StudioUI &ui) noexcept: m_ui(ui) { explicit StudioUIDrawer(StudioUI &ui) noexcept: m_ui(ui) {
} }
void preDraw(turbine::Context&) noexcept final {
m_ui.preDraw();
}
void draw(turbine::Context&) noexcept final { void draw(turbine::Context&) noexcept final {
m_ui.draw(); m_ui.draw();
} }
@@ -91,7 +99,9 @@ static ox::Error runStudio(
ox::StringViewCR appName, ox::StringViewCR appName,
ox::StringViewCR projectDataDir, ox::StringViewCR projectDataDir,
ox::UPtr<ox::FileSystem> &&fs) noexcept { ox::UPtr<ox::FileSystem> &&fs) noexcept {
OX_REQUIRE_M(ctx, turbine::init(std::move(fs), appName)); OX_REQUIRE_M(
ctx,
turbine::init(std::move(fs), appName, olympic::appId));
oxLogError(turbine::setWindowIcon(*ctx, WindowIcons())); oxLogError(turbine::setWindowIcon(*ctx, WindowIcons()));
turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName); turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName);
turbine::setKeyEventHandler(*ctx, keyEventHandler); turbine::setKeyEventHandler(*ctx, keyEventHandler);
@@ -105,6 +115,20 @@ static ox::Error runStudio(
return err; return err;
} }
static void listCmds() noexcept {
oxOut("Insufficient arguments for sub-command\n\nValid commands:\n\n");
for (auto const m : modules()) {
if (m->commands().empty()) {
continue;
}
oxOutf("\t{}\n", m->id());
for (auto const &c : m->commands()) {
oxOutf("\t\t{}\n", c.name);
}
oxOut("\n");
}
}
static ox::Error run( static ox::Error run(
ox::StringViewCR appName, ox::StringViewCR appName,
ox::StringViewCR projectDataDir, ox::StringViewCR projectDataDir,
@@ -119,7 +143,8 @@ static ox::Error run(
}); });
if (args.size() > 1 && ox::StringView{args[1]} == "cmd") { if (args.size() > 1 && ox::StringView{args[1]} == "cmd") {
if (args.size() < 5) { if (args.size() < 5) {
return ox::Error{1, "insufficient arguments for sub-command"}; listCmds();
return ox::Error{1};
} }
auto constexpr numCmdArgs = 5; auto constexpr numCmdArgs = 5;
ox::StringView const projectDir = args[2]; ox::StringView const projectDir = args[2];
@@ -129,13 +154,10 @@ static ox::Error run(
if (m->id() == moduleId) { if (m->id() == moduleId) {
for (auto const &c : m->commands()) { for (auto const &c : m->commands()) {
if (c.name == subCmd) { if (c.name == subCmd) {
auto kctx = keel::init( OX_REQUIRE(kctx, keel::init(
ox::make_unique<ox::PassThroughFS>(projectDir), ox::make_unique<ox::PassThroughFS>(projectDir),
c.name); c.name).reoriginate(2, "failed to load project directory"));
if (kctx.error) { Project project{*kctx, projectDir, projectDataDir};
return ox::Error{2, "failed to load project directory"};
}
Project project{*kctx.value, projectDir, projectDataDir};
return c.func( return c.func(
project, project,
args.size() > numCmdArgs ? args.size() > numCmdArgs ?
@@ -4,8 +4,6 @@
#include <imgui.h> #include <imgui.h>
#include <studio/context.hpp>
#include "clawviewer.hpp" #include "clawviewer.hpp"
namespace studio { namespace studio {
+2
View File
@@ -1,5 +1,7 @@
// Generated // Generated
#pragma once
#include <ox/std/span.hpp> #include <ox/std/span.hpp>
namespace studio::files { namespace studio::files {
@@ -8,7 +8,7 @@
#include "about.hpp" #include "about.hpp"
namespace olympic { namespace olympic {
extern ox::String appVersion; extern ox::StringLiteral appVersion;
} }
namespace studio { namespace studio {
@@ -22,7 +22,7 @@ AboutPopup::AboutPopup(turbine::Context &ctx) noexcept:
#endif #endif
} }
void AboutPopup::draw(Context &sctx) noexcept { void AboutPopup::draw(Context&) noexcept {
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
close(); close();
return; return;
@@ -37,12 +37,12 @@ void AboutPopup::draw(Context &sctx) noexcept {
case Stage::Open: { case Stage::Open: {
constexpr auto modalFlags = constexpr auto modalFlags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
ig::centerNextWindow(sctx.tctx); ig::centerNextWindow();
auto open = true; auto open = true;
if (ImGui::BeginPopupModal("About", &open, modalFlags)) { if (ImGui::BeginPopupModal("About", &open, modalFlags)) {
ImGui::Text("%s\n\nBuild date: %s", m_text.c_str(), __DATE__); ImGui::Text("%s\n\nBuild date: %s", m_text.c_str(), __DATE__);
ImGui::NewLine(); ImGui::NewLine();
ImGui::Dummy({148.0f, 0.0f}); ig::Dummy({148.0f, 0.0f});
ImGui::SameLine(); ImGui::SameLine();
if (ig::PushButton("Close")) { if (ig::PushButton("Close")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
@@ -37,7 +37,7 @@ ox::Error FileInfo::open(ox::StringParam filePath) noexcept {
return {}; return {};
} }
void FileInfo::draw(Context &sctx) noexcept { void FileInfo::draw(Context&) noexcept {
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
close(); close();
return; return;
@@ -52,7 +52,7 @@ void FileInfo::draw(Context &sctx) noexcept {
case Stage::Open: { case Stage::Open: {
constexpr auto modalFlags = constexpr auto modalFlags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
ig::centerNextWindow(sctx.tctx); ig::centerNextWindow();
auto open = true; auto open = true;
if (ImGui::BeginPopupModal(m_title.c_str(), &open, modalFlags)) { if (ImGui::BeginPopupModal(m_title.c_str(), &open, modalFlags)) {
drawTable(); drawTable();
@@ -79,13 +79,14 @@ void FileInfo::draw(Context &sctx) noexcept {
} }
void FileInfo::drawTable() const noexcept { void FileInfo::drawTable() const noexcept {
auto const scale = ig::dpiScale();
ig::IDStackItem const idStackItem{"FileInfo"}; ig::IDStackItem const idStackItem{"FileInfo"};
ImGui::Text("%s", m_filePath.c_str()); ImGui::Text("%s", m_filePath.c_str());
if (m_fileInfo && ImGui::BeginTable( if (m_fileInfo && ImGui::BeginTable(
"Table", 2, "Table", 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_Borders |
ImGuiTableFlags_RowBg)) { ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, 70); ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, 70 * scale);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_NoHide);
// asset id // asset id
ImGui::TableNextRow(); ImGui::TableNextRow();
@@ -37,9 +37,10 @@ void MakeCopyPopup::draw(Context &ctx) noexcept {
m_stage = Stage::Open; m_stage = Stage::Open;
m_open = true; m_open = true;
[[fallthrough]]; [[fallthrough]];
case Stage::Open: case Stage::Open: {
ig::centerNextWindow(ctx.tctx); auto const scale = ig::dpiScale();
ImGui::SetNextWindowSize({250, 0}); ig::centerNextWindow();
ImGui::SetNextWindowSize({250 * scale, 0});
constexpr auto modalFlags = constexpr auto modalFlags =
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoMove |
@@ -67,6 +68,7 @@ void MakeCopyPopup::draw(Context &ctx) noexcept {
ImGui::EndPopup(); ImGui::EndPopup();
} }
break; break;
}
} }
} }
@@ -134,9 +134,10 @@ void NewMenu::drawNewItemPath(Context &sctx) noexcept {
} }
void NewMenu::drawButtons(Stage const next) noexcept { void NewMenu::drawButtons(Stage const next) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 198); auto const scale = ig::dpiScale();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 198 * scale);
constexpr ImVec2 btnSz{60, 20}; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 22 * scale);
ImVec2 const btnSz{60 * scale, ig::BtnSz.y * scale};
if (ImGui::Button("Back", btnSz)) { if (ImGui::Button("Back", btnSz)) {
if (auto const p = m_prev.back(); p.ok()) { if (auto const p = m_prev.back(); p.ok()) {
m_stage = *p.value; m_stage = *p.value;
@@ -156,9 +157,10 @@ void NewMenu::drawButtons(Stage const next) noexcept {
} }
void NewMenu::drawFirstPageButtons(Stage const next) noexcept { void NewMenu::drawFirstPageButtons(Stage const next) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 130); auto const scale = ig::dpiScale();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 130 * scale);
constexpr ImVec2 btnSz{60, 20}; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 22 * scale);
ImVec2 const btnSz{60 * scale, ig::BtnSz.y * scale};
if (ImGui::Button("Next", btnSz)) { if (ImGui::Button("Next", btnSz)) {
m_prev.emplace_back(m_stage); m_prev.emplace_back(m_stage);
m_stage = next; m_stage = next;
@@ -171,9 +173,10 @@ void NewMenu::drawFirstPageButtons(Stage const next) noexcept {
} }
void NewMenu::drawLastPageButtons(Context &sctx) noexcept { void NewMenu::drawLastPageButtons(Context &sctx) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 198); auto const scale = ig::dpiScale();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 198 * scale);
constexpr ImVec2 btnSz{60, 20}; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 22 * scale);
ImVec2 const btnSz{60 * scale, ig::BtnSz.y * scale};
if (ImGui::Button("Back", btnSz)) { if (ImGui::Button("Back", btnSz)) {
if (auto const p = m_prev.back(); p.ok()) { if (auto const p = m_prev.back(); p.ok()) {
m_stage = *p.value; m_stage = *p.value;
@@ -65,8 +65,9 @@ void NewProject::drawNewProjectName(Context &sctx) noexcept {
} }
void NewProject::drawLastPageButtons(Context&) noexcept { void NewProject::drawLastPageButtons(Context&) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 110); auto const scale = ig::dpiScale();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 22); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 110 * scale);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 22 * scale);
if (ig::PushButton("Finish")) { if (ig::PushButton("Finish")) {
finish(); finish();
} }
+50 -23
View File
@@ -43,7 +43,7 @@ ox::Vector<Module const*> const &modules() noexcept {
return g_modules; return g_modules;
} }
void registerModule(Module const*mod) noexcept { void registerModule(Module const *mod) noexcept {
if (mod) { if (mod) {
g_modules.emplace_back(mod); g_modules.emplace_back(mod);
} }
@@ -121,19 +121,6 @@ using StudioConfig = StudioConfigV2;
StudioUI::StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept: StudioUI::StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept:
m_sctx{*this, tctx}, m_sctx{*this, tctx},
m_projectDataDir{std::move(projectDataDir)} { m_projectDataDir{std::move(projectDataDir)} {
{
ImFontConfig fontCfg;
fontCfg.FontDataOwnedByAtlas = false;
auto const &io = ImGui::GetIO();
auto const font = files::RobotoMedium_ttf();
// const_cast is needed because this data is definitely const,
// but AddFontFromMemoryTTF requires a mutable buffer.
// However, setting fontCfg.FontDataOwnedByAtlas ensures
// that it will still be treated as const.
// ImGui documentation recognizes that this is a bad design,
// and hopefully it will change at some point.
io.Fonts->AddFontFromMemoryTTF(const_cast<uint8_t*>(font.data()), static_cast<int>(font.size()), 13, &fontCfg);
}
auto &kctx = keelCtx(m_tctx); auto &kctx = keelCtx(m_tctx);
kctx.converters.emplace_back(keel::Converter::make<convertStudioConfigV1ToStudioConfigV2>()); kctx.converters.emplace_back(keel::Converter::make<convertStudioConfigV1ToStudioConfigV2>());
oxLogError(headerizeConfigFile<StudioConfigV1>(kctx)); oxLogError(headerizeConfigFile<StudioConfigV1>(kctx));
@@ -196,7 +183,16 @@ void StudioUI::handleNavigationChange(ox::StringParam path, ox::StringParam navA
m_navAction.emplace(std::move(path), std::move(navArgs)); m_navAction.emplace(std::move(path), std::move(navArgs));
} }
void StudioUI::preDraw() noexcept {
loadFont();
}
void StudioUI::draw() noexcept { void StudioUI::draw() noexcept {
if (turbine::isWayland() || ox::defines::OS == ox::OS::Darwin) {
ig::setDpiScale(1.f);
} else {
ig::setDpiScale(ImGui::GetWindowDpiScale());
}
glutils::clearScreen(); glutils::clearScreen();
drawMenu(); drawMenu();
auto const &viewport = *ImGui::GetMainViewport(); auto const &viewport = *ImGui::GetMainViewport();
@@ -215,7 +211,7 @@ void StudioUI::draw() noexcept {
ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy); ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
if (m_showProjectExplorer) { if (m_showProjectExplorer) {
auto const v = ImGui::GetContentRegionAvail(); auto const v = ImGui::GetContentRegionAvail();
m_projectExplorer.draw(m_sctx, {300, v.y}); m_projectExplorer.draw(m_sctx, {300 * ig::dpiScale(), v.y});
ImGui::SameLine(); ImGui::SameLine();
} }
drawTabBar(); drawTabBar();
@@ -229,6 +225,29 @@ void StudioUI::draw() noexcept {
procFileMoves(); procFileMoves();
} }
void StudioUI::loadFont() noexcept {
auto const scale = turbine::isWayland() || ox::defines::OS == ox::OS::Darwin ?
1.f : turbine::scale(m_tctx);
if (m_fontScale != scale) {
m_fontScale = scale;
ImFontConfig fontCfg;
fontCfg.FontDataOwnedByAtlas = false;
auto const &io = ImGui::GetIO();
auto const font = files::RobotoMedium_ttf();
// const_cast is needed because this data is definitely const,
// but AddFontFromMemoryTTF requires a mutable buffer.
// However, setting fontCfg.FontDataOwnedByAtlas ensures
// that it will still be treated as const.
// ImGui documentation recognizes that this is a bad design,
// and hopefully it will change at some point.
io.Fonts->AddFontFromMemoryTTF(
const_cast<uint8_t*>(font.data()),
static_cast<int>(font.size()),
13 * scale,
&fontCfg);
}
}
bool StudioUI::handleShutdown() noexcept { bool StudioUI::handleShutdown() noexcept {
auto const out = ox::all_of(m_editors.begin(), m_editors.end(), [](ox::UPtr<BaseEditor> const &e) { auto const out = ox::all_of(m_editors.begin(), m_editors.end(), [](ox::UPtr<BaseEditor> const &e) {
return !e->unsavedChanges(); return !e->unsavedChanges();
@@ -322,12 +341,14 @@ void StudioUI::drawMenu() noexcept {
if (ImGui::BeginMenu("Navigate")) { if (ImGui::BeginMenu("Navigate")) {
constexpr auto backShortcut = ox::defines::OS == ox::OS::Darwin ? "Cmd+[" : "Alt+Left Arrow"; constexpr auto backShortcut = ox::defines::OS == ox::OS::Darwin ? "Cmd+[" : "Alt+Left Arrow";
constexpr auto fwdShortcut = ox::defines::OS == ox::OS::Darwin ? "Cmd+]" : "Alt+Right Arrow"; constexpr auto fwdShortcut = ox::defines::OS == ox::OS::Darwin ? "Cmd+]" : "Alt+Right Arrow";
if (ImGui::MenuItem("Back", backShortcut, false, m_sctx.navIdx > 1)) { if (ImGui::MenuItem(
"Back", backShortcut, false,
m_sctx.navIdx && *m_sctx.navIdx > 1)) {
navigateBack(m_sctx); navigateBack(m_sctx);
} }
if (ImGui::MenuItem( if (ImGui::MenuItem(
"Forward", fwdShortcut, false, "Forward", fwdShortcut, false,
m_sctx.navIdx < m_sctx.navStack.size())) { m_sctx.navIdx && *m_sctx.navIdx < m_sctx.navStack.size())) {
navigateForward(m_sctx); navigateForward(m_sctx);
} }
ImGui::EndMenu(); ImGui::EndMenu();
@@ -358,7 +379,7 @@ void StudioUI::drawTabs() noexcept {
auto const &e = *it; auto const &e = *it;
auto open = true; auto open = true;
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0; auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0; auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
auto const flags = unsavedChanges | selected | ImGuiTabItemFlags_NoAssumedClosure; auto const flags = unsavedChanges | selected | ImGuiTabItemFlags_NoAssumedClosure;
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) { if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) {
if (m_activeEditor != e.get()) [[unlikely]] { if (m_activeEditor != e.get()) [[unlikely]] {
@@ -376,11 +397,17 @@ void StudioUI::drawTabs() noexcept {
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] { if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
m_activeEditor->onActivated(); m_activeEditor->onActivated();
if (!m_sctx.navIdx || if (!m_sctx.navIdx ||
(m_sctx.navIdx <= m_sctx.navStack.size() && *m_sctx.navIdx == m_sctx.navStack.size() ||
m_sctx.navStack[m_sctx.navIdx - 1].filePath != m_activeEditor->itemPath())) { (*m_sctx.navIdx < m_sctx.navStack.size() &&
m_sctx.navStack.resize(m_sctx.navIdx); m_sctx.navStack[*m_sctx.navIdx].filePath != m_activeEditor->itemPath())) {
++m_sctx.navIdx; auto constexpr defaultVal =
m_sctx.navStack.emplace_back(ox::String{m_activeEditor->itemPath()}, ox::String{""}); ox::MaxValue<ox::remove_reference_t<decltype(*m_sctx.navIdx)>>;
auto &navIdx = m_sctx.navIdx.emplace(m_sctx.navIdx.or_value(defaultVal));
++navIdx;
m_sctx.navStack.resize(navIdx);
m_sctx.navStack.emplace_back(
ox::String{m_activeEditor->itemPath()},
ox::String{});
} }
} }
if (m_closeActiveTab) [[unlikely]] { if (m_closeActiveTab) [[unlikely]] {
@@ -84,6 +84,7 @@ class StudioUI final: public ox::SignalHandler {
ox::String args; ox::String args;
}; };
ox::Optional<NavAction> m_navAction; ox::Optional<NavAction> m_navAction;
float m_fontScale{};
public: public:
explicit StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept; explicit StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept;
@@ -102,9 +103,13 @@ class StudioUI final: public ox::SignalHandler {
bool handleShutdown() noexcept; bool handleShutdown() noexcept;
protected: protected:
void preDraw() noexcept;
void draw() noexcept; void draw() noexcept;
private: private:
void loadFont() noexcept;
void drawMenu() noexcept; void drawMenu() noexcept;
void drawTabBar() noexcept; void drawTabBar() noexcept;
@@ -2,9 +2,11 @@
target_sources( target_sources(
StudioAppLib PRIVATE StudioAppLib PRIVATE
change-format/change-format.cpp change-format/change-format.cpp
pack/pack.cpp
) )
target_link_libraries( target_link_libraries(
StudioAppLib PUBLIC StudioAppLib PUBLIC
OxClArgs OxClArgs
KeelPack-PackLib
) )
@@ -20,13 +20,12 @@ static ox::Error convertFile(
OX_REQUIRE(uuid, keel::readUuidHeader(buff)); OX_REQUIRE(uuid, keel::readUuidHeader(buff));
OX_REQUIRE(obj, keel::readAsset(ts, buff).reoriginate(1, "unable to parse file")); OX_REQUIRE(obj, keel::readAsset(ts, buff).reoriginate(1, "unable to parse file"));
buff.clear(); buff.clear();
ox::BufferWriter wrtr{&buff}; ox::BufferWriter writer{&buff};
OX_RETURN_ERROR(keel::writeUuidHeader(wrtr, uuid)); OX_RETURN_ERROR(keel::writeUuidHeader(writer, uuid));
OX_RETURN_ERROR(ox::writeClaw(obj, wrtr, fmt)); OX_RETURN_ERROR(ox::writeClaw(obj, writer, fmt));
if (fmt == ox::ClawFormat::Organic) { if (fmt == ox::ClawFormat::Organic) {
*buff.back().value = '\n'; *buff.back().value = '\n';
} }
OX_RETURN_ERROR(wrtr.write(buff.data(), buff.size()));
OX_RETURN_ERROR(fs.write(path, buff).reoriginate(1, "unable to write file")); OX_RETURN_ERROR(fs.write(path, buff).reoriginate(1, "unable to write file"));
return {}; return {};
} }
@@ -37,9 +36,9 @@ static void printUsage() noexcept {
[[nodiscard]] [[nodiscard]]
static constexpr ox::Result<ox::ClawFormat> getFmt(ox::StringViewCR fmtStr) noexcept { static constexpr ox::Result<ox::ClawFormat> getFmt(ox::StringViewCR fmtStr) noexcept {
if (caseInsensitiveEquals(fmtStr, "mc") == 0) { if (caseInsensitiveStrCmp(fmtStr, "mc") == 0) {
return ox::ClawFormat::Metal; return ox::ClawFormat::Metal;
} else if (caseInsensitiveEquals(fmtStr, "oc") == 0) { } else if (caseInsensitiveStrCmp(fmtStr, "oc") == 0) {
return ox::ClawFormat::Organic; return ox::ClawFormat::Organic;
} }
return ox::Error{1, "invalid format"}; return ox::Error{1, "invalid format"};
@@ -4,6 +4,8 @@
#pragma once #pragma once
#include <studio/project.hpp>
namespace studio { namespace studio {
ox::Error cmdChangeFormat(Project &project, ox::SpanView<ox::CString> args) noexcept; ox::Error cmdChangeFormat(Project &project, ox::SpanView<ox::CString> args) noexcept;
@@ -0,0 +1,19 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <keel/pack-packlib.hpp>
#include "pack.hpp"
namespace olympic {
extern ox::StringLiteral projectName;
}
namespace studio {
ox::Error cmdPack(Project &project, ox::SpanView<ox::CString> args) noexcept {
return keel::pack(project.projectPath(), olympic::projectName, args);
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <studio/project.hpp>
namespace studio {
ox::Error cmdPack(Project &project, ox::SpanView<ox::CString> args) noexcept;
}
@@ -28,7 +28,7 @@ struct Context {
ox::String filePath; ox::String filePath;
ox::String navArgs; ox::String navArgs;
}; };
size_t navIdx{}; ox::Optional<size_t> navIdx{};
ox::Vector<NavPath> navStack; ox::Vector<NavPath> navStack;
std::function<void(ox::StringParam filePath, ox::StringParam navArgs)> navCallback; std::function<void(ox::StringParam filePath, ox::StringParam navArgs)> navCallback;
Context(StudioUI &pUi, turbine::Context &pTctx) noexcept: Context(StudioUI &pUi, turbine::Context &pTctx) noexcept:
@@ -27,6 +27,10 @@ namespace studio::ig {
inline constexpr auto BtnSz = ImVec2{52, 22}; inline constexpr auto BtnSz = ImVec2{52, 22};
void setDpiScale(float scale) noexcept;
[[nodiscard]]
float dpiScale() noexcept;
constexpr ImTextureID toImTextureID(ox::Unsigned_c auto id) noexcept constexpr ImTextureID toImTextureID(ox::Unsigned_c auto id) noexcept
requires(sizeof(id) <= sizeof(ox::Uint<sizeof(ImTextureID)*8>)) { requires(sizeof(id) <= sizeof(ox::Uint<sizeof(ImTextureID)*8>)) {
@@ -133,7 +137,7 @@ auto dragDropTarget(auto const &cb) noexcept {
class ChildStackItem { class ChildStackItem {
public: public:
explicit ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz = {}) noexcept; explicit ChildStackItem(ox::CStringViewCR id, ImVec2 sz = {}) noexcept;
~ChildStackItem() noexcept; ~ChildStackItem() noexcept;
}; };
@@ -153,7 +157,12 @@ class IndentStackItem {
~IndentStackItem() noexcept; ~IndentStackItem() noexcept;
}; };
void centerNextWindow(turbine::Context &ctx) noexcept; void centerNextWindow() noexcept;
inline void Dummy(ImVec2 const &sz) noexcept {
auto const scale = dpiScale();
ImGui::Dummy({sz.x * scale, sz.y * scale});
}
bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz = BtnSz) noexcept; bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz = BtnSz) noexcept;
@@ -173,10 +182,10 @@ TextInput<ox::IString<MaxChars>> InputText(
ox::StringViewCR currentText, ox::StringViewCR currentText,
ImGuiInputTextFlags const flags = 0, ImGuiInputTextFlags const flags = 0,
ImGuiInputTextCallback const callback = nullptr, ImGuiInputTextCallback const callback = nullptr,
void *user_data = nullptr) noexcept { void *const userData = nullptr) noexcept {
TextInput<ox::IString<MaxChars>> out = {.text = currentText}; TextInput<ox::IString<MaxChars>> out = {.text = currentText};
out.changed = ImGui::InputText( out.changed = ImGui::InputText(
label.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data); label.c_str(), out.text.data(), MaxChars + 1, flags, callback, userData);
if (out.changed) { if (out.changed) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN OX_ALLOW_UNSAFE_BUFFERS_BEGIN
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str())); std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
@@ -192,10 +201,10 @@ TextInput<ox::IString<MaxChars>> InputTextWithHint(
ox::StringViewCR currentText, ox::StringViewCR currentText,
ImGuiInputTextFlags const flags = 0, ImGuiInputTextFlags const flags = 0,
ImGuiInputTextCallback const callback = nullptr, ImGuiInputTextCallback const callback = nullptr,
void *user_data = nullptr) noexcept { void *userData = nullptr) noexcept {
TextInput<ox::IString<MaxChars>> out = {.text = currentText}; TextInput<ox::IString<MaxChars>> out = {.text = currentText};
out.changed = ImGui::InputTextWithHint( out.changed = ImGui::InputTextWithHint(
label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data); label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, userData);
if (out.changed) { if (out.changed) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN OX_ALLOW_UNSAFE_BUFFERS_BEGIN
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str())); std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
@@ -210,9 +219,9 @@ bool InputText(
ox::IString<StrCap> &text, ox::IString<StrCap> &text,
ImGuiInputTextFlags const flags = 0, ImGuiInputTextFlags const flags = 0,
ImGuiInputTextCallback const callback = nullptr, ImGuiInputTextCallback const callback = nullptr,
void *user_data = nullptr) noexcept { void *userData = nullptr) noexcept {
auto const out = ImGui::InputText( auto const out = ImGui::InputText(
label.c_str(), text.data(), StrCap + 1, flags, callback, user_data); label.c_str(), text.data(), StrCap + 1, flags, callback, userData);
if (out) { if (out) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN OX_ALLOW_UNSAFE_BUFFERS_BEGIN
std::ignore = text.unsafeResize(ox::strlen(text.c_str())); std::ignore = text.unsafeResize(ox::strlen(text.c_str()));
@@ -243,7 +252,7 @@ PopupResponse PopupControlsOk(
ox::CStringViewCR ok); ox::CStringViewCR ok);
[[nodiscard]] [[nodiscard]]
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz = {285, 0}); bool BeginPopup(ox::CStringViewCR popupName, bool &show, ImVec2 sz = {285, 0});
/** /**
* *
@@ -290,7 +299,7 @@ bool ListBox(
std::function<ox::CStringView(size_t)> const &f, std::function<ox::CStringView(size_t)> const &f,
size_t strCnt, size_t strCnt,
size_t &selIdx, size_t &selIdx,
ImVec2 const &sz = {0, 0}) noexcept; ImVec2 sz = {0, 0}) noexcept;
/** /**
* *
+24 -18
View File
@@ -15,12 +15,9 @@ void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs)
} }
path = p; path = p;
} }
//if ( auto constexpr defaultVal =
// auto const [np, err] = ctx.navStack.back(); ox::MaxValue<ox::remove_reference_t<decltype(*ctx.navIdx)>>;
// !err && np->filePath == path && np->navArgs == navArgs.view()) { ctx.navStack.resize(ctx.navIdx.or_value(defaultVal) + 1);
// return;
//}
ctx.navStack.resize(ctx.navIdx + 1);
ctx.navStack.emplace_back(ox::String{path}, ox::String{navArgs.view()}); ctx.navStack.emplace_back(ox::String{path}, ox::String{navArgs.view()});
try { try {
ctx.navCallback(std::move(path), std::move(navArgs)); ctx.navCallback(std::move(path), std::move(navArgs));
@@ -34,15 +31,20 @@ void navigateBack(Context &ctx) noexcept {
if (!ctx.navIdx) { if (!ctx.navIdx) {
return; return;
} }
--ctx.navIdx; auto &navIdx = *ctx.navIdx;
while (ctx.navIdx < ctx.navStack.size() && ctx.navIdx) { while (navIdx && navIdx < ctx.navStack.size()) {
auto const i = ctx.navIdx - 1; --navIdx;
auto const &n = ctx.navStack[i]; auto const &n = ctx.navStack[navIdx];
// check for invalid entry
if (!ctx.project->exists(n.filePath)) { if (!ctx.project->exists(n.filePath)) {
std::ignore = ctx.navStack.erase(i); // remove the invalid entry, then reset and retry
--ctx.navIdx; auto const err = ctx.navStack.erase(navIdx);
if (err.error) {
return;
}
continue; continue;
} }
// entry is valid, do the navigation
try { try {
ctx.navCallback(n.filePath, n.navArgs); ctx.navCallback(n.filePath, n.navArgs);
} catch (std::exception const &e) { } catch (std::exception const &e) {
@@ -54,20 +56,24 @@ void navigateBack(Context &ctx) noexcept {
} }
void navigateForward(Context &ctx) noexcept { void navigateForward(Context &ctx) noexcept {
auto const nextIdx = ctx.navIdx + 1; if (!ctx.navIdx) {
ctx.navIdx.emplace(0u);
}
auto &navIdx = *ctx.navIdx;
auto const nextIdx = navIdx + 1;
while (nextIdx < ctx.navStack.size()) { while (nextIdx < ctx.navStack.size()) {
auto const &n = ctx.navStack[nextIdx]; auto const &n = ctx.navStack[nextIdx];
if (!ctx.project->exists(n.filePath)) {
std::ignore = ctx.navStack.erase(nextIdx);
continue;
}
try { try {
ctx.navCallback(n.filePath, n.navArgs); ctx.navCallback(n.filePath, n.navArgs);
} catch (std::exception const &e) { } catch (std::exception const &e) {
oxAssert(ctx.navCallback != nullptr, "navCallback is null"); oxAssert(ctx.navCallback != nullptr, "navCallback is null");
oxErrf("navigateForward failed: {}", e.what()); oxErrf("navigateForward failed: {}", e.what());
} }
if (!ctx.project->exists(n.filePath)) { ++navIdx;
std::ignore = ctx.navStack.erase(nextIdx);
continue;
}
++ctx.navIdx;
break; break;
} }
} }
@@ -2,6 +2,8 @@
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/ */
#include <turbine/turbine.hpp>
#include <studio/imguiutil.hpp> #include <studio/imguiutil.hpp>
#include <studio/filepickerpopup.hpp> #include <studio/filepickerpopup.hpp>
@@ -83,9 +85,10 @@ ox::Optional<ox::String> FilePickerPopup::draw(Context &ctx) noexcept {
if (!m_open) { if (!m_open) {
return out; return out;
} }
if (ig::BeginPopup(ctx.tctx, m_name, m_open, {380, 340})) { auto const scale = turbine::scale(ctx.tctx);
if (ig::BeginPopup(m_name, m_open, {380, 340})) {
auto const vp = ImGui::GetContentRegionAvail(); auto const vp = ImGui::GetContentRegionAvail();
m_explorer.draw(ctx, {vp.x, vp.y - 30}); m_explorer.draw(ctx, {vp.x, vp.y - 30 * scale});
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK || m_explorer.opened) { if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK || m_explorer.opened) {
out = handlePick(); out = handlePick();
} }
+55 -29
View File
@@ -10,7 +10,25 @@
namespace studio::ig { namespace studio::ig {
ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz) noexcept { inline void scaleSz(ImVec2 &sz) noexcept {
auto const scale = dpiScale();
sz.x *= scale;
sz.y *= scale;
}
static thread_local float g_dpiScale = 1.f;
void setDpiScale(float const scale) noexcept {
g_dpiScale = scale;
}
[[nodiscard]]
float dpiScale() noexcept {
return g_dpiScale;
}
ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 sz) noexcept {
scaleSz(sz);
ImGui::BeginChild(id.c_str(), sz); ImGui::BeginChild(id.c_str(), sz);
} }
@@ -33,7 +51,7 @@ IDStackItem::~IDStackItem() noexcept {
} }
IndentStackItem::IndentStackItem(float indent) noexcept: m_indent(indent) { IndentStackItem::IndentStackItem(float id) noexcept: m_indent(id) {
ImGui::Indent(m_indent); ImGui::Indent(m_indent);
} }
@@ -42,16 +60,18 @@ IndentStackItem::~IndentStackItem() noexcept {
} }
void centerNextWindow(turbine::Context &ctx) noexcept { void centerNextWindow() noexcept {
auto const sz = turbine::getScreenSize(ctx); auto const &io = ImGui::GetIO();
auto const screenW = static_cast<float>(sz.width); auto const sz = io.DisplaySize;
auto const screenH = static_cast<float>(sz.height); ImGui::SetNextWindowPos(
auto const mod = ImGui::GetWindowDpiScale() * 2; {sz.x * 0.5f, sz.y * 0.5f},
ImGui::SetNextWindowPos(ImVec2(screenW / mod, screenH / mod), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); ImGuiCond_Always,
{0.5f, 0.5f});
} }
bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz) noexcept { bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz) noexcept {
return ImGui::Button(lbl.c_str(), btnSz); auto const scale = dpiScale();
return ImGui::Button(lbl.c_str(), {btnSz.x * scale, btnSz.y * scale});
} }
PopupResponse PopupControlsOkCancel( PopupResponse PopupControlsOkCancel(
@@ -60,9 +80,10 @@ PopupResponse PopupControlsOkCancel(
ox::CStringViewCR ok, ox::CStringViewCR ok,
ox::CStringViewCR cancel) { ox::CStringViewCR cancel) {
auto out = PopupResponse::None; auto out = PopupResponse::None;
constexpr auto btnSz = ImVec2{50, BtnSz.y}; auto const scale = dpiScale();
auto const btnSz = ImVec2{50 * scale, BtnSz.y * scale};
ImGui::Separator(); ImGui::Separator();
ImGui::SetCursorPosX(popupWidth - 118); ImGui::SetCursorPosX(popupWidth - 118 * scale);
if (ImGui::Button(ok.c_str(), btnSz)) { if (ImGui::Button(ok.c_str(), btnSz)) {
popupOpen = false; popupOpen = false;
out = PopupResponse::OK; out = PopupResponse::OK;
@@ -79,16 +100,18 @@ PopupResponse PopupControlsOkCancel(
bool &popupOpen, bool &popupOpen,
ox::CStringViewCR ok, ox::CStringViewCR ok,
ox::CStringViewCR cancel) { ox::CStringViewCR cancel) {
return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17, popupOpen, ok, cancel); auto const scale = dpiScale();
return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17 * scale, popupOpen, ok, cancel);
} }
PopupResponse PopupControlsOk( PopupResponse PopupControlsOk(
bool &popupOpen, bool &popupOpen,
ox::CStringViewCR ok) { ox::CStringViewCR ok) {
auto const scale = dpiScale();
auto out = PopupResponse::None; auto out = PopupResponse::None;
constexpr auto btnSz = ImVec2{50, BtnSz.y}; auto const btnSz = ImVec2{50 * scale, BtnSz.y * scale};
ImGui::Separator(); ImGui::Separator();
ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 42); ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 42 * scale);
if (ImGui::Button(ok.c_str(), btnSz)) { if (ImGui::Button(ok.c_str(), btnSz)) {
popupOpen = false; popupOpen = false;
out = PopupResponse::OK; out = PopupResponse::OK;
@@ -101,23 +124,24 @@ PopupResponse PopupControlsOk(
return out; return out;
} }
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz) { bool BeginPopup(ox::CStringViewCR popupName, bool &show, ImVec2 sz) {
scaleSz(sz);
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
centerNextWindow(ctx); centerNextWindow();
ImGui::OpenPopup(popupName.c_str()); ImGui::OpenPopup(popupName.c_str());
ImGui::SetNextWindowSize(sz); ImGui::SetNextWindowSize(sz);
return ImGui::BeginPopupModal(popupName.c_str(), &show, modalFlags); return ImGui::BeginPopupModal(popupName.c_str(), &show, modalFlags);
} }
bool ComboBox( bool ComboBox(
ox::CStringView const &lbl, ox::CStringViewCR lbl,
ox::SpanView<ox::CStringView> const list, ox::SpanView<ox::CStringView> const list,
size_t &selectedIdx) noexcept { size_t &selectedIdx) noexcept {
bool out{}; bool out{};
auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : ""; auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : "";
if (ImGui::BeginCombo(lbl.c_str(), first, 0)) { if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
for (auto i = 0u; i < list.size(); ++i) { for (auto i = 0u; i < list.size(); ++i) {
auto const selected = (selectedIdx == i); auto const selected = selectedIdx == i;
if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) { if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) {
selectedIdx = i; selectedIdx = i;
out = true; out = true;
@@ -129,14 +153,14 @@ bool ComboBox(
} }
bool ComboBox( bool ComboBox(
ox::CStringView const &lbl, ox::CStringViewCR lbl,
ox::Span<ox::String const> const list, ox::Span<ox::String const> const list,
size_t &selectedIdx) noexcept { size_t &selectedIdx) noexcept {
bool out{}; bool out{};
auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : ""; auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : "";
if (ImGui::BeginCombo(lbl.c_str(), first, 0)) { if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
for (auto i = 0u; i < list.size(); ++i) { for (auto i = 0u; i < list.size(); ++i) {
auto const selected = (selectedIdx == i); auto const selected = selectedIdx == i;
if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) { if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) {
selectedIdx = i; selectedIdx = i;
out = true; out = true;
@@ -181,12 +205,14 @@ bool ListBox(
std::function<ox::CStringView(size_t)> const &f, std::function<ox::CStringView(size_t)> const &f,
size_t const strCnt, size_t const strCnt,
size_t &selIdx, size_t &selIdx,
ImVec2 const &sz) noexcept { ImVec2 sz) noexcept {
auto const scale = dpiScale();
sz = {sz.x * scale, sz.y * scale};
auto out = false; auto out = false;
if (ImGui::BeginListBox(name.c_str(), sz)) { if (ImGui::BeginListBox(name.c_str(), sz)) {
for (size_t i = 0; i < strCnt; ++i) { for (size_t i = 0; i < strCnt; ++i) {
auto str = f(i); auto const str = f(i);
ig::IDStackItem const idStackItem2(static_cast<int>(i)); IDStackItem const idStackItem2(static_cast<int>(i));
if (ImGui::Selectable(str.c_str(), selIdx == i)) { if (ImGui::Selectable(str.c_str(), selIdx == i)) {
if (i != selIdx) { if (i != selIdx) {
selIdx = i; selIdx = i;
@@ -200,13 +226,13 @@ bool ListBox(
} }
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const &list, size_t &selIdx) noexcept { bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const &list, size_t &selIdx) noexcept {
return ListBox(name, [list](size_t i) -> ox::CStringView { return ListBox(name, [list](size_t const i) -> ox::CStringView {
return list[i]; return list[i];
}, list.size(), selIdx); }, list.size(), selIdx);
} }
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::CStringView> const &list, size_t &selIdx) noexcept { bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::CStringView> const &list, size_t &selIdx) noexcept {
return ListBox(name, [list](size_t i) -> ox::CStringView { return ListBox(name, [list](size_t const i) -> ox::CStringView {
return list[i]; return list[i];
}, list.size(), selIdx); }, list.size(), selIdx);
} }
@@ -229,7 +255,7 @@ void FilePicker::draw() noexcept {
} }
auto constexpr popupSz = ImVec2{450.f, 0}; auto constexpr popupSz = ImVec2{450.f, 0};
IDStackItem const idStackItem(m_title); IDStackItem const idStackItem(m_title);
if (BeginPopup(m_sctx.tctx, m_title, m_show, popupSz)) { if (BeginPopup(m_title, m_show, popupSz)) {
auto const &list = m_sctx.project->fileList(m_fileExt); auto const &list = m_sctx.project->fileList(m_fileExt);
size_t selIdx{}; size_t selIdx{};
ComboBox(m_title, list, selIdx); ComboBox(m_title, list, selIdx);
@@ -284,7 +310,7 @@ void QuestionPopup::draw(Context &ctx) noexcept {
turbine::requireRefreshFor(ctx.tctx, 1000); turbine::requireRefreshFor(ctx.tctx, 1000);
[[fallthrough]]; [[fallthrough]];
case Stage::Open: case Stage::Open:
centerNextWindow(ctx.tctx); centerNextWindow();
ImGui::SetNextWindowSize({}); ImGui::SetNextWindowSize({});
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) { if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
@@ -319,7 +345,7 @@ void MessagePopup::show(ox::StringParam msg) noexcept {
open(); open();
} }
void MessagePopup::draw(Context &ctx) noexcept { void MessagePopup::draw(Context&) noexcept {
switch (m_stage) { switch (m_stage) {
case Stage::Closed: case Stage::Closed:
break; break;
@@ -329,7 +355,7 @@ void MessagePopup::draw(Context &ctx) noexcept {
m_open = true; m_open = true;
[[fallthrough]]; [[fallthrough]];
case Stage::Open: case Stage::Open:
centerNextWindow(ctx.tctx); centerNextWindow();
ImGui::SetNextWindowSize({}); ImGui::SetNextWindowSize({});
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) { if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
+3 -3
View File
@@ -7,9 +7,9 @@
namespace studio { namespace studio {
void Popup::drawWindow(turbine::Context &ctx, bool &open, std::function<void()> const &drawContents) { void Popup::drawWindow(turbine::Context&, bool &open, std::function<void()> const &drawContents) {
ig::centerNextWindow(ctx); ig::centerNextWindow();
ImGui::SetNextWindowSize(static_cast<ImVec2>(m_size)); ImGui::SetNextWindowSize(static_cast<ImVec2>(m_size * ig::dpiScale()));
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
if (ImGui::BeginPopupModal(m_title.c_str(), &open, modalFlags)) { if (ImGui::BeginPopupModal(m_title.c_str(), &open, modalFlags)) {
drawContents(); drawContents();
@@ -18,6 +18,7 @@ namespace gl {
class Drawer { class Drawer {
public: public:
virtual ~Drawer() = default; virtual ~Drawer() = default;
virtual void preDraw(Context&) noexcept {}
virtual void draw(Context&) noexcept = 0; virtual void draw(Context&) noexcept = 0;
}; };
void addDrawer(Context &ctx, Drawer *cd) noexcept; void addDrawer(Context &ctx, Drawer *cd) noexcept;
@@ -11,7 +11,6 @@
#include "gfx.hpp" #include "gfx.hpp"
namespace turbine { namespace turbine {
class Context; class Context;
using TimeMs = uint64_t; using TimeMs = uint64_t;
@@ -91,9 +90,15 @@ KeyEventHandler keyEventHandler(Context const &ctx) noexcept;
[[nodiscard]] [[nodiscard]]
bool buttonDown(Context const &ctx, Key) noexcept; bool buttonDown(Context const &ctx, Key) noexcept;
ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> &&fs, ox::StringViewCR appName) noexcept; ox::Result<ox::UPtr<Context>> init(
ox::UPtr<ox::FileSystem> &&fs,
ox::StringViewCR appName,
ox::StringViewCR appId = {}) noexcept;
ox::Result<ox::UPtr<Context>> init(ox::StringViewCR fsPath, ox::StringViewCR appName) noexcept; ox::Result<ox::UPtr<Context>> init(
ox::StringViewCR fsPath,
ox::StringViewCR appName,
ox::StringViewCR appId = {}) noexcept;
ox::Error run(Context &ctx) noexcept; ox::Error run(Context &ctx) noexcept;
@@ -112,4 +117,10 @@ void setShutdownHandler(Context &ctx, ShutdownHandler handler) noexcept;
// sleep time is a minimum of ~16 milliseconds. // sleep time is a minimum of ~16 milliseconds.
void setUpdateHandler(Context &ctx, UpdateHandler) noexcept; void setUpdateHandler(Context &ctx, UpdateHandler) noexcept;
[[nodiscard]]
float scale(Context const &ctx) noexcept;
[[nodiscard]]
bool isWayland() noexcept;
} }
+9 -1
View File
@@ -95,7 +95,7 @@ ox::Error setWindowBounds(Context&, ox::Bounds const&) noexcept {
} }
ox::Result<ox::UPtr<Context>> init( ox::Result<ox::UPtr<Context>> init(
ox::UPtr<ox::FileSystem> &&fs, ox::StringViewCR appName) noexcept { ox::UPtr<ox::FileSystem> &&fs, ox::StringViewCR appName, ox::StringViewCR) noexcept {
auto ctx = ox::make_unique<Context>(); auto ctx = ox::make_unique<Context>();
OX_RETURN_ERROR(keel::init(ctx->keelCtx, std::move(fs), appName)); OX_RETURN_ERROR(keel::init(ctx->keelCtx, std::move(fs), appName));
#ifdef OX_BARE_METAL #ifdef OX_BARE_METAL
@@ -139,4 +139,12 @@ KeyEventHandler keyEventHandler(Context const &ctx) noexcept {
return ctx.keyEventHandler; return ctx.keyEventHandler;
} }
float scale(Context const&) noexcept {
return 1;
}
bool isWayland() noexcept {
return false;
}
} }
+1
View File
@@ -32,6 +32,7 @@ class Context {
uint64_t draws = 0; uint64_t draws = 0;
bool running{}; bool running{};
ShutdownHandler shutdownHandler{}; ShutdownHandler shutdownHandler{};
float scale{};
Context() noexcept = default; Context() noexcept = default;
+32 -2
View File
@@ -235,12 +235,15 @@ void requireRefreshFor(Context &ctx, int ms) noexcept {
static void draw(Context &ctx) noexcept { static void draw(Context &ctx) noexcept {
// draw start // draw start
for (auto const d : ctx.drawers) {
d->preDraw(ctx);
}
#if TURBINE_USE_IMGUI #if TURBINE_USE_IMGUI
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
#endif #endif
for (auto d : ctx.drawers) { for (auto const d : ctx.drawers) {
d->draw(ctx); d->draw(ctx);
} }
#if TURBINE_USE_IMGUI #if TURBINE_USE_IMGUI
@@ -330,7 +333,9 @@ static void handleGlfwWindowCloseEvent(GLFWwindow *window) noexcept {
} }
ox::Result<ox::UPtr<Context>> init( ox::Result<ox::UPtr<Context>> init(
ox::UPtr<ox::FileSystem> &&fs, ox::StringViewCR appName) noexcept { ox::UPtr<ox::FileSystem> &&fs,
ox::StringViewCR appName,
ox::StringViewCR appId) noexcept {
auto ctx = ox::make_unique<Context>(); auto ctx = ox::make_unique<Context>();
OX_RETURN_ERROR(keel::init(ctx->keelCtx, std::move(fs), appName)); OX_RETURN_ERROR(keel::init(ctx->keelCtx, std::move(fs), appName));
using namespace std::chrono; using namespace std::chrono;
@@ -339,12 +344,20 @@ ox::Result<ox::UPtr<Context>> init(
setMandatoryRefreshPeriod(*ctx, ticksMs(*ctx) + config::MandatoryRefreshPeriod); setMandatoryRefreshPeriod(*ctx, ticksMs(*ctx) + config::MandatoryRefreshPeriod);
// init GLFW context // init GLFW context
glfwInit(); glfwInit();
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
glfwSetErrorCallback(handleGlfwError); glfwSetErrorCallback(handleGlfwError);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
{
auto appIdCstr = ox_malloca(appId.bytes() + 1, char);
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
ox::strncpy(appIdCstr.get(), appId.data(), appId.bytes());
OX_ALLOW_UNSAFE_BUFFERS_END
glfwWindowHintString(GLFW_WAYLAND_APP_ID, appIdCstr.get());
}
constexpr auto Scale = 5; constexpr auto Scale = 5;
ctx->window = glfwCreateWindow( ctx->window = glfwCreateWindow(
240 * Scale, 160 * Scale, ctx->keelCtx.appName.c_str(), nullptr, nullptr); 240 * Scale, 160 * Scale, ctx->keelCtx.appName.c_str(), nullptr, nullptr);
@@ -373,6 +386,13 @@ ox::Result<ox::UPtr<Context>> init(
ImGui_ImplOpenGL3_Init(); ImGui_ImplOpenGL3_Init();
io.IniFilename = nullptr; io.IniFilename = nullptr;
themeImgui(); themeImgui();
float xscale{}, yscale{};
glfwGetWindowContentScale(ctx->window, &xscale, &yscale);
ctx->scale = isWayland() || ox::defines::OS == ox::OS::Darwin ?
1.f : ox::max(xscale, yscale);
io.DisplayFramebufferScale = ImVec2{ctx->scale, ctx->scale};
auto &style = ImGui::GetStyle();
style.ScaleAllSizes(ctx->scale);
#endif #endif
return ctx; return ctx;
} }
@@ -471,4 +491,14 @@ KeyEventHandler keyEventHandler(Context const &ctx) noexcept {
return ctx.keyEventHandler; return ctx.keyEventHandler;
} }
float scale(Context const &ctx) noexcept {
float x{}, y{};
glfwGetWindowContentScale(ctx.window, &x, &y);
return y;
}
bool isWayland() noexcept {
return glfwGetPlatform() == GLFW_PLATFORM_WAYLAND;
}
} }
+5 -2
View File
@@ -8,9 +8,12 @@
namespace turbine { namespace turbine {
ox::Result<ox::UPtr<Context>> init(ox::StringViewCR fsPath, ox::StringViewCR appName) noexcept { ox::Result<ox::UPtr<Context>> init(
ox::StringViewCR fsPath,
ox::StringViewCR appName,
ox::StringViewCR appId) noexcept {
OX_REQUIRE_M(fs, keel::loadRomFs(fsPath)); OX_REQUIRE_M(fs, keel::loadRomFs(fsPath));
return init(std::move(fs), appName); return init(std::move(fs), appName, appId);
} }
} }
+1 -1
View File
@@ -64,7 +64,7 @@ def proc_rsrc_file(rsrc_path: str):# Open and read the JSON file
pop_ns = '' pop_ns = ''
cpp = '// Generated\n\n#include <ox/std/array.hpp>\n' cpp = '// Generated\n\n#include <ox/std/array.hpp>\n'
cpp += '#include <ox/std/span.hpp>\n\n' cpp += '#include <ox/std/span.hpp>\n\n'
hpp = '// Generated\n\n#include <ox/std/span.hpp>\n\n' hpp = '// Generated\n\n#pragma once\n\n#include <ox/std/span.hpp>\n\n'
cpp += push_ns cpp += push_ns
hpp += push_ns hpp += push_ns
all_files_func_decl = '' all_files_func_decl = ''
+8 -14
View File
@@ -8,36 +8,30 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# #
import platform
import shutil import shutil
import subprocess import subprocess
import sys import sys
os = platform.system().lower()
arch = platform.machine()
host_env = f'{os}-{arch}'
def run(args: list[str]): def run(args: list[str]):
if subprocess.run(args).returncode != 0: if subprocess.run(args).returncode != 0:
sys.exit(1) sys.exit(1)
# get the current build type
with open(".current_build", "r") as f:
current_build = f.readlines()[0]
if current_build[-1] == '\n':
current_build = current_build[:-1]
project_dir = sys.argv[1] studio_path = sys.argv[1]
project_name = sys.argv[2] project_dir = sys.argv[2]
bin = f'./build/{host_env}-{current_build}/bin/' project_name = sys.argv[3]
project_bin = f'build/gba-release/bin/{project_name}.bin' project_bin = f'build/gba-release/bin/{project_name}.bin'
project_gba = f'{project_name}.gba' project_gba = f'{project_name}.gba'
project_manifest = f'{project_name.lower()}-manifest.json' project_manifest = f'{project_name.lower()}-manifest.json'
shutil.copyfile(project_bin, project_gba) shutil.copyfile(project_bin, project_gba)
run([ run([
f'{bin}/{project_name.lower()}-pack', studio_path,
'-src', project_dir, 'cmd',
project_dir,
'net.drinkingtea.studio',
'pack',
'-rom-bin', project_gba, '-rom-bin', project_gba,
'-manifest', project_manifest]) '-manifest', project_manifest])
run(['gbafix', project_gba]) run(['gbafix', project_gba])