Squashed 'deps/nostalgia/' changes from e78c4050..857587c1
857587c1 [studio] Cleanup eb3d53c9 [studio] Cleanup 14d58f3f [studio] Fix Navigation shortcuts for non-Mac systems 5f239790 [studio,nostalgia/gfx/studio/tilesheet] Fix copy/cut/paste enablement when there is no selection 58e0ecb4 [studio] Make FilePickerPopup accept on double click of a file 8838bf42 [studio] Fix to properly copy file that has the same name as deleted file bddc544d [nostalgia] Update release notes a9437191 [studio,turbine] Add support for mouse back/forward buttons 9d8da7cc [ox/std] Make strToInt return error for empty string 394b568e [studio] Add Back/Forward navigation 78e9f70d [nostalgia] Update release notes 12e5623f [ox/logconn] Add exception handling for logger thread cfdfb0a8 [studio] Fix file deletion to close file even if not active 56e66530 [studio] Cleanup 7415ce4b [nostalgia/gfx/studio] Cleanup 05f42150 [olympic] Add new loc command to Makefile 8ea2bc69 [nostalgia] Update release notes c7809241 [studio] Add [DEBUG] tag to About in debug builds 8c538560 [nostalgia/gfx/studio/palette] Make RGB key shortcuts work when color channel inputs are focused c3e75bdb [nostalgia/gfx/studio/tilesheet] Cleanup git-subtree-dir: deps/nostalgia git-subtree-split: 857587c18b4695eacd31457e3c30b4971b4e46e8
This commit is contained in:
22
Makefile
22
Makefile
@ -52,3 +52,25 @@ configure-gba:
|
||||
.PHONY: configure-gba-debug
|
||||
configure-gba-debug:
|
||||
${BC_CMD_SETUP_BUILD} --toolchain=deps/gbabuildcore/cmake/modules/GBA.cmake --target=gba --current_build=0 --build_type=debug --build_root=${BC_VAR_BUILD_PATH}
|
||||
|
||||
.PHONY: loc
|
||||
loc:
|
||||
${BC_PY3} util/scripts/loc.py \
|
||||
--search-dirs \
|
||||
src \
|
||||
deps/ox/src \
|
||||
deps/buildcore \
|
||||
deps/gbabuildcore \
|
||||
deps/glutils \
|
||||
deps/teagba \
|
||||
--include-exts \
|
||||
.cpp \
|
||||
.hpp \
|
||||
.py \
|
||||
.s \
|
||||
.cmake \
|
||||
--exclude-paths \
|
||||
deps/teagba/src/gba_crt0.s \
|
||||
src/olympic/studio/applib/src/font.cpp \
|
||||
src/olympic/studio/applib/src/font.hpp \
|
||||
src/nostalgia/studio/icondata.cpp
|
||||
|
31
deps/ox/src/ox/logconn/logconn.cpp
vendored
31
deps/ox/src/ox/logconn/logconn.cpp
vendored
@ -91,23 +91,28 @@ ox::Error LoggerConn::sendInit(const InitTraceMsg &msg) noexcept {
|
||||
}
|
||||
|
||||
void LoggerConn::msgSend() noexcept {
|
||||
while (true) {
|
||||
std::unique_lock lk(m_waitMut);
|
||||
m_waitCond.wait(lk);
|
||||
if (!m_running) {
|
||||
break;
|
||||
}
|
||||
std::lock_guard const buffLk(m_buffMut);
|
||||
try {
|
||||
while (true) {
|
||||
Array<char, units::KB> tmp;
|
||||
const auto read = m_buff.read(tmp.data(), tmp.size());
|
||||
if (!read) {
|
||||
std::unique_lock lk(m_waitMut);
|
||||
m_waitCond.wait(lk);
|
||||
if (!m_running) {
|
||||
break;
|
||||
}
|
||||
oxAssert(read <= tmp.size(), "logger trying to read too much data");
|
||||
//std::printf("LoggerConn: sending %lu bytes\n", read);
|
||||
std::ignore = send(tmp.data(), read);
|
||||
std::lock_guard const buffLk(m_buffMut);
|
||||
while (true) {
|
||||
Array<char, units::KB> tmp;
|
||||
const auto read = m_buff.read(tmp.data(), tmp.size());
|
||||
if (!read) {
|
||||
break;
|
||||
}
|
||||
oxAssert(read <= tmp.size(), "logger trying to read too much data");
|
||||
//std::printf("LoggerConn: sending %lu bytes\n", read);
|
||||
std::ignore = send(tmp.data(), read);
|
||||
}
|
||||
}
|
||||
} catch (std::exception const &e) {
|
||||
oxErrf("Exception in logger thread: {}\n", e.what());
|
||||
oxAssert(false, "logger thread exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
5
deps/ox/src/ox/std/stringview.hpp
vendored
5
deps/ox/src/ox/std/stringview.hpp
vendored
@ -104,13 +104,16 @@ constexpr ox::Result<int> strToInt(StringViewCR str) noexcept {
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
int total = 0;
|
||||
int multiplier = 1;
|
||||
if (str.len() == 0) [[unlikely]] {
|
||||
return Error{1, "Empty string passed to strToInt"};
|
||||
}
|
||||
for (auto i = static_cast<int64_t>(str.len()) - 1; i != -1; --i) {
|
||||
auto s = static_cast<std::size_t>(i);
|
||||
if (str[s] >= '0' && str[s] <= '9') {
|
||||
total += (str[s] - '0') * multiplier;
|
||||
multiplier *= 10;
|
||||
} else {
|
||||
return ox::Error(1);
|
||||
return ox::Error{1};
|
||||
}
|
||||
}
|
||||
return total;
|
||||
|
@ -1,8 +1,16 @@
|
||||
# d2025.06.0
|
||||
|
||||
* Add ability to remember recent projects in config
|
||||
* Add navigation support (back and forward)
|
||||
* Fix file deletion to close file even if not active
|
||||
* Fix file copy to work when creating a copy with the name of a previously
|
||||
deleted file
|
||||
* Fix copy/cut/paste enablement when there is no selection
|
||||
* Make file picker popup accept on double click of a file
|
||||
* PaletteEditor: Add RGB key shortcuts for focusing color channels
|
||||
* PaletteEditor: Add color preview to color editor
|
||||
* PaletteEditor: Make RGB key shortcuts work when color channel inputs are
|
||||
focused
|
||||
|
||||
# d2025.05.2
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
namespace nostalgia::gfx {
|
||||
|
||||
RemovePageCommand::RemovePageCommand(Palette &pal, size_t idx) noexcept:
|
||||
RemovePageCommand::RemovePageCommand(Palette &pal, size_t const idx) noexcept:
|
||||
m_pal(pal),
|
||||
m_idx(idx) {}
|
||||
|
||||
|
@ -9,13 +9,13 @@ namespace nostalgia::gfx {
|
||||
|
||||
UpdateColorCommand::UpdateColorCommand(
|
||||
Palette &pal,
|
||||
size_t page,
|
||||
size_t idx,
|
||||
Color16 newColor):
|
||||
m_pal(pal),
|
||||
m_page(page),
|
||||
m_idx(idx),
|
||||
m_altColor(newColor) {
|
||||
size_t const page,
|
||||
size_t const idx,
|
||||
Color16 const newColor):
|
||||
m_pal{pal},
|
||||
m_page{page},
|
||||
m_idx{idx},
|
||||
m_altColor{newColor} {
|
||||
if (color(m_pal, m_page, m_idx) == newColor) {
|
||||
throw studio::NoChangesException();
|
||||
}
|
||||
|
@ -302,11 +302,12 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
|
||||
int const a = alpha16(c);
|
||||
auto const newName = ig::InputText<50>(
|
||||
"Name", m_pal.colorNames[m_selectedColorRow]);
|
||||
bool inputFocused = ImGui::IsItemFocused();
|
||||
auto const nameInputFocused = ImGui::IsItemFocused();
|
||||
ImGui::Separator();
|
||||
colorInput("Red", r, inputFocused, FocusCmd::Red);
|
||||
colorInput("Green", g, inputFocused, FocusCmd::Green);
|
||||
colorInput("Blue", b, inputFocused, FocusCmd::Blue);
|
||||
bool colorInFocused{};
|
||||
colorInput("Red", r, colorInFocused, FocusCmd::Red);
|
||||
colorInput("Green", g, colorInFocused, FocusCmd::Green);
|
||||
colorInput("Blue", b, colorInFocused, FocusCmd::Blue);
|
||||
// color preview
|
||||
{
|
||||
ImGui::PushStyleColor(
|
||||
@ -320,20 +321,24 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
|
||||
std::ignore = pushCommand<ApplyColorAllPagesCommand>(
|
||||
m_pal, m_page, m_selectedColorRow);
|
||||
}
|
||||
if (ig::mainWinHasFocus() && !inputFocused && !ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
|
||||
if (ig::mainWinHasFocus() && !ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
|
||||
if (!ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
|
||||
numShortcuts(m_selectedColorRow, largestPage(m_pal));
|
||||
if (!nameInputFocused && !colorInFocused) {
|
||||
numShortcuts(m_selectedColorRow, largestPage(m_pal));
|
||||
}
|
||||
} else {
|
||||
numShortcuts(m_page, m_pal.pages.size());
|
||||
}
|
||||
if (ImGui::IsKeyDown(ImGuiKey_R)) {
|
||||
m_focusCmd = FocusCmd::Red;
|
||||
}
|
||||
if (ImGui::IsKeyDown(ImGuiKey_G)) {
|
||||
m_focusCmd = FocusCmd::Green;
|
||||
}
|
||||
if (ImGui::IsKeyDown(ImGuiKey_B)) {
|
||||
m_focusCmd = FocusCmd::Blue;
|
||||
if (!nameInputFocused) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_R)) {
|
||||
m_focusCmd = FocusCmd::Red;
|
||||
}
|
||||
if (ImGui::IsKeyDown(ImGuiKey_G)) {
|
||||
m_focusCmd = FocusCmd::Green;
|
||||
}
|
||||
if (ImGui::IsKeyDown(ImGuiKey_B)) {
|
||||
m_focusCmd = FocusCmd::Blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const newColor = color16(r, g, b, a);
|
||||
|
@ -13,20 +13,19 @@ DeleteTilesCommand::DeleteTilesCommand(
|
||||
TileSheet::SubSheetIdx idx,
|
||||
std::size_t const tileIdx,
|
||||
std::size_t const tileCnt) noexcept:
|
||||
m_img(img),
|
||||
m_idx(std::move(idx)) {
|
||||
constexpr unsigned bytesPerTile = PixelsPerTile;
|
||||
m_deletePos = tileIdx * bytesPerTile;
|
||||
m_deleteSz = tileCnt * bytesPerTile;
|
||||
m_deletedPixels.resize(m_deleteSz);
|
||||
// copy pixels to be erased
|
||||
{
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto dst = m_deletedPixels.begin();
|
||||
auto src = s.pixels.begin() + m_deletePos;
|
||||
ox::copy_n(src, m_deleteSz, dst);
|
||||
}
|
||||
}
|
||||
m_img{img},
|
||||
m_idx{std::move(idx)},
|
||||
m_deletePos{tileIdx * PixelsPerTile},
|
||||
m_deleteSz{tileCnt * PixelsPerTile},
|
||||
m_deletedPixels{[this] {
|
||||
ox::Vector<uint8_t> deletedPixels(m_deleteSz);
|
||||
// copy pixels to be erased
|
||||
auto const &s = getSubSheet(m_img, m_idx);
|
||||
auto const dst = deletedPixels.begin();
|
||||
auto const src = s.pixels.begin() + m_deletePos;
|
||||
ox::copy_n(src, m_deleteSz, dst);
|
||||
return deletedPixels;
|
||||
}()} {}
|
||||
|
||||
ox::Error DeleteTilesCommand::redo() noexcept {
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
|
@ -11,10 +11,10 @@ namespace nostalgia::gfx {
|
||||
class DeleteTilesCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
std::size_t m_deletePos = 0;
|
||||
std::size_t m_deleteSz = 0;
|
||||
ox::Vector<uint8_t> m_deletedPixels = {};
|
||||
TileSheet::SubSheetIdx const m_idx;
|
||||
std::size_t const m_deletePos = 0;
|
||||
std::size_t const m_deleteSz = 0;
|
||||
ox::Vector<uint8_t> const m_deletedPixels;
|
||||
|
||||
public:
|
||||
DeleteTilesCommand(
|
||||
|
@ -62,11 +62,11 @@ constexpr void iterateLine(ox::Point const &a, ox::Point const &b, auto const &f
|
||||
DrawCommand::DrawCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
std::size_t idx,
|
||||
std::size_t const idx,
|
||||
int const palIdx) noexcept:
|
||||
m_img(img),
|
||||
m_subSheetIdx(std::move(subSheetIdx)),
|
||||
m_palIdx(palIdx) {
|
||||
m_img{img},
|
||||
m_subSheetIdx{std::move(subSheetIdx)},
|
||||
m_palIdx{palIdx} {
|
||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(subsheet, idx));
|
||||
}
|
||||
|
@ -12,19 +12,19 @@ InsertTilesCommand::InsertTilesCommand(
|
||||
std::size_t const tileIdx,
|
||||
std::size_t const tileCnt) noexcept:
|
||||
m_img{img},
|
||||
m_idx{std::move(idx)} {
|
||||
m_insertPos = tileIdx * PixelsPerTile;
|
||||
m_insertCnt = tileCnt * PixelsPerTile;
|
||||
m_deletedPixels.resize(m_insertCnt);
|
||||
// copy pixels to be erased
|
||||
{
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto const dst = m_deletedPixels.begin();
|
||||
auto const src = p.begin() + p.size() - m_insertCnt;
|
||||
ox::copy_n(src, m_insertCnt, dst);
|
||||
}
|
||||
}
|
||||
m_idx{std::move(idx)},
|
||||
m_insertPos{tileIdx * PixelsPerTile},
|
||||
m_insertCnt{tileCnt * PixelsPerTile},
|
||||
m_insertedPixels{[this] {
|
||||
ox::Vector<uint8_t> insertedPixels(m_insertCnt);
|
||||
// copy pixels to be erased
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto const dst = insertedPixels.begin();
|
||||
auto const src = p.begin() + p.size() - m_insertCnt;
|
||||
ox::copy_n(src, m_insertCnt, dst);
|
||||
return insertedPixels;
|
||||
}()} {}
|
||||
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
|
||||
@ -52,7 +52,7 @@ ox::Error InsertTilesCommand::undo() noexcept {
|
||||
auto const src = &p[srcIdx];
|
||||
ox::memmove(dst1, src, sz);
|
||||
}
|
||||
ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
||||
ox::memcpy(dst2, m_insertedPixels.data(), m_insertedPixels.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -11,10 +11,10 @@ namespace nostalgia::gfx {
|
||||
class InsertTilesCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
std::size_t m_insertPos = 0;
|
||||
std::size_t m_insertCnt = 0;
|
||||
ox::Vector<uint8_t> m_deletedPixels = {};
|
||||
TileSheet::SubSheetIdx const m_idx;
|
||||
std::size_t const m_insertPos{};
|
||||
std::size_t const m_insertCnt{};
|
||||
ox::Vector<uint8_t> const m_insertedPixels;
|
||||
|
||||
public:
|
||||
InsertTilesCommand(
|
||||
|
@ -15,7 +15,6 @@ UpdateSubSheetCommand::UpdateSubSheetCommand(
|
||||
m_img{img},
|
||||
m_idx{std::move(idx)},
|
||||
m_sheet{getSubSheet(m_img, m_idx)} {
|
||||
m_sheet = getSubSheet(m_img, m_idx);
|
||||
m_sheet.name = std::move(name);
|
||||
OX_THROW_ERROR(resizeSubsheet(m_sheet, {cols, rows}));
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace nostalgia::gfx {
|
||||
class UpdateSubSheetCommand: public TileSheetCommand {
|
||||
private:
|
||||
TileSheet &m_img;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
TileSheet::SubSheetIdx const m_idx;
|
||||
TileSheet::SubSheet m_sheet;
|
||||
|
||||
public:
|
||||
|
@ -192,6 +192,8 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const do
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::draw(studio::Context&) noexcept {
|
||||
setCopyEnabled(m_model.hasSelection());
|
||||
setCutEnabled(m_model.hasSelection());
|
||||
if (ig::mainWinHasFocus() && m_tool == TileSheetTool::Select) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_A)) {
|
||||
|
@ -84,9 +84,9 @@ void TileSheetEditorModel::cut() {
|
||||
auto const pt1 = m_selection->a;
|
||||
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
|
||||
turbine::setClipboardObject(m_tctx, std::move(cb));
|
||||
if (auto const cmd = ox::makeCatch<CutPasteCommand>(
|
||||
if (auto cmd = ox::make_unique_catch<CutPasteCommand>(
|
||||
CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb); cmd.ok()) {
|
||||
std::ignore = pushCommand(cmd.value);
|
||||
std::ignore = pushCommand(std::move(cmd.value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,9 +125,9 @@ void TileSheetEditorModel::paste() {
|
||||
auto const&s = activeSubSheet();
|
||||
auto const pt1 = m_selection->a;
|
||||
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
|
||||
if (auto const cmd = ox::makeCatch<CutPasteCommand>(
|
||||
if (auto cmd = ox::make_unique_catch<CutPasteCommand>(
|
||||
CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb); cmd.value) {
|
||||
std::ignore = pushCommand(cmd.value);
|
||||
std::ignore = pushCommand(std::move(cmd.value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ ox::String const &TileSheetEditorModel::palPath() const & noexcept {
|
||||
|
||||
ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept {
|
||||
OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path));
|
||||
std::ignore = pushCommand(ox::make<PaletteChangeCommand>(
|
||||
std::ignore = pushCommand(ox::make_unique<PaletteChangeCommand>(
|
||||
activeSubSheetIdx(), m_img, uuid->toString()));
|
||||
return {};
|
||||
}
|
||||
@ -169,7 +169,7 @@ void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const pa
|
||||
if (m_ongoingDrawCommand) {
|
||||
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
|
||||
} else if (activeSubSheet.pixels[idx] != palIdx) {
|
||||
std::ignore = pushCommand(ox::make<DrawCommand>(
|
||||
std::ignore = pushCommand(ox::make_unique<DrawCommand>(
|
||||
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
|
||||
}
|
||||
}
|
||||
@ -188,7 +188,7 @@ void TileSheetEditorModel::drawLineCommand(ox::Point const &pt, std::size_t cons
|
||||
m_ongoingDrawCommand->lineUpdate(m_lineStartPt, pt);
|
||||
m_updated = true;
|
||||
} else {
|
||||
std::ignore = pushCommand(ox::make<DrawCommand>(
|
||||
std::ignore = pushCommand(ox::make_unique<DrawCommand>(
|
||||
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
|
||||
m_lineStartPt = pt;
|
||||
}
|
||||
@ -203,27 +203,27 @@ void TileSheetEditorModel::endDrawCommand() noexcept {
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const &parentIdx) noexcept {
|
||||
std::ignore = pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
|
||||
std::ignore = pushCommand(ox::make_unique<AddSubSheetCommand>(m_img, parentIdx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const &idx) noexcept {
|
||||
std::ignore = pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
|
||||
std::ignore = pushCommand(ox::make_unique<RmSubSheetCommand>(m_img, idx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::insertTiles(
|
||||
TileSheet::SubSheetIdx const &idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
|
||||
std::ignore = pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
std::ignore = pushCommand(ox::make_unique<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::deleteTiles(
|
||||
TileSheet::SubSheetIdx const &idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
|
||||
std::ignore = pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
std::ignore = pushCommand(ox::make_unique<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::updateSubsheet(
|
||||
TileSheet::SubSheetIdx const &idx, ox::StringViewCR name, int const cols, int const rows) noexcept {
|
||||
OX_REQUIRE(cmd, ox::makeCatch<UpdateSubSheetCommand>(m_img, idx, name, cols, rows));
|
||||
std::ignore = pushCommand(cmd);
|
||||
OX_REQUIRE_M(cmd, ox::make_unique_catch<UpdateSubSheetCommand>(m_img, idx, name, cols, rows));
|
||||
std::ignore = pushCommand(std::move(cmd));
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -255,29 +255,29 @@ void TileSheetEditorModel::fill(ox::Point const &pt, uint8_t const palIdx) noexc
|
||||
if (m_ongoingDrawCommand) {
|
||||
m_updated = m_updated || m_ongoingDrawCommand->append(idxList);
|
||||
} else if (getPixel(activeSubSheet, pt) != palIdx) {
|
||||
std::ignore = pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
||||
std::ignore = pushCommand(ox::make_unique<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::rotateLeft() noexcept {
|
||||
auto &ss = activeSubSheet();
|
||||
auto const &ss = activeSubSheet();
|
||||
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
|
||||
if (m_selection) {
|
||||
pt1 = m_selection->a;
|
||||
pt2 = m_selection->b;
|
||||
}
|
||||
return pushCommand(ox::make<RotateCommand>(
|
||||
return pushCommand(ox::make_unique<RotateCommand>(
|
||||
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Left));
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::rotateRight() noexcept {
|
||||
auto &ss = activeSubSheet();
|
||||
auto const &ss = activeSubSheet();
|
||||
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
|
||||
if (m_selection) {
|
||||
pt1 = m_selection->a;
|
||||
pt2 = m_selection->b;
|
||||
}
|
||||
return pushCommand(ox::make<RotateCommand>(
|
||||
return pushCommand(ox::make_unique<RotateCommand>(
|
||||
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Right));
|
||||
}
|
||||
|
||||
@ -315,6 +315,10 @@ void TileSheetEditorModel::clearSelection() noexcept {
|
||||
m_selection.reset();
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::hasSelection() const noexcept {
|
||||
return m_selection.has_value();
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::updated() const noexcept {
|
||||
return m_updated;
|
||||
}
|
||||
@ -365,7 +369,7 @@ ox::Error TileSheetEditorModel::flipX() noexcept {
|
||||
a = m_selection->a;
|
||||
b = m_selection->b;
|
||||
}
|
||||
return pushCommand(ox::make<FlipXCommand>(m_img, m_activeSubsSheetIdx, a, b));
|
||||
return pushCommand(ox::make_unique<FlipXCommand>(m_img, m_activeSubsSheetIdx, a, b));
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::flipY() noexcept {
|
||||
@ -376,7 +380,7 @@ ox::Error TileSheetEditorModel::flipY() noexcept {
|
||||
a = m_selection->a;
|
||||
b = m_selection->b;
|
||||
}
|
||||
return pushCommand(ox::make<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
|
||||
return pushCommand(ox::make_unique<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::rotateEligible() const noexcept {
|
||||
@ -390,7 +394,7 @@ bool TileSheetEditorModel::rotateEligible() const noexcept {
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept {
|
||||
return pushCommand(ox::make<MoveSubSheetCommand>(m_img, std::move(src), std::move(dst)));
|
||||
return pushCommand(ox::make_unique<MoveSubSheetCommand>(m_img, std::move(src), std::move(dst)));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::getFillPixels(
|
||||
@ -437,9 +441,9 @@ void TileSheetEditorModel::setPalPath() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
|
||||
std::ignore = m_undoStack.push(ox::UPtr{cmd});
|
||||
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
|
||||
ox::Error TileSheetEditorModel::pushCommand(ox::UPtr<studio::UndoCommand> &&cmd) noexcept {
|
||||
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd.get());
|
||||
std::ignore = m_undoStack.push(std::move(cmd));
|
||||
m_updated = true;
|
||||
return {};
|
||||
}
|
||||
|
@ -118,6 +118,9 @@ class TileSheetEditorModel final: public ox::SignalHandler {
|
||||
|
||||
void clearSelection() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool hasSelection() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool updated() const noexcept;
|
||||
|
||||
@ -152,7 +155,7 @@ class TileSheetEditorModel final: public ox::SignalHandler {
|
||||
|
||||
void setPalPath() noexcept;
|
||||
|
||||
ox::Error pushCommand(studio::UndoCommand *cmd) noexcept;
|
||||
ox::Error pushCommand(ox::UPtr<studio::UndoCommand> &&cmd) noexcept;
|
||||
|
||||
ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const &id) noexcept;
|
||||
|
||||
|
@ -28,10 +28,15 @@ class StudioUIDrawer: public turbine::gl::Drawer {
|
||||
};
|
||||
|
||||
static void keyEventHandler(turbine::Context &ctx, turbine::Key key, bool down) noexcept {
|
||||
auto sctx = turbine::applicationData<studio::Context>(ctx);
|
||||
auto const sctx = turbine::applicationData<studio::Context>(ctx);
|
||||
sctx->ui.handleKeyEvent(key, down);
|
||||
}
|
||||
|
||||
static void mouseButtonEventHandler(turbine::Context &ctx, int const btn, bool const down) noexcept {
|
||||
auto const sctx = turbine::applicationData<studio::Context>(ctx);
|
||||
sctx->ui.handleMouseButtonEvent(btn, down);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<ox::SpanView<uint8_t>> WindowIcons() noexcept;
|
||||
|
||||
@ -43,6 +48,7 @@ static ox::Error runApp(
|
||||
oxLogError(turbine::setWindowIcon(*ctx, WindowIcons()));
|
||||
turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName);
|
||||
turbine::setKeyEventHandler(*ctx, keyEventHandler);
|
||||
turbine::setMouseButtonEventHandler(*ctx, mouseButtonEventHandler);
|
||||
turbine::requireRefreshWithin(*ctx, 0);
|
||||
StudioUI ui(*ctx, projectDataDir);
|
||||
StudioUIDrawer drawer(ui);
|
||||
|
@ -15,7 +15,11 @@ namespace studio {
|
||||
|
||||
AboutPopup::AboutPopup(turbine::Context &ctx) noexcept:
|
||||
Popup("About"),
|
||||
#ifdef DEBUG
|
||||
m_text{sfmt("{} [DEBUG] - {}", keelCtx(ctx).appName, olympic::appVersion)} {
|
||||
#else
|
||||
m_text{sfmt("{} - {}", keelCtx(ctx).appName, olympic::appVersion)} {
|
||||
#endif
|
||||
}
|
||||
|
||||
void AboutPopup::draw(Context &sctx) noexcept {
|
||||
|
@ -80,9 +80,9 @@ void NewMenu::addItemMaker(ox::UPtr<ItemMaker> &&im) noexcept {
|
||||
});
|
||||
}
|
||||
|
||||
void NewMenu::installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept {
|
||||
void NewMenu::installItemTemplate(ox::UPtr<ItemTemplate> &&tmplt) noexcept {
|
||||
for (auto const&im : m_types) {
|
||||
if (im->installTemplate(tmplt)) {
|
||||
if (im->installTemplate(std::move(tmplt))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class NewMenu final: public Popup {
|
||||
|
||||
void addItemMaker(ox::UPtr<ItemMaker> &&im) noexcept;
|
||||
|
||||
void installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept;
|
||||
void installItemTemplate(ox::UPtr<ItemTemplate> &&tmplt) noexcept;
|
||||
|
||||
private:
|
||||
void drawNewItemType(Context const&sctx) noexcept;
|
||||
|
@ -34,18 +34,6 @@ static bool shutdownHandler(turbine::Context &ctx) {
|
||||
return sctx->ui.handleShutdown();
|
||||
}
|
||||
|
||||
void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept {
|
||||
ox::String path = std::move(filePath);
|
||||
if (beginsWith(path, "uuid://")) {
|
||||
auto [p, err] = keel::uuidUrlToPath(keelCtx(ctx), path);
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
path = p;
|
||||
}
|
||||
ctx.ui.navigateTo(std::move(path), std::move(navArgs));
|
||||
}
|
||||
|
||||
namespace ig {
|
||||
extern bool s_mainWinHasFocus;
|
||||
}
|
||||
@ -126,9 +114,8 @@ static ox::Error convertStudioConfigV1ToStudioConfigV2(
|
||||
using StudioConfig = StudioConfigV2;
|
||||
|
||||
|
||||
StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept:
|
||||
m_sctx{*this, ctx},
|
||||
m_tctx{ctx},
|
||||
StudioUI::StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept:
|
||||
m_sctx{*this, tctx},
|
||||
m_projectDataDir{std::move(projectDataDir)} {
|
||||
{
|
||||
ImFontConfig fontCfg;
|
||||
@ -139,7 +126,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
|
||||
// but AddFontFromMemoryTTF requires a mutable buffer.
|
||||
// However, setting fontCfg.FontDataOwnedByAtlas ensures
|
||||
// that it will still be treated as const.
|
||||
// ImGui documentation recognize that this is a bad design,
|
||||
// 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);
|
||||
}
|
||||
@ -148,6 +135,9 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
|
||||
oxLogError(headerizeConfigFile<StudioConfigV1>(kctx));
|
||||
turbine::setApplicationData(m_tctx, &m_sctx);
|
||||
turbine::setShutdownHandler(m_tctx, shutdownHandler);
|
||||
m_sctx.navCallback = [this](ox::StringParam filePath, ox::StringParam navArgs) {
|
||||
handleNavigationChange(std::move(filePath), std::move(navArgs));
|
||||
};
|
||||
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
|
||||
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
|
||||
m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
|
||||
@ -187,7 +177,17 @@ void StudioUI::handleKeyEvent(turbine::Key const key, bool const down) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::navigateTo(ox::StringParam path, ox::StringParam navArgs) noexcept {
|
||||
void StudioUI::handleMouseButtonEvent(int const btn, bool const down) noexcept {
|
||||
if (down) {
|
||||
if (btn == 3) { // back button
|
||||
navigateBack(m_sctx);
|
||||
} else if (btn == 4) { // forward button
|
||||
navigateForward(m_sctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StudioUI::handleNavigationChange(ox::StringParam path, ox::StringParam navArgs) noexcept {
|
||||
m_navAction.emplace(std::move(path), std::move(navArgs));
|
||||
}
|
||||
|
||||
@ -271,7 +271,7 @@ void StudioUI::drawMenu() noexcept {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
||||
auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
||||
if (ImGui::MenuItem(
|
||||
"Undo", STUDIO_CTRL "+Z", false, undoStack && undoStack->canUndo())) {
|
||||
oxLogError(undoStack->undo());
|
||||
@ -311,6 +311,19 @@ void StudioUI::drawMenu() noexcept {
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Navigate")) {
|
||||
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";
|
||||
if (ImGui::MenuItem("Back", backShortcut, false, m_sctx.navIdx > 1)) {
|
||||
navigateBack(m_sctx);
|
||||
}
|
||||
if (ImGui::MenuItem(
|
||||
"Forward", fwdShortcut, false,
|
||||
m_sctx.navIdx < m_sctx.navStack.size())) {
|
||||
navigateForward(m_sctx);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) {
|
||||
m_aboutPopup.open();
|
||||
@ -354,10 +367,16 @@ void StudioUI::drawTabs() noexcept {
|
||||
}
|
||||
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
|
||||
m_activeEditor->onActivated();
|
||||
if (!m_sctx.navIdx ||
|
||||
(m_sctx.navIdx <= m_sctx.navStack.size() &&
|
||||
m_sctx.navStack[m_sctx.navIdx - 1].filePath != m_activeEditor->itemPath())) {
|
||||
m_sctx.navStack.resize(m_sctx.navIdx);
|
||||
++m_sctx.navIdx;
|
||||
m_sctx.navStack.emplace_back(ox::String{m_activeEditor->itemPath()}, ox::String{""});
|
||||
}
|
||||
}
|
||||
if (m_closeActiveTab) [[unlikely]] {
|
||||
ImGui::SetTabItemClosed(e->itemDisplayName().c_str());
|
||||
|
||||
} else if (open) [[likely]] {
|
||||
e->draw(m_sctx);
|
||||
}
|
||||
@ -397,9 +416,10 @@ void StudioUI::drawTabs() noexcept {
|
||||
m_closeActiveTab = false;
|
||||
}
|
||||
if (m_navAction) {
|
||||
oxLogError(openFile(m_navAction->path));
|
||||
m_activeEditor->navigateTo(m_navAction->args);
|
||||
m_navAction.reset();
|
||||
if (!openFile(m_navAction->path)) {
|
||||
m_activeEditor->navigateTo(m_navAction->args);
|
||||
m_navAction.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -418,7 +438,7 @@ void StudioUI::loadModule(Module const &mod) noexcept {
|
||||
}
|
||||
auto tmplts = mod.itemTemplates(m_sctx);
|
||||
for (auto &t : tmplts) {
|
||||
m_newMenu.installItemTemplate(t);
|
||||
m_newMenu.installItemTemplate(std::move(t));
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,6 +528,22 @@ void StudioUI::handleKeyInput() noexcept {
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (ox::defines::OS == ox::OS::Darwin) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
|
||||
navigateBack(m_sctx);
|
||||
} else if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
|
||||
navigateForward(m_sctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (ox::defines::OS != ox::OS::Darwin) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
|
||||
navigateBack(m_sctx);
|
||||
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
|
||||
navigateForward(m_sctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -553,24 +589,36 @@ ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR ne
|
||||
|
||||
ox::Error StudioUI::handleDeleteDir(ox::StringViewCR path) noexcept {
|
||||
auto const p = sfmt("{}/", path);
|
||||
for (auto &e : m_editors) {
|
||||
if (beginsWith(e->itemPath(), p)) {
|
||||
oxLogError(closeFile(path));
|
||||
m_closeActiveTab = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::ignore = m_editors.erase(
|
||||
std::remove_if(
|
||||
m_editors.begin(), m_editors.end(),
|
||||
[&](ox::UPtr<BaseEditor> const &e) {
|
||||
if (beginsWith(e->itemPath(), p)) {
|
||||
oxLogError(closeFile(path));
|
||||
if (e.get() != m_activeEditor) {
|
||||
return true;
|
||||
}
|
||||
m_closeActiveTab = true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
return m_projectExplorer.refreshProjectTreeModel();
|
||||
}
|
||||
|
||||
ox::Error StudioUI::handleDeleteFile(ox::StringViewCR path) noexcept {
|
||||
for (auto &e : m_editors) {
|
||||
if (path == e->itemPath()) {
|
||||
oxLogError(closeFile(path));
|
||||
m_closeActiveTab = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::ignore = m_editors.erase(
|
||||
std::remove_if(
|
||||
m_editors.begin(), m_editors.end(),
|
||||
[&](ox::UPtr<BaseEditor> const &e) {
|
||||
if (path == e->itemPath()) {
|
||||
oxLogError(closeFile(path));
|
||||
if (e.get() != m_activeEditor) {
|
||||
return true;
|
||||
}
|
||||
m_closeActiveTab = true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
return m_projectExplorer.refreshProjectTreeModel();
|
||||
}
|
||||
|
||||
@ -654,7 +702,10 @@ ox::Error StudioUI::openFile(ox::StringViewCR path) noexcept {
|
||||
|
||||
ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActiveTab) noexcept {
|
||||
if (!m_project) {
|
||||
return ox::Error(1, "No project open to open a file from");
|
||||
return ox::Error(1, "no project open to open a file from");
|
||||
}
|
||||
if (!m_project->romFs().exists(path)) {
|
||||
return ox::Error{1, "file does note exist"};
|
||||
}
|
||||
if (m_openFiles.contains(path)) {
|
||||
for (auto &e : m_editors) {
|
||||
@ -668,10 +719,10 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActi
|
||||
}
|
||||
OX_REQUIRE(ext, fileExt(path));
|
||||
// create Editor
|
||||
BaseEditor *editor = nullptr;
|
||||
ox::UPtr<BaseEditor> editor;
|
||||
auto const err = m_editorMakers.contains(ext) ?
|
||||
m_editorMakers[ext](path).moveTo(editor) :
|
||||
ox::makeCatch<ClawEditor>(m_sctx, path).moveTo(editor);
|
||||
m_editorMakers[ext](path).to<ox::UPtr<BaseEditor>>().moveTo(editor) :
|
||||
ox::make_unique_catch<ClawEditor>(m_sctx, path).moveTo(editor);
|
||||
if (err) {
|
||||
if constexpr(!ox::defines::Debug) {
|
||||
oxErrf("Could not open Editor: {}\n", toStr(err));
|
||||
@ -681,11 +732,11 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActi
|
||||
return err;
|
||||
}
|
||||
editor->closed.connect(this, &StudioUI::closeFile);
|
||||
m_editors.emplace_back(editor);
|
||||
auto const &e = m_editors.emplace_back(std::move(editor));
|
||||
m_openFiles.emplace_back(path);
|
||||
if (makeActiveTab) {
|
||||
m_activeEditor = m_editors.back().value->get();
|
||||
m_activeEditorUpdatePending = editor;
|
||||
m_activeEditorUpdatePending = e.get();
|
||||
}
|
||||
// save to config
|
||||
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&path](StudioConfig &config) {
|
||||
|
@ -25,12 +25,12 @@
|
||||
|
||||
namespace studio {
|
||||
|
||||
class StudioUI: public ox::SignalHandler {
|
||||
class StudioUI final: public ox::SignalHandler {
|
||||
friend class StudioUIDrawer;
|
||||
|
||||
private:
|
||||
Context m_sctx;
|
||||
turbine::Context &m_tctx;
|
||||
turbine::Context &m_tctx{m_sctx.tctx};
|
||||
ox::String m_projectDataDir;
|
||||
ox::UPtr<Project> m_project;
|
||||
TaskRunner m_taskRunner;
|
||||
@ -83,11 +83,13 @@ class StudioUI: public ox::SignalHandler {
|
||||
ox::Optional<NavAction> m_navAction;
|
||||
|
||||
public:
|
||||
explicit StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept;
|
||||
explicit StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept;
|
||||
|
||||
void handleKeyEvent(turbine::Key, bool down) noexcept;
|
||||
|
||||
void navigateTo(ox::StringParam path, ox::StringParam navArgs) noexcept;
|
||||
void handleMouseButtonEvent(int btn, bool down) noexcept;
|
||||
|
||||
void handleNavigationChange(ox::StringParam path, ox::StringParam navArgs) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Project *project() noexcept {
|
||||
|
@ -15,11 +15,24 @@ namespace studio {
|
||||
class StudioUI;
|
||||
|
||||
struct Context {
|
||||
StudioUI &ui;
|
||||
Project *project = nullptr;
|
||||
turbine::Context &tctx;
|
||||
Context(StudioUI &pUi, turbine::Context &pTctx) noexcept:
|
||||
ui(pUi), tctx(pTctx) {}
|
||||
public:
|
||||
friend void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept;
|
||||
friend void navigateBack(Context &ctx) noexcept;
|
||||
friend void navigateForward(Context &ctx) noexcept;
|
||||
friend StudioUI;
|
||||
StudioUI &ui;
|
||||
Project *project = nullptr;
|
||||
turbine::Context &tctx;
|
||||
protected:
|
||||
struct NavPath {
|
||||
ox::String filePath;
|
||||
ox::String navArgs;
|
||||
};
|
||||
size_t navIdx{};
|
||||
ox::Vector<NavPath> navStack;
|
||||
std::function<void(ox::StringParam filePath, ox::StringParam navArgs)> navCallback;
|
||||
Context(StudioUI &pUi, turbine::Context &pTctx) noexcept:
|
||||
ui{pUi}, tctx{pTctx} {}
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
@ -27,6 +40,15 @@ inline keel::Context &keelCtx(Context &ctx) noexcept {
|
||||
return keelCtx(ctx.tctx);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
inline keel::Context const &keelCtx(Context const &ctx) noexcept {
|
||||
return keelCtx(ctx.tctx);
|
||||
}
|
||||
|
||||
void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs = "") noexcept;
|
||||
|
||||
void navigateBack(Context &ctx) noexcept;
|
||||
|
||||
void navigateForward(Context &ctx) noexcept;
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,11 @@ class FilePickerPopup {
|
||||
|
||||
private:
|
||||
ox::String m_name;
|
||||
FileExplorer m_explorer;
|
||||
struct Explorer: public FileExplorer {
|
||||
mutable bool opened{};
|
||||
explicit Explorer(keel::Context &kctx);
|
||||
void fileOpened(ox::StringViewCR path) const noexcept override;
|
||||
} m_explorer;
|
||||
ox::Vector<ox::String> const m_fileExts;
|
||||
bool m_open{};
|
||||
|
||||
@ -33,6 +37,10 @@ class FilePickerPopup {
|
||||
|
||||
ox::Optional<ox::String> draw(Context &ctx) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
ox::Optional<ox::String> handlePick() noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -46,14 +46,14 @@ ox::Result<T> getDragDropPayload() noexcept {
|
||||
static_cast<size_t>(payload->DataSize)});
|
||||
}
|
||||
|
||||
ox::Error setDragDropPayload(ox::CStringViewCR name, auto const&obj) noexcept {
|
||||
ox::Error setDragDropPayload(ox::CStringViewCR name, auto const &obj) noexcept {
|
||||
OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
|
||||
ImGui::SetDragDropPayload(name.c_str(), buff.data(), buff.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Error setDragDropPayload(T const&obj) noexcept {
|
||||
ox::Error setDragDropPayload(T const &obj) noexcept {
|
||||
OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
|
||||
ImGui::SetDragDropPayload(ox::ModelTypeName_v<T>, buff.data(), buff.size());
|
||||
return {};
|
||||
@ -77,7 +77,7 @@ class DragDropSource {
|
||||
}
|
||||
};
|
||||
|
||||
auto dragDropSource(auto const&cb, ImGuiDragDropFlags const flags = 0) noexcept {
|
||||
auto dragDropSource(auto const &cb, ImGuiDragDropFlags const flags = 0) noexcept {
|
||||
if constexpr(ox::is_same_v<decltype(cb()), ox::Error>) {
|
||||
if (ig::DragDropSource const tgt{flags}; tgt) [[unlikely]] {
|
||||
return cb();
|
||||
@ -108,7 +108,7 @@ class DragDropTarget {
|
||||
}
|
||||
};
|
||||
|
||||
auto dragDropTarget(auto const&cb) noexcept {
|
||||
auto dragDropTarget(auto const &cb) noexcept {
|
||||
if constexpr(ox::is_same_v<decltype(cb()), ox::Error>) {
|
||||
if (ig::DragDropTarget const tgt; tgt) [[unlikely]] {
|
||||
return cb();
|
||||
@ -124,7 +124,7 @@ auto dragDropTarget(auto const&cb) noexcept {
|
||||
|
||||
class ChildStackItem {
|
||||
public:
|
||||
explicit ChildStackItem(ox::CStringViewCR id, ImVec2 const&sz = {}) noexcept;
|
||||
explicit ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz = {}) noexcept;
|
||||
~ChildStackItem() noexcept;
|
||||
};
|
||||
|
||||
@ -146,7 +146,7 @@ class IndentStackItem {
|
||||
|
||||
void centerNextWindow(turbine::Context &ctx) noexcept;
|
||||
|
||||
bool PushButton(ox::CStringViewCR lbl, ImVec2 const&btnSz = BtnSz) noexcept;
|
||||
bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz = BtnSz) noexcept;
|
||||
|
||||
template<typename Str>
|
||||
struct TextInput {
|
||||
@ -234,7 +234,7 @@ PopupResponse PopupControlsOk(
|
||||
ox::CStringViewCR ok);
|
||||
|
||||
[[nodiscard]]
|
||||
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz = {285, 0});
|
||||
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz = {285, 0});
|
||||
|
||||
/**
|
||||
*
|
||||
@ -266,7 +266,7 @@ bool ComboBox(ox::CStringView lbl, ox::Span<const ox::String> list, size_t &sele
|
||||
*/
|
||||
bool ComboBox(
|
||||
ox::CStringViewCR lbl,
|
||||
std::function<ox::CStringView(size_t)> const&f,
|
||||
std::function<ox::CStringView(size_t)> const &f,
|
||||
size_t strCnt,
|
||||
size_t &selectedIdx) noexcept;
|
||||
|
||||
@ -278,10 +278,10 @@ bool FileComboBox(
|
||||
|
||||
bool ListBox(
|
||||
ox::CStringViewCR name,
|
||||
std::function<ox::CStringView(size_t)> const&f,
|
||||
std::function<ox::CStringView(size_t)> const &f,
|
||||
size_t strCnt,
|
||||
size_t &selIdx,
|
||||
ImVec2 const&sz = {0, 0}) noexcept;
|
||||
ImVec2 const &sz = {0, 0}) noexcept;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -290,7 +290,7 @@ bool ListBox(
|
||||
* @param selIdx
|
||||
* @return true if new value selected, false otherwise
|
||||
*/
|
||||
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;
|
||||
|
||||
class FilePicker {
|
||||
private:
|
||||
@ -306,7 +306,7 @@ class FilePicker {
|
||||
studio::Context &sctx,
|
||||
ox::StringParam title,
|
||||
ox::StringParam fileExt,
|
||||
ImVec2 const&size = {}) noexcept;
|
||||
ImVec2 const &size = {}) noexcept;
|
||||
|
||||
void draw() noexcept;
|
||||
|
||||
@ -348,6 +348,8 @@ class QuestionPopup: public Popup {
|
||||
|
||||
QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;
|
||||
|
||||
void setQuestion(ox::StringParam msg) noexcept;
|
||||
|
||||
void draw(Context &ctx) noexcept override;
|
||||
|
||||
};
|
||||
|
@ -109,7 +109,7 @@ class ItemMaker {
|
||||
return m_typeDisplayName;
|
||||
}
|
||||
|
||||
bool installTemplate(ox::UPtr<ItemTemplate> &tmpl) {
|
||||
bool installTemplate(ox::UPtr<ItemTemplate> &&tmpl) {
|
||||
if (typeName() == tmpl->typeName() &&
|
||||
typeVersion() <= tmpl->typeVersion()) {
|
||||
m_templates.emplace_back(std::move(tmpl));
|
||||
@ -120,10 +120,6 @@ class ItemMaker {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool installTemplate(ox::UPtr<ItemTemplate> &&tmpl) {
|
||||
return installTemplate(tmpl);
|
||||
}
|
||||
|
||||
constexpr ox::Vector<ox::UPtr<ItemTemplate>> const&itemTemplates() const noexcept {
|
||||
return m_templates;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
add_library(
|
||||
Studio
|
||||
configio.cpp
|
||||
context.cpp
|
||||
editor.cpp
|
||||
filepickerpopup.cpp
|
||||
filetreemodel.cpp
|
||||
|
69
src/olympic/studio/modlib/src/context.cpp
Normal file
69
src/olympic/studio/modlib/src/context.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept {
|
||||
ox::String path = std::move(filePath);
|
||||
if (beginsWith(path, "uuid://")) {
|
||||
auto const [p, err] = keel::uuidUrlToPath(keelCtx(ctx), path);
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
path = p;
|
||||
}
|
||||
ctx.navStack.resize(ctx.navIdx + 1);
|
||||
ctx.navStack.emplace_back(ox::String{path}, ox::String{navArgs.view()});
|
||||
try {
|
||||
ctx.navCallback(std::move(path), std::move(navArgs));
|
||||
} catch (std::exception const &e) {
|
||||
oxErrf("Nav error: {}", e.what());
|
||||
oxAssert(false, "Nav error");
|
||||
}
|
||||
}
|
||||
|
||||
void navigateBack(Context &ctx) noexcept {
|
||||
if (!ctx.navIdx) {
|
||||
return;
|
||||
}
|
||||
--ctx.navIdx;
|
||||
while (ctx.navIdx < ctx.navStack.size() && ctx.navIdx) {
|
||||
auto const i = ctx.navIdx - 1;
|
||||
auto const &n = ctx.navStack[i];
|
||||
if (!ctx.project->exists(n.filePath)) {
|
||||
std::ignore = ctx.navStack.erase(i);
|
||||
--ctx.navIdx;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ctx.navCallback(n.filePath, n.navArgs);
|
||||
} catch (std::exception const &e) {
|
||||
oxAssert(ctx.navCallback != nullptr, "navCallback is null");
|
||||
oxErrf("navigateForward failed: {}", e.what());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void navigateForward(Context &ctx) noexcept {
|
||||
while (ctx.navIdx < ctx.navStack.size()) {
|
||||
auto const &n = ctx.navStack[ctx.navIdx];
|
||||
try {
|
||||
ctx.navCallback(n.filePath, n.navArgs);
|
||||
} catch (std::exception const &e) {
|
||||
oxAssert(ctx.navCallback != nullptr, "navCallback is null");
|
||||
oxErrf("navigateForward failed: {}", e.what());
|
||||
}
|
||||
if (!ctx.project->exists(n.filePath)) {
|
||||
std::ignore = ctx.navStack.erase(ctx.navIdx);
|
||||
continue;
|
||||
}
|
||||
++ctx.navIdx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -72,8 +72,10 @@ bool BaseEditor::exportable() const noexcept {
|
||||
}
|
||||
|
||||
void BaseEditor::setCutEnabled(bool v) {
|
||||
m_cutEnabled = v;
|
||||
cutEnabledChanged.emit(v);
|
||||
if (m_cutEnabled != v) {
|
||||
m_cutEnabled = v;
|
||||
cutEnabledChanged.emit(v);
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseEditor::cutEnabled() const noexcept {
|
||||
@ -81,8 +83,10 @@ bool BaseEditor::cutEnabled() const noexcept {
|
||||
}
|
||||
|
||||
void BaseEditor::setCopyEnabled(bool v) {
|
||||
m_copyEnabled = v;
|
||||
copyEnabledChanged.emit(v);
|
||||
if (m_copyEnabled != v) {
|
||||
m_copyEnabled = v;
|
||||
copyEnabledChanged.emit(v);
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseEditor::copyEnabled() const noexcept {
|
||||
@ -90,8 +94,10 @@ bool BaseEditor::copyEnabled() const noexcept {
|
||||
}
|
||||
|
||||
void BaseEditor::setPasteEnabled(bool v) {
|
||||
m_pasteEnabled = v;
|
||||
pasteEnabledChanged.emit(v);
|
||||
if (m_pasteEnabled != v) {
|
||||
m_pasteEnabled = v;
|
||||
pasteEnabledChanged.emit(v);
|
||||
}
|
||||
}
|
||||
|
||||
bool BaseEditor::pasteEnabled() const noexcept {
|
||||
|
@ -8,6 +8,15 @@
|
||||
|
||||
namespace studio {
|
||||
|
||||
FilePickerPopup::Explorer::Explorer(keel::Context &kctx):
|
||||
FileExplorer{kctx} {
|
||||
}
|
||||
|
||||
void FilePickerPopup::Explorer::fileOpened(ox::StringViewCR) const noexcept {
|
||||
opened = true;
|
||||
}
|
||||
|
||||
|
||||
FilePickerPopup::FilePickerPopup(
|
||||
ox::StringParam name,
|
||||
keel::Context &kctx,
|
||||
@ -41,6 +50,7 @@ void FilePickerPopup::refresh() noexcept {
|
||||
void FilePickerPopup::open() noexcept {
|
||||
refresh();
|
||||
m_open = true;
|
||||
m_explorer.opened = false;
|
||||
}
|
||||
|
||||
void FilePickerPopup::close() noexcept {
|
||||
@ -60,16 +70,22 @@ ox::Optional<ox::String> FilePickerPopup::draw(Context &ctx) noexcept {
|
||||
if (ig::BeginPopup(ctx.tctx, m_name, m_open, {380, 340})) {
|
||||
auto const vp = ImGui::GetContentRegionAvail();
|
||||
m_explorer.draw(ctx, {vp.x, vp.y - 30});
|
||||
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK) {
|
||||
auto p = m_explorer.selectedPath();
|
||||
if (p) {
|
||||
out.emplace(*p);
|
||||
}
|
||||
close();
|
||||
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK || m_explorer.opened) {
|
||||
out = handlePick();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ox::Optional<ox::String> FilePickerPopup::handlePick() noexcept {
|
||||
ox::Optional<ox::String> out;
|
||||
auto p = m_explorer.selectedPath();
|
||||
if (p) {
|
||||
out.emplace(*p);
|
||||
}
|
||||
close();
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
namespace studio::ig {
|
||||
|
||||
ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 const&sz) noexcept {
|
||||
ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz) noexcept {
|
||||
ImGui::BeginChild(id.c_str(), sz);
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ void centerNextWindow(turbine::Context &ctx) noexcept {
|
||||
ImGui::SetNextWindowPos(ImVec2(screenW / mod, screenH / mod), ImGuiCond_Always, ImVec2(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);
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ PopupResponse PopupControlsOk(
|
||||
return out;
|
||||
}
|
||||
|
||||
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz) {
|
||||
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz) {
|
||||
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
centerNextWindow(ctx);
|
||||
ImGui::OpenPopup(popupName.c_str());
|
||||
@ -149,7 +149,7 @@ bool ComboBox(
|
||||
|
||||
bool ComboBox(
|
||||
ox::CStringViewCR lbl,
|
||||
std::function<ox::CStringView(size_t)> const&f,
|
||||
std::function<ox::CStringView(size_t)> const &f,
|
||||
size_t strCnt,
|
||||
size_t &selectedIdx) noexcept {
|
||||
bool out{};
|
||||
@ -172,16 +172,16 @@ bool FileComboBox(
|
||||
Context &sctx,
|
||||
ox::StringViewCR fileExt,
|
||||
size_t &selectedIdx) noexcept {
|
||||
auto const&list = sctx.project->fileList(fileExt);
|
||||
auto const &list = sctx.project->fileList(fileExt);
|
||||
return ComboBox(lbl, list, selectedIdx);
|
||||
}
|
||||
|
||||
bool ListBox(
|
||||
ox::CStringViewCR name,
|
||||
std::function<ox::CStringView(size_t)> const&f,
|
||||
std::function<ox::CStringView(size_t)> const &f,
|
||||
size_t const strCnt,
|
||||
size_t &selIdx,
|
||||
ImVec2 const&sz) noexcept {
|
||||
ImVec2 const &sz) noexcept {
|
||||
auto out = false;
|
||||
if (ImGui::BeginListBox(name.c_str(), sz)) {
|
||||
for (size_t i = 0; i < strCnt; ++i) {
|
||||
@ -199,13 +199,13 @@ bool ListBox(
|
||||
return out;
|
||||
}
|
||||
|
||||
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 list[i];
|
||||
}, 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 list[i];
|
||||
}, list.size(), selIdx);
|
||||
@ -216,7 +216,7 @@ FilePicker::FilePicker(
|
||||
Context &sctx,
|
||||
ox::StringParam title,
|
||||
ox::StringParam fileExt,
|
||||
ImVec2 const&size) noexcept:
|
||||
ImVec2 const &size) noexcept:
|
||||
m_sctx(sctx),
|
||||
m_title(std::move(title)),
|
||||
m_fileExt(std::move(fileExt)),
|
||||
@ -230,7 +230,7 @@ void FilePicker::draw() noexcept {
|
||||
auto constexpr popupSz = ImVec2{450.f, 0};
|
||||
IDStackItem const idStackItem(m_title);
|
||||
if (BeginPopup(m_sctx.tctx, 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{};
|
||||
ComboBox(m_title, list, selIdx);
|
||||
if (PopupControlsOkCancel(popupSz.x, m_show) == ig::PopupResponse::OK) {
|
||||
@ -267,6 +267,10 @@ QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) no
|
||||
m_question{std::move(question)} {
|
||||
}
|
||||
|
||||
void QuestionPopup::setQuestion(ox::StringParam msg) noexcept {
|
||||
m_question = std::move(msg);
|
||||
}
|
||||
|
||||
void QuestionPopup::draw(Context &ctx) noexcept {
|
||||
switch (m_stage) {
|
||||
case Stage::Closed:
|
||||
|
@ -101,8 +101,8 @@ ox::Result<ox::FileStat> Project::stat(ox::StringViewCR path) const noexcept {
|
||||
ox::Error Project::copyItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
|
||||
OX_REQUIRE_M(buff, loadBuff(src));
|
||||
OX_REQUIRE(id, keel::regenerateUuidHeader(buff));
|
||||
OX_RETURN_ERROR(writeBuff(dest, buff));
|
||||
createUuidMapping(m_kctx, dest, id);
|
||||
OX_RETURN_ERROR(writeBuff(dest, ox::BufferView{buff} + keel::K1HdrSz));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -79,11 +79,14 @@ enum Key {
|
||||
};
|
||||
|
||||
using KeyEventHandler = void(*)(Context&, Key, bool);
|
||||
using MouseButtonEventHandler = void(*)(Context&, int btn, bool);
|
||||
|
||||
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept;
|
||||
|
||||
void setMouseButtonEventHandler(Context &ctx, MouseButtonEventHandler h) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
KeyEventHandler keyEventHandler(Context &ctx) noexcept;
|
||||
KeyEventHandler keyEventHandler(Context const &ctx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
bool buttonDown(Context const&ctx, Key) noexcept;
|
||||
|
@ -133,7 +133,9 @@ void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
|
||||
ctx.keyEventHandler = h;
|
||||
}
|
||||
|
||||
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
|
||||
void setMouseButtonEventHandler(Context&, MouseButtonEventHandler) noexcept {}
|
||||
|
||||
KeyEventHandler keyEventHandler(Context const &ctx) noexcept {
|
||||
return ctx.keyEventHandler;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ class Context {
|
||||
UpdateHandler updateHandler = [](Context&) -> int {return -1;};
|
||||
keel::Context keelCtx;
|
||||
KeyEventHandler keyEventHandler = nullptr;
|
||||
MouseButtonEventHandler mouseButtonEventHandler = nullptr;
|
||||
ox::AnyPtr applicationData;
|
||||
|
||||
// GLFW impl data ////////////////////////////////////////////////////////
|
||||
|
@ -299,9 +299,14 @@ static void handleKeyPress(Context &ctx, int const key, bool const down) noexcep
|
||||
static void handleGlfwCursorPosEvent(GLFWwindow*, double, double) noexcept {
|
||||
}
|
||||
|
||||
static void handleGlfwMouseButtonEvent(GLFWwindow *window, int, int, int) noexcept {
|
||||
static void handleGlfwMouseButtonEvent(
|
||||
GLFWwindow *window,
|
||||
int const btn,
|
||||
int const action,
|
||||
int) noexcept {
|
||||
auto &ctx = *static_cast<Context*>(glfwGetWindowUserPointer(window));
|
||||
setMandatoryRefreshPeriod(ctx, ticksMs(ctx) + config::MandatoryRefreshPeriod);
|
||||
ctx.mouseButtonEventHandler(ctx, btn, action == 1);
|
||||
}
|
||||
|
||||
static void handleGlfwKeyEvent(GLFWwindow *window, int const key, int, int const action, int) noexcept {
|
||||
@ -339,7 +344,8 @@ ox::Result<ox::UPtr<Context>> init(
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
||||
constexpr auto Scale = 5;
|
||||
ctx->window = glfwCreateWindow(240 * Scale, 160 * Scale, ctx->keelCtx.appName.c_str(), nullptr, nullptr);
|
||||
ctx->window = glfwCreateWindow(
|
||||
240 * Scale, 160 * Scale, ctx->keelCtx.appName.c_str(), nullptr, nullptr);
|
||||
//ctx.window = glfwCreateWindow(876, 743, ctx.keelCtx.appName.c_str(), nullptr, nullptr);
|
||||
if (ctx->window == nullptr) {
|
||||
return ox::Error(1, "Could not open GLFW window");
|
||||
@ -402,14 +408,14 @@ ox::Error run(Context &ctx) noexcept {
|
||||
glfwPollEvents();
|
||||
auto const ticks = ticksMs(ctx);
|
||||
if (ctx.wakeupTime <= ticks) {
|
||||
auto const st = ctx.updateHandler(ctx);
|
||||
if (st >= 0) {
|
||||
ctx.wakeupTime = ticks + static_cast<unsigned>(st);
|
||||
sleepTime = static_cast<uint64_t>(st);
|
||||
} else {
|
||||
ctx.wakeupTime = ~uint64_t{0};
|
||||
sleepTime = ctx.wakeupTime - ticks;
|
||||
}
|
||||
auto const st = ctx.updateHandler(ctx);
|
||||
if (st >= 0) {
|
||||
ctx.wakeupTime = ticks + static_cast<unsigned>(st);
|
||||
sleepTime = static_cast<uint64_t>(st);
|
||||
} else {
|
||||
ctx.wakeupTime = ~uint64_t{0};
|
||||
sleepTime = ctx.wakeupTime - ticks;
|
||||
}
|
||||
} else {
|
||||
sleepTime = ctx.wakeupTime - ticks;
|
||||
}
|
||||
@ -447,15 +453,19 @@ void setShutdownHandler(Context &ctx, ShutdownHandler const handler) noexcept {
|
||||
ctx.shutdownHandler = handler;
|
||||
}
|
||||
|
||||
void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
|
||||
void setUpdateHandler(Context &ctx, UpdateHandler const h) noexcept {
|
||||
ctx.updateHandler = h;
|
||||
}
|
||||
|
||||
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
|
||||
void setKeyEventHandler(Context &ctx, KeyEventHandler const h) noexcept {
|
||||
ctx.keyEventHandler = h;
|
||||
}
|
||||
|
||||
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
|
||||
void setMouseButtonEventHandler(Context &ctx, MouseButtonEventHandler const h) noexcept {
|
||||
ctx.mouseButtonEventHandler = h;
|
||||
}
|
||||
|
||||
KeyEventHandler keyEventHandler(Context const &ctx) noexcept {
|
||||
return ctx.keyEventHandler;
|
||||
}
|
||||
|
||||
|
59
util/scripts/loc.py
Executable file
59
util/scripts/loc.py
Executable file
@ -0,0 +1,59 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Count and sort lines of code in selected files.")
|
||||
parser.add_argument(
|
||||
"--search-dirs", nargs="+", required=True,
|
||||
help="List of directories to search (recursively)."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-exts", nargs="+", required=True,
|
||||
help="List of file extensions to include (e.g. .cpp .hpp .py)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exclude-paths", nargs="*", default=[],
|
||||
help="Full file paths to exclude from counting."
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
include_exts = tuple(args.include_exts)
|
||||
exclude_paths = {Path(p).resolve() for p in args.exclude_paths}
|
||||
files = []
|
||||
for base in args.search_dirs:
|
||||
path = Path(base)
|
||||
if path.is_dir():
|
||||
for file in path.rglob("*"):
|
||||
try:
|
||||
resolved_file = file.resolve()
|
||||
if (
|
||||
file.is_file()
|
||||
and file.name.endswith(include_exts)
|
||||
and resolved_file not in exclude_paths
|
||||
):
|
||||
files.append(str(resolved_file))
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
line_counts = []
|
||||
total_lines = 0
|
||||
for f in files:
|
||||
try:
|
||||
with open(f, "r", encoding="utf-8", errors="ignore") as file:
|
||||
lines = sum(1 for _ in file)
|
||||
line_counts.append((lines, f))
|
||||
total_lines += lines
|
||||
except Exception as e:
|
||||
print(f"Failed to read {f}: {e}")
|
||||
line_counts.sort()
|
||||
for count, filename in line_counts:
|
||||
print(f"{count:>7} {filename}")
|
||||
print(f"{total_lines:>7} total")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Reference in New Issue
Block a user