Compare commits

..

No commits in common. "161194c8b268d8fb0159f251844292660b0a7922" and "e8a0ce88c5938c7c2f1c0d87bc74cd4afd314121" have entirely different histories.

12 changed files with 36 additions and 192 deletions

View File

@ -88,11 +88,11 @@ static ox::Error toPngFile(
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path): TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path):
Editor(std::move(path)), Editor(std::move(path)),
m_sctx{sctx}, m_sctx(sctx),
m_tctx{m_sctx.tctx}, m_tctx(m_sctx.tctx),
m_palPicker{"Palette Chooser", keelCtx(sctx), FileExt_npal}, m_view(m_sctx, itemPath(), *undoStack()),
m_view{m_sctx, itemPath(), *undoStack()}, m_model(m_view.model()) {
m_model{m_view.model()} { std::ignore = setPaletteSelection();
// connect signal/slots // connect signal/slots
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet); m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng); m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
@ -132,10 +132,10 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
if (key == turbine::Key::Escape) { if (key == turbine::Key::Escape) {
m_subsheetEditor.close(); m_subsheetEditor.close();
m_exportMenu.close(); m_exportMenu.close();
m_palPicker.close();
} }
auto const popupOpen = m_subsheetEditor.isOpen() || m_exportMenu.isOpen();
auto const pal = m_model.pal(); auto const pal = m_model.pal();
if (ig::mainWinHasFocus() && !m_palPathFocused) { if (!popupOpen) {
auto const colorCnt = gfx::colorCnt(pal, m_model.palettePage()); auto const colorCnt = gfx::colorCnt(pal, m_model.palettePage());
if (key == turbine::Key::Alpha_D) { if (key == turbine::Key::Alpha_D) {
m_tool = TileSheetTool::Draw; m_tool = TileSheetTool::Draw;
@ -183,8 +183,9 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
} }
void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
if (ig::mainWinHasFocus() && m_tool == TileSheetTool::Select) { auto const popupOpen = m_subsheetEditor.isOpen() || m_exportMenu.isOpen();
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) { if (!popupOpen && m_tool == TileSheetTool::Select) {
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
if (ImGui::IsKeyPressed(ImGuiKey_A)) { if (ImGui::IsKeyPressed(ImGuiKey_A)) {
auto const&img = m_model.activeSubSheet(); auto const&img = m_model.activeSubSheet();
m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}}); m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}});
@ -276,11 +277,6 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
ImGui::EndChild(); ImGui::EndChild();
m_subsheetEditor.draw(m_tctx); m_subsheetEditor.draw(m_tctx);
m_exportMenu.draw(m_tctx); m_exportMenu.draw(m_tctx);
if (auto pal = m_palPicker.draw(m_sctx)) {
if (*pal != m_model.palPath()) {
oxLogError(m_model.setPalette(*pal));
}
}
} }
void TileSheetEditorImGui::drawSubsheetSelector( void TileSheetEditorImGui::drawSubsheetSelector(
@ -441,26 +437,20 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
} }
void TileSheetEditorImGui::drawPaletteMenu() noexcept { void TileSheetEditorImGui::drawPaletteMenu() noexcept {
ig::IDStackItem const idStackItem{"PaletteMenu"};
auto constexpr comboWidthSub = 62; auto constexpr comboWidthSub = 62;
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub);
auto constexpr palTags = ImGuiInputTextFlags_ReadOnly; auto constexpr palTags = ImGuiInputTextFlags_ReadOnly;
if (ig::InputTextWithHint("##Palette", "Path to Palette", m_model.palPath(), palTags)) { if (ig::InputText("Palette", m_selectedPalette, palTags)) {
oxLogError(m_model.setPalette(m_model.palPath())); oxLogError(m_model.setPalette(m_selectedPalette));
} }
m_palPathFocused = ImGui::IsItemFocused();
if (ig::DragDropTarget const dragDropTarget; dragDropTarget) { if (ig::DragDropTarget const dragDropTarget; dragDropTarget) {
auto const [ref, err] = ig::getDragDropPayload<studio::FileRef>("FileRef"); auto const [ref, err] = ig::getDragDropPayload<studio::FileRef>("FileRef");
if (!err && endsWith(ref.path, FileExt_npal)) { if (!err && endsWith(ref.path, FileExt_npal)) {
if (ref.path != m_model.palPath()) { if (ref.path != m_selectedPalette) {
oxLogError(m_model.setPalette(ref.path)); oxLogError(m_model.setPalette(ref.path));
} }
} }
} }
ImGui::SameLine();
if (ImGui::Button("Browse")) {
m_palPicker.open();
}
auto const pages = m_model.pal().pages.size(); auto const pages = m_model.pal().pages.size();
if (pages > 1) { if (pages > 1) {
ig::IndentStackItem const indentStackItem{20}; ig::IndentStackItem const indentStackItem{20};
@ -531,6 +521,7 @@ ox::Error TileSheetEditorImGui::updateActiveSubsheet(ox::StringView const&name,
} }
ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept { ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
m_selectedPalette = m_model.palPath();
return {}; return {};
} }

View File

@ -9,7 +9,6 @@
#include <glutils/glutils.hpp> #include <glutils/glutils.hpp>
#include <studio/editor.hpp> #include <studio/editor.hpp>
#include <studio/filepickerpopup.hpp>
#include "tilesheetpixelgrid.hpp" #include "tilesheetpixelgrid.hpp"
#include "tilesheetpixels.hpp" #include "tilesheetpixels.hpp"
@ -47,18 +46,17 @@ class TileSheetEditorImGui: public studio::Editor {
constexpr bool isOpen() const noexcept { return m_show; } constexpr bool isOpen() const noexcept { return m_show; }
}; };
static constexpr float s_palViewWidth = 300; static constexpr float s_palViewWidth = 300;
ox::String m_selectedPalette;
studio::StudioContext &m_sctx; studio::StudioContext &m_sctx;
turbine::Context &m_tctx; turbine::Context &m_tctx;
ox::Vector<ox::String> m_paletteList; ox::Vector<ox::String> m_paletteList;
SubSheetEditor m_subsheetEditor; SubSheetEditor m_subsheetEditor;
ExportMenu m_exportMenu; ExportMenu m_exportMenu;
studio::FilePickerPopup m_palPicker;
glutils::FrameBuffer m_framebuffer; glutils::FrameBuffer m_framebuffer;
TileSheetEditorView m_view; TileSheetEditorView m_view;
TileSheetEditorModel &m_model; TileSheetEditorModel &m_model;
ox::Vec2 m_prevMouseDownPos; ox::Vec2 m_prevMouseDownPos;
TileSheetTool m_tool = TileSheetTool::Draw; TileSheetTool m_tool = TileSheetTool::Draw;
bool m_palPathFocused{};
public: public:
TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path); TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path);

View File

@ -4,6 +4,8 @@
#include <ox/claw/read.hpp> #include <ox/claw/read.hpp>
#include <ox/std/algorithm.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/memory.hpp> #include <ox/std/memory.hpp>
#include <turbine/clipboard.hpp> #include <turbine/clipboard.hpp>
@ -84,9 +86,9 @@ void TileSheetEditorModel::copy() {
auto cb = ox::make_unique<TileSheetClipboard>(); auto cb = ox::make_unique<TileSheetClipboard>();
iterateSelectionRows(*m_selection, [&](int const x, int const y) { iterateSelectionRows(*m_selection, [&](int const x, int const y) {
auto pt = ox::Point{x, y}; auto pt = ox::Point{x, y};
auto const&s = activeSubSheet(); const auto&s = activeSubSheet();
auto const idx = gfx::idx(s, pt); const auto idx = gfx::idx(s, pt);
auto const c = getPixel(s, idx); const auto c = getPixel(s, idx);
pt -= m_selection->a; pt -= m_selection->a;
cb->addPixel(pt, c); cb->addPixel(pt, c);
}); });
@ -147,11 +149,11 @@ size_t TileSheetEditorModel::palettePage() const noexcept {
} }
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const palIdx) noexcept { void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const palIdx) noexcept {
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx); const auto &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) { if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
return; return;
} }
auto const idx = gfx::idx(activeSubSheet, pt); const auto idx = gfx::idx(activeSubSheet, pt);
if (m_ongoingDrawCommand) { if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idx); m_updated = m_updated || m_ongoingDrawCommand->append(idx);
} else if (getPixel(activeSubSheet, idx) != palIdx) { } else if (getPixel(activeSubSheet, idx) != palIdx) {
@ -253,16 +255,14 @@ bool TileSheetEditorModel::updated() const noexcept {
ox::Error TileSheetEditorModel::markUpdatedCmdId(studio::UndoCommand const*cmd) noexcept { ox::Error TileSheetEditorModel::markUpdatedCmdId(studio::UndoCommand const*cmd) noexcept {
m_updated = true; m_updated = true;
auto const cmdId = cmd->commandId(); const auto cmdId = cmd->commandId();
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) { if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
if (readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).moveTo(m_pal)) { OX_RETURN_ERROR(readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).moveTo(m_pal));
m_pal = keel::AssetRef<Palette>{}; m_palettePage = ox::min<size_t>(m_pal->pages.size(), 0);
}
m_palettePage = ox::min<size_t>(pal().pages.size(), 0);
paletteChanged.emit(); paletteChanged.emit();
} }
auto const tsCmd = dynamic_cast<TileSheetCommand const*>(cmd); auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
auto const idx = validateSubSheetIdx(m_img, tsCmd->subsheetIdx()); auto idx = validateSubSheetIdx(m_img, tsCmd->subsheetIdx());
if (idx != m_activeSubsSheetIdx) { if (idx != m_activeSubsSheetIdx) {
setActiveSubsheet(idx); setActiveSubsheet(idx);
} }

View File

@ -22,10 +22,6 @@ void ProjectExplorer::fileOpened(ox::StringViewCR path) const noexcept {
fileChosen.emit(path); fileChosen.emit(path);
} }
void ProjectExplorer::fileDeleted(ox::StringViewCR path) const noexcept {
deleteItem.emit(path);
}
void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept { void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept {
if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) {
if (ImGui::MenuItem("Delete")) { if (ImGui::MenuItem("Delete")) {

View File

@ -28,8 +28,6 @@ class ProjectExplorer final: public FileExplorer {
protected: protected:
void fileOpened(ox::StringViewCR path) const noexcept override; void fileOpened(ox::StringViewCR path) const noexcept override;
void fileDeleted(ox::StringViewCR path) const noexcept override;
void fileContextMenu(ox::StringViewCR path) const noexcept override; void fileContextMenu(ox::StringViewCR path) const noexcept override;
void dirContextMenu(ox::StringViewCR path) const noexcept override; void dirContextMenu(ox::StringViewCR path) const noexcept override;

View File

@ -225,9 +225,7 @@ void StudioUI::drawTabs() noexcept {
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] { if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
m_activeEditor->onActivated(); m_activeEditor->onActivated();
} }
if (open) [[likely]] { e->draw(m_sctx);
e->draw(m_sctx);
}
m_activeEditorOnLastDraw = e.get(); m_activeEditorOnLastDraw = e.get();
} }
ImGui::EndTabItem(); ImGui::EndTabItem();

View File

@ -1,38 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include "popup.hpp"
#include "filetreemodel.hpp"
namespace studio {
class FilePickerPopup {
private:
ox::String m_name;
FileExplorer m_explorer;
ox::Vector<ox::String> const m_fileExts;
bool m_open{};
public:
explicit FilePickerPopup(ox::StringParam name, keel::Context &kctx, ox::StringParam fileExt) noexcept;
explicit FilePickerPopup(ox::StringParam name, keel::Context &kctx, ox::Vector<ox::String> fileExts) noexcept;
void refresh() noexcept;
void open() noexcept;
void close() noexcept;
[[nodiscard]]
bool isOpen() const noexcept;
ox::Optional<ox::String> draw(StudioContext &ctx) noexcept;
};
}

View File

@ -15,7 +15,7 @@
namespace studio { namespace studio {
constexpr void safeDelete(class FileTreeModel const *m) noexcept; constexpr void safeDelete(class FileTreeModel *m) noexcept;
class FileExplorer: public ox::SignalHandler { class FileExplorer: public ox::SignalHandler {
@ -32,6 +32,8 @@ class FileExplorer: public ox::SignalHandler {
m_kctx{kctx}, m_kctx{kctx},
m_fileDraggable{fileDraggable} {} m_fileDraggable{fileDraggable} {}
virtual ~FileExplorer() = default;
void draw(StudioContext &ctx, ImVec2 const &sz) const noexcept; void draw(StudioContext &ctx, ImVec2 const &sz) const noexcept;
void setModel(ox::UPtr<FileTreeModel> &&model, bool selectRoot = false) noexcept; void setModel(ox::UPtr<FileTreeModel> &&model, bool selectRoot = false) noexcept;
@ -43,8 +45,6 @@ class FileExplorer: public ox::SignalHandler {
virtual void fileOpened(ox::StringViewCR path) const noexcept; virtual void fileOpened(ox::StringViewCR path) const noexcept;
virtual void fileDeleted(ox::StringViewCR path) const noexcept;
void drawFileContextMenu(ox::CStringViewCR path) const noexcept; void drawFileContextMenu(ox::CStringViewCR path) const noexcept;
void drawDirContextMenu(ox::CStringViewCR path) const noexcept; void drawDirContextMenu(ox::CStringViewCR path) const noexcept;
@ -116,7 +116,7 @@ class FileTreeModel {
}; };
constexpr void safeDelete(FileTreeModel const *m) noexcept { constexpr void safeDelete(FileTreeModel *m) noexcept {
delete m; delete m;
} }

View File

@ -155,23 +155,6 @@ TextInput<ox::IString<MaxChars>> InputText(
return out; return out;
} }
template<size_t MaxChars = 50>
TextInput<ox::IString<MaxChars>> InputTextWithHint(
ox::CStringViewCR label,
ox::CStringViewCR hint,
ox::StringViewCR currentText,
ImGuiInputTextFlags const flags = 0,
ImGuiInputTextCallback const callback = nullptr,
void *user_data = nullptr) noexcept {
TextInput<ox::IString<MaxChars>> out = {.text = currentText};
out.changed = ImGui::InputTextWithHint(
label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
if (out.changed) {
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
}
return out;
}
template<size_t StrCap> template<size_t StrCap>
bool InputText( bool InputText(
ox::CStringViewCR label, ox::CStringViewCR label,

View File

@ -2,7 +2,6 @@ add_library(
Studio Studio
configio.cpp configio.cpp
editor.cpp editor.cpp
filepickerpopup.cpp
filetreemodel.cpp filetreemodel.cpp
imguiutil.cpp imguiutil.cpp
module.cpp module.cpp

View File

@ -1,75 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/imguiutil.hpp>
#include <studio/filepickerpopup.hpp>
namespace studio {
FilePickerPopup::FilePickerPopup(
ox::StringParam name,
keel::Context &kctx,
ox::StringParam fileExt) noexcept:
m_name{std::move(name)},
m_explorer{kctx},
m_fileExts{std::move(fileExt)} {
}
FilePickerPopup::FilePickerPopup(
ox::StringParam name,
keel::Context &kctx,
ox::Vector<ox::String> fileExts) noexcept:
m_name{std::move(name)},
m_explorer{kctx},
m_fileExts{std::move(fileExts)} {
}
void FilePickerPopup::refresh() noexcept {
m_explorer.setModel(buildFileTreeModel(
m_explorer,
[this](ox::StringViewCR path, ox::FileStat const &s) {
auto const [ext, err] = fileExt(path);
return
s.fileType == ox::FileType::Directory ||
(s.fileType == ox::FileType::NormalFile && !err && m_fileExts.contains(ext));
},
false).or_value(ox::UPtr<FileTreeModel>{}));
}
void FilePickerPopup::open() noexcept {
refresh();
m_open = true;
}
void FilePickerPopup::close() noexcept {
m_explorer.setModel(ox::UPtr<FileTreeModel>{});
m_open = false;
}
bool FilePickerPopup::isOpen() const noexcept {
return m_open;
}
ox::Optional<ox::String> FilePickerPopup::draw(StudioContext &ctx) noexcept {
ox::Optional<ox::String> out;
if (!m_open) {
return out;
}
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();
}
ImGui::EndPopup();
}
return out;
}
}

View File

@ -39,8 +39,6 @@ ox::Optional<ox::String> FileExplorer::selectedPath() const {
void FileExplorer::fileOpened(ox::StringViewCR) const noexcept {} void FileExplorer::fileOpened(ox::StringViewCR) const noexcept {}
void FileExplorer::fileDeleted(ox::StringViewCR) const noexcept {}
void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept { void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept {
ig::IDStackItem const idStackItem{path}; ig::IDStackItem const idStackItem{path};
fileContextMenu(path); fileContextMenu(path);
@ -86,7 +84,7 @@ void FileTreeModel::draw(turbine::Context &tctx) const noexcept {
auto const selected = m_explorer.selected(this) ? ImGuiTreeNodeFlags_Selected : 0; auto const selected = m_explorer.selected(this) ? ImGuiTreeNodeFlags_Selected : 0;
if (!m_children.empty()) { if (!m_children.empty()) {
auto const nodeOpen = ImGui::TreeNodeEx(m_name.c_str(), dirFlags | selected); auto const nodeOpen = ImGui::TreeNodeEx(m_name.c_str(), dirFlags | selected);
if (ImGui::IsItemActivated() || ImGui::IsItemClicked(1)) { if (ImGui::IsItemClicked()) {
m_explorer.setSelectedNode(this); m_explorer.setSelectedNode(this);
} }
ig::IDStackItem const idStackItem{m_name}; ig::IDStackItem const idStackItem{m_name};
@ -99,15 +97,11 @@ void FileTreeModel::draw(turbine::Context &tctx) const noexcept {
} }
} else { } else {
if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) { if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) {
if (ImGui::IsItemActivated() || ImGui::IsItemClicked(1)) { if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
m_explorer.setSelectedNode(this);
}
if ((ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) ||
(ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter))) {
m_explorer.fileOpened(m_fullPath); m_explorer.fileOpened(m_fullPath);
} }
if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete)) { if (ImGui::IsItemClicked()) {
m_explorer.fileDeleted(m_fullPath); m_explorer.setSelectedNode(this);
} }
m_explorer.drawFileContextMenu(m_fullPath); m_explorer.drawFileContextMenu(m_fullPath);
ImGui::TreePop(); ImGui::TreePop();