diff --git a/dpf b/dpf index 2fa5873..f3d3818 160000 --- a/dpf +++ b/dpf @@ -1 +1 @@ -Subproject commit 2fa587358b9224646a69c0ac8bb5a31790acac3b +Subproject commit f3d38188c95b482d3311e84e9140837734f28af6 diff --git a/src/AsyncDialog.cpp b/src/AsyncDialog.cpp new file mode 100644 index 0000000..75b573b --- /dev/null +++ b/src/AsyncDialog.cpp @@ -0,0 +1,127 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +#include "AsyncDialog.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace asyncDialog +{ + +using namespace rack; +using namespace rack::ui; +using namespace rack::widget; + +struct AsyncDialog : OpaqueWidget +{ + SequentialLayout* layout; + SequentialLayout* contentLayout; + SequentialLayout* buttonLayout; + Label* label; + + AsyncDialog(const char* const message, const std::function action) + { + box.size = math::Vec(400, 120); + const float margin = 10; + const float buttonWidth = 100; + + layout = new SequentialLayout; + layout->box.pos = math::Vec(0, 0); + layout->box.size = box.size; + layout->orientation = SequentialLayout::VERTICAL_ORIENTATION; + layout->margin = math::Vec(margin, margin); + layout->spacing = math::Vec(margin, margin); + layout->wrap = false; + addChild(layout); + + contentLayout = new SequentialLayout; + contentLayout->spacing = math::Vec(margin, margin); + layout->addChild(contentLayout); + + buttonLayout = new SequentialLayout; + buttonLayout->alignment = SequentialLayout::CENTER_ALIGNMENT; + buttonLayout->box.size = box.size; + buttonLayout->spacing = math::Vec(margin, margin); + layout->addChild(buttonLayout); + + label = new Label; + label->box.size.x = box.size.x - 2*margin; + label->box.size.y = box.size.y - 2*margin - 40; + label->fontSize = 16; + label->text = message; + contentLayout->addChild(label); + + struct AsyncCancelButton : Button { + AsyncDialog* dialog; + void onAction(const ActionEvent& e) override { + dialog->getParent()->requestDelete(); + } + }; + AsyncCancelButton* const cancelButton = new AsyncCancelButton; + cancelButton->box.size.x = buttonWidth; + cancelButton->text = "Cancel"; + cancelButton->dialog = this; + buttonLayout->addChild(cancelButton); + + struct AsyncOkButton : Button { + AsyncDialog* dialog; + std::function action; + void onAction(const ActionEvent& e) override { + action(); + dialog->getParent()->requestDelete(); + } + }; + AsyncOkButton* const okButton = new AsyncOkButton; + okButton->box.size.x = buttonWidth; + okButton->text = "Ok"; + okButton->dialog = this; + okButton->action = action; + buttonLayout->addChild(okButton); + } + + void step() override + { + OpaqueWidget::step(); + + box.pos = parent->box.size.minus(box.size).div(2).round(); + } + + void draw(const DrawArgs& args) override + { + bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0); + Widget::draw(args); + } +}; + +void create(const char* const message, const std::function action) +{ + MenuOverlay* const overlay = new MenuOverlay; + overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33); + + AsyncDialog* const dialog = new AsyncDialog(message, action); + overlay->addChild(dialog); + + APP->scene->addChild(overlay); +} + +} diff --git a/src/AsyncDialog.hpp b/src/AsyncDialog.hpp new file mode 100644 index 0000000..4fc4c85 --- /dev/null +++ b/src/AsyncDialog.hpp @@ -0,0 +1,27 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +#include + +#pragma once + +namespace asyncDialog +{ + +void create(const char* message, std::function action); + +} diff --git a/src/Makefile b/src/Makefile index 1679091..6f8fa98 100644 --- a/src/Makefile +++ b/src/Makefile @@ -36,6 +36,7 @@ FILES_DSP += \ else FILES_UI = \ CardinalUI.cpp \ + AsyncDialog.cpp \ override/MenuBar.cpp \ override/Window.cpp endif diff --git a/src/override/MenuBar.cpp b/src/override/MenuBar.cpp index 596a75c..4b88a23 100644 --- a/src/override/MenuBar.cpp +++ b/src/override/MenuBar.cpp @@ -28,8 +28,6 @@ #include #include -#include - #include #include #include @@ -57,11 +55,18 @@ # undef DEBUG #endif +// for finding home dir +#ifndef ARCH_WIN +# include +# include +#endif + #ifdef HAVE_LIBLO # include #endif #include +#include "../AsyncDialog.hpp" #include "../PluginContext.hpp" // #define REMOTE_HOST "localhost" @@ -95,6 +100,14 @@ struct MenuButton : ui::Button { //////////////////// +static void promptClear(const char* const message, const std::function action) +{ + if (APP->history->isSaved() || APP->scene->rack->hasModules()) + return action(); + + asyncDialog::create(message, action); +} + struct FileButton : MenuButton { Window& window; const bool isStandalone; @@ -129,16 +142,45 @@ struct FileButton : MenuButton { menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() { - // APP->patch->loadTemplateDialog(); - APP->patch->loadTemplate(); + // see APP->patch->loadTemplateDialog(); + promptClear("The current patch is unsaved. Clear it and start a new patch?", []() { + APP->patch->loadTemplate(); + }); })); menu->addChild(createMenuItem("Open", RACK_MOD_CTRL_NAME "+O", [this]() { - Window::FileBrowserOptions opts; - const std::string dir = system::getDirectory(APP->patch->path); - opts.startDir = dir.c_str(); - window.openFileBrowser(opts); - // APP->patch->loadDialog(); + // see APP->patch->loadDialog(); + promptClear("The current patch is unsaved. Clear it and open a new patch?", [this]() { + std::string dir; + if (! APP->patch->path.empty()) + { + dir = system::getDirectory(APP->patch->path); + } + else + { + // find home directory +#ifdef ARCH_WIN + if (const char* const userprofile = getenv("USERPROFILE")) + { + dir = userprofile; + } + else if (const char* const homedrive = getenv("HOMEDRIVE")) + { + if (const char* const homepath = getenv("HOMEPATH")) + dir = system::join(homedrive, homepath); + } +#else + if (struct passwd* const pwd = getpwuid(getuid())) + dir = pwd->pw_dir; + else if (const char* const home = getenv("HOME")) + dir = home; +#endif + } + + Window::FileBrowserOptions opts; + opts.startDir = dir.c_str(); + window.openFileBrowser(opts); + }); })); /* @@ -191,8 +233,12 @@ struct FileButton : MenuButton { menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { // APP->patch->revertDialog(); - APP->patch->loadAction(APP->patch->path); - }, APP->patch->path == "")); + if (APP->patch->path.empty()) + return; + promptClear("Revert patch to the last saved state?", []{ + APP->patch->loadAction(APP->patch->path); + }); + }, APP->patch->path.empty())); if (isStandalone) { menu->addChild(new ui::MenuSeparator);