diff --git a/applications/gui2/CMakeLists.txt b/applications/gui2/CMakeLists.txt index 7dce75891115c42d423e3d674dc0626f37592ed5..a4c7beacea4843a25809c4cea7207ab7358eea76 100644 --- a/applications/gui2/CMakeLists.txt +++ b/applications/gui2/CMakeLists.txt @@ -25,6 +25,7 @@ set(GUI2SRC src/widgets/popupbutton.cpp src/widgets/imageview.cpp src/widgets/combobox.cpp + src/widgets/leftbutton.cpp ) add_gui_module("themes") diff --git a/applications/gui2/src/modules/addsource.cpp b/applications/gui2/src/modules/addsource.cpp index 2bbc90ef7a7b744b72c9a7fea70f0b264268d2a0..8b0aa3267f086282a0bec2337053cd58151abcf4 100644 --- a/applications/gui2/src/modules/addsource.cpp +++ b/applications/gui2/src/modules/addsource.cpp @@ -33,6 +33,10 @@ std::vector<std::string> AddCtrl::getHosts() { return std::move(io->feed()->knownHosts()); } +std::vector<std::string> AddCtrl::getGroups() { + return {}; +} + std::set<ftl::stream::SourceInfo> AddCtrl::getRecent() { return std::move(io->feed()->recentSources()); } diff --git a/applications/gui2/src/modules/addsource.hpp b/applications/gui2/src/modules/addsource.hpp index 7e6570bb4357793281ce102bec93ad48865f1d45..b76daad9b0575ac1d78432bbc1dcd5f9abbb6562 100644 --- a/applications/gui2/src/modules/addsource.hpp +++ b/applications/gui2/src/modules/addsource.hpp @@ -26,6 +26,7 @@ public: std::vector<std::string> getNetSources(); std::vector<std::string> getFileSources(); std::vector<std::string> getDeviceSources(); + std::vector<std::string> getGroups(); std::string getSourceName(const std::string &uri); bool isSourceActive(const std::string &uri); diff --git a/applications/gui2/src/modules/themes.cpp b/applications/gui2/src/modules/themes.cpp index 87ed20bbc223b5b755547307aacc7fe601dcc1d8..67f7f7760defd4181449e8f6d88a99cb3245c92e 100644 --- a/applications/gui2/src/modules/themes.cpp +++ b/applications/gui2/src/modules/themes.cpp @@ -65,9 +65,9 @@ void Themes::init() { windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(50,255); windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,255);*/ windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(60,255); - windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(30,30,40,180); + windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(35,35,40,180); windowtheme_dark->mButtonGradientTopFocused = nanogui::Color(60,255); - windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(30,30,40,180); + windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(35,35,40,180); windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(90,180); windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,180); windowtheme_dark->mButtonFontSize = 16; diff --git a/applications/gui2/src/views/addsource.cpp b/applications/gui2/src/views/addsource.cpp index 83fb59d9be9ce2b41521bd3a700fcc50a4c8b955..9ca1b0d1b1f6254b7f80131ef3be8f818aff3b0d 100644 --- a/applications/gui2/src/views/addsource.cpp +++ b/applications/gui2/src/views/addsource.cpp @@ -104,6 +104,20 @@ void AddSourceWindow::rebuild() { _addButton(s.uri, recentscroll); } + auto *group_tab = tabs->createTab("Groups"); + group_tab->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); + vscroll = new VScrollPanel(group_tab); + vscroll->setFixedHeight(150); + Widget *groupscroll = new Widget(vscroll); + groupscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); + + auto groups = ctrl_->getGroups(); + + for (auto &s : groups) { + _addButton(s, groupscroll); + } + auto *dev_tab = tabs->createTab("Devices"); dev_tab->setLayout(new nanogui::BoxLayout (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); @@ -126,6 +140,15 @@ void AddSourceWindow::rebuild() { Widget *hostscroll = new Widget(vscroll); hostscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); + button = new Button(hostscroll, "Add Host", ENTYPO_ICON_PLUS); + button->setIconPosition(Button::IconPosition::Left); + button->setIconExtraScale(1.2); + button->setFontSize(18); + button->setTooltip("Manually add a new connection URI"); + button->setCallback([this]() { + + }); + auto hostsrcs = ctrl_->getHosts(); for (auto &s : hostsrcs) { @@ -154,7 +177,7 @@ void AddSourceWindow::rebuild() { Widget *filescroll = new Widget(vscroll); filescroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); - button = new Button(filescroll, "Open", ENTYPO_ICON_FOLDER); + button = new Button(filescroll, "Open", ENTYPO_ICON_PLUS); button->setIconPosition(Button::IconPosition::Left); button->setIconExtraScale(1.2); button->setFontSize(18); diff --git a/applications/gui2/src/views/config.cpp b/applications/gui2/src/views/config.cpp index 17a0ec07ce8fef46ef3ef0f53a51939263a565b0..896f773ccb4ea309ae67d4322aa3253d79eee7c4 100644 --- a/applications/gui2/src/views/config.cpp +++ b/applications/gui2/src/views/config.cpp @@ -16,6 +16,7 @@ #include "config.hpp" #include "../screen.hpp" +#include "../widgets/leftbutton.hpp" using ftl::gui2::ConfigWindow; using std::string; @@ -85,7 +86,7 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl) using namespace nanogui; - setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_light")); + setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark")); auto close = new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS); close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark")); @@ -93,6 +94,7 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl) close->setCallback([this](){ dispose();}); setLayout(new GroupLayout(15, 6, 14, 10)); + setFixedWidth(400); setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f)); auto configurables = ftl::config::list(); @@ -110,7 +112,37 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl) searchBox->setButtons(buttons); std::vector<std::string> configurable_titles(size); - for (size_t i = 0; i < size; ++i) { + + std::set<std::string> sorted_cfgs; + sorted_cfgs.insert(configurables.begin(), configurables.end()); + + for (auto &c : sorted_cfgs) { + ftl::URI uri(c); + + std::string spacing = ""; + for (size_t i=0; i<uri.getPathLength(); ++i) { + spacing += " "; + } + + //if (uri.getFragment().size() == 0) { + std::string title = spacing + titleForURI(uri); + //configurable_titles[i] = title; + auto itembutton = new ftl::gui2::LeftButton(buttons, title); + + /*if (_isEmpty(c)) { + itembutton->setEnabled(false); + }*/ + itembutton->setTooltip(c); + //itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f)); + itembutton->setCallback([this,c]() { + _buildForm(c); + setVisible(false); + dispose(); + }); + //} + } + + /*for (size_t i = 0; i < size; ++i) { ftl::URI uri(configurables[i]); std::string label = uri.getFragment(); @@ -149,7 +181,7 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl) setVisible(false); dispose(); }); - } + }*/ } ConfigWindow::~ConfigWindow() { @@ -213,10 +245,11 @@ void ConfigWindow::__addElements(nanogui::FormHelper *form, const std::string &s string nuri; // Checking the URI with exists() prevents unloaded local configurations from being shown. - if (suri.find('#') != string::npos && exists(suri+string("/")+key)) { + //if (suri.find('#') != string::npos && exists(suri+string("/")+key)) { + // nuri = suri+string("/")+key; + //} else + if (exists(suri+string("/")+key)) { nuri = suri+string("/")+key; - } else if (exists(suri+string("#")+key)) { - nuri = suri+string("#")+key; } if (!nuri.empty()) { @@ -226,6 +259,7 @@ void ConfigWindow::__addElements(nanogui::FormHelper *form, const std::string &s }); button->setIcon(ENTYPO_ICON_FOLDER); + button->setIconPosition(nanogui::Button::IconPosition::Left); if (_isEmpty(nuri)) { button->setEnabled(false); } @@ -284,7 +318,7 @@ void ConfigWindow::buildForm(nanogui::Screen *screen, const std::string &suri) { ftl::URI uri(suri); FormHelper *form = new FormHelper(screen); - form->addWindow(Vector2i(100,50), uri.getFragment()); + form->addWindow(Vector2i(100,50), titleForURI(uri)); //form->window()->setTheme(theme()); { @@ -292,6 +326,10 @@ void ConfigWindow::buildForm(nanogui::Screen *screen, const std::string &suri) { existing_configs[suri] = form->window(); } + auto *window = form->window(); + window->setTheme(dynamic_cast<ftl::gui2::Screen*>(window->screen())->getTheme("window_dark")); + window->setWidth(200); + __addElements(form, suri); // prevent parent window from being destroyed too early @@ -301,8 +339,6 @@ void ConfigWindow::buildForm(nanogui::Screen *screen, const std::string &suri) { close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen)->getTheme("window_dark")); //close->setBackgroundColor(theme()->mWindowHeaderGradientBot); - auto *window = form->window(); - close->setCallback([window, suri](){ window->dispose(); //decRef(); diff --git a/applications/gui2/src/widgets/leftbutton.cpp b/applications/gui2/src/widgets/leftbutton.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c448ebaf2474674d8acfcaf2a7f783869154dc5f --- /dev/null +++ b/applications/gui2/src/widgets/leftbutton.cpp @@ -0,0 +1,121 @@ +#include "leftbutton.hpp" +#include <nanogui/button.h> +#include <nanogui/theme.h> +#include <nanogui/opengl.h> + +void ftl::gui2::LeftButton::draw(NVGcontext* ctx) { + using namespace nanogui; + + Widget::draw(ctx); + + NVGcolor gradTop = mTheme->mButtonGradientTopUnfocused; + NVGcolor gradBot = mTheme->mButtonGradientBotUnfocused; + + if (mPushed) { + gradTop = mTheme->mButtonGradientTopPushed; + gradBot = mTheme->mButtonGradientBotPushed; + } else if (mMouseFocus && mEnabled) { + gradTop = mTheme->mButtonGradientTopFocused; + gradBot = mTheme->mButtonGradientBotFocused; + } + + nvgBeginPath(ctx); + + nvgRoundedRect(ctx, mPos.x() + 1, mPos.y() + 1.0f, mSize.x() - 2, + mSize.y() - 2, mTheme->mButtonCornerRadius - 1); + + if (mBackgroundColor.w() != 0) { + nvgFillColor(ctx, Color(mBackgroundColor.head<3>(), 1.f)); + nvgFill(ctx); + if (mPushed) { + gradTop.a = gradBot.a = 0.8f; + } else { + double v = 1 - mBackgroundColor.w(); + gradTop.a = gradBot.a = mEnabled ? v : v * .5f + .5f; + } + } + + NVGpaint bg = nvgLinearGradient(ctx, mPos.x(), mPos.y(), mPos.x(), + mPos.y() + mSize.y(), gradTop, gradBot); + + nvgFillPaint(ctx, bg); + nvgFill(ctx); + + nvgBeginPath(ctx); + nvgStrokeWidth(ctx, 1.0f); + nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + (mPushed ? 0.5f : 1.5f), mSize.x() - 1, + mSize.y() - 1 - (mPushed ? 0.0f : 1.0f), mTheme->mButtonCornerRadius); + nvgStrokeColor(ctx, mTheme->mBorderLight); + nvgStroke(ctx); + + nvgBeginPath(ctx); + nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + 0.5f, mSize.x() - 1, + mSize.y() - 2, mTheme->mButtonCornerRadius); + nvgStrokeColor(ctx, mTheme->mBorderDark); + nvgStroke(ctx); + + int fontSize = mFontSize == -1 ? mTheme->mButtonFontSize : mFontSize; + nvgFontSize(ctx, fontSize); + nvgFontFace(ctx, "sans-bold"); + float tw = nvgTextBounds(ctx, 0,0, mCaption.c_str(), nullptr, nullptr); + + Vector2f center = mPos.cast<float>() + mSize.cast<float>() * 0.5f; + Vector2f textPos(mPos.x() + 8, center.y() - 1); + NVGcolor textColor = + mTextColor.w() == 0 ? mTheme->mTextColor : mTextColor; + if (!mEnabled) + textColor = mTheme->mDisabledTextColor; + + if (mIcon) { + auto icon = utf8(mIcon); + + float iw, ih = fontSize; + if (nvgIsFontIcon(mIcon)) { + ih *= icon_scale(); + nvgFontSize(ctx, ih); + nvgFontFace(ctx, "icons"); + iw = nvgTextBounds(ctx, 0, 0, icon.data(), nullptr, nullptr); + } else { + int w, h; + ih *= 0.9f; + nvgImageSize(ctx, mIcon, &w, &h); + iw = w * ih / h; + } + if (mCaption != "") + iw += mSize.y() * 0.15f; + nvgFillColor(ctx, textColor); + nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + Vector2f iconPos = center; + iconPos.y() -= 1; + + if (mIconPosition == IconPosition::LeftCentered) { + iconPos.x() -= (tw + iw) * 0.5f; + textPos.x() += iw * 0.5f; + } else if (mIconPosition == IconPosition::RightCentered) { + textPos.x() -= iw * 0.5f; + iconPos.x() += tw * 0.5f; + } else if (mIconPosition == IconPosition::Left) { + iconPos.x() = mPos.x() + 8; + } else if (mIconPosition == IconPosition::Right) { + iconPos.x() = mPos.x() + mSize.x() - iw - 8; + } + + if (nvgIsFontIcon(mIcon)) { + nvgText(ctx, iconPos.x(), iconPos.y()+1, icon.data(), nullptr); + } else { + NVGpaint imgPaint = nvgImagePattern(ctx, + iconPos.x(), iconPos.y() - ih/2, iw, ih, 0, mIcon, mEnabled ? 0.5f : 0.25f); + + nvgFillPaint(ctx, imgPaint); + nvgFill(ctx); + } + } + + nvgFontSize(ctx, fontSize); + nvgFontFace(ctx, "sans-bold"); + nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgFillColor(ctx, mTheme->mTextColorShadow); + nvgText(ctx, textPos.x(), textPos.y(), mCaption.c_str(), nullptr); + nvgFillColor(ctx, textColor); + nvgText(ctx, textPos.x(), textPos.y() + 1, mCaption.c_str(), nullptr); +} \ No newline at end of file diff --git a/applications/gui2/src/widgets/leftbutton.hpp b/applications/gui2/src/widgets/leftbutton.hpp new file mode 100644 index 0000000000000000000000000000000000000000..51efe156beb4e3b3ae4ce1b3f5c9d8cb58694cf4 --- /dev/null +++ b/applications/gui2/src/widgets/leftbutton.hpp @@ -0,0 +1,23 @@ +#pragma once +#include <nanogui/button.h> + +namespace ftl { +namespace gui2 { + +/** + * Allow left aligned button text. + */ +class LeftButton : public nanogui::Button { +public: + LeftButton(nanogui::Widget *parent, const std::string &caption = "", + int buttonIcon = 0) : nanogui::Button(parent, caption, buttonIcon) {}; + virtual ~LeftButton() {}; + + virtual void draw(NVGcontext* ctx) override; + +public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW +}; + +} +} diff --git a/components/common/cpp/include/ftl/uri.hpp b/components/common/cpp/include/ftl/uri.hpp index 3a80439fca816ace6ecec81cfc80af921c3b848a..49bf3e6533070aad927a036c2a24223bd53e14d6 100644 --- a/components/common/cpp/include/ftl/uri.hpp +++ b/components/common/cpp/include/ftl/uri.hpp @@ -32,7 +32,8 @@ namespace ftl { SCHEME_IPC, SCHEME_FILE, SCHEME_OTHER, - SCHEME_DEVICE + SCHEME_DEVICE, + SCHEME_GROUP }; bool isValid() const { return m_valid; }; @@ -57,6 +58,8 @@ namespace ftl { std::string getPathSegment(int n) const; + inline size_t getPathLength() const { return m_pathseg.size(); } + void setAttribute(const std::string &key, const std::string &value); void setAttribute(const std::string &key, int value); diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp index 29ee191f098ff7d13f72d1bf933202641ac40aab..96044339473e25f3f92093548b0eafcc6c7d65a1 100644 --- a/components/common/cpp/src/configuration.cpp +++ b/components/common/cpp/src/configuration.cpp @@ -631,7 +631,7 @@ static void process_options(Configurable *root, const map<string, string> &opts) auto v = nlohmann::json::parse(opt.second); ftl::config::update(*root->get<string>("$id") + string("/") + opt.first, v); } catch(...) { - LOG(ERROR) << "Unrecognised option: " << *root->get<string>("$id") << "#" << opt.first; + LOG(ERROR) << "Unrecognised option: " << *root->get<string>("$id") << "/" << opt.first; } } } @@ -734,22 +734,22 @@ std::vector<nlohmann::json*> ftl::config::_createArray(ftl::Configurable *parent if (entity.is_object()) { if (!entity["$id"].is_string()) { std::string id_str = *parent->get<std::string>("$id"); - if (id_str.find('#') != std::string::npos) { + //if (id_str.find('#') != std::string::npos) { entity["$id"] = id_str + std::string("/") + name + std::string("/") + std::to_string(i); - } else { - entity["$id"] = id_str + std::string("#") + name + std::string("/") + std::to_string(i); - } + //} else { + // entity["$id"] = id_str + std::string("#") + name + std::string("/") + std::to_string(i); + //} } result.push_back(&entity); } else if (entity.is_null()) { // Must create the object from scratch... std::string id_str = *parent->get<std::string>("$id"); - if (id_str.find('#') != std::string::npos) { + //if (id_str.find('#') != std::string::npos) { id_str = id_str + std::string("/") + name + std::string("/") + std::to_string(i); - } else { - id_str = id_str + std::string("#") + name + std::string("/") + std::to_string(i); - } + //} else { + // id_str = id_str + std::string("#") + name + std::string("/") + std::to_string(i); + //} parent->getConfig()[name] = { // cppcheck-suppress constStatement {"$id", id_str} @@ -775,22 +775,22 @@ nlohmann::json &ftl::config::_create(ftl::Configurable *parent, const std::strin if (entity.is_object()) { if (!entity["$id"].is_string()) { std::string id_str = *parent->get<std::string>("$id"); - if (id_str.find('#') != std::string::npos) { + //if (id_str.find('#') != std::string::npos) { entity["$id"] = id_str + std::string("/") + name; - } else { - entity["$id"] = id_str + std::string("#") + name; - } + //} else { + // entity["$id"] = id_str + std::string("#") + name; + //} } return entity; } else if (entity.is_null()) { // Must create the object from scratch... std::string id_str = *parent->get<std::string>("$id"); - if (id_str.find('#') != std::string::npos) { + //if (id_str.find('#') != std::string::npos) { id_str = id_str + std::string("/") + name; - } else { - id_str = id_str + std::string("#") + name; - } + //} else { + // id_str = id_str + std::string("#") + name; + //} parent->getConfig()[name] = { // cppcheck-suppress constStatement {"$id", id_str} diff --git a/components/common/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp index 9311b841b2fd227d16f63c572b0ba5dcb80a454c..db5bb12d8ec895eb164d946d6c5c018d5187ab7c 100644 --- a/components/common/cpp/src/uri.cpp +++ b/components/common/cpp/src/uri.cpp @@ -82,6 +82,7 @@ void URI::_parse(uri_t puri) { else if (prototext == "ipc") m_proto = SCHEME_IPC; else if (prototext == "device") m_proto = SCHEME_DEVICE; else if (prototext == "file") m_proto = SCHEME_FILE; + else if (prototext == "group") m_proto = SCHEME_GROUP; else m_proto = SCHEME_OTHER; m_protostr = prototext; diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp index a06e43b04663f072e3bee641a162af0704360e85..a4bea0efeff283f61d59647417caa35bd9b33eb6 100644 --- a/components/streams/src/feed.cpp +++ b/components/streams/src/feed.cpp @@ -563,6 +563,7 @@ std::set<ftl::stream::SourceInfo> Feed::recentSources() { for (auto &f : recent.items()) { ftl::stream::SourceInfo info; info.uri = f.key(); + if (f.value().contains("uri")) info.uri = f.value()["uri"].get<std::string>(); info.last_used = f.value()["last_open"].get<int64_t>(); result.insert(info); } @@ -641,7 +642,7 @@ std::string Feed::getName(const std::string &puri) { if (uri.getPathSegment(0) == "realsense") return "Realsense"; if (uri.getPathSegment(0) == "screen") return "Screen Capture"; if (uri.getPathSegment(0) == "render") return "3D Virtual"; - if (uri.getPathSegment(0) == "openvr") return "VR"; + if (uri.getPathSegment(0) == "openvr") return "OpenVR"; return "Unknown Device"; } else if (uri.getScheme() == ftl::URI::SCHEME_FILE) { return getConfig()["recent_files"][uri.getBaseURI()].value("name", "FTLFile"); @@ -663,6 +664,7 @@ nlohmann::json &Feed::_add_recent_source(const ftl::URI &uri) { name = name.substr(0, name.find_last_of('.')); } + details["uri"] = uri.to_string(); details["name"] = name; details["last_open"] = ftl::timer::get_time(); return details;