diff --git a/glass/src/lib/native/cpp/Context.cpp b/glass/src/lib/native/cpp/Context.cpp index 1c3f9ff532..a55cf8273d 100644 --- a/glass/src/lib/native/cpp/Context.cpp +++ b/glass/src/lib/native/cpp/Context.cpp @@ -546,15 +546,24 @@ void glass::PopID() { bool glass::PopupEditName(const char* label, std::string* name) { bool rv = false; if (ImGui::BeginPopupContextItem(label)) { - ImGui::Text("Edit name:"); - if (ImGui::InputText("##editname", name)) { - rv = true; - } - if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) || - ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) { - ImGui::CloseCurrentPopup(); - } + rv = ItemEditName(name); + ImGui::EndPopup(); } return rv; } + +bool glass::ItemEditName(std::string* name) { + bool rv = false; + + ImGui::Text("Edit name:"); + if (ImGui::InputText("##editname", name)) { + rv = true; + } + if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) || + ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) { + ImGui::CloseCurrentPopup(); + } + + return rv; +} diff --git a/glass/src/lib/native/cpp/View.cpp b/glass/src/lib/native/cpp/View.cpp index e01c4df2be..3f28200d7d 100644 --- a/glass/src/lib/native/cpp/View.cpp +++ b/glass/src/lib/native/cpp/View.cpp @@ -25,3 +25,9 @@ std::unique_ptr glass::MakeFunctionView( } void View::Hidden() {} + +void View::Settings() {} + +bool View::HasSettings() { + return false; +} diff --git a/glass/src/lib/native/cpp/Window.cpp b/glass/src/lib/native/cpp/Window.cpp index 6296fab046..f43c0eecb1 100644 --- a/glass/src/lib/native/cpp/Window.cpp +++ b/glass/src/lib/native/cpp/Window.cpp @@ -4,11 +4,13 @@ #include "glass/Window.h" +#include #include #include #include "glass/Context.h" #include "glass/Storage.h" +#include "glass/support/ExtraGuiWidgets.h" using namespace glass; @@ -59,9 +61,57 @@ void Window::Display() { m_id.c_str()); if (Begin(label, &m_visible, m_flags)) { - if (m_renamePopupEnabled) { - PopupEditName(nullptr, &m_name); + if (m_renamePopupEnabled || m_view->HasSettings()) { + bool isClicked = (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && + ImGui::IsItemHovered()); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + + bool settingsButtonClicked = false; + // Not docked, and window has just enough for the circles not to be + // touching + if (!ImGui::IsWindowDocked() && + ImGui::GetWindowWidth() > (ImGui::GetFontSize() + 2) * 3 + + ImGui::GetStyle().FramePadding.x * 2) { + const ImGuiItemFlags itemFlagsRestore = + ImGui::GetCurrentContext()->CurrentItemFlags; + + ImGui::GetCurrentContext()->CurrentItemFlags |= + ImGuiItemFlags_NoNavDefaultFocus; + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; + + // Allow to draw outside of normal window + ImGui::PushClipRect(window->OuterRectClipped.Min, + window->OuterRectClipped.Max, false); + + const ImRect titleBarRect = ImGui::GetCurrentWindow()->TitleBarRect(); + const ImVec2 position = {titleBarRect.Max.x - + (ImGui::GetStyle().FramePadding.x * 3) - + (ImGui::GetFontSize() * 2), + titleBarRect.Min.y}; + settingsButtonClicked = + HamburgerButton(ImGui::GetID("#SETTINGS"), position); + + ImGui::PopClipRect(); + + ImGui::GetCurrentContext()->CurrentItemFlags = itemFlagsRestore; + } + if (settingsButtonClicked || isClicked) { + ImGui::OpenPopup(window->ID); + } + + if (ImGui::BeginPopupEx(window->ID, + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoSavedSettings)) { + if (m_renamePopupEnabled) { + ItemEditName(&m_name); + } + m_view->Settings(); + + ImGui::EndPopup(); + } } + m_view->Display(); } else { m_view->Hidden(); diff --git a/glass/src/lib/native/cpp/other/Field2D.cpp b/glass/src/lib/native/cpp/other/Field2D.cpp index a5c31bfb7e..66e90b18f1 100644 --- a/glass/src/lib/native/cpp/other/Field2D.cpp +++ b/glass/src/lib/native/cpp/other/Field2D.cpp @@ -1217,10 +1217,14 @@ void glass::DisplayField2D(Field2DModel* model, const ImVec2& contentSize) { } void Field2DView::Display() { - if (ImGui::BeginPopupContextItem()) { - DisplayField2DSettings(m_model); - ImGui::EndPopup(); - } DisplayField2D(m_model, ImGui::GetWindowContentRegionMax() - ImGui::GetWindowContentRegionMin()); } + +void Field2DView::Settings() { + DisplayField2DSettings(m_model); +} + +bool Field2DView::HasSettings() { + return true; +} diff --git a/glass/src/lib/native/cpp/other/Log.cpp b/glass/src/lib/native/cpp/other/Log.cpp index 9a1d2c5cc2..accf024063 100644 --- a/glass/src/lib/native/cpp/other/Log.cpp +++ b/glass/src/lib/native/cpp/other/Log.cpp @@ -62,18 +62,21 @@ void glass::DisplayLog(LogData* data, bool autoScroll) { } void LogView::Display() { - if (ImGui::BeginPopupContextItem()) { - ImGui::Checkbox("Auto-scroll", &m_autoScroll); - if (ImGui::Selectable("Clear")) { - m_data->Clear(); - } - const auto& buf = m_data->GetBuffer(); - if (ImGui::Selectable("Copy to Clipboard", false, - buf.empty() ? ImGuiSelectableFlags_Disabled : 0)) { - ImGui::SetClipboardText(buf.c_str()); - } - ImGui::EndPopup(); - } - DisplayLog(m_data, m_autoScroll); } + +void LogView::Settings() { + ImGui::Checkbox("Auto-scroll", &m_autoScroll); + if (ImGui::Selectable("Clear")) { + m_data->Clear(); + } + const auto& buf = m_data->GetBuffer(); + if (ImGui::Selectable("Copy to Clipboard", false, + buf.empty() ? ImGuiSelectableFlags_Disabled : 0)) { + ImGui::SetClipboardText(buf.c_str()); + } +} + +bool LogView::HasSettings() { + return true; +} diff --git a/glass/src/lib/native/cpp/other/Mechanism2D.cpp b/glass/src/lib/native/cpp/other/Mechanism2D.cpp index aa801a773b..722585b2e4 100644 --- a/glass/src/lib/native/cpp/other/Mechanism2D.cpp +++ b/glass/src/lib/native/cpp/other/Mechanism2D.cpp @@ -249,10 +249,14 @@ void glass::DisplayMechanism2D(Mechanism2DModel* model, } void Mechanism2DView::Display() { - if (ImGui::BeginPopupContextItem()) { - DisplayMechanism2DSettings(m_model); - ImGui::EndPopup(); - } DisplayMechanism2D(m_model, ImGui::GetWindowContentRegionMax() - ImGui::GetWindowContentRegionMin()); } + +void Mechanism2DView::Settings() { + DisplayMechanism2DSettings(m_model); +} + +bool Mechanism2DView::HasSettings() { + return true; +} diff --git a/glass/src/lib/native/cpp/other/Plot.cpp b/glass/src/lib/native/cpp/other/Plot.cpp index 5f4dd6eebb..13d7c962a8 100644 --- a/glass/src/lib/native/cpp/other/Plot.cpp +++ b/glass/src/lib/native/cpp/other/Plot.cpp @@ -182,6 +182,8 @@ class PlotView : public View { PlotView(PlotProvider* provider, Storage& storage); void Display() override; + void Settings() override; + bool HasSettings() override; void MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex); @@ -767,71 +769,6 @@ PlotView::PlotView(PlotProvider* provider, Storage& storage) } void PlotView::Display() { - if (ImGui::BeginPopupContextItem()) { - if (ImGui::Button("Add plot")) { - m_plotsStorage.emplace_back(std::make_unique()); - m_plots.emplace_back(std::make_unique(*m_plotsStorage.back())); - } - - for (size_t i = 0; i < m_plots.size(); ++i) { - auto& plot = m_plots[i]; - ImGui::PushID(i); - - char name[64]; - if (!plot->GetName().empty()) { - std::snprintf(name, sizeof(name), "%s", plot->GetName().c_str()); - } else { - std::snprintf(name, sizeof(name), "Plot %d", static_cast(i)); - } - - char label[90]; - std::snprintf(label, sizeof(label), "%s###header%d", name, - static_cast(i)); - - bool open = ImGui::CollapsingHeader(label); - - // DND source and target for Plot - if (ImGui::BeginDragDropSource()) { - PlotSeriesRef ref = {this, i, 0}; - ImGui::SetDragDropPayload("Plot", &ref, sizeof(ref)); - ImGui::TextUnformatted(name); - ImGui::EndDragDropSource(); - } - plot->DragDropTarget(*this, i, false); - - if (open) { - if (ImGui::Button("Move Up")) { - if (i > 0) { - std::swap(m_plotsStorage[i - 1], m_plotsStorage[i]); - std::swap(m_plots[i - 1], plot); - } - } - - ImGui::SameLine(); - if (ImGui::Button("Move Down")) { - if (i < (m_plots.size() - 1)) { - std::swap(m_plotsStorage[i], m_plotsStorage[i + 1]); - std::swap(plot, m_plots[i + 1]); - } - } - - ImGui::SameLine(); - if (ImGui::Button("Delete")) { - m_plotsStorage.erase(m_plotsStorage.begin() + i); - m_plots.erase(m_plots.begin() + i); - ImGui::PopID(); - continue; - } - - plot->EmitSettings(i); - } - - ImGui::PopID(); - } - - ImGui::EndPopup(); - } - if (m_plots.empty()) { if (ImGui::Button("Add plot")) { m_plotsStorage.emplace_back(std::make_unique()); @@ -968,6 +905,73 @@ PlotProvider::PlotProvider(Storage& storage) : WindowManager{storage} { }); } +void PlotView::Settings() { + if (ImGui::Button("Add plot")) { + m_plotsStorage.emplace_back(std::make_unique()); + m_plots.emplace_back(std::make_unique(*m_plotsStorage.back())); + } + + for (size_t i = 0; i < m_plots.size(); ++i) { + auto& plot = m_plots[i]; + ImGui::PushID(i); + + char name[64]; + if (!plot->GetName().empty()) { + std::snprintf(name, sizeof(name), "%s", plot->GetName().c_str()); + } else { + std::snprintf(name, sizeof(name), "Plot %d", static_cast(i)); + } + + char label[90]; + std::snprintf(label, sizeof(label), "%s###header%d", name, + static_cast(i)); + + bool open = ImGui::CollapsingHeader(label); + + // DND source and target for Plot + if (ImGui::BeginDragDropSource()) { + PlotSeriesRef ref = {this, i, 0}; + ImGui::SetDragDropPayload("Plot", &ref, sizeof(ref)); + ImGui::TextUnformatted(name); + ImGui::EndDragDropSource(); + } + plot->DragDropTarget(*this, i, false); + + if (open) { + if (ImGui::Button("Move Up")) { + if (i > 0) { + std::swap(m_plotsStorage[i - 1], m_plotsStorage[i]); + std::swap(m_plots[i - 1], plot); + } + } + + ImGui::SameLine(); + if (ImGui::Button("Move Down")) { + if (i < (m_plots.size() - 1)) { + std::swap(m_plotsStorage[i], m_plotsStorage[i + 1]); + std::swap(plot, m_plots[i + 1]); + } + } + + ImGui::SameLine(); + if (ImGui::Button("Delete")) { + m_plotsStorage.erase(m_plotsStorage.begin() + i); + m_plots.erase(m_plots.begin() + i); + ImGui::PopID(); + continue; + } + + plot->EmitSettings(i); + } + + ImGui::PopID(); + } +} + +bool PlotView::HasSettings() { + return true; +} + PlotProvider::~PlotProvider() = default; void PlotProvider::DisplayMenu() { diff --git a/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp b/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp index 56f4e77a28..191634e461 100644 --- a/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp +++ b/glass/src/lib/native/cpp/support/ExtraGuiWidgets.cpp @@ -4,6 +4,8 @@ #include "glass/support/ExtraGuiWidgets.h" +#include + #define IMGUI_DEFINE_MATH_OPERATORS #include @@ -174,4 +176,46 @@ bool HeaderDeleteButton(const char* label) { return rv; } +bool HamburgerButton(const ImGuiID id, const ImVec2 position) { + const ImGuiStyle& style = ImGui::GetStyle(); + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + + // Frame padding on both sides, then one character in the middle + const ImRect bb{ + position, position + ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()) + + style.FramePadding * 2.0f}; + + ImGui::ItemAdd(bb, id); + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held); + + const ImU32 bgCol = + ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); + const ImVec2 center = bb.GetCenter(); + if (hovered) { + window->DrawList->AddCircleFilled( + center, ImMax(2.0f, ImGui::GetFontSize() * 0.5f + 1.0f), bgCol, 12); + } + + const ImU32 fgCol = ImGui::GetColorU32(ImGuiCol_Text); + + const float halfLineWidth = ImGui::GetFontSize() * 0.5 * 0.7071; + const float halfTotalHeight = halfLineWidth * 0.875; + ImVec2 lineStart = {center.x - halfLineWidth, center.y - halfTotalHeight}; + ImVec2 lineEnd = {center.x + halfLineWidth, center.y - halfTotalHeight}; + + ImVec2 increment = {0.0, halfTotalHeight}; + + for (int i = 0; i < 3; i++) { + window->DrawList->AddLine(lineStart, lineEnd, fgCol); + + lineStart += increment; + lineEnd += increment; + } + + return pressed; +} + } // namespace glass diff --git a/glass/src/lib/native/include/glass/Context.h b/glass/src/lib/native/include/glass/Context.h index 14ade2c983..e8dada347e 100644 --- a/glass/src/lib/native/include/glass/Context.h +++ b/glass/src/lib/native/include/glass/Context.h @@ -201,4 +201,6 @@ void PopID(); bool PopupEditName(const char* label, std::string* name); +bool ItemEditName(std::string* name); + } // namespace glass diff --git a/glass/src/lib/native/include/glass/View.h b/glass/src/lib/native/include/glass/View.h index 886c29ea21..f52ebc659f 100644 --- a/glass/src/lib/native/include/glass/View.h +++ b/glass/src/lib/native/include/glass/View.h @@ -35,6 +35,17 @@ class View { * ImGui::Begin() returns false). */ virtual void Hidden(); + + /** + * Called from within ImGui::BeginContextPopupItem() and ImGui::EndPopup(). + * Used to display the settings for the view + */ + virtual void Settings(); + + /** + * If the view has settings and if the result of Settings should be displayed. + */ + virtual bool HasSettings(); }; /** diff --git a/glass/src/lib/native/include/glass/other/Field2D.h b/glass/src/lib/native/include/glass/other/Field2D.h index 7b99292512..9c9f72ae6f 100644 --- a/glass/src/lib/native/include/glass/other/Field2D.h +++ b/glass/src/lib/native/include/glass/other/Field2D.h @@ -46,6 +46,8 @@ class Field2DView : public View { explicit Field2DView(Field2DModel* model) : m_model{model} {} void Display() override; + void Settings() override; + bool HasSettings() override; private: Field2DModel* m_model; diff --git a/glass/src/lib/native/include/glass/other/Log.h b/glass/src/lib/native/include/glass/other/Log.h index 3d9c59b669..f054e6639c 100644 --- a/glass/src/lib/native/include/glass/other/Log.h +++ b/glass/src/lib/native/include/glass/other/Log.h @@ -35,6 +35,8 @@ class LogView : public View { explicit LogView(LogData* data) : m_data{data} {} void Display() override; + void Settings() override; + bool HasSettings() override; private: LogData* m_data; diff --git a/glass/src/lib/native/include/glass/other/Mechanism2D.h b/glass/src/lib/native/include/glass/other/Mechanism2D.h index 7617e6f3ca..ab5ccdce54 100644 --- a/glass/src/lib/native/include/glass/other/Mechanism2D.h +++ b/glass/src/lib/native/include/glass/other/Mechanism2D.h @@ -55,6 +55,8 @@ class Mechanism2DView : public View { explicit Mechanism2DView(Mechanism2DModel* model) : m_model{model} {} void Display() override; + void Settings() override; + bool HasSettings() override; private: Mechanism2DModel* m_model; diff --git a/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h b/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h index 3a353359c3..678843403b 100644 --- a/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h +++ b/glass/src/lib/native/include/glass/support/ExtraGuiWidgets.h @@ -93,4 +93,9 @@ bool DeleteButton(ImGuiID id, const ImVec2& pos); */ bool HeaderDeleteButton(const char* label); +/** + * Settings button similar to ImGui::CloseButton. + */ +bool HamburgerButton(const ImGuiID id, const ImVec2 position); + } // namespace glass diff --git a/glass/src/libnt/native/cpp/NetworkTables.cpp b/glass/src/libnt/native/cpp/NetworkTables.cpp index a21e3dd341..d368359257 100644 --- a/glass/src/libnt/native/cpp/NetworkTables.cpp +++ b/glass/src/libnt/native/cpp/NetworkTables.cpp @@ -1506,9 +1506,13 @@ void NetworkTablesFlagsSettings::DisplayMenu() { void NetworkTablesView::Display() { m_flags.Update(); - if (ImGui::BeginPopupContextItem()) { - m_flags.DisplayMenu(); - ImGui::EndPopup(); - } DisplayNetworkTables(m_model, m_flags.GetFlags()); } + +void NetworkTablesView::Settings() { + m_flags.DisplayMenu(); +} + +bool NetworkTablesView::HasSettings() { + return true; +} diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h index a9a1b9bf3d..a7aa514809 100644 --- a/glass/src/libnt/native/include/glass/networktables/NetworkTables.h +++ b/glass/src/libnt/native/include/glass/networktables/NetworkTables.h @@ -226,6 +226,8 @@ class NetworkTablesView : public View { : m_model{model}, m_flags{defaultFlags} {} void Display() override; + void Settings() override; + bool HasSettings() override; private: NetworkTablesModel* m_model;