Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context menu #233

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ add_library(nanogui-obj OBJECT
include/nanogui/stackedwidget.h src/stackedwidget.cpp
include/nanogui/tabheader.h src/tabheader.cpp
include/nanogui/tabwidget.h src/tabwidget.cpp
include/nanogui/contextmenu.h src/contextmenu.cpp
include/nanogui/glcanvas.h src/glcanvas.cpp
include/nanogui/formhelper.h
include/nanogui/toolbutton.h
Expand Down
141 changes: 141 additions & 0 deletions include/nanogui/contextmenu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
nanogui/contextmenu.h -- Standard context menu with support for submenus

NanoGUI was developed by Wenzel Jakob <[email protected]>.
The widget drawing code is based on the NanoVG demo application
by Mikko Mononen.

All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
/** \file */

#pragma once

#include <nanogui/widget.h>
#include <unordered_map>

NAMESPACE_BEGIN(nanogui)

/**
* \class ContextMenu contextmenu.h nanogui/contextmenu.h
*
* \brief Nestable context menu.
*
* A context menu may be used in either 'disposable' or 'non-disposable' mode.
* In disposable mode, it may be created and activated on-the-fly in an event
* handler, and the menu will automatically delete itself after use.
* In non-disposable mode, it may be created and stored before it is actually
* needed, and then activated inside an event handler. In this mode the menu
* instance is reusable as long as its parent is alive.
*
* \rst
* .. code-block:: cpp
*
* auto menu = new ContextMenu(this, true);
* menu->addItem("Item 1", [](){
* std::cout << "Item 1 clicked!" << std::endl;
* });
* auto submenu = menu->addSubMenu("Submenu 1");
* submenu->addItem("Subitem 1", [](){
* std::cout << "Subitem 1 clicked!"
* });
* menu->activate();
*
* \endrst
*/
class NANOGUI_EXPORT ContextMenu : public Widget {
public:
/**
* \brief Construct a new ContextMenu.
* \param parent Parent widget.
* \param disposable When true, the context menu and all submenus will be
* destroyed upon deactivation.
*/
ContextMenu(Widget* parent, bool disposable);

/**
* \brief Activate the context menu at the given position.
*
* Make the context menu visible. When an item is clicked, the context menu
* will be deactivated.
*
* \param pos The desired position of the top left corner of the context
* menu, relative to the parent.
*/
void activate(const Vector2i& pos);

/**
* \brief Deactivate the context menu.
*
* This method is called automatically after an item is clicked. If the
* context menu is disposable, it will be removed from its parent and
* destroyed.
*/
void deactivate();

/**
* \brief Add an item to the menu. The callback is called when the item is clicked.
* \param name Name of the item. The name is displayed in the context menu.
* \param cb Callback to be executed when the item is clicked.
* \param icon Optional icon to display to the left of the label.
*/
void addItem(const std::string& name, const std::function<void()>& cb, int icon=0);

/**
* Add a submenu to the menu.
*
* \param name The display text of the item.
* \returns nullptr if a submenu or item already exists under the given name.
* \param icon Optional icon to display to the left of the label.
*/
ContextMenu* addSubMenu(const std::string& name, int icon = 0);

Vector2i preferredSize(NVGcontext* ctx) const override;
bool mouseEnterEvent(const Vector2i& p, bool enter) override;
bool mouseMotionEvent(const Vector2i& p, const Vector2i& rel, int button, int modifiers) override;
bool mouseButtonEvent(const Vector2i& p, int button, bool down, int modifiers) override;
void draw(NVGcontext* ctx) override;
bool focusEvent(bool focused) override;

protected:
/**
* \brief Determine if an item opens a new context menu.
* \param name Name of the item.
*/
bool isSubMenu_(const std::string& name);

/**
* \brief Determine if a row contains the point `p`.
* \param name Name of the item that is on the desired row.
* \param p Point relative to self.
*/
bool isRowSelected_(const std::string& name, const Vector2i& p) const;

private:
/// Activate a submenu.
void activateSubmenu(const std::string& name);
/// Deactivate the currently active submenu if there is one.
void deactivateSubmenu();
/// Remove the context menu and all submenus from their parent widget.
void dispose();
/// Calculate a submenus position.
Vector2i submenuPosition(const std::string& name) const;

private:
Widget *mItemContainer;
AdvancedGridLayout *mItemLayout;
std::unordered_map<std::string, std::function<void()>> mItems;
std::unordered_map<std::string, ContextMenu*> mSubmenus;
std::unordered_map<std::string, Label*> mLabels;
Label *mHighlightedItem;
ContextMenu *mActiveSubmenu;
ContextMenu *mRootMenu;
bool mDisposable;
bool mActivated;
bool mUpdateLayout;

Color mBackgroundColor, mMarginColor, mHighlightColor;
};

NAMESPACE_END(nanogui)
1 change: 1 addition & 0 deletions include/nanogui/nanogui.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <nanogui/toolbutton.h>
#include <nanogui/popup.h>
#include <nanogui/popupbutton.h>
#include <nanogui/contextmenu.h>
#include <nanogui/combobox.h>
#include <nanogui/progressbar.h>
#include <nanogui/entypo.h>
Expand Down
Loading