[studio] Add popup to warn about UUID duplication
This commit is contained in:
parent
d4329981e7
commit
ff1e8f260b
@ -17,6 +17,12 @@
|
|||||||
#include "font.hpp"
|
#include "font.hpp"
|
||||||
#include "studioui.hpp"
|
#include "studioui.hpp"
|
||||||
|
|
||||||
|
#ifdef OX_OS_Darwin
|
||||||
|
#define STUDIO_CTRL "Cmd"
|
||||||
|
#else
|
||||||
|
#define STUDIO_CTRL "Ctrl"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace studio {
|
namespace studio {
|
||||||
|
|
||||||
static bool shutdownHandler(turbine::Context &ctx) {
|
static bool shutdownHandler(turbine::Context &ctx) {
|
||||||
@ -174,45 +180,64 @@ bool StudioUI::handleShutdown() noexcept {
|
|||||||
void StudioUI::drawMenu() 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", false, m_project)) {
|
if (ImGui::MenuItem("New...", STUDIO_CTRL "+N", false, m_project)) {
|
||||||
m_newMenu.open();
|
m_newMenu.open();
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("New Project...", "Ctrl+Shift+N")) {
|
if (ImGui::MenuItem("New Project...", STUDIO_CTRL "+Shift+N")) {
|
||||||
m_newProject.open();
|
m_newProject.open();
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Open Project...", "Ctrl+O")) {
|
if (ImGui::MenuItem("Open Project...", STUDIO_CTRL "+O")) {
|
||||||
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
|
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Save", "Ctrl+S", false, m_activeEditor && m_activeEditor->unsavedChanges())) {
|
if (ImGui::MenuItem(
|
||||||
|
"Save",
|
||||||
|
STUDIO_CTRL "+S",
|
||||||
|
false,
|
||||||
|
m_activeEditor && m_activeEditor->unsavedChanges())) {
|
||||||
m_activeEditor->save();
|
m_activeEditor->save();
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
|
if (ImGui::MenuItem("Quit", STUDIO_CTRL "+Q")) {
|
||||||
turbine::requestShutdown(m_tctx);
|
turbine::requestShutdown(m_tctx);
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginMenu("Edit")) {
|
if (ImGui::BeginMenu("Edit")) {
|
||||||
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
|
||||||
if (ImGui::MenuItem("Undo", "Ctrl+Z", false, undoStack && undoStack->canUndo())) {
|
if (ImGui::MenuItem(
|
||||||
|
"Undo", STUDIO_CTRL "+Z", false, undoStack && undoStack->canUndo())) {
|
||||||
oxLogError(undoStack->undo());
|
oxLogError(undoStack->undo());
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Redo", "Ctrl+Y", false, undoStack && undoStack->canRedo())) {
|
if (ImGui::MenuItem(
|
||||||
|
"Redo", STUDIO_CTRL "+Y", false, undoStack && undoStack->canRedo())) {
|
||||||
oxLogError(undoStack->redo());
|
oxLogError(undoStack->redo());
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("Copy", "Ctrl+C", false, m_activeEditor && m_activeEditor->copyEnabled())) {
|
if (ImGui::MenuItem(
|
||||||
|
"Copy",
|
||||||
|
STUDIO_CTRL "+C",
|
||||||
|
false,
|
||||||
|
m_activeEditor && m_activeEditor->copyEnabled())) {
|
||||||
m_activeEditor->copy();
|
m_activeEditor->copy();
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Cut", "Ctrl+X", false, m_activeEditor && m_activeEditor->cutEnabled())) {
|
if (ImGui::MenuItem(
|
||||||
|
"Cut",
|
||||||
|
STUDIO_CTRL "+X",
|
||||||
|
false,
|
||||||
|
m_activeEditor && m_activeEditor->cutEnabled())) {
|
||||||
m_activeEditor->cut();
|
m_activeEditor->cut();
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Paste", "Ctrl+V", false, m_activeEditor && m_activeEditor->pasteEnabled())) {
|
if (ImGui::MenuItem(
|
||||||
|
"Paste",
|
||||||
|
STUDIO_CTRL "+V",
|
||||||
|
false,
|
||||||
|
m_activeEditor && m_activeEditor->pasteEnabled())) {
|
||||||
m_activeEditor->paste();
|
m_activeEditor->paste();
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginMenu("View")) {
|
if (ImGui::BeginMenu("View")) {
|
||||||
if (ImGui::MenuItem("Project Explorer", "Ctrl+Shift+1", m_showProjectExplorer)) {
|
if (ImGui::MenuItem(
|
||||||
|
"Project Explorer", STUDIO_CTRL "+Shift+1", m_showProjectExplorer)) {
|
||||||
toggleProjectExplorer();
|
toggleProjectExplorer();
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
@ -278,12 +303,8 @@ void StudioUI::drawTabs() noexcept {
|
|||||||
if (m_activeEditor == (*it).get()) {
|
if (m_activeEditor == (*it).get()) {
|
||||||
m_activeEditor = nullptr;
|
m_activeEditor = nullptr;
|
||||||
}
|
}
|
||||||
try {
|
if (auto const err = m_editors.erase(it).moveTo(it)) {
|
||||||
OX_THROW_ERROR(m_editors.erase(it).moveTo(it));
|
oxErrf("Editor tab deletion failed: {} ({}:{})\n", toStr(err), err.src.file_name(), err.src.line());
|
||||||
} catch (ox::Exception const&ex) {
|
|
||||||
oxErrf("Editor tab deletion failed: {} ({}:{})\n", ex.what(), ex.src.file_name(), ex.src.line());
|
|
||||||
} catch (std::exception const&ex) {
|
|
||||||
oxErrf("Editor tab deletion failed: {}\n", ex.what());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -478,12 +499,25 @@ ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
|
|||||||
|
|
||||||
ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
|
ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
|
||||||
OX_REQUIRE_M(fs, keel::loadRomFs(path.view()));
|
OX_REQUIRE_M(fs, keel::loadRomFs(path.view()));
|
||||||
OX_RETURN_ERROR(keel::setRomFs(keelCtx(m_tctx), std::move(fs)));
|
keel::DuplicateSet ds;
|
||||||
|
OX_RETURN_ERROR(keel::setRomFs(keelCtx(m_tctx), std::move(fs), ds));
|
||||||
|
if (ds.size()) {
|
||||||
|
ox::String msg;
|
||||||
|
msg += "Multiple files have the same UUID:\n";
|
||||||
|
for (auto const &k : ds.keys()) {
|
||||||
|
msg += ox::sfmt("\n\t{}:\n", k.toString());
|
||||||
|
for (auto const &v : ds[k]) {
|
||||||
|
msg += ox::sfmt("\t\t - {}\n", v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_messagePopup.show(msg);
|
||||||
|
}
|
||||||
OX_RETURN_ERROR(
|
OX_RETURN_ERROR(
|
||||||
ox::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
|
ox::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
|
||||||
.moveTo(m_project));
|
.moveTo(m_project));
|
||||||
m_sctx.project = m_project.get();
|
m_sctx.project = m_project.get();
|
||||||
turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
|
turbine::setWindowTitle(
|
||||||
|
m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
|
||||||
m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem);
|
m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem);
|
||||||
m_copyFilePopup.makeCopy.connect(m_sctx.project, &Project::copyItem);
|
m_copyFilePopup.makeCopy.connect(m_sctx.project, &Project::copyItem);
|
||||||
m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir);
|
m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir);
|
||||||
|
@ -52,11 +52,12 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
"Close Application?",
|
"Close Application?",
|
||||||
"There are files with unsaved changes. Close?"
|
"There are files with unsaved changes. Close?"
|
||||||
};
|
};
|
||||||
|
ig::MessagePopup m_messagePopup{"Message", ""};
|
||||||
MakeCopyPopup m_copyFilePopup;
|
MakeCopyPopup m_copyFilePopup;
|
||||||
RenameFile m_renameFile;
|
RenameFile m_renameFile;
|
||||||
NewProject m_newProject;
|
NewProject m_newProject;
|
||||||
AboutPopup m_aboutPopup;
|
AboutPopup m_aboutPopup;
|
||||||
ox::Array<Widget*, 9> const m_widgets {
|
ox::Array<Widget*, 10> const m_widgets {
|
||||||
&m_closeFileConfirm,
|
&m_closeFileConfirm,
|
||||||
&m_closeAppConfirm,
|
&m_closeAppConfirm,
|
||||||
&m_copyFilePopup,
|
&m_copyFilePopup,
|
||||||
@ -66,6 +67,7 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
&m_deleteConfirmation,
|
&m_deleteConfirmation,
|
||||||
&m_newDirDialog,
|
&m_newDirDialog,
|
||||||
&m_renameFile,
|
&m_renameFile,
|
||||||
|
&m_messagePopup,
|
||||||
};
|
};
|
||||||
bool m_showProjectExplorer = true;
|
bool m_showProjectExplorer = true;
|
||||||
struct NavAction {
|
struct NavAction {
|
||||||
|
@ -169,7 +169,9 @@ TextInput<ox::IString<MaxChars>> InputText(
|
|||||||
out.changed = ImGui::InputText(
|
out.changed = ImGui::InputText(
|
||||||
label.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
|
label.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
|
||||||
if (out.changed) {
|
if (out.changed) {
|
||||||
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||||
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
|
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
|
||||||
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -186,7 +188,9 @@ TextInput<ox::IString<MaxChars>> InputTextWithHint(
|
|||||||
out.changed = ImGui::InputTextWithHint(
|
out.changed = ImGui::InputTextWithHint(
|
||||||
label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
|
label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
|
||||||
if (out.changed) {
|
if (out.changed) {
|
||||||
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||||
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
|
std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
|
||||||
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -201,7 +205,9 @@ bool InputText(
|
|||||||
auto const out = ImGui::InputText(
|
auto const out = ImGui::InputText(
|
||||||
label.c_str(), text.data(), StrCap + 1, flags, callback, user_data);
|
label.c_str(), text.data(), StrCap + 1, flags, callback, user_data);
|
||||||
if (out) {
|
if (out) {
|
||||||
|
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||||
std::ignore = text.unsafeResize(ox::strlen(text.c_str()));
|
std::ignore = text.unsafeResize(ox::strlen(text.c_str()));
|
||||||
|
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -223,6 +229,10 @@ PopupResponse PopupControlsOkCancel(
|
|||||||
ox::CStringViewCR ok = "OK",
|
ox::CStringViewCR ok = "OK",
|
||||||
ox::CStringViewCR cancel = "Cancel");
|
ox::CStringViewCR cancel = "Cancel");
|
||||||
|
|
||||||
|
PopupResponse PopupControlsOk(
|
||||||
|
bool &popupOpen,
|
||||||
|
ox::CStringViewCR ok);
|
||||||
|
|
||||||
[[nodiscard]]
|
[[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});
|
||||||
|
|
||||||
@ -250,7 +260,7 @@ bool ComboBox(ox::CStringView lbl, ox::Span<const ox::String> list, size_t &sele
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param lbl
|
* @param lbl
|
||||||
* @param callback
|
* @param f callback function
|
||||||
* @param selectedIdx
|
* @param selectedIdx
|
||||||
* @return true if new value selected, false otherwise
|
* @return true if new value selected, false otherwise
|
||||||
*/
|
*/
|
||||||
@ -285,7 +295,7 @@ bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const&list, size_t
|
|||||||
class FilePicker {
|
class FilePicker {
|
||||||
private:
|
private:
|
||||||
bool m_show{};
|
bool m_show{};
|
||||||
studio::StudioContext &m_sctx;
|
StudioContext &m_sctx;
|
||||||
ox::String const m_title;
|
ox::String const m_title;
|
||||||
ox::String const m_fileExt;
|
ox::String const m_fileExt;
|
||||||
ImVec2 const m_size;
|
ImVec2 const m_size;
|
||||||
@ -304,8 +314,8 @@ class FilePicker {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class QuestionPopup: public Widget {
|
class Popup: public Widget {
|
||||||
private:
|
protected:
|
||||||
enum class Stage {
|
enum class Stage {
|
||||||
Closed,
|
Closed,
|
||||||
Opening,
|
Opening,
|
||||||
@ -314,12 +324,11 @@ class QuestionPopup: public Widget {
|
|||||||
Stage m_stage = Stage::Closed;
|
Stage m_stage = Stage::Closed;
|
||||||
bool m_open{};
|
bool m_open{};
|
||||||
ox::String m_title;
|
ox::String m_title;
|
||||||
ox::String m_question;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ox::Signal<ox::Error(PopupResponse)> response;
|
ox::Signal<ox::Error(PopupResponse)> response;
|
||||||
|
|
||||||
QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;
|
explicit Popup(ox::StringParam title) noexcept;
|
||||||
|
|
||||||
void open() noexcept;
|
void open() noexcept;
|
||||||
|
|
||||||
@ -328,7 +337,33 @@ class QuestionPopup: public Widget {
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
bool isOpen() const noexcept;
|
bool isOpen() const noexcept;
|
||||||
|
|
||||||
void draw(StudioContext &ctx) noexcept;
|
};
|
||||||
|
|
||||||
|
class QuestionPopup: public Popup {
|
||||||
|
private:
|
||||||
|
ox::String m_question;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ox::Signal<ox::Error(PopupResponse)> response;
|
||||||
|
|
||||||
|
QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;
|
||||||
|
|
||||||
|
void draw(StudioContext &ctx) noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class MessagePopup: public Popup {
|
||||||
|
private:
|
||||||
|
ox::String m_msg;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ox::Signal<ox::Error(PopupResponse)> response;
|
||||||
|
|
||||||
|
MessagePopup(ox::StringParam title, ox::StringParam msg) noexcept;
|
||||||
|
|
||||||
|
void show(ox::StringParam msg) noexcept;
|
||||||
|
|
||||||
|
void draw(StudioContext &ctx) noexcept override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ PopupResponse PopupControlsOk(
|
|||||||
auto out = PopupResponse::None;
|
auto out = PopupResponse::None;
|
||||||
constexpr auto btnSz = ImVec2{50, BtnSz.y};
|
constexpr auto btnSz = ImVec2{50, BtnSz.y};
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 101);
|
ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 42);
|
||||||
if (ImGui::Button(ok.c_str(), btnSz)) {
|
if (ImGui::Button(ok.c_str(), btnSz)) {
|
||||||
popupOpen = false;
|
popupOpen = false;
|
||||||
out = PopupResponse::OK;
|
out = PopupResponse::OK;
|
||||||
@ -245,24 +245,28 @@ void FilePicker::show() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept:
|
Popup::Popup(ox::StringParam title) noexcept: m_title{std::move(title)} {
|
||||||
m_title{std::move(title)},
|
|
||||||
m_question{std::move(question)} {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuestionPopup::open() noexcept {
|
void Popup::open() noexcept {
|
||||||
m_stage = Stage::Opening;
|
m_stage = Stage::Opening;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuestionPopup::close() noexcept {
|
void Popup::close() noexcept {
|
||||||
m_stage = Stage::Closed;
|
m_stage = Stage::Closed;
|
||||||
m_open = false;
|
m_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuestionPopup::isOpen() const noexcept {
|
bool Popup::isOpen() const noexcept {
|
||||||
return m_open;
|
return m_open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept:
|
||||||
|
Popup{std::move(title)},
|
||||||
|
m_question{std::move(question)} {
|
||||||
|
}
|
||||||
|
|
||||||
void QuestionPopup::draw(StudioContext &ctx) noexcept {
|
void QuestionPopup::draw(StudioContext &ctx) noexcept {
|
||||||
switch (m_stage) {
|
switch (m_stage) {
|
||||||
case Stage::Closed:
|
case Stage::Closed:
|
||||||
@ -298,6 +302,49 @@ void QuestionPopup::draw(StudioContext &ctx) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MessagePopup::MessagePopup(ox::StringParam title, ox::StringParam msg) noexcept:
|
||||||
|
Popup{std::move(title)},
|
||||||
|
m_msg{std::move(msg)} {
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePopup::show(ox::StringParam msg) noexcept {
|
||||||
|
m_msg = std::move(msg);
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePopup::draw(StudioContext &ctx) noexcept {
|
||||||
|
switch (m_stage) {
|
||||||
|
case Stage::Closed:
|
||||||
|
break;
|
||||||
|
case Stage::Opening:
|
||||||
|
ImGui::OpenPopup(m_title.c_str());
|
||||||
|
m_stage = Stage::Open;
|
||||||
|
m_open = true;
|
||||||
|
[[fallthrough]];
|
||||||
|
case Stage::Open:
|
||||||
|
centerNextWindow(ctx.tctx);
|
||||||
|
ImGui::SetNextWindowSize({});
|
||||||
|
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||||
|
if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
|
||||||
|
ImGui::Text("%s", m_msg.c_str());
|
||||||
|
auto const r = PopupControlsOk(m_open, "OK");
|
||||||
|
switch (r) {
|
||||||
|
case PopupResponse::None:
|
||||||
|
break;
|
||||||
|
case PopupResponse::OK:
|
||||||
|
response.emit(r);
|
||||||
|
close();
|
||||||
|
break;
|
||||||
|
case PopupResponse::Cancel:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool s_mainWinHasFocus{};
|
bool s_mainWinHasFocus{};
|
||||||
bool mainWinHasFocus() noexcept {
|
bool mainWinHasFocus() noexcept {
|
||||||
return s_mainWinHasFocus;
|
return s_mainWinHasFocus;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user