[studio] Add popup to warn about UUID duplication
This commit is contained in:
		@@ -17,6 +17,12 @@
 | 
			
		||||
#include "font.hpp"
 | 
			
		||||
#include "studioui.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef OX_OS_Darwin
 | 
			
		||||
#define STUDIO_CTRL "Cmd"
 | 
			
		||||
#else
 | 
			
		||||
#define STUDIO_CTRL "Ctrl"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace studio {
 | 
			
		||||
 | 
			
		||||
static bool shutdownHandler(turbine::Context &ctx) {
 | 
			
		||||
@@ -174,45 +180,64 @@ bool StudioUI::handleShutdown() noexcept {
 | 
			
		||||
void StudioUI::drawMenu() noexcept {
 | 
			
		||||
	if (ImGui::BeginMainMenuBar()) {
 | 
			
		||||
		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();
 | 
			
		||||
			}
 | 
			
		||||
			if (ImGui::MenuItem("New Project...", "Ctrl+Shift+N")) {
 | 
			
		||||
			if (ImGui::MenuItem("New Project...", STUDIO_CTRL "+Shift+N")) {
 | 
			
		||||
				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));
 | 
			
		||||
			}
 | 
			
		||||
			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();
 | 
			
		||||
			}
 | 
			
		||||
			if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
 | 
			
		||||
			if (ImGui::MenuItem("Quit", STUDIO_CTRL "+Q")) {
 | 
			
		||||
				turbine::requestShutdown(m_tctx);
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::EndMenu();
 | 
			
		||||
		}
 | 
			
		||||
		if (ImGui::BeginMenu("Edit")) {
 | 
			
		||||
			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());
 | 
			
		||||
			}
 | 
			
		||||
			if (ImGui::MenuItem("Redo", "Ctrl+Y", false, undoStack && undoStack->canRedo())) {
 | 
			
		||||
			if (ImGui::MenuItem(
 | 
			
		||||
					"Redo", STUDIO_CTRL "+Y", false, undoStack && undoStack->canRedo())) {
 | 
			
		||||
				oxLogError(undoStack->redo());
 | 
			
		||||
			}
 | 
			
		||||
			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();
 | 
			
		||||
			}
 | 
			
		||||
			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();
 | 
			
		||||
			}
 | 
			
		||||
			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();
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::EndMenu();
 | 
			
		||||
		}
 | 
			
		||||
		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();
 | 
			
		||||
			}
 | 
			
		||||
			ImGui::EndMenu();
 | 
			
		||||
@@ -278,12 +303,8 @@ void StudioUI::drawTabs() noexcept {
 | 
			
		||||
				if (m_activeEditor == (*it).get()) {
 | 
			
		||||
					m_activeEditor = nullptr;
 | 
			
		||||
				}
 | 
			
		||||
				try {
 | 
			
		||||
					OX_THROW_ERROR(m_editors.erase(it).moveTo(it));
 | 
			
		||||
				} 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());
 | 
			
		||||
				if (auto const err = m_editors.erase(it).moveTo(it)) {
 | 
			
		||||
					oxErrf("Editor tab deletion failed: {} ({}:{})\n", toStr(err), err.src.file_name(), err.src.line());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -478,12 +499,25 @@ ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
 | 
			
		||||
 | 
			
		||||
ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
 | 
			
		||||
	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::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
 | 
			
		||||
			        .moveTo(m_project));
 | 
			
		||||
	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_copyFilePopup.makeCopy.connect(m_sctx.project, &Project::copyItem);
 | 
			
		||||
	m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir);
 | 
			
		||||
 
 | 
			
		||||
@@ -52,11 +52,12 @@ class StudioUI: public ox::SignalHandler {
 | 
			
		||||
			"Close Application?",
 | 
			
		||||
			"There are files with unsaved changes. Close?"
 | 
			
		||||
		};
 | 
			
		||||
		ig::MessagePopup m_messagePopup{"Message", ""};
 | 
			
		||||
		MakeCopyPopup m_copyFilePopup;
 | 
			
		||||
		RenameFile m_renameFile;
 | 
			
		||||
		NewProject m_newProject;
 | 
			
		||||
		AboutPopup m_aboutPopup;
 | 
			
		||||
		ox::Array<Widget*, 9> const m_widgets {
 | 
			
		||||
		ox::Array<Widget*, 10> const m_widgets {
 | 
			
		||||
			&m_closeFileConfirm,
 | 
			
		||||
			&m_closeAppConfirm,
 | 
			
		||||
			&m_copyFilePopup,
 | 
			
		||||
@@ -66,6 +67,7 @@ class StudioUI: public ox::SignalHandler {
 | 
			
		||||
			&m_deleteConfirmation,
 | 
			
		||||
			&m_newDirDialog,
 | 
			
		||||
			&m_renameFile,
 | 
			
		||||
			&m_messagePopup,
 | 
			
		||||
		};
 | 
			
		||||
		bool m_showProjectExplorer = true;
 | 
			
		||||
		struct NavAction {
 | 
			
		||||
 
 | 
			
		||||
@@ -169,7 +169,9 @@ TextInput<ox::IString<MaxChars>> InputText(
 | 
			
		||||
	out.changed = ImGui::InputText(
 | 
			
		||||
			label.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
 | 
			
		||||
	if (out.changed) {
 | 
			
		||||
		OX_ALLOW_UNSAFE_BUFFERS_BEGIN
 | 
			
		||||
		std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
 | 
			
		||||
		OX_ALLOW_UNSAFE_BUFFERS_END
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
@@ -186,7 +188,9 @@ TextInput<ox::IString<MaxChars>> InputTextWithHint(
 | 
			
		||||
	out.changed = ImGui::InputTextWithHint(
 | 
			
		||||
			label.c_str(), hint.c_str(), out.text.data(), MaxChars + 1, flags, callback, user_data);
 | 
			
		||||
	if (out.changed) {
 | 
			
		||||
		OX_ALLOW_UNSAFE_BUFFERS_BEGIN
 | 
			
		||||
		std::ignore = out.text.unsafeResize(ox::strlen(out.text.c_str()));
 | 
			
		||||
		OX_ALLOW_UNSAFE_BUFFERS_END
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
@@ -201,7 +205,9 @@ bool InputText(
 | 
			
		||||
	auto const out = ImGui::InputText(
 | 
			
		||||
		label.c_str(), text.data(), StrCap + 1, flags, callback, user_data);
 | 
			
		||||
	if (out) {
 | 
			
		||||
		OX_ALLOW_UNSAFE_BUFFERS_BEGIN
 | 
			
		||||
		std::ignore = text.unsafeResize(ox::strlen(text.c_str()));
 | 
			
		||||
		OX_ALLOW_UNSAFE_BUFFERS_END
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
@@ -223,6 +229,10 @@ PopupResponse PopupControlsOkCancel(
 | 
			
		||||
	ox::CStringViewCR ok = "OK",
 | 
			
		||||
	ox::CStringViewCR cancel = "Cancel");
 | 
			
		||||
 | 
			
		||||
PopupResponse PopupControlsOk(
 | 
			
		||||
	bool &popupOpen,
 | 
			
		||||
	ox::CStringViewCR ok);
 | 
			
		||||
 | 
			
		||||
[[nodiscard]]
 | 
			
		||||
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 callback
 | 
			
		||||
 * @param f callback function
 | 
			
		||||
 * @param selectedIdx
 | 
			
		||||
 * @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 {
 | 
			
		||||
	private:
 | 
			
		||||
		bool m_show{};
 | 
			
		||||
		studio::StudioContext &m_sctx;
 | 
			
		||||
		StudioContext &m_sctx;
 | 
			
		||||
		ox::String const m_title;
 | 
			
		||||
		ox::String const m_fileExt;
 | 
			
		||||
		ImVec2 const m_size;
 | 
			
		||||
@@ -304,8 +314,8 @@ class FilePicker {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class QuestionPopup: public Widget {
 | 
			
		||||
 	private:
 | 
			
		||||
class Popup: public Widget {
 | 
			
		||||
	protected:
 | 
			
		||||
		enum class Stage {
 | 
			
		||||
			Closed,
 | 
			
		||||
			Opening,
 | 
			
		||||
@@ -314,12 +324,11 @@ class QuestionPopup: public Widget {
 | 
			
		||||
		Stage m_stage = Stage::Closed;
 | 
			
		||||
		bool m_open{};
 | 
			
		||||
		ox::String m_title;
 | 
			
		||||
		ox::String m_question;
 | 
			
		||||
 | 
			
		||||
	public:
 | 
			
		||||
		ox::Signal<ox::Error(PopupResponse)> response;
 | 
			
		||||
 | 
			
		||||
		QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;
 | 
			
		||||
		explicit Popup(ox::StringParam title) noexcept;
 | 
			
		||||
 | 
			
		||||
		void open() noexcept;
 | 
			
		||||
 | 
			
		||||
@@ -328,7 +337,33 @@ class QuestionPopup: public Widget {
 | 
			
		||||
		[[nodiscard]]
 | 
			
		||||
		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;
 | 
			
		||||
	constexpr auto btnSz = ImVec2{50, BtnSz.y};
 | 
			
		||||
	ImGui::Separator();
 | 
			
		||||
	ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 101);
 | 
			
		||||
	ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - 42);
 | 
			
		||||
	if (ImGui::Button(ok.c_str(), btnSz)) {
 | 
			
		||||
		popupOpen = false;
 | 
			
		||||
		out = PopupResponse::OK;
 | 
			
		||||
@@ -245,24 +245,28 @@ void FilePicker::show() noexcept {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept:
 | 
			
		||||
	m_title{std::move(title)},
 | 
			
		||||
	m_question{std::move(question)} {
 | 
			
		||||
Popup::Popup(ox::StringParam title) noexcept: m_title{std::move(title)} {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QuestionPopup::open() noexcept {
 | 
			
		||||
void Popup::open() noexcept {
 | 
			
		||||
	m_stage = Stage::Opening;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QuestionPopup::close() noexcept {
 | 
			
		||||
void Popup::close() noexcept {
 | 
			
		||||
	m_stage = Stage::Closed;
 | 
			
		||||
	m_open = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool QuestionPopup::isOpen() const noexcept {
 | 
			
		||||
bool Popup::isOpen() const noexcept {
 | 
			
		||||
	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 {
 | 
			
		||||
	switch (m_stage) {
 | 
			
		||||
		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 mainWinHasFocus() noexcept {
 | 
			
		||||
	return s_mainWinHasFocus;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user