mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -25,3 +25,9 @@ std::unique_ptr<View> glass::MakeFunctionView(
|
||||
}
|
||||
|
||||
void View::Hidden() {}
|
||||
|
||||
void View::Settings() {}
|
||||
|
||||
bool View::HasSettings() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -201,4 +201,6 @@ void PopID();
|
||||
|
||||
bool PopupEditName(const char* label, std::string* name);
|
||||
|
||||
bool ItemEditName(std::string* name);
|
||||
|
||||
} // namespace glass
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user