[glass] Add hamburger menu icon to titlebars (#4874)

This does the same thing as right clicking, but provides a visual indicator.
The icon disappears if the window is too small or docked (right click keeps working).
This commit is contained in:
ohowe
2023-01-01 21:05:09 -07:00
committed by GitHub
parent f0fa8205ac
commit b0c6724eed
16 changed files with 254 additions and 100 deletions

View File

@@ -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;
}

View File

@@ -25,3 +25,9 @@ std::unique_ptr<View> glass::MakeFunctionView(
}
void View::Hidden() {}
void View::Settings() {}
bool View::HasSettings() {
return false;
}

View File

@@ -4,11 +4,13 @@
#include "glass/Window.h"
#include <imgui.h>
#include <imgui_internal.h>
#include <wpi/StringExtras.h>
#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();

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<Storage>());
m_plots.emplace_back(std::make_unique<Plot>(*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<int>(i));
}
char label[90];
std::snprintf(label, sizeof(label), "%s###header%d", name,
static_cast<int>(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<Storage>());
@@ -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<Storage>());
m_plots.emplace_back(std::make_unique<Plot>(*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<int>(i));
}
char label[90];
std::snprintf(label, sizeof(label), "%s###header%d", name,
static_cast<int>(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() {

View File

@@ -4,6 +4,8 @@
#include "glass/support/ExtraGuiWidgets.h"
#include <imgui.h>
#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>
@@ -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

View File

@@ -201,4 +201,6 @@ void PopID();
bool PopupEditName(const char* label, std::string* name);
bool ItemEditName(std::string* name);
} // namespace glass

View File

@@ -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();
};
/**

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;