diff --git a/src/ui/app.h b/src/ui/app.h index fdacdb37..1879d96f 100644 --- a/src/ui/app.h +++ b/src/ui/app.h @@ -40,6 +40,7 @@ class App { OpenDashboardPage openDashboardPage; SDLState *sdlState; bool running = true; + std::string mainViewMode = ""; bool prototypeMode = true; std::chrono::milliseconds execTime; /// time it took to handle events and draw one frame @@ -50,7 +51,11 @@ class App { std::array fontBigger = { nullptr, nullptr }; /// 0: production 1: prototype use std::array fontLarge = { nullptr, nullptr }; /// 0: production 1: prototype use ImFont *fontIcons; + ImFont *fontIconsBig; + ImFont *fontIconsLarge; ImFont *fontIconsSolid; + ImFont *fontIconsSolidBig; + ImFont *fontIconsSolidLarge; std::chrono::seconds editPaneCloseDelay{ 15 }; private: diff --git a/src/ui/app_header/CMakeLists.txt b/src/ui/app_header/CMakeLists.txt index 285ebc3c..627d50bd 100644 --- a/src/ui/app_header/CMakeLists.txt +++ b/src/ui/app_header/CMakeLists.txt @@ -1,6 +1,7 @@ add_library( app_header INTERFACE "fair_header.h" + RadialCircularMenu.hpp ) target_include_directories(app_header INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(app_header INTERFACE stb fmt) diff --git a/src/ui/app_header/RadialCircularMenu.hpp b/src/ui/app_header/RadialCircularMenu.hpp new file mode 100644 index 00000000..902c055b --- /dev/null +++ b/src/ui/app_header/RadialCircularMenu.hpp @@ -0,0 +1,258 @@ +#ifndef OPENDIGITIZER_RADIALCIRCULARMENU_HPP +#define OPENDIGITIZER_RADIALCIRCULARMENU_HPP + +#include +#include +#include +#include +#include + +namespace fair { + +namespace detail { +ImVec4 lightenColor(const ImVec4 &color, float percent) { + float h, s, v; + ImGui::ColorConvertRGBtoHSV(color.x, color.y, color.z, h, s, v); + s = std::max(0.0f, s * percent); + float r, g, b; + ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); + return ImVec4(r, g, b, color.w); +} + +ImVec4 darkenColor(const ImVec4 &color, float percent) { + float h, s, v; + ImGui::ColorConvertRGBtoHSV(color.x, color.y, color.z, h, s, v); + v = std::max(0.0f, v * percent); + float r, g, b; + ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); + return ImVec4(r, g, b, color.w); +} +} // namespace detail + +struct RadialButton { + using CallbackFun = std::variant, std::function>; + std::string label; + float size; + CallbackFun onClick; + ImFont *font = nullptr; + std::string toolTip; + bool isTransparent = false; + float padding = std::max(ImGui::GetStyle().FramePadding.x, ImGui::GetStyle().FramePadding.y); + ImVec4 buttonColor = ImGui::GetStyleColorVec4(ImGuiCol_Button); + + [[nodiscard]] bool create() { + const std::string buttonId = fmt::format("#{}", label); + ImGui::PushFont(font); + bool isClicked = false; + const ImVec2 textSize = ImGui::CalcTextSize(label.c_str()); + const float maxSize = std::max(textSize.x, textSize.y); + const float actualButtonSize = std::max(size, 2.f * padding + maxSize); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, .5f * actualButtonSize); + + if (!isTransparent) { + ImVec4 buttonColorHover = detail::lightenColor(buttonColor, 0.7f); + ImVec4 buttonColorActive = detail::darkenColor(buttonColor, 0.7f); + buttonColor.w = 1.0; + buttonColorHover.w = 1.0; + buttonColorActive.w = 1.0; + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColorHover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColorActive); + } + if (ImGui::Button(label.c_str(), ImVec2{ actualButtonSize, actualButtonSize })) { + isClicked = true; + } + if (!isTransparent) { + ImGui::PopStyleColor(3); + } + ImGui::PopStyleVar(); + ImGui::PopFont(); + + if (ImGui::IsItemHovered() && !toolTip.empty()) { + ImGui::SetTooltip("%s", toolTip.c_str()); + } + + return isClicked; + } +}; + +template +class RadialCircularMenu { + static std::vector _buttons; + static const std::string _popupId; + static float _animationProgress; + static bool _isOpen; + const float _padding = ImGui::GetStyle().WindowPadding.x; + ImVec2 _menuSize; + float _startAngle = 0.f; + float _stopAngle = 90.f; + float _extraRadius = 0.f; + float _animationSpeed = 0.25f; + float _timeOut = 0.5f; // Time in seconds to close the menu when the mouse is out of range + + // + [[nodiscard]] float maxButtonSize(std::size_t firstButtonIndex = 0) const { + if (_buttons.empty()) { + return std::max(_menuSize.x, _menuSize.y); + } + float max = 0.0; + for (std::size_t index = firstButtonIndex; index < _buttons.size(); ++index) { + max = std::max(max, _buttons[index].size); + } + return max; + } + + [[nodiscard]] std::pair maxButtonNumberAndSizeForArc(float baseArcRadius, std::size_t firstButtonIndex = 0) const { + const float totalAngle = _stopAngle - _startAngle; + const float arcLength = std::numbers::pi_v * baseArcRadius * (totalAngle / 180.f); + std::size_t buttonCount = 0UL; + float maxButtonSize = .0f; + float cumulativeLength = .0f; + for (auto i = firstButtonIndex; i < _buttons.size(); ++i) { + const auto &button = _buttons[i]; + if ((cumulativeLength + button.size + _padding) > arcLength) { + break; + } + ++buttonCount; + maxButtonSize = std::max(button.size, maxButtonSize); + cumulativeLength += button.size + _padding; + } + return { buttonCount, maxButtonSize }; + } + +public: + explicit RadialCircularMenu(ImVec2 menuSize = { 100, 100 }, float startAngle = 0.f, float stopAngle = 360.f, float extraRadius = 0.f, float animationSpeed = .25f, float timeOut = 0.5f) + : _menuSize(menuSize), _startAngle(startAngle), _stopAngle(stopAngle), _extraRadius(extraRadius), _animationSpeed(animationSpeed), _timeOut(timeOut) { + updateAndDraw(); + } + + void addButton(std::string label, auto onClick, float buttonSize = -1.f, std::string toolTip = "") { + if (_animationProgress > 0.f) { // we do not allow to add buttons when the popup is already open or animating + return; + } + _buttons.emplace_back(label, buttonSize, std::move(onClick), nullptr, std::move(toolTip)); + _isOpen = true; + } + + void addButton(std::string label, auto onClick, ImFont *font, std::string toolTip = "") { + if (_animationProgress > 0.f) { // we do not allow to add buttons when the popup is already open or animating + return; + } + float buttonSize = 0.f; + if (font == nullptr) { + buttonSize = ImGui::CalcTextSize(label.c_str()).y; + } else { + buttonSize = font->FontSize; + } + _buttons.emplace_back(label, buttonSize, std::move(onClick), font, std::move(toolTip)); + _isOpen = true; + } + + [[nodiscard]] bool isOpen() const noexcept { + return _isOpen; + } + + void forceClose() { + _isOpen = false; + _animationProgress = 0.0; + _buttons.clear(); + ImGui::CloseCurrentPopup(); + } + + void updateAndDraw() { + static float timeOutOfRadius = 0.0f; + static ImVec2 centre{ -1.f, -1.f }; + + const float deltaTime = ImGui::GetIO().DeltaTime; + _animationProgress = _isOpen ? std::min(1.0f, _animationProgress + deltaTime / _animationSpeed) : std::max(0.0f, _animationProgress - deltaTime / _animationSpeed); + + if (!_isOpen && _animationProgress <= 0.f) { + centre = { -1.f, -1.f }; + if (!_buttons.empty()) { + _buttons.clear(); + } + return; + } else if (_isOpen && (centre.x < 0.f || centre.y < 0.f)) { + centre = ImGui::GetMousePos(); + } + + std::size_t nButtonRows = 1UL; + const float buttonSize = maxButtonSize(); + if (_animationProgress >= 0.0f) { + const ImVec2 oldPos = ImGui::GetCursorPos(); + float requiredPopupSize = 2.f * (_extraRadius + buttonSize * _buttons.size()); + ImGui::SetNextWindowSize(ImVec2(requiredPopupSize, requiredPopupSize)); + ImGui::SetNextWindowPos(ImVec2(centre.x - .5f * requiredPopupSize / 2, centre.y - .5f * requiredPopupSize)); + + ImGui::OpenPopup(_popupId.c_str()); + if (ImGui::BeginPopup(_popupId.c_str(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration)) { + nButtonRows = drawButtonsOnArc(centre, _extraRadius + buttonSize + _padding); + ImGui::EndPopup(); + } + ImGui::SetCursorScreenPos(oldPos); + } + + const ImVec2 mousePos = ImGui::GetMousePos(); + const float mouseDistance = std::hypot(mousePos.x - centre.x, mousePos.y - centre.y); + const float mouseAngle = std::atan2(mousePos.y - centre.y, mousePos.x - centre.x) * 180.0f / std::numbers::pi_v; + const float arcRadius = _extraRadius + static_cast(nButtonRows + 1) * (buttonSize + _padding); + // the last statement is to keep the menu open when the mouse is outside the arc segment but on the calling button. + const bool mouseInArc = (mouseDistance <= arcRadius && mouseAngle >= _startAngle && mouseAngle <= _stopAngle) || mouseDistance <= std::max(_menuSize.x, _menuSize.y); + timeOutOfRadius = !mouseInArc ? timeOutOfRadius + deltaTime : 0.f; + if (timeOutOfRadius >= _timeOut) { + _isOpen = false; + } + } + +private: + void drawButton(RadialButton &button) { + if (button.create()) { + std::visit([&](auto &&onClick) { + using T = std::decay_t; + if constexpr (std::is_same_v>) { + onClick(); + } else if constexpr (std::is_same_v>) { + onClick(button); + } + }, + button.onClick); + } + } + + [[nodiscard]] std::size_t drawButtonsOnArc(const ImVec2 ¢re, float arcRadius) { + const float totalAngle = _stopAngle - _startAngle; + + std::size_t currentRow = 0UL; + for (std::size_t buttonIndex = 0UL; buttonIndex < _buttons.size();) { + // get the maximum number of buttons and button size for the current arc row + const auto [maxButtonsInRow, maxButtonSizeInRow] = maxButtonNumberAndSizeForArc(arcRadius, buttonIndex); + const float angleBetweenButtons = totalAngle / maxButtonsInRow; + const float shiftFactor = 0.5f * totalAngle / maxButtonsInRow; // Shift every second row + + for (std::size_t buttonsInRow = 0UL; buttonIndex < _buttons.size() && buttonsInRow < maxButtonsInRow; ++buttonIndex) { + auto &button = _buttons[buttonIndex]; + const float angle = _startAngle + shiftFactor + static_cast(buttonsInRow) * angleBetweenButtons; + const float angleRad = angle * _animationProgress * (std::numbers::pi_v / 180.0f); + + ImGui::SetCursorScreenPos(ImVec2(centre.x + arcRadius * std::cos(angleRad) - 0.5f * button.size, centre.y + arcRadius * std::sin(angleRad) - 0.5f * button.size)); + drawButton(button); + ++buttonsInRow; + } + ++currentRow; + arcRadius += maxButtonSizeInRow + 2.f * _padding; // update arc radius + } + return currentRow; + } +}; +template +bool RadialCircularMenu::_isOpen = false; +template +const std::string RadialCircularMenu::_popupId = fmt::format("RadialMenuPopup_{}", unique_id); +template +float RadialCircularMenu::_animationProgress = 0.f; +template +std::vector RadialCircularMenu::_buttons; + +} // namespace fair + +#endif // OPENDIGITIZER_RADIALCIRCULARMENU_HPP diff --git a/src/ui/app_header/fair_header.h b/src/ui/app_header/fair_header.h index 7a92bc75..f1e51d28 100644 --- a/src/ui/app_header/fair_header.h +++ b/src/ui/app_header/fair_header.h @@ -8,6 +8,9 @@ #include #include +#include "../app.h" +#include + CMRC_DECLARE(ui_assets); namespace app_header { @@ -23,16 +26,15 @@ void TextCentered(const std::string_view text) { void TextRight(const std::string_view text) { auto windowWidth = ImGui::GetWindowSize().x; auto textWidth = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x; - ImGui::SetCursorPosX(windowWidth - textWidth - ImGui::GetStyle().ItemSpacing.x - 20.0f); + ImGui::SetCursorPosX(windowWidth - textWidth - ImGui::GetStyle().ItemSpacing.x); ImGui::Text("%s", text.data()); } -int img_fair_w = 0; -int img_fair_h = 0; -GLuint img_fair_tex = 0; -GLuint img_fair_tex_dark = 0; +ImVec2 logoSize{ 0.f, 0.f }; +GLuint imgFairLogo = 0; +GLuint imgFairLogoDark = 0; -bool LoadTextureFromFile(const char *filename, GLuint *out_texture, int *out_width, int *out_height) { +bool LoadTextureFromFile(const char *filename, GLuint *out_texture, ImVec2 &textureSize) { // Load from file int image_width = 0; int image_height = 0; @@ -41,8 +43,9 @@ bool LoadTextureFromFile(const char *filename, GLuint *out_texture, int *out_w auto file = fs.open(filename); unsigned char *image_data = stbi_load_from_memory(reinterpret_cast(file.begin()), file.size(), &image_width, &image_height, nullptr, 4); - if (image_data == NULL) + if (image_data == nullptr) { return false; + } // Create a OpenGL texture identifier GLuint image_texture; @@ -62,9 +65,9 @@ bool LoadTextureFromFile(const char *filename, GLuint *out_texture, int *out_w glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data); stbi_image_free(image_data); - *out_texture = image_texture; - *out_width = image_width; - *out_height = image_height; + *out_texture = image_texture; + textureSize.x = static_cast(image_width); + textureSize.y = static_cast(image_height); return true; } @@ -77,11 +80,11 @@ enum class Style { void load_header_assets() { [[maybe_unused]] bool ret = detail::LoadTextureFromFile("assets/fair-logo/FAIR_Logo_rgb_72dpi.png", - &detail::img_fair_tex, &detail::img_fair_w, &detail::img_fair_h); + &detail::imgFairLogo, detail::logoSize); IM_ASSERT(ret); ret = detail::LoadTextureFromFile("assets/fair-logo/FAIR_Logo_rgb_72dpi_dark.png", - &detail::img_fair_tex_dark, &detail::img_fair_w, &detail::img_fair_h); + &detail::imgFairLogoDark, detail::logoSize); IM_ASSERT(ret); } @@ -109,18 +112,85 @@ void draw_header_bar(std::string_view title, ImFont *title_font, Style style) { // const auto utc_clock = std::chrono::utc_clock::now(); // c++20 timezone is not implemented in gcc or clang yet // date + tz library unfortunately doesn't play too well with emscripten/fetchcontent // const auto localtime = fmt::format("{:%H:%M:%S (%Z)}", date::make_zoned("utc", clock).get_sys_time()); - std::time_t utc = std::chrono::time_point_cast(clock).time_since_epoch().count(); std::string utctime; // assume maximum of 32 characters for datetime length utctime.resize(32); pos.y += ImGui::GetTextLineHeightWithSpacing(); ImGui::SetCursorPos(pos); + const auto utc = std::chrono::system_clock::to_time_t(clock); const auto len = strftime(utctime.data(), utctime.size(), "%H:%M:%S (UTC)", gmtime(&utc)); TextRight(std::string_view(utctime.data(), len)); + // draw menu + // draw fair logo ImGui::SetCursorPos(topLeft); - const auto scale = titleSize.y / img_fair_h; - ImGui::Image((void *) (intptr_t) (style == Style::Light ? img_fair_tex : img_fair_tex_dark), ImVec2(scale * img_fair_w, scale * img_fair_h)); + const auto scale = titleSize.y / logoSize.y; + const auto localLogoSize = ImVec2(scale * logoSize.x, scale * logoSize.y); + fair::RadialCircularMenu<1> menu(localLogoSize, -15.f, 105.f, 6.f); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.8f, .8f, .8f, 0.4f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, .5f * scale * logoSize.y); + const ImTextureID imgLogo = (void *) (intptr_t) (style == Style::Light ? imgFairLogo : imgFairLogoDark); + if (ImGui::ImageButton(imgLogo, localLogoSize)) { + using fair::RadialButton; + const bool wasAlreadyOpen = menu.isOpen(); + auto &app = DigitizerUi::App::instance(); + + ImGui::PushStyleColor(ImGuiCol_Button, { 0.f, .7f, 0.f, 1.f }); // green + menu.addButton( + "\uF201", [&app](RadialButton &button) { + app.mainViewMode = "View"; + }, + app.fontIconsSolidBig, "switch to view mode"); + + menu.addButton( + "\uF248", [&app](RadialButton &button) { + app.mainViewMode = "Layout"; + }, + app.fontIconsSolidBig, "switch to layout mode"); + menu.addButton( + "\uF542", [&app](RadialButton &button) { + app.mainViewMode = "FlowGraph"; + }, + app.fontIconsSolidBig, "click to edit flow-graph"); + menu.addButton( + "", [&app](RadialButton &button) { + app.mainViewMode = "OpenSaveDashboard"; + }, + app.fontIconsSolidBig, "click to open/save new dashboards"); + ImGui::PopStyleColor(); + + ImGui::PushStyleColor(ImGuiCol_Button, { 1.f, .0f, 0.f, 1.f }); // red + menu.addButton( + "", [&app]() { fmt::print("request exit: {} \n", app.running); app.running = false; }, app.fontIconsLarge, "close app"); + ImGui::PopStyleColor(); + + ImGui::PushStyleColor(ImGuiCol_Button, { .3f, .3f, 1.0f, 1.f }); // blue + menu.addButton( + app.prototypeMode ? "" : "", [&app](RadialButton &button) { + app.prototypeMode = !app.prototypeMode; + button.label = app.prototypeMode ? "" : ""; + ImGui::GetIO().FontDefault = app.fontNormal[app.prototypeMode]; + }, + app.fontIconsSolid, "switch between prototype and production mode"); + + using enum DigitizerUi::Style; + menu.addButton((app.style() == Light) ? "" : "", [&app](RadialButton &button) { + const bool isDarkMode = app.style() == Dark; + app.setStyle(isDarkMode ? Light : Dark); + button.label = isDarkMode ? "" : ""; + button.toolTip = isDarkMode ? "switch to light mode" : "switch to dark mode"; + }, + app.fontIconsSolid, "switch between light and dark mode"); + ImGui::PopStyleColor(); + if (wasAlreadyOpen) { + fmt::print("was already open -> closing\n"); + menu.forceClose(); + } + } + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); } } // namespace app_header diff --git a/src/ui/main.cpp b/src/ui/main.cpp index 0208df1a..cd2facd1 100644 --- a/src/ui/main.cpp +++ b/src/ui/main.cpp @@ -93,7 +93,7 @@ static void loadFonts(DigitizerUi::App &app) { loadDefaultFont(cmrc::ui_assets::get_filesystem().open("assets/xkcd/xkcd-script.ttf"), cmrc::fonts::get_filesystem().open("Roboto-Medium.ttf"), 1, rangeLatinExtended); ImGui::GetIO().FontDefault = app.fontNormal[app.prototypeMode]; - auto loadIconsFont = [](auto name) { + auto loadIconsFont = [](auto name, float fontSize) { ImGuiIO &io = ImGui::GetIO(); static const ImWchar glyphRanges[] = { 0XF005, 0XF2ED, // 0xf005 is "", 0xf2ed is "trash can" @@ -108,11 +108,15 @@ static void loadFonts(DigitizerUi::App &app) { auto file = fs.open(name); ImFontConfig cfg; cfg.FontDataOwnedByAtlas = false; - return io.Fonts->AddFontFromMemoryTTF(const_cast(file.begin()), file.size(), 12, &cfg, glyphRanges); + return io.Fonts->AddFontFromMemoryTTF(const_cast(file.begin()), file.size(), fontSize, &cfg, glyphRanges); }; - app.fontIcons = loadIconsFont("assets/fontawesome/fa-regular-400.otf"); - app.fontIconsSolid = loadIconsFont("assets/fontawesome/fa-solid-900.otf"); + app.fontIcons = loadIconsFont("assets/fontawesome/fa-regular-400.otf", 12); + app.fontIconsBig = loadIconsFont("assets/fontawesome/fa-regular-400.otf", 24); + app.fontIconsLarge = loadIconsFont("assets/fontawesome/fa-regular-400.otf", 45); + app.fontIconsSolid = loadIconsFont("assets/fontawesome/fa-solid-900.otf", 12); + app.fontIconsSolidBig = loadIconsFont("assets/fontawesome/fa-solid-900.otf", 24); + app.fontIconsSolidLarge = loadIconsFont("assets/fontawesome/fa-solid-900.otf", 45); } int main(int argc, char **argv) { @@ -280,51 +284,48 @@ static void main_loop(void *arg) { app_header::draw_header_bar("OpenDigitizer", app->fontLarge[app->prototypeMode], app->style() == DigitizerUi::Style::Light ? app_header::Style::Light : app_header::Style::Dark); - const bool dashboardLoaded = app->dashboard != nullptr; - if (!dashboardLoaded) { + if (app->dashboard == nullptr) { ImGui::BeginDisabled(); } - auto pos = ImGui::GetCursorPos(); - ImGui::BeginTabBar("maintabbar"); + auto pos = ImGui::GetCursorPos(); ImGuiID viewId; - if (ImGui::BeginTabItem("View")) { + if (app->mainViewMode == "View" || app->mainViewMode == "") { viewId = ImGui::GetID(""); - if (dashboardLoaded) { + if (app->dashboard != nullptr) { app->dashboard->localFlowGraph.update(); app->dashboardPage.draw(app, app->dashboard.get()); } - - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Layout")) { + } else if (app->mainViewMode == "Layout") { // The ID of this tab is different than the ID of the view tab. That means that the plots in the two tabs // are considered to be different plots, so changing e.g. the zoom level of a plot in the view tab would // not reflect in the layout tab. // To fix that we use the PushOverrideID() function to force the ID of this tab to be the same as the ID // of the view tab. ImGui::PushOverrideID(viewId); - if (dashboardLoaded) { + if (app->dashboard != nullptr) { app->dashboard->localFlowGraph.update(); app->dashboardPage.draw(app, app->dashboard.get(), DigitizerUi::DashboardPage::Mode::Layout); } ImGui::PopID(); - ImGui::EndTabItem(); - } - - if (ImGui::BeginTabItem("Flowgraph")) { - if (dashboardLoaded) { + } else if (app->mainViewMode == "FlowGraph") { + if (app->dashboard != nullptr) { auto contentRegion = ImGui::GetContentRegionAvail(); app->fgItem.draw(&app->dashboard->localFlowGraph, contentRegion); } - - ImGui::EndTabItem(); + } else if (app->mainViewMode == "OpenSaveDashboard") { + app->openDashboardPage.draw(app); + } else { + fmt::print("unknown view mode {}\n", app->mainViewMode); } + // TODO: tab-bar is optional and should be eventually eliminated to optimise viewing area for data DigitizerUi::Dashboard::Service *service = nullptr; - if (dashboardLoaded) { + if (app->dashboard != nullptr) { + if (!app->dashboard->remoteServices().empty()) { + ImGui::BeginTabBar("maintabbar"); + } for (auto &s : app->dashboard->remoteServices()) { auto name = fmt::format("Flowgraph of {}", s.name); auto contentRegion = ImGui::GetContentRegionAvail(); @@ -334,17 +335,13 @@ static void main_loop(void *arg) { ImGui::EndTabItem(); } } + if (!app->dashboard->remoteServices().empty()) { + ImGui::EndTabBar(); + } } else { ImGui::EndDisabled(); } - if (ImGui::BeginTabItem("File", nullptr, dashboardLoaded ? 0 : ImGuiTabItemFlags_SetSelected)) { - app->openDashboardPage.draw(app); - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - if (service) { ImGui::SameLine(); ImGui::SetCursorPosX(static_cast(width) - 150.f); @@ -353,49 +350,6 @@ static void main_loop(void *arg) { } } - ImGui::SetCursorPos(pos + ImVec2(static_cast(width) - 75.f, 0.f)); - ImGui::PushFont(app->fontIcons); - if (app->prototypeMode) { - if (ImGui::Button("")) { - app->prototypeMode = false; - ImGui::GetIO().FontDefault = app->fontNormal[app->prototypeMode]; - } - ImGui::PopFont(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("switch to production mode"); - } - } else { - if (ImGui::Button("")) { - app->prototypeMode = true; - ImGui::GetIO().FontDefault = app->fontNormal[app->prototypeMode]; - } - ImGui::PopFont(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("switch to prototype mode"); - } - } - ImGui::SameLine(); - ImGui::PushFont(app->fontIcons); - if (app->style() == DigitizerUi::Style::Light) { - if (ImGui::Button("")) { - app->setStyle(DigitizerUi::Style::Dark); - } - ImGui::PopFont(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("switch to dark mode"); - } - } else if (app->style() == DigitizerUi::Style::Dark) { - if (ImGui::Button("")) { - app->setStyle(DigitizerUi::Style::Light); - } - ImGui::PopFont(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("switch to light mode"); - } - } else { - ImGui::PopFont(); - } - ImGui::End(); // Rendering