[nostalgia] Update Studio to handle tabs and open directory dialog on Mac, Update core::init
This commit is contained in:
parent
e29f65f351
commit
ad743565b2
@ -107,6 +107,22 @@ constexpr float bluef(Color16 c) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[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]]
|
[[nodiscard]]
|
||||||
constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b) noexcept {
|
constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b) noexcept {
|
||||||
return r | (g << 5) | (b << 10);
|
return r | (g << 5) | (b << 10);
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include <ox/fs/fs.hpp>
|
#include <ox/fs/fs.hpp>
|
||||||
|
|
||||||
|
#include "assetmanager.hpp"
|
||||||
|
|
||||||
namespace nostalgia::core {
|
namespace nostalgia::core {
|
||||||
|
|
||||||
class Context;
|
class Context;
|
||||||
@ -23,15 +25,19 @@ class Drawer {
|
|||||||
// User Input Output
|
// User Input Output
|
||||||
class Context {
|
class Context {
|
||||||
public:
|
public:
|
||||||
ox::FileSystem *rom = nullptr;
|
ox::UniquePtr<ox::FileSystem> rom;
|
||||||
ox::Vector<Drawer*, 5> drawers;
|
ox::Vector<Drawer*, 5> drawers;
|
||||||
|
const char *appName = "Nostalgia";
|
||||||
|
#ifndef OX_BARE_METAL
|
||||||
|
AssetManager assetManager;
|
||||||
|
#endif
|
||||||
private:
|
private:
|
||||||
void *m_customData = nullptr;
|
void *m_customData = nullptr;
|
||||||
void *m_windowerData = nullptr;
|
void *m_windowerData = nullptr;
|
||||||
void *m_rendererData = nullptr;
|
void *m_rendererData = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
constexpr Context() noexcept = default;
|
Context() noexcept = default;
|
||||||
|
|
||||||
Context(Context &other) noexcept = delete;
|
Context(Context &other) noexcept = delete;
|
||||||
Context(const Context &other) noexcept = delete;
|
Context(const Context &other) noexcept = delete;
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <ox/fs/fs.hpp>
|
#include <ox/fs/fs.hpp>
|
||||||
|
|
||||||
|
#include "assetmanager.hpp"
|
||||||
#include "clipboard.hpp"
|
#include "clipboard.hpp"
|
||||||
#include "consts.hpp"
|
#include "consts.hpp"
|
||||||
#include "gfx.hpp"
|
#include "gfx.hpp"
|
||||||
@ -20,7 +21,7 @@ namespace nostalgia::core {
|
|||||||
|
|
||||||
using event_handler = int(*)(Context*);
|
using event_handler = int(*)(Context*);
|
||||||
|
|
||||||
ox::Result<ox::UniquePtr<Context>> init(ox::FileSystem *fs) noexcept;
|
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, const char *appName = "Nostalgia") noexcept;
|
||||||
|
|
||||||
ox::Error run(Context *ctx) noexcept;
|
ox::Error run(Context *ctx) noexcept;
|
||||||
|
|
||||||
|
@ -40,9 +40,10 @@ static void initTimer() noexcept {
|
|||||||
REG_IE = REG_IE | Int_timer0;
|
REG_IE = REG_IE | Int_timer0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Result<ox::UniquePtr<Context>> init(ox::FileSystem *fs) noexcept {
|
ox::Result<ox::UniquePtr<Context>> init(ox::UniquePtr<ox::FileSystem> fs, const char *appName) noexcept {
|
||||||
auto ctx = ox::make_unique<Context>();
|
auto ctx = ox::make_unique<Context>();
|
||||||
ctx->rom = fs;
|
ctx->rom = std::move(fs);
|
||||||
|
ctx->appName = std::move(appName);
|
||||||
oxReturnError(initGfx(ctx.get()));
|
oxReturnError(initGfx(ctx.get()));
|
||||||
initTimer();
|
initTimer();
|
||||||
initIrq();
|
initIrq();
|
||||||
|
@ -162,7 +162,8 @@ ox::Error initConsole(Context *ctx) noexcept {
|
|||||||
ctx = new (ox_alloca(sizeof(Context))) Context();
|
ctx = new (ox_alloca(sizeof(Context))) Context();
|
||||||
oxRequire(rom, loadRom());
|
oxRequire(rom, loadRom());
|
||||||
ox::FileStore32 fs(rom, 32 * ox::units::MB);
|
ox::FileStore32 fs(rom, 32 * ox::units::MB);
|
||||||
ctx->rom = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
|
auto romFs = new (ox_alloca(sizeof(ox::FileSystem32))) ox::FileSystem32(fs);
|
||||||
|
new (&ctx->rom) ox::UniquePtr<ox::FileSystem>(romFs);
|
||||||
}
|
}
|
||||||
return loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr);
|
return loadBgTileSheet(ctx, 0, TilesheetAddr, PaletteAddr);
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,8 @@ static int eventHandler(core::Context *ctx) noexcept {
|
|||||||
return 16;
|
return 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Error run(ox::FileSystem *fs) noexcept {
|
ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
|
||||||
oxRequireM(ctx, core::init(fs));
|
oxRequireM(ctx, core::init(std::move(fs)));
|
||||||
constexpr auto TileSheetAddr = "/TileSheets/Charset.ng";
|
constexpr auto TileSheetAddr = "/TileSheets/Charset.ng";
|
||||||
constexpr auto PaletteAddr = "/Palettes/Charset.npal";
|
constexpr auto PaletteAddr = "/Palettes/Charset.npal";
|
||||||
oxReturnError(core::loadSpriteTileSheet(ctx.get(), 0, TileSheetAddr, PaletteAddr));
|
oxReturnError(core::loadSpriteTileSheet(ctx.get(), 0, TileSheetAddr, PaletteAddr));
|
||||||
|
@ -6,4 +6,6 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class ox::Error run(class ox::FileSystem *fs) noexcept;
|
#include <ox/std/memory.hpp>
|
||||||
|
|
||||||
|
class ox::Error run(ox::UniquePtr<class ox::FileSystem> fs) noexcept;
|
||||||
|
@ -17,8 +17,8 @@ static ox::Error run(int argc, const char **argv) noexcept {
|
|||||||
return OxError(1);
|
return OxError(1);
|
||||||
}
|
}
|
||||||
const auto path = argv[1];
|
const auto path = argv[1];
|
||||||
oxRequire(fs, nostalgia::core::loadRomFs(path));
|
oxRequireM(fs, nostalgia::core::loadRomFs(path));
|
||||||
return run(fs.get());
|
return run(std::move(fs));
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, const char **argv) {
|
int main(int argc, const char **argv) {
|
||||||
|
@ -13,6 +13,7 @@ target_link_libraries(
|
|||||||
nostalgia-studio
|
nostalgia-studio
|
||||||
OxClArgs
|
OxClArgs
|
||||||
OxFS
|
OxFS
|
||||||
|
NostalgiaCore-Studio
|
||||||
NostalgiaCore-Userspace
|
NostalgiaCore-Userspace
|
||||||
NostalgiaStudio
|
NostalgiaStudio
|
||||||
NostalgiaPack
|
NostalgiaPack
|
||||||
|
24
src/nostalgia/studio/builtinmodules.hpp
Normal file
24
src/nostalgia/studio/builtinmodules.hpp
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2021 gary@drinkingtea.net
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ox/std/memory.hpp>
|
||||||
|
|
||||||
|
#include <nostalgia/core/studio/module.hpp>
|
||||||
|
|
||||||
|
namespace nostalgia {
|
||||||
|
|
||||||
|
//[[maybe_unused]] // GCC warns about the existence of this "unused" constexpr list in a header file...
|
||||||
|
//constexpr auto BuiltinModules = {
|
||||||
|
// [] {
|
||||||
|
// return ox::make_unique<core::Module>();
|
||||||
|
// },
|
||||||
|
//};
|
||||||
|
|
||||||
|
}
|
@ -11,15 +11,20 @@ add_library(
|
|||||||
NostalgiaStudio
|
NostalgiaStudio
|
||||||
configio.cpp
|
configio.cpp
|
||||||
editor.cpp
|
editor.cpp
|
||||||
|
module.cpp
|
||||||
project.cpp
|
project.cpp
|
||||||
task.cpp
|
task.cpp
|
||||||
undostack.cpp
|
undostack.cpp
|
||||||
widget.cpp
|
widget.cpp
|
||||||
window.cpp
|
window.cpp
|
||||||
filedialog_gtk.cpp
|
filedialog_gtk.cpp
|
||||||
$<$<BOOL:${APPLE}>:filedialog.mm>
|
$<$<BOOL:${APPLE}>:filedialog_mac.mm>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(NOT MSVC)
|
||||||
|
target_compile_options(NostalgiaStudio PUBLIC -Wsign-conversion)
|
||||||
|
endif()
|
||||||
|
|
||||||
install(TARGETS NostalgiaStudio
|
install(TARGETS NostalgiaStudio
|
||||||
LIBRARY DESTINATION ${NOSTALGIA_DIST_LIB}/nostalgia)
|
LIBRARY DESTINATION ${NOSTALGIA_DIST_LIB}/nostalgia)
|
||||||
|
|
||||||
@ -46,11 +51,15 @@ target_link_libraries(
|
|||||||
|
|
||||||
install(
|
install(
|
||||||
FILES
|
FILES
|
||||||
|
configio.hpp
|
||||||
editor.hpp
|
editor.hpp
|
||||||
filedialog.hpp
|
filedialog.hpp
|
||||||
|
module.hpp
|
||||||
project.hpp
|
project.hpp
|
||||||
task.hpp
|
task.hpp
|
||||||
undostack.hpp
|
undostack.hpp
|
||||||
|
widget.hpp
|
||||||
|
window.hpp
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/nostalgiastudio_export.h
|
${CMAKE_CURRENT_BINARY_DIR}/nostalgiastudio_export.h
|
||||||
DESTINATION
|
DESTINATION
|
||||||
include/nostalgia/studio/lib
|
include/nostalgia/studio/lib
|
||||||
|
@ -19,18 +19,20 @@
|
|||||||
#include <ox/std/trace.hpp>
|
#include <ox/std/trace.hpp>
|
||||||
#include <ox/std/string.hpp>
|
#include <ox/std/string.hpp>
|
||||||
|
|
||||||
|
#include <nostalgia/core/context.hpp>
|
||||||
|
|
||||||
namespace nostalgia::studio {
|
namespace nostalgia::studio {
|
||||||
|
|
||||||
constexpr auto ConfigDir = [] {
|
constexpr auto ConfigDir = [] {
|
||||||
switch (ox::defines::OS) {
|
switch (ox::defines::OS) {
|
||||||
case ox::defines::OS::Darwin:
|
case ox::defines::OS::Darwin:
|
||||||
return "{}/Library/Preferences/NostalgiaStudio";
|
return "{}/Library/Preferences/{}";
|
||||||
case ox::defines::OS::DragonFlyBSD:
|
case ox::defines::OS::DragonFlyBSD:
|
||||||
case ox::defines::OS::FreeBSD:
|
case ox::defines::OS::FreeBSD:
|
||||||
case ox::defines::OS::Linux:
|
case ox::defines::OS::Linux:
|
||||||
case ox::defines::OS::NetBSD:
|
case ox::defines::OS::NetBSD:
|
||||||
case ox::defines::OS::OpenBSD:
|
case ox::defines::OS::OpenBSD:
|
||||||
return "{}/.config/NostalgiaStudio";
|
return "{}/.config/{}";
|
||||||
case ox::defines::OS::BareMetal:
|
case ox::defines::OS::BareMetal:
|
||||||
case ox::defines::OS::Windows:
|
case ox::defines::OS::Windows:
|
||||||
return "";
|
return "";
|
||||||
@ -38,13 +40,14 @@ constexpr auto ConfigDir = [] {
|
|||||||
}();
|
}();
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
ox::Result<T> readConfig(const ox::String &name = ox::getModelTypeName<T>()) noexcept {
|
ox::Result<T> readConfig(core::Context *ctx, const ox::String &name = ox::getModelTypeName<T>()) noexcept {
|
||||||
|
oxAssert(name != "", "Config type has no TypeName");
|
||||||
const auto homeDir = std::getenv("HOME");
|
const auto homeDir = std::getenv("HOME");
|
||||||
const auto configPath = ox::sfmt(ConfigDir, homeDir).toStdString();
|
const auto configPath = ox::sfmt(ConfigDir, homeDir, ctx->appName).toStdString();
|
||||||
const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
|
const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
|
||||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||||
if (!file.good()) {
|
if (!file.good()) {
|
||||||
oxErrorf("Could not find config file: {}", path);
|
oxErrf("Could not find config file: {}\n", path);
|
||||||
return OxError(1, "Could not find config file");
|
return OxError(1, "Could not find config file");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -54,15 +57,16 @@ ox::Result<T> readConfig(const ox::String &name = ox::getModelTypeName<T>()) noe
|
|||||||
file.read(buff.data(), size);
|
file.read(buff.data(), size);
|
||||||
return ox::readOC<T>(buff);
|
return ox::readOC<T>(buff);
|
||||||
} catch (const std::ios_base::failure &e) {
|
} catch (const std::ios_base::failure &e) {
|
||||||
oxErrorf("Could not read config file: {}", e.what());
|
oxErrf("Could not read config file: {}\n", e.what());
|
||||||
return OxError(2, "Could not read config file");
|
return OxError(2, "Could not read config file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
ox::Error writeConfig(const ox::String &name, T *data) noexcept {
|
ox::Error writeConfig(core::Context *ctx, const ox::String &name, T *data) noexcept {
|
||||||
|
oxAssert(name != "", "Config type has no TypeName");
|
||||||
const auto homeDir = std::getenv("HOME");
|
const auto homeDir = std::getenv("HOME");
|
||||||
const auto configPath = ox::sfmt(ConfigDir, homeDir).toStdString();
|
const auto configPath = ox::sfmt(ConfigDir, homeDir, ctx->appName).toStdString();
|
||||||
const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
|
const auto path = ox::sfmt("{}/{}.json", configPath, name).toStdString();
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::create_directory(configPath, ec);
|
std::filesystem::create_directory(configPath, ec);
|
||||||
@ -71,7 +75,7 @@ ox::Error writeConfig(const ox::String &name, T *data) noexcept {
|
|||||||
}
|
}
|
||||||
std::ofstream file(path, std::ios::binary | std::ios::ate);
|
std::ofstream file(path, std::ios::binary | std::ios::ate);
|
||||||
if (!file.good()) {
|
if (!file.good()) {
|
||||||
oxErrorf("Could not find config file: {}", path);
|
oxErrf("Could not find config file: {}\n", path);
|
||||||
return OxError(1, "Could not find config file");
|
return OxError(1, "Could not find config file");
|
||||||
}
|
}
|
||||||
oxRequireM(buff, ox::writeOC(data));
|
oxRequireM(buff, ox::writeOC(data));
|
||||||
@ -80,26 +84,42 @@ ox::Error writeConfig(const ox::String &name, T *data) noexcept {
|
|||||||
file.write(buff.data(), static_cast<ox::Signed<decltype(buff.size())>>(buff.size()));
|
file.write(buff.data(), static_cast<ox::Signed<decltype(buff.size())>>(buff.size()));
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
} catch (const std::ios_base::failure &e) {
|
} catch (const std::ios_base::failure &e) {
|
||||||
oxErrorf("Could not read config file: {}", e.what());
|
oxErrf("Could not read config file: {}\n", e.what());
|
||||||
return OxError(2, "Could not read config file");
|
return OxError(2, "Could not read config file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
ox::Error writeConfig(T *data) noexcept {
|
ox::Error writeConfig(core::Context *ctx, T *data) noexcept {
|
||||||
return writeConfig(ox::getModelTypeName<T>(), data);
|
const auto TypeName = ox::getModelTypeName<T>();
|
||||||
|
return writeConfig(ctx, TypeName, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename Func>
|
template<typename T, typename Func>
|
||||||
ox::Error editConfig(const ox::String &name, Func f) noexcept {
|
void openConfig(core::Context *ctx, const ox::String &name, Func f) noexcept {
|
||||||
auto c = readConfig<T>(name);
|
oxAssert(name != "", "Config type has no TypeName");
|
||||||
|
const auto c = readConfig<T>(ctx, name);
|
||||||
f(&c.value);
|
f(&c.value);
|
||||||
return writeConfig(name, &c.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename Func>
|
template<typename T, typename Func>
|
||||||
ox::Error editConfig(Func f) noexcept {
|
void openConfig(core::Context *ctx, Func f) noexcept {
|
||||||
return editConfig<T>(ox::getModelTypeName<T>(), f);
|
const auto TypeName = ox::getModelTypeName<T>();
|
||||||
|
openConfig<T>(ctx, TypeName, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename Func>
|
||||||
|
void editConfig(core::Context *ctx, const ox::String &name, Func f) noexcept {
|
||||||
|
oxAssert(name != "", "Config type has no TypeName");
|
||||||
|
auto c = readConfig<T>(ctx, name);
|
||||||
|
f(&c.value);
|
||||||
|
oxLogError(writeConfig(ctx, name, &c.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename Func>
|
||||||
|
void editConfig(core::Context *ctx, Func f) noexcept {
|
||||||
|
const auto TypeName = ox::getModelTypeName<T>();
|
||||||
|
editConfig<T>(ctx, TypeName, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
namespace nostalgia::studio {
|
namespace nostalgia::studio {
|
||||||
|
|
||||||
|
ox::String Editor::itemDisplayName() const {
|
||||||
|
return itemName();
|
||||||
|
}
|
||||||
|
|
||||||
void Editor::cut() {
|
void Editor::cut() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +28,10 @@ void Editor::paste() {
|
|||||||
void Editor::exportFile() {
|
void Editor::exportFile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Editor::close() {
|
||||||
|
this->closed.emit(itemName());
|
||||||
|
}
|
||||||
|
|
||||||
void Editor::save() {
|
void Editor::save() {
|
||||||
saveItem();
|
saveItem();
|
||||||
setUnsavedChanges(false);
|
setUnsavedChanges(false);
|
||||||
|
@ -11,12 +11,13 @@
|
|||||||
#include <ox/event/signal.hpp>
|
#include <ox/event/signal.hpp>
|
||||||
|
|
||||||
#include "undostack.hpp"
|
#include "undostack.hpp"
|
||||||
|
#include "widget.hpp"
|
||||||
|
|
||||||
#include "nostalgiastudio_export.h"
|
#include "nostalgiastudio_export.h"
|
||||||
|
|
||||||
namespace nostalgia::studio {
|
namespace nostalgia::studio {
|
||||||
|
|
||||||
class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
|
class NOSTALGIASTUDIO_EXPORT Editor: public Widget {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UndoStack m_cmdStack;
|
UndoStack m_cmdStack;
|
||||||
@ -35,6 +36,8 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
virtual ox::String itemName() const = 0;
|
virtual ox::String itemName() const = 0;
|
||||||
|
|
||||||
|
virtual ox::String itemDisplayName() const;
|
||||||
|
|
||||||
virtual void cut();
|
virtual void cut();
|
||||||
|
|
||||||
virtual void copy();
|
virtual void copy();
|
||||||
@ -43,6 +46,8 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
|
|||||||
|
|
||||||
virtual void exportFile();
|
virtual void exportFile();
|
||||||
|
|
||||||
|
void close();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save changes to item being edited.
|
* Save changes to item being edited.
|
||||||
*/
|
*/
|
||||||
@ -89,12 +94,14 @@ class NOSTALGIASTUDIO_EXPORT Editor: public ox::SignalHandler {
|
|||||||
*/
|
*/
|
||||||
virtual void saveItem();
|
virtual void saveItem();
|
||||||
|
|
||||||
|
// slots
|
||||||
public:
|
public:
|
||||||
ox::Signal<ox::Error(bool)> unsavedChangesChanged;
|
ox::Signal<ox::Error(bool)> unsavedChangesChanged;
|
||||||
ox::Signal<ox::Error(bool)> exportableChanged;
|
ox::Signal<ox::Error(bool)> exportableChanged;
|
||||||
ox::Signal<ox::Error(bool)> cutEnabledChanged;
|
ox::Signal<ox::Error(bool)> cutEnabledChanged;
|
||||||
ox::Signal<ox::Error(bool)> copyEnabledChanged;
|
ox::Signal<ox::Error(bool)> copyEnabledChanged;
|
||||||
ox::Signal<ox::Error(bool)> pasteEnabledChanged;
|
ox::Signal<ox::Error(bool)> pasteEnabledChanged;
|
||||||
|
ox::Signal<ox::Error(const ox::String&)> closed;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,20 +6,11 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "context.hpp"
|
|
||||||
#include "module.hpp"
|
#include "module.hpp"
|
||||||
|
|
||||||
namespace nostalgia::studio {
|
namespace nostalgia::studio {
|
||||||
|
|
||||||
QVector<WizardMaker> Module::newWizards(const Context*) {
|
ox::Vector<EditorMaker> Module::editors(core::Context*) {
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<WizardMaker> Module::importWizards(const Context*) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<EditorMaker> Module::editors(const Context*) {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,33 +10,25 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <QVector>
|
#include <ox/std/string.hpp>
|
||||||
#include <QWizardPage>
|
#include <ox/std/vector.hpp>
|
||||||
|
|
||||||
#include "wizard.hpp"
|
#include <nostalgia/core/context.hpp>
|
||||||
|
|
||||||
namespace nostalgia::studio {
|
namespace nostalgia::studio {
|
||||||
|
|
||||||
struct EditorMaker {
|
struct EditorMaker {
|
||||||
QStringList fileTypes;
|
using Func = std::function<class Editor*(ox::String)>;
|
||||||
std::function<class Editor*(QString)> make;
|
ox::Vector<ox::String> fileTypes;
|
||||||
|
Func make;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Module {
|
class Module {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~Module() = default;
|
virtual ~Module() noexcept = default;
|
||||||
|
|
||||||
virtual QVector<WizardMaker> newWizards(const class Context *ctx);
|
virtual ox::Vector<EditorMaker> editors(core::Context *ctx);
|
||||||
|
|
||||||
virtual QVector<WizardMaker> importWizards(const Context *ctx);
|
|
||||||
|
|
||||||
virtual QVector<EditorMaker> editors(const class Context *ctx);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define PluginInterface_iid "net.drinkingtea.nostalgia.studio.Module"
|
|
||||||
|
|
||||||
Q_DECLARE_INTERFACE(nostalgia::studio::Module, PluginInterface_iid)
|
|
||||||
|
@ -21,7 +21,7 @@ ox::String filePathToName(const ox::String &path, const ox::String &prefix, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Project::Project(const ox::String &path) noexcept: m_fs(ox::make_unique<ox::PassThroughFS>(path.c_str())) {
|
Project::Project(ox::FileSystem *fs, const ox::String &path) noexcept: m_fs(fs) {
|
||||||
oxTracef("nostalgia::studio", "Project: {}", path);
|
oxTracef("nostalgia::studio", "Project: {}", path);
|
||||||
m_path = path;
|
m_path = path;
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ ox::Error Project::create() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ox::FileSystem *Project::romFs() noexcept {
|
ox::FileSystem *Project::romFs() noexcept {
|
||||||
return m_fs.get();
|
return m_fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Error Project::mkdir(const ox::String &path) const noexcept {
|
ox::Error Project::mkdir(const ox::String &path) const noexcept {
|
||||||
|
@ -39,10 +39,10 @@ ox::String filePathToName(const ox::String &path, const ox::String &prefix, cons
|
|||||||
class NOSTALGIASTUDIO_EXPORT Project {
|
class NOSTALGIASTUDIO_EXPORT Project {
|
||||||
private:
|
private:
|
||||||
ox::String m_path = "";
|
ox::String m_path = "";
|
||||||
mutable ox::UniquePtr<ox::FileSystem> m_fs;
|
mutable ox::FileSystem *m_fs = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Project(const ox::String &path) noexcept;
|
explicit Project(ox::FileSystem *fs, const ox::String &path) noexcept;
|
||||||
|
|
||||||
ox::Error create() noexcept;
|
ox::Error create() noexcept;
|
||||||
|
|
||||||
|
@ -6,16 +6,16 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include "task.hpp"
|
#include "task.hpp"
|
||||||
|
|
||||||
namespace nostalgia::studio {
|
namespace nostalgia::studio {
|
||||||
|
|
||||||
void TaskRunner::update(core::Context *ctx) noexcept {
|
void TaskRunner::update(core::Context *ctx) noexcept {
|
||||||
for (auto i = 0u; i < m_tasks.size(); ++i) {
|
oxIgnoreError(m_tasks.erase(std::remove_if(m_tasks.begin(), m_tasks.end(), [&](auto &t) {
|
||||||
if (m_tasks[i]->update(ctx) == studio::TaskState::Done) {
|
return t->update(ctx) == TaskState::Done;
|
||||||
oxIgnoreError(m_tasks.erase(i--));
|
})));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TaskRunner::add(Task *task) noexcept {
|
void TaskRunner::add(Task *task) noexcept {
|
||||||
|
@ -21,24 +21,29 @@ class StudioUIDrawer: public core::Drawer {
|
|||||||
explicit StudioUIDrawer(StudioUI *ui) noexcept: m_ui(ui) {
|
explicit StudioUIDrawer(StudioUI *ui) noexcept: m_ui(ui) {
|
||||||
}
|
}
|
||||||
protected:
|
protected:
|
||||||
void draw(core::Context *ctx) noexcept final {
|
void draw(core::Context*) noexcept final {
|
||||||
m_ui->draw(ctx);
|
m_ui->draw();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static int eventHandler(core::Context *ctx) noexcept {
|
static int eventHandler(core::Context *ctx) noexcept {
|
||||||
auto ui = ctx->customData<StudioUI>();
|
auto ui = ctx->customData<StudioUI>();
|
||||||
ui->update(ctx);
|
ui->update();
|
||||||
return 16;
|
return 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ox::Error run(ox::FileSystem *fs) noexcept {
|
static ox::Error run(ox::UniquePtr<ox::FileSystem> fs) noexcept {
|
||||||
oxRequireM(ctx, core::init(fs));
|
oxRequireM(ctx, core::init(std::move(fs), "NostalgiaStudio"));
|
||||||
core::setWindowTitle(ctx.get(), "Nostalgia Studio");
|
core::setWindowTitle(ctx.get(), "Nostalgia Studio");
|
||||||
core::setEventHandler(ctx.get(), eventHandler);
|
core::setEventHandler(ctx.get(), eventHandler);
|
||||||
StudioUI ui;
|
ox::UniquePtr<StudioUI> ui;
|
||||||
StudioUIDrawer drawer(&ui);
|
try {
|
||||||
ctx->setCustomData(&ui);
|
ui = ox::make_unique<StudioUI>(ctx.get());
|
||||||
|
} catch (ox::Exception &ex) {
|
||||||
|
return ex.toError();
|
||||||
|
}
|
||||||
|
StudioUIDrawer drawer(ui.get());
|
||||||
|
ctx->setCustomData(ui.get());
|
||||||
ctx->drawers.emplace_back(&drawer);
|
ctx->drawers.emplace_back(&drawer);
|
||||||
return core::run(ctx.get());
|
return core::run(ctx.get());
|
||||||
}
|
}
|
||||||
@ -47,10 +52,10 @@ static ox::Error run(int argc, const char **argv) noexcept {
|
|||||||
ox::trace::init();
|
ox::trace::init();
|
||||||
if (argc >= 2) {
|
if (argc >= 2) {
|
||||||
const auto path = argv[1];
|
const auto path = argv[1];
|
||||||
oxRequire(fs, core::loadRomFs(path));
|
oxRequireM(fs, core::loadRomFs(path));
|
||||||
return run(fs.get());
|
return run(std::move(fs));
|
||||||
} else {
|
} else {
|
||||||
return run(nullptr);
|
return run(ox::UniquePtr<ox::FileSystem>(nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <nostalgia/common/bounds.hpp>
|
#include <nostalgia/common/bounds.hpp>
|
||||||
@ -14,6 +16,31 @@
|
|||||||
|
|
||||||
namespace nostalgia {
|
namespace nostalgia {
|
||||||
|
|
||||||
|
static ox::Result<ox::UniquePtr<ProjectTreeModel>>
|
||||||
|
buildProjectTreeModel(ProjectExplorer *explorer, const ox::String &name, const ox::String &path, ProjectTreeModel *parent) noexcept {
|
||||||
|
const auto fs = explorer->romFs();
|
||||||
|
oxRequire(stat, fs->stat(path.c_str()));
|
||||||
|
auto out = ox::make_unique<ProjectTreeModel>(explorer, name, parent);
|
||||||
|
if (stat.fileType == ox::FileType::Directory) {
|
||||||
|
oxRequireM(children, fs->ls(path));
|
||||||
|
std::sort(children.begin(), children.end());
|
||||||
|
ox::Vector<ox::UniquePtr<ProjectTreeModel>> outChildren;
|
||||||
|
for (const auto &childName : children) {
|
||||||
|
if (childName[0] != '.') {
|
||||||
|
const auto childPath = ox::sfmt("{}/{}", path, childName);
|
||||||
|
oxRequireM(child, buildProjectTreeModel(explorer, childName, childPath, out.get()));
|
||||||
|
outChildren.emplace_back(std::move(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out->setChildren(std::move(outChildren));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectExplorer::ProjectExplorer(core::Context *ctx) noexcept {
|
||||||
|
m_ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectExplorer::draw(core::Context *ctx) noexcept {
|
void ProjectExplorer::draw(core::Context *ctx) noexcept {
|
||||||
const auto viewport = ImGui::GetMainViewport();
|
const auto viewport = ImGui::GetMainViewport();
|
||||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 71));
|
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 71));
|
||||||
@ -23,9 +50,7 @@ void ProjectExplorer::draw(core::Context *ctx) noexcept {
|
|||||||
| ImGuiWindowFlags_NoMove
|
| ImGuiWindowFlags_NoMove
|
||||||
| ImGuiWindowFlags_NoScrollbar
|
| ImGuiWindowFlags_NoScrollbar
|
||||||
| ImGuiWindowFlags_NoSavedSettings;
|
| ImGuiWindowFlags_NoSavedSettings;
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
|
||||||
ImGui::Begin("ProjectExplorer", nullptr, flags);
|
ImGui::Begin("ProjectExplorer", nullptr, flags);
|
||||||
ImGui::PopStyleVar();
|
|
||||||
ImGui::SetNextTreeNodeOpen(true);
|
ImGui::SetNextTreeNodeOpen(true);
|
||||||
if (m_treeModel) {
|
if (m_treeModel) {
|
||||||
m_treeModel->draw(ctx);
|
m_treeModel->draw(ctx);
|
||||||
@ -37,5 +62,11 @@ void ProjectExplorer::setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept {
|
|||||||
m_treeModel = std::move(model);
|
m_treeModel = std::move(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ox::Error ProjectExplorer::refreshProjectTreeModel(const ox::String&) noexcept {
|
||||||
|
oxRequireM(model, buildProjectTreeModel(this, "Project", "/", nullptr));
|
||||||
|
setModel(std::move(model));
|
||||||
|
return OxError(0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <ox/event/signal.hpp>
|
||||||
#include <ox/std/memory.hpp>
|
#include <ox/std/memory.hpp>
|
||||||
|
|
||||||
#include "lib/widget.hpp"
|
#include "lib/widget.hpp"
|
||||||
@ -18,10 +19,23 @@ namespace nostalgia {
|
|||||||
class ProjectExplorer: public studio::Widget {
|
class ProjectExplorer: public studio::Widget {
|
||||||
private:
|
private:
|
||||||
ox::UniquePtr<ProjectTreeModel> m_treeModel;
|
ox::UniquePtr<ProjectTreeModel> m_treeModel;
|
||||||
|
core::Context *m_ctx = nullptr;
|
||||||
public:
|
public:
|
||||||
|
explicit ProjectExplorer(core::Context *ctx) noexcept;
|
||||||
|
|
||||||
void draw(core::Context *ctx) noexcept override;
|
void draw(core::Context *ctx) noexcept override;
|
||||||
|
|
||||||
void setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept;
|
void setModel(ox::UniquePtr<ProjectTreeModel> model) noexcept;
|
||||||
|
|
||||||
|
ox::Error refreshProjectTreeModel(const ox::String& = {}) noexcept;
|
||||||
|
|
||||||
|
constexpr ox::FileSystem *romFs() noexcept {
|
||||||
|
return m_ctx->rom.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// slots
|
||||||
|
public:
|
||||||
|
ox::Signal<ox::Error(const ox::String&)> fileChosen;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
@ -8,14 +8,16 @@
|
|||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include "projectexplorer.hpp"
|
||||||
#include "projecttreemodel.hpp"
|
#include "projecttreemodel.hpp"
|
||||||
|
|
||||||
namespace nostalgia {
|
namespace nostalgia {
|
||||||
|
|
||||||
ProjectTreeModel::ProjectTreeModel(const ox::String &name,
|
ProjectTreeModel::ProjectTreeModel(ProjectExplorer *explorer, const ox::String &name,
|
||||||
ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept {
|
ProjectTreeModel *parent) noexcept {
|
||||||
|
m_explorer = explorer;
|
||||||
m_name = name;
|
m_name = name;
|
||||||
m_children = std::move(children);
|
m_parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept {
|
ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept {
|
||||||
@ -32,11 +34,23 @@ void ProjectTreeModel::draw(core::Context *ctx) noexcept {
|
|||||||
}
|
}
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
} else {
|
} else if (auto path = fullPath(); ImGui::TreeNodeEx(ox::sfmt("{}##{}", m_name, path).c_str(), ImGuiTreeNodeFlags_Leaf)) {
|
||||||
if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf)) {
|
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||||
|
m_explorer->fileChosen.emit(path);
|
||||||
|
}
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProjectTreeModel::setChildren(ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept {
|
||||||
|
m_children = std::move(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::String ProjectTreeModel::fullPath() noexcept {
|
||||||
|
if (m_parent) {
|
||||||
|
return m_parent->fullPath() + "/" + m_name;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -17,15 +17,23 @@ namespace nostalgia {
|
|||||||
|
|
||||||
class ProjectTreeModel {
|
class ProjectTreeModel {
|
||||||
private:
|
private:
|
||||||
|
class ProjectExplorer *m_explorer = nullptr;
|
||||||
|
ProjectTreeModel *m_parent = nullptr;
|
||||||
ox::String m_name;
|
ox::String m_name;
|
||||||
ox::Vector<ox::UniquePtr<ProjectTreeModel>> m_children;
|
ox::Vector<ox::UniquePtr<ProjectTreeModel>> m_children;
|
||||||
public:
|
public:
|
||||||
explicit ProjectTreeModel(const ox::String &name,
|
explicit ProjectTreeModel(class ProjectExplorer *explorer, const ox::String &name,
|
||||||
ox::Vector <ox::UniquePtr<ProjectTreeModel>> children) noexcept;
|
ProjectTreeModel *parent = nullptr) noexcept;
|
||||||
|
|
||||||
ProjectTreeModel(ProjectTreeModel &&other) noexcept;
|
ProjectTreeModel(ProjectTreeModel &&other) noexcept;
|
||||||
|
|
||||||
void draw(core::Context *ctx) noexcept;
|
void draw(core::Context *ctx) noexcept;
|
||||||
|
|
||||||
|
void setChildren(ox::Vector<ox::UniquePtr<ProjectTreeModel>> children) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]]
|
||||||
|
ox::String fullPath() noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
#include "lib/editor.hpp"
|
#include "lib/editor.hpp"
|
||||||
#include "lib/filedialog.hpp"
|
#include "lib/filedialog.hpp"
|
||||||
|
#include "lib/module.hpp"
|
||||||
#include "lib/project.hpp"
|
#include "lib/project.hpp"
|
||||||
#include "lib/task.hpp"
|
#include "lib/task.hpp"
|
||||||
#include "lib/undostack.hpp"
|
#include "lib/undostack.hpp"
|
||||||
|
#include "lib/widget.hpp"
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <nostalgia/core/core.hpp>
|
#include <nostalgia/core/core.hpp>
|
||||||
|
|
||||||
#include "lib/configio.hpp"
|
#include "lib/configio.hpp"
|
||||||
|
#include "builtinmodules.hpp"
|
||||||
#include "filedialogmanager.hpp"
|
#include "filedialogmanager.hpp"
|
||||||
#include "studioapp.hpp"
|
#include "studioapp.hpp"
|
||||||
|
|
||||||
@ -20,58 +21,62 @@ struct StudioConfig {
|
|||||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.studio.StudioConfig";
|
static constexpr auto TypeName = "net.drinkingtea.nostalgia.studio.StudioConfig";
|
||||||
static constexpr auto TypeVersion = 1;
|
static constexpr auto TypeVersion = 1;
|
||||||
ox::String projectPath;
|
ox::String projectPath;
|
||||||
|
ox::Vector<ox::String> openFiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
constexpr ox::Error model(T *h, StudioConfig *config) noexcept {
|
constexpr ox::Error model(T *h, StudioConfig *config) noexcept {
|
||||||
|
h->template setTypeInfo<StudioConfig>();
|
||||||
oxReturnError(h->field("project_path", &config->projectPath));
|
oxReturnError(h->field("project_path", &config->projectPath));
|
||||||
|
oxReturnError(h->field("open_files", &config->openFiles));
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
StudioUI::StudioUI() noexcept {
|
StudioUI::StudioUI(core::Context *ctx) noexcept {
|
||||||
m_widgets.push_back(&m_projectExplorer);
|
m_ctx = ctx;
|
||||||
if (const auto [config, err] = studio::readConfig<StudioConfig>(); !err) {
|
m_projectExplorer = new ProjectExplorer(m_ctx);
|
||||||
|
m_widgets.emplace_back(m_projectExplorer);
|
||||||
|
m_projectExplorer->fileChosen.connect(this, &StudioUI::openFile);
|
||||||
|
loadModules();
|
||||||
|
// open project and files
|
||||||
|
const auto [config, err] = studio::readConfig<StudioConfig>(ctx);
|
||||||
|
if (!err) {
|
||||||
oxIgnoreError(openProject(config.projectPath));
|
oxIgnoreError(openProject(config.projectPath));
|
||||||
|
for (const auto &f : config.openFiles) {
|
||||||
|
oxLogError(openFile(f));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (err.msg) {
|
||||||
oxErrf("Could not open studio config file: {}\n", err.msg);
|
oxErrf("Could not open studio config file: {}\n", err.msg);
|
||||||
|
} else {
|
||||||
|
oxErrf("Could not open studio config file: {} ({}:{})\n", err.errCode, err.file, err.line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ox::Result<ox::UniquePtr<ProjectTreeModel>>
|
void StudioUI::update() noexcept {
|
||||||
buildProjectTreeModel(const ox::String &name, const ox::String &path, ox::FileSystem *fs) noexcept {
|
m_taskRunner.update(m_ctx);
|
||||||
ox::Vector<ox::UniquePtr<ProjectTreeModel>> outChildren;
|
|
||||||
oxRequire(stat, fs->stat(path.c_str()));
|
|
||||||
if (stat.fileType == ox::FileType::Directory) {
|
|
||||||
oxRequireM(children, fs->ls(path));
|
|
||||||
//std::sort(children.begin(), children.end());
|
|
||||||
for (const auto &childName : children) {
|
|
||||||
if (childName[0] != '.') {
|
|
||||||
const auto childPath = ox::sfmt("{}/{}", path, childName);
|
|
||||||
oxRequireM(child, buildProjectTreeModel(childName, childPath, fs));
|
|
||||||
outChildren.emplace_back(std::move(child));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ox::make_unique<ProjectTreeModel>(name, std::move(outChildren));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StudioUI::update(core::Context *ctx) noexcept {
|
void StudioUI::draw() noexcept {
|
||||||
m_taskRunner.update(ctx);
|
drawMenu();
|
||||||
}
|
drawToolbar();
|
||||||
|
drawTabs();
|
||||||
void StudioUI::draw(core::Context *ctx) noexcept {
|
|
||||||
drawMenu(ctx);
|
|
||||||
drawToolbar(ctx);
|
|
||||||
for (auto &w : m_widgets) {
|
for (auto &w : m_widgets) {
|
||||||
w->draw(ctx);
|
w->draw(m_ctx);
|
||||||
}
|
}
|
||||||
|
constexpr auto aboutFlags = ImGuiWindowFlags_NoCollapse
|
||||||
|
| ImGuiWindowFlags_NoResize
|
||||||
|
| ImGuiWindowFlags_Modal;
|
||||||
if (m_aboutEnabled &&
|
if (m_aboutEnabled &&
|
||||||
ImGui::Begin("About", &m_aboutEnabled, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize)) {
|
ImGui::Begin("About", &m_aboutEnabled, aboutFlags)) {
|
||||||
|
ImGui::SetWindowSize(ImVec2(400, 250));
|
||||||
|
ImGui::Text("Nostalgia Studio - dev build");
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StudioUI::drawMenu(core::Context *ctx) noexcept {
|
void StudioUI::drawMenu() noexcept {
|
||||||
if (ImGui::BeginMainMenuBar()) {
|
if (ImGui::BeginMainMenuBar()) {
|
||||||
if (ImGui::BeginMenu("File")) {
|
if (ImGui::BeginMenu("File")) {
|
||||||
if (ImGui::MenuItem("New...", "Ctrl+N")) {
|
if (ImGui::MenuItem("New...", "Ctrl+N")) {
|
||||||
@ -82,7 +87,7 @@ void StudioUI::drawMenu(core::Context *ctx) noexcept {
|
|||||||
if (ImGui::MenuItem("Save", "Ctrl+S", false, m_saveEnabled)) {
|
if (ImGui::MenuItem("Save", "Ctrl+S", false, m_saveEnabled)) {
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
|
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
|
||||||
core::shutdown(ctx);
|
core::shutdown(m_ctx);
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
@ -99,45 +104,134 @@ void StudioUI::drawMenu(core::Context *ctx) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StudioUI::drawToolbar(core::Context*) noexcept {
|
void StudioUI::drawToolbar() noexcept {
|
||||||
constexpr auto BtnWidth = 96;
|
constexpr auto BtnWidth = 96;
|
||||||
constexpr auto BtnHeight = 32;
|
constexpr auto BtnHeight = 32;
|
||||||
const auto viewport = ImGui::GetMainViewport();
|
const auto viewport = ImGui::GetMainViewport();
|
||||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + 20));
|
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + 1, viewport->Pos.y + 21));
|
||||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, 48));
|
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 1, 48));
|
||||||
const auto flags = ImGuiWindowFlags_NoTitleBar
|
const auto flags = ImGuiWindowFlags_NoTitleBar
|
||||||
| ImGuiWindowFlags_NoResize
|
| ImGuiWindowFlags_NoResize
|
||||||
| ImGuiWindowFlags_NoMove
|
| ImGuiWindowFlags_NoMove
|
||||||
| ImGuiWindowFlags_NoScrollbar
|
| ImGuiWindowFlags_NoScrollbar
|
||||||
| ImGuiWindowFlags_NoSavedSettings;
|
| ImGuiWindowFlags_NoSavedSettings;
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
ImGui::Begin("MainToolbar##MainWindow", nullptr, flags);
|
||||||
ImGui::Begin("MainToolbar", nullptr, flags);
|
|
||||||
ImGui::PopStyleVar();
|
|
||||||
{
|
{
|
||||||
ImGui::Button("New##MainToolbar", ImVec2(BtnWidth, BtnHeight));
|
ImGui::Button("New##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Open Project##MainToolbar", ImVec2(BtnWidth, BtnHeight))) {
|
if (ImGui::Button("Open Project##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight))) {
|
||||||
m_taskRunner.add(new FileDialogManager(this, &StudioUI::openProject));
|
m_taskRunner.add(new FileDialogManager(this, &StudioUI::openProject));
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Button("Save##MainToolbar", ImVec2(BtnWidth, BtnHeight));
|
ImGui::Button("Save##MainToolbar##MainWindow", ImVec2(BtnWidth, BtnHeight));
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Error StudioUI::openProject(const ox::String &path) noexcept {
|
void StudioUI::drawTabs() noexcept {
|
||||||
m_project = ox::make_unique<studio::Project>(path);
|
const auto viewport = ImGui::GetMainViewport();
|
||||||
m_project->fileAdded.connect(this, &StudioUI::refreshProjectTreeModel);
|
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x + 316, viewport->Pos.y + 71));
|
||||||
m_project->fileDeleted.connect(this, &StudioUI::refreshProjectTreeModel);
|
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x - 316, viewport->Size.y - 71));
|
||||||
oxReturnError(studio::editConfig<StudioConfig>([path](StudioConfig *config) {
|
constexpr auto windowFlags = ImGuiWindowFlags_NoTitleBar
|
||||||
config->projectPath = path;
|
| ImGuiWindowFlags_NoResize
|
||||||
}));
|
| ImGuiWindowFlags_NoMove
|
||||||
return refreshProjectTreeModel();
|
| ImGuiWindowFlags_NoScrollbar
|
||||||
|
| ImGuiWindowFlags_NoSavedSettings;
|
||||||
|
ImGui::Begin("TabWindow##MainWindow", nullptr, windowFlags);
|
||||||
|
constexpr auto tabBarFlags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_TabListPopupButton;
|
||||||
|
if (ImGui::BeginTabBar("TabBar##TabWindow##MainWindow", tabBarFlags)) {
|
||||||
|
for (auto &e : m_editors) {
|
||||||
|
bool open = true;
|
||||||
|
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open)) {
|
||||||
|
e->draw(m_ctx);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
if (!open) {
|
||||||
|
e->close();
|
||||||
|
try {
|
||||||
|
oxThrowError(m_editors.erase(e));
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
oxErrf("Editor tab deletion failed: {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
ox::Error StudioUI::refreshProjectTreeModel(const ox::String&) noexcept {
|
void StudioUI::loadEditorMaker(const studio::EditorMaker &editorMaker) noexcept {
|
||||||
oxRequireM(model, buildProjectTreeModel("Project", "/", m_project->romFs()));
|
for (auto &ext : editorMaker.fileTypes) {
|
||||||
m_projectExplorer.setModel(std::move(model));
|
m_editorMakers[ext] = editorMaker.make;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StudioUI::loadModule(studio::Module *module) noexcept {
|
||||||
|
for (auto &editorMaker : module->editors(m_ctx)) {
|
||||||
|
loadEditorMaker(editorMaker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StudioUI::loadModules() noexcept {
|
||||||
|
//for (auto &moduleMaker : BuiltinModules) {
|
||||||
|
// const auto module = moduleMaker();
|
||||||
|
// loadModule(module.get());
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error StudioUI::openProject(const ox::String &path) noexcept {
|
||||||
|
oxRequireM(fs, core::loadRomFs(path.c_str()));
|
||||||
|
m_ctx->rom = std::move(fs);
|
||||||
|
core::setWindowTitle(m_ctx, ox::sfmt("Nostalgia Studio - {}", path).c_str());
|
||||||
|
m_project = ox::make_unique<studio::Project>(m_ctx->rom.get(), path);
|
||||||
|
m_project->fileAdded.connect(m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
||||||
|
m_project->fileDeleted.connect(m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
|
||||||
|
studio::editConfig<StudioConfig>(m_ctx, [&](StudioConfig *config) {
|
||||||
|
config->projectPath = path;
|
||||||
|
});
|
||||||
|
return m_projectExplorer->refreshProjectTreeModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error StudioUI::openFile(const ox::String &path) noexcept {
|
||||||
|
if (m_openFiles.contains(path)) {
|
||||||
|
return OxError(0);
|
||||||
|
}
|
||||||
|
// find file extension
|
||||||
|
const auto extStart = std::find(path.crbegin(), path.crend(), '.').offset();
|
||||||
|
if (!extStart) {
|
||||||
|
return OxError(1, "Cannot open a file without valid extension.");
|
||||||
|
}
|
||||||
|
const auto ext = path.substr(extStart + 1);
|
||||||
|
// create Editor
|
||||||
|
if (!m_editorMakers.contains(ext)) {
|
||||||
|
return OxError(1, "There is no editor for this file extension");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
auto editor = m_editorMakers[ext](path);
|
||||||
|
editor->closed.connect(this, &StudioUI::closeFile);
|
||||||
|
m_editors.emplace_back(editor);
|
||||||
|
} catch (const ox::Exception &ex) {
|
||||||
|
return ex.toError();
|
||||||
|
}
|
||||||
|
m_openFiles.emplace_back(path);
|
||||||
|
// save to config
|
||||||
|
studio::editConfig<StudioConfig>(m_ctx, [&](StudioConfig *config) {
|
||||||
|
if (!config->openFiles.contains((path))) {
|
||||||
|
config->openFiles.emplace_back(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return OxError(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error StudioUI::closeFile(const ox::String &path) noexcept {
|
||||||
|
if (!m_openFiles.contains(path)) {
|
||||||
|
return OxError(0);
|
||||||
|
}
|
||||||
|
oxIgnoreError(m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path)));
|
||||||
|
// save to config
|
||||||
|
studio::editConfig<StudioConfig>(m_ctx, [&](StudioConfig *config) {
|
||||||
|
oxIgnoreError(config->openFiles.erase(std::remove(config->openFiles.begin(), config->openFiles.end(), path)));
|
||||||
|
});
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include <ox/std/memory.hpp>
|
#include <ox/std/memory.hpp>
|
||||||
#include <ox/std/string.hpp>
|
#include <ox/std/string.hpp>
|
||||||
|
|
||||||
|
#include "lib/module.hpp"
|
||||||
#include "lib/project.hpp"
|
#include "lib/project.hpp"
|
||||||
#include "lib/task.hpp"
|
#include "lib/task.hpp"
|
||||||
#include "projectexplorer.hpp"
|
#include "projectexplorer.hpp"
|
||||||
@ -25,28 +26,41 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
private:
|
private:
|
||||||
ox::UniquePtr<studio::Project> m_project;
|
ox::UniquePtr<studio::Project> m_project;
|
||||||
studio::TaskRunner m_taskRunner;
|
studio::TaskRunner m_taskRunner;
|
||||||
ox::Vector<studio::Widget*> m_widgets;
|
core::Context *m_ctx = nullptr;
|
||||||
ox::UniquePtr<ProjectTreeModel> m_treeModel;
|
ox::Vector<ox::UniquePtr<studio::Editor>> m_editors;
|
||||||
ProjectExplorer m_projectExplorer;
|
ox::Vector<ox::UniquePtr<studio::Widget>> m_widgets;
|
||||||
|
ox::HashMap<ox::String, studio::EditorMaker::Func> m_editorMakers;
|
||||||
|
ProjectExplorer *m_projectExplorer = nullptr;
|
||||||
|
ox::Vector<ox::String> m_openFiles;
|
||||||
bool m_saveEnabled = false;
|
bool m_saveEnabled = false;
|
||||||
bool m_aboutEnabled = false;
|
bool m_aboutEnabled = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StudioUI() noexcept;
|
explicit StudioUI(core::Context *ctx) noexcept;
|
||||||
|
|
||||||
void update(core::Context *ctx) noexcept;
|
void update() noexcept;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void draw(core::Context *ctx) noexcept;
|
void draw() noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawMenu(core::Context *ctx) noexcept;
|
void drawMenu() noexcept;
|
||||||
|
|
||||||
void drawToolbar(core::Context *ctx) noexcept;
|
void drawToolbar() noexcept;
|
||||||
|
|
||||||
|
void drawTabs() noexcept;
|
||||||
|
|
||||||
|
void loadEditorMaker(const studio::EditorMaker &editorMaker) noexcept;
|
||||||
|
|
||||||
|
void loadModule(studio::Module *module) noexcept;
|
||||||
|
|
||||||
|
void loadModules() noexcept;
|
||||||
|
|
||||||
ox::Error openProject(const ox::String &path) noexcept;
|
ox::Error openProject(const ox::String &path) noexcept;
|
||||||
|
|
||||||
ox::Error refreshProjectTreeModel(const ox::String& = {}) noexcept;
|
ox::Error openFile(const ox::String &path) noexcept;
|
||||||
|
|
||||||
|
ox::Error closeFile(const ox::String &path) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user