Browse Source

Add tip window. Add "showTipsOnLaunch" and "tipIndex" to settings.

tags/v2.0.0
Andrew Belt 4 years ago
parent
commit
7a500c9c26
12 changed files with 299 additions and 30 deletions
  1. +1
    -0
      include/app/Scene.hpp
  2. +14
    -0
      include/app/TipWindow.hpp
  3. +1
    -0
      include/app/common.hpp
  4. +2
    -0
      include/settings.hpp
  5. +3
    -1
      include/ui/SequentialLayout.hpp
  6. +1
    -0
      include/widget/Widget.hpp
  7. +11
    -0
      src/app/MenuBar.cpp
  8. +5
    -0
      src/app/Scene.cpp
  9. +199
    -0
      src/app/TipWindow.cpp
  10. +14
    -0
      src/settings.cpp
  11. +43
    -29
      src/ui/SequentialLayout.cpp
  12. +5
    -0
      src/widget/Widget.cpp

+ 1
- 0
include/app/Scene.hpp View File

@@ -18,6 +18,7 @@ struct Scene : widget::OpaqueWidget {
RackWidget* rack;
widget::Widget* menuBar;
widget::Widget* moduleBrowser;
widget::Widget* tipWindow;
widget::Widget* frameRateWidget;

double lastAutosaveTime = 0.0;


+ 14
- 0
include/app/TipWindow.hpp View File

@@ -0,0 +1,14 @@
#pragma once
#include <app/common.hpp>
#include <widget/Widget.hpp>


namespace rack {
namespace app {


widget::Widget* tipWindowCreate();


} // namespace app
} // namespace rack

+ 1
- 0
include/app/common.hpp View File

@@ -3,6 +3,7 @@

#include <common.hpp>
#include <math.hpp>
#include <ui/common.hpp>


namespace rack {


+ 2
- 0
include/settings.hpp View File

@@ -54,6 +54,8 @@ extern std::vector<NVGcolor> cableColors;
// pluginSlug -> moduleSlugs
extern std::map<std::string, std::set<std::string>> moduleWhitelist;
extern bool autoCheckUpdates;
extern bool showTipsOnLaunch;
extern int tipIndex;

json_t* toJson();
void fromJson(json_t* rootJ);


+ 3
- 1
include/ui/SequentialLayout.hpp View File

@@ -21,7 +21,9 @@ struct SequentialLayout : widget::Widget {

Orientation orientation = HORIZONTAL_ORIENTATION;
Alignment alignment = LEFT_ALIGNMENT;
/** Space between adjacent elements */
/** Space between box bounds. */
math::Vec margin;
/** Space between adjacent elements. */
math::Vec spacing;

void step() override;


+ 1
- 0
include/widget/Widget.hpp View File

@@ -40,6 +40,7 @@ struct Widget : WeakBase {
void setPosition(math::Vec pos);
math::Vec getSize();
void setSize(math::Vec size);
widget::Widget* getParent();
bool isVisible();
void setVisible(bool visible);
void show() {


+ 11
- 0
src/app/MenuBar.cpp View File

@@ -887,6 +887,13 @@ struct CheckAppUpdateItem : ui::MenuItem {
};


struct TipItem : ui::MenuItem {
void onAction(const event::Action& e) override {
APP->scene->tipWindow->show();
}
};


struct HelpButton : MenuButton {
NotificationIcon* notification;

@@ -912,6 +919,10 @@ struct HelpButton : MenuButton {
menu->addChild(checkAppUpdateItem);
}

TipItem* tipItem = new TipItem;
tipItem->text = "Tips";
menu->addChild(tipItem);

UrlItem* manualItem = new UrlItem;
manualItem->text = "Manual";
manualItem->rightText = "F1";


+ 5
- 0
src/app/Scene.cpp View File

@@ -4,6 +4,7 @@

#include <app/Scene.hpp>
#include <app/ModuleBrowser.hpp>
#include <app/TipWindow.hpp>
#include <app/MenuBar.hpp>
#include <context.hpp>
#include <system.hpp>
@@ -45,6 +46,10 @@ Scene::Scene() {
moduleBrowser->hide();
addChild(moduleBrowser);

tipWindow = tipWindowCreate();
tipWindow->setVisible(settings::showTipsOnLaunch);
addChild(tipWindow);

frameRateWidget = new FrameRateWidget;
frameRateWidget->box.size = math::Vec(80.0, 30.0);
frameRateWidget->hide();


+ 199
- 0
src/app/TipWindow.cpp View File

@@ -0,0 +1,199 @@
#include <thread>

#include <app/TipWindow.hpp>
#include <widget/OpaqueWidget.hpp>
#include <ui/Label.hpp>
#include <ui/Button.hpp>
#include <ui/MenuItem.hpp>
#include <ui/SequentialLayout.hpp>
#include <settings.hpp>
#include <system.hpp>


namespace rack {
namespace app {


struct TipOverlay : widget::OpaqueWidget {
void step() override {
box = parent->box.zeroPos();
OpaqueWidget::step();
}

void onButton(const event::Button& e) override {
OpaqueWidget::onButton(e);
if (e.getTarget() != this)
return;

if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
hide();
e.consume(this);
}
}
};


struct UrlButton : ui::Button {
std::string url;
void onAction(const event::Action& e) override {
std::thread t([=] {
system::openBrowser(url);
});
t.detach();
}
};


struct TipInfo {
std::string text;
std::string linkText;
std::string linkUrl;
};


static std::vector<TipInfo> tipInfos = {
{"To add a module to the rack, right-click an empty rack space or press Enter. Click and drag a module from the Module Browser into the desired rack space.\n\nYou can force-move modules by holding " RACK_MOD_CTRL_NAME " while dragging it.", "", ""}, // reviewed
{"Pan around the rack by using the scroll bars, dragging while holding the middle mouse button, or pressing the arrow keys. Arrow key panning speed can be modified by holding " RACK_MOD_CTRL_NAME ", " RACK_MOD_SHIFT_NAME ", or " RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME ".\n\nZoom in and out using the View menu, " RACK_MOD_CTRL_NAME "+scroll, or " RACK_MOD_CTRL_NAME "+= / " RACK_MOD_CTRL_NAME "+-.", "", ""}, // reviewed
// {"Want to use VCV Rack as a plugin in your DAW? VCV Rack for DAWs is available now as a 64-bit VST 2 plugin for Ableton Live, Cubase, FL Studio, Reason, Studio One, REAPER, Bitwig, and more. Other plugin formats coming soon.", "Learn more", "https://vcvrack.com/RackForDAWs"}, // reviewed
{"You can use Rack fullscreen by selecting View > Fullscreen or pressing F11.\n\nIn fullscreen mode, the menu bar and scroll bars are hidden. This is ideal for screen recording with VCV Recorder.", "Get VCV Recorder", "https://vcvrack.com/Recorder"}, // reviewed
{"You can browse over 2400 modules on the VCV Library.", "VCV Library", "https://library.vcvrack.com/"},
{"Some plugin developers accept donations for their work. Right-click a module panel and select Info > Donate.\n\nYou can support VCV Rack by purchasing VCV plugins.", "VCV Library", "https://library.vcvrack.com/"}, // reviewed
{"You can learn more about VCV Rack by browsing the official Rack manual.", "VCV Rack manual", "https://vcvrack.com/manual/"},
{"Follow VCV Rack on Twitter for new modules, product announcements, and development news.", "Twitter @vcvrack", "https://twitter.com/vcvrack"}, // reviewed
// {"", "", ""},
};


struct TipWindow : widget::OpaqueWidget {
ui::Label* label;
UrlButton* linkButton;

TipWindow() {
float margin = 10;
float buttonWidth = 80;
box.size.x = buttonWidth*5 + margin*6;

ui::Label* header = new ui::Label;
header->box.pos.x = margin;
header->box.pos.y = 20;
// header->box.size.x = box.size.x - margin*2;
header->box.size.y = 20;
header->fontSize = 20;
header->text = "Welcome to VCV Rack v" + APP_VERSION;
addChild(header);

label = new ui::Label;
label->box.pos.x = margin;
label->box.pos.y = header->box.getBottom() + margin;
label->box.size.y = 80;
label->box.size.x = box.size.x - margin*2;
addChild(label);

linkButton = new UrlButton;
linkButton->box.pos.x = margin;
linkButton->box.pos.y = label->box.getBottom() + margin;
linkButton->box.size.x = box.size.x - margin*2;
addChild(linkButton);

ui::SequentialLayout* buttonLayout = new ui::SequentialLayout;
buttonLayout->box.pos.x = margin;
buttonLayout->box.pos.y = linkButton->box.getBottom() + margin;
buttonLayout->box.size.x = box.size.x - margin*2;
buttonLayout->spacing = math::Vec(margin, margin);
addChild(buttonLayout);

struct ShowButton : ui::Button {
void step() override {
text = settings::showTipsOnLaunch ? "Don't show at startup" : "Show tips at startup";
}
void onAction(const event::Action& e) override {
settings::showTipsOnLaunch ^= true;
}
};
ShowButton* showButton = new ShowButton;
showButton->box.size.x = buttonWidth * 2 + margin;
buttonLayout->addChild(showButton);

struct PreviousButton : ui::Button {
TipWindow* tipWindow;
void onAction(const event::Action& e) override {
tipWindow->advanceTip(-1);
}
};
PreviousButton* prevButton = new PreviousButton;
prevButton->box.size.x = buttonWidth;
prevButton->text = "Previous";
prevButton->tipWindow = this;
buttonLayout->addChild(prevButton);

struct NextButton : ui::Button {
TipWindow* tipWindow;
void onAction(const event::Action& e) override {
tipWindow->advanceTip();
}
};
NextButton* nextButton = new NextButton;
nextButton->box.size.x = buttonWidth;
nextButton->text = "Next";
nextButton->tipWindow = this;
buttonLayout->addChild(nextButton);

struct CloseButton : ui::Button {
TipWindow* tipWindow;
void onAction(const event::Action& e) override {
tipWindow->getParent()->hide();
}
};
CloseButton* closeButton = new CloseButton;
closeButton->box.size.x = buttonWidth;
closeButton->text = "Close";
closeButton->tipWindow = this;
buttonLayout->addChild(closeButton);

buttonLayout->box.size.y = closeButton->box.size.y;
box.size.y = buttonLayout->box.getBottom() + margin;

// When the TipWindow is created, choose the next tip
advanceTip();
}

void advanceTip(int delta = 1) {
// Increment tip index
settings::tipIndex = math::eucMod(settings::tipIndex + delta, (int) tipInfos.size());

TipInfo& tipInfo = tipInfos[settings::tipIndex];
label->text = tipInfo.text;
linkButton->setVisible(tipInfo.linkText != "");
linkButton->text = tipInfo.linkText;
linkButton->url = tipInfo.linkUrl;
}

void step() override {
box.pos = parent->box.size.minus(box.size).div(2);
OpaqueWidget::step();
}

void draw(const DrawArgs& args) override {
bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
Widget::draw(args);
}

void onShow(const event::Show& e) override {
advanceTip();
OpaqueWidget::onShow(e);
}
};


widget::Widget* tipWindowCreate() {
TipOverlay* overlay = new TipOverlay;

TipWindow* tipWindow = new TipWindow;
overlay->addChild(tipWindow);

return overlay;
}


} // namespace app
} // namespace rack

+ 14
- 0
src/settings.cpp View File

@@ -51,6 +51,8 @@ std::vector<NVGcolor> cableColors = {
};
std::map<std::string, std::set<std::string>> moduleWhitelist = {};
bool autoCheckUpdates = true;
bool showTipsOnLaunch = true;
int tipIndex = -1;


json_t* toJson() {
@@ -122,6 +124,10 @@ json_t* toJson() {

json_object_set_new(rootJ, "autoCheckUpdates", json_boolean(autoCheckUpdates));

json_object_set_new(rootJ, "showTipsOnLaunch", json_boolean(showTipsOnLaunch));

json_object_set_new(rootJ, "tipIndex", json_integer(tipIndex));

return rootJ;
}

@@ -249,6 +255,14 @@ void fromJson(json_t* rootJ) {
json_t* autoCheckUpdatesJ = json_object_get(rootJ, "autoCheckUpdates");
if (autoCheckUpdatesJ)
autoCheckUpdates = json_boolean_value(autoCheckUpdatesJ);

json_t* showTipsOnLaunchJ = json_object_get(rootJ, "showTipsOnLaunch");
if (showTipsOnLaunchJ)
showTipsOnLaunch = json_boolean_value(showTipsOnLaunchJ);

json_t* tipIndexJ = json_object_get(rootJ, "tipIndex");
if (tipIndexJ)
tipIndex = json_integer_value(tipIndexJ);
}

void save(const std::string& path) {


+ 43
- 29
src/ui/SequentialLayout.cpp View File

@@ -14,53 +14,67 @@ namespace ui {
void SequentialLayout::step() {
Widget::step();

// Sort widgets into rows (or columns if vertical)
std::vector<std::vector<widget::Widget*>> rows;
rows.resize(1);
float rowWidth = 0.0;
for (widget::Widget* child : children) {
if (!child->visible)
continue;
math::Rect bound;
bound.pos = margin;
bound.size = box.size.minus(margin.mult(2));

// Should we wrap the widget now?
if (!rows.back().empty() && rowWidth + X(child->box.size) >= X(box.size)) {
rowWidth = 0.0;
rows.resize(rows.size() + 1);
}

rows.back().push_back(child);
rowWidth += X(child->box.size) + X(spacing);
}

// Position widgets
math::Vec p;
for (auto& row : rows) {
// Sort widgets into rows (or columns if vertical)
std::vector<widget::Widget*> row;
math::Vec cursor = bound.pos;
auto flushRow = [&]() {
// For center and right alignment, compute offset from the left margin
float offset = 0.0;
float offset = 0.f;
if (alignment != LEFT_ALIGNMENT) {
float rowWidth = 0.0;
float rowWidth = 0.f;
for (widget::Widget* child : row) {
rowWidth += X(child->box.size) + X(spacing);
}
rowWidth -= X(spacing);

if (alignment == CENTER_ALIGNMENT)
offset = (X(box.size) - rowWidth) / 2;
offset = (X(bound.size) - rowWidth) / 2;
else if (alignment == RIGHT_ALIGNMENT)
offset = X(box.size) - rowWidth;
offset = X(bound.size) - rowWidth;
}

float maxHeight = 0.0;
// Set positions of widgets
float maxHeight = 0.f;
for (widget::Widget* child : row) {
child->box.pos = p;
child->box.pos = cursor;
X(child->box.pos) += offset;
X(cursor) += X(child->box.size) + X(spacing);

X(p) += X(child->box.size) + X(spacing);
if (Y(child->box.size) > maxHeight)
maxHeight = Y(child->box.size);
}
X(p) = 0.0;
Y(p) += maxHeight + Y(spacing);
row.clear();

// Reset cursor to next line
X(cursor) = X(bound.pos);
Y(cursor) += maxHeight + Y(spacing);
};

// Iterate through children until row is full
float rowWidth = 0.0;
for (widget::Widget* child : children) {
if (!child->isVisible()) {
child->box.pos = math::Vec();
continue;
}

// Should we wrap the widget now?
if (!row.empty() && rowWidth + X(child->box.size) > X(bound.size)) {
flushRow();
rowWidth = 0.0;
}

row.push_back(child);
rowWidth += X(child->box.size) + X(spacing);
}

// Flush last row
if (!row.empty()) {
flushRow();
}
}



+ 5
- 0
src/widget/Widget.cpp View File

@@ -56,6 +56,11 @@ void Widget::setSize(math::Vec size) {
}


widget::Widget *Widget::getParent() {
return parent;
}


bool Widget::isVisible() {
return visible;
}


Loading…
Cancel
Save