FifeGUI 0.3.0
A C++ GUI library designed for games.
menubar.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
2// SPDX-FileCopyrightText: 2026 Fifengine contributors
3
4// Corresponding header include
5#include "fifechan/widgets/menubar.hpp"
6
7// Standard library includes
8#include <cassert>
9#include <memory>
10#include <string>
11#include <utility>
12
13// Project headers
14#include "fifechan/graphics.hpp"
15#include "fifechan/widgets/menuitem.hpp"
16#include "fifechan/widgets/menupopup.hpp"
17
18namespace fcn
19{
21 {
23 setOpaque(true);
25 // Register as mouse listener to handle clicks on menu bar
26 addMouseListener(this);
27 }
28
29 Widget* MenuBar::addMenu(std::string const & text, MenuPopup* popup)
30 {
31 auto item = std::make_unique<MenuItem>(text);
32
33 item->setType(MenuItem::Type::Submenu);
34
35 // Register MenuBar as an action listener so clicks on the top-level
36 // menu item are routed to MenuBar::action.
37 item->addActionListener(this);
38
39 if (popup != nullptr) {
40 item->setSubmenu(popup);
41 popup->setParentMenuItem(item.get());
42 }
43
44 // If a popup is supplied, add it to the top-level container so
45 // it becomes part of the widget tree and can be shown/hidden.
46 if (popup != nullptr) {
47 Container* topContainer = nullptr;
48 if (auto* p = getTop()) {
49 topContainer = dynamic_cast<Container*>(p);
50 }
51 if (topContainer != nullptr) {
52 // Ensure popup is hidden initially
53 popup->setVisible(false);
54 // Add popup at position 0,0 initially (will be repositioned when shown)
55 topContainer->add(popup, 0, 0);
56 }
57 }
58
59 add(item.get());
60 return item.release();
61 }
62
64 {
65 if (mOpenMenu != nullptr) {
66 mOpenMenu->hide();
67 mOpenMenu = nullptr;
68 }
69 }
70
72 {
73 return mOpenMenu != nullptr;
74 }
75
77 {
78 return mOpenMenu;
79 }
80
81 void MenuBar::action(ActionEvent const & event)
82 {
83 // Handle menu item click
84 auto* source = dynamic_cast<MenuItem*>(event.getSource());
85
86 if (source == nullptr) {
87 return;
88 }
89
90 if (source->getSubmenu() != nullptr) {
91 MenuPopup* popup = source->getSubmenu();
92
93 if (mOpenMenu == popup) {
94 // Close already open menu (ModalScope in popup->hide() handles modal pop)
95 popup->hide();
96 // Restore focus to MenuBar after closing
98 mOpenMenu = nullptr;
99 } else {
100 // Close previously open menu
101 if (mOpenMenu != nullptr) {
102 mOpenMenu->hide();
103 }
104
105 // Request focus on MenuBar before opening menu
106 requestFocus();
107
108 // Open new menu at absolute coordinates
109 // Get absolute position from the menu item's position + parent's position chain
110 int ax = source->getX();
111 int ay = source->getY();
112
113 Widget const * parent = source->getParent();
114
115 while (parent != nullptr) {
116 ax += parent->getX();
117 ay += parent->getY();
118 parent = parent->getParent();
119 }
120 // Add the menu bar's position too
121 ax += getX();
122 ay += getY();
123 popup->show(ax, ay + source->getHeight());
124 mOpenMenu = popup;
125 }
126 }
127 }
128
130 {
131 // Handle keyboard navigation in menu bar
132 Key const key = event.getKey();
133
134 // ESC closes any open menu
135 if (key.getValue() == fcn::Key::ESCAPE) {
136 if (mOpenMenu != nullptr) {
137 mOpenMenu->hide();
138 // Restore focus to MenuBar when ESC closes menu
139 requestFocus();
140 mOpenMenu = nullptr;
141 event.consume();
142 }
143 return;
144 }
145
146 // Left/Right to navigate between menu items
147 if (key.getValue() == fcn::Key::LEFT || key.getValue() == fcn::Key::RIGHT) {
148 unsigned const childCount = getChildrenCount();
149 if (childCount == 0) {
150 return;
151 }
152
153 if (key.getValue() == fcn::Key::LEFT) {
154 mSelectedIndex = (mSelectedIndex - 1 + static_cast<int>(childCount)) % static_cast<int>(childCount);
155 } else {
156 mSelectedIndex = (mSelectedIndex + 1) % static_cast<int>(childCount);
157 }
158
159 // Focus the selected menu item
160 Widget* child = getChild(mSelectedIndex);
161 if (child != nullptr) {
162 child->requestFocus();
163 event.consume();
164 }
165 return;
166 }
167
168 // Enter or Down to open the selected menu
169 if (key.getValue() == fcn::Key::KEY_RETURN || key.getValue() == fcn::Key::DOWN) {
170 if (mSelectedIndex >= 0 && std::cmp_less(mSelectedIndex, getChildrenCount())) {
171 Widget* child = getChild(mSelectedIndex);
172 if (auto const * menuItem = dynamic_cast<MenuItem*>(child)) {
173 if (menuItem->getSubmenu() != nullptr) {
174 action(ActionEvent(this, ""));
175 }
176 }
177 } else {
178 // If no item selected, select the first one
179 mSelectedIndex = 0;
180 }
181 event.consume();
182 return;
183 }
184 }
185
187 {
188 // No action needed on key release
189 }
190
192 {
193 // If a menu is already open, handle clicks specially:
194 // - Clicking the same MenuItem that opened the popup: do nothing here so
195 // the MenuItem's action can toggle the popup.
196 // - Clicking another MenuItem: close current popup but DON'T consume the
197 // event so the other MenuItem's action can open its popup.
198 // - Clicking elsewhere on the MenuBar: close current popup and consume
199 // the event.
200 if (mOpenMenu != nullptr) {
201 Widget* target = getWidgetAt(event.getX(), event.getY());
202 if (auto const * mi = dynamic_cast<MenuItem*>(target)) {
203 if (mi->getSubmenu() == mOpenMenu) {
204 // Let the MenuItem's action handler toggle the popup.
205 return;
206 }
207
208 // Clicked another menu item: close current menu and allow the
209 // click to propagate so the new menu's action can open it.
210 mOpenMenu->hide();
211 mOpenMenu = nullptr;
212 return;
213 }
214
215 // Clicked not on a menu item: close and consume the event.
216 mOpenMenu->hide();
217 requestFocus();
218 mOpenMenu = nullptr;
219 event.consume();
220 }
221 }
222
223 void MenuBar::draw(Graphics* graphics)
224 {
225 assert("graphics must not be null" && graphics != nullptr);
226
227 // Draw background and a fine 1px bottom border line
228 if (isOpaque()) {
229 graphics->setColor(getBackgroundColor());
230 graphics->fillRectangle(Rectangle(0, 0, getWidth(), getHeight()));
231 }
232
233 // Draw children (Container::draw will call drawBorder when border size > 0)
234 HorizontalBar::draw(graphics);
235 }
236} // namespace fcn
Represents an action trigger (e.g., button click).
A composite widget capable of holding and managing child widgets.
Definition container.hpp:36
virtual bool isOpaque() const
Checks if the container is opaque or not.
Definition container.cpp:88
virtual void add(Widget *widget)
Adds a widget to the container.
Definition container.cpp:93
Widget * getChild(unsigned int index) const
Gets child by index.
virtual void setOpaque(bool opaque)
Sets the container to be opaque or not.
Definition container.cpp:83
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:58
virtual void setColor(Color const &color)=0
Sets the color to use when drawing.
virtual void fillRectangle(Rectangle const &rectangle)=0
Draws a filled rectangle.
void draw(Graphics *graphics) override
Draws the widget.
void setFixedHeight(unsigned int height)
Sets the default height of the bar.
Represents a key event.
Definition keyevent.hpp:26
Widget * addMenu(std::string const &text, MenuPopup *popup=nullptr)
Adds a menu item to the menu bar.
Definition menubar.cpp:29
MenuPopup * getOpenMenu() const
Gets the currently open menu popup.
Definition menubar.cpp:76
bool isMenuOpen() const
Checks if any menu is currently open.
Definition menubar.cpp:71
void mousePressed(MouseEvent &event) override
Called when a mouse button has been pressed down on the widget area.
Definition menubar.cpp:191
void action(ActionEvent const &event) override
Handles an action event emitted by a widget.
Definition menubar.cpp:81
void keyPressed(KeyEvent &event) override
Called if a key is pressed when the widget has keyboard focus.
Definition menubar.cpp:129
MenuBar()
Constructor.
Definition menubar.cpp:20
void keyReleased(KeyEvent &event) override
Called if a key is released when the widget has keyboard focus.
Definition menubar.cpp:186
void closeAll()
Closes all open menus.
Definition menubar.cpp:63
void draw(Graphics *graphics) override
Draws the menu bar (background and bottom border).
Definition menubar.cpp:223
A menu item widget for use in menus.
Definition menuitem.hpp:124
A menu popup widget that displays a dropdown menu.
Definition menupopup.hpp:38
void setParentMenuItem(Widget *parent)
Sets the parent menu item that opened this popup.
void hide()
Hides the popup.
void show(int x, int y)
Shows the popup at a specific position.
Represents a mouse event.
int getX() const
Gets the x coordinate of the mouse event.
int getY() const
Gets the y coordinate of the mouse event.
Represents a rectangular area (X, Y, Width, Height).
Definition rectangle.hpp:22
Abstract base class defining the common behavior, properties, and lifecycle of all GUI elements.
Definition widget.hpp:56
void setVisible(bool visible)
Sets the widget to be visible, or not.
Definition widget.cpp:685
int getY() const
Gets the y coordinate of the widget.
Definition widget.cpp:301
unsigned int getChildrenCount() const
Gets how many childs the widget have.
Definition widget.cpp:339
int getWidth() const
Gets the width of the widget.
Definition widget.cpp:252
virtual Widget * getParent() const
Gets the widget's parent container.
Definition widget.cpp:239
int getX() const
Gets the x coordinate of the widget.
Definition widget.cpp:288
virtual void requestFocus()
Requests focus for the widget.
Definition widget.cpp:660
Color const & getBackgroundColor() const
Gets the background color.
Definition widget.cpp:762
void addMouseListener(MouseListener *mouseListener)
Adds a mouse listener to the widget.
Definition widget.cpp:907
Widget * getWidgetAt(int x, int y)
Gets a widget at a certain position in the widget.
Definition widget.hpp:1287
unsigned int getBorderStyle() const
Get the current border drawing style.
Definition widget.cpp:494
virtual Widget * getTop() const
Gets the top widget, or top parent, of this widget.
Definition widget.cpp:1316
void setBorderBottom(unsigned int size, unsigned int style)
Convenience helper: set a bottom-only border with size and style.
Definition widget.cpp:506
int getHeight() const
Gets the height of the widget.
Definition widget.cpp:265
Used replacement tokens by configure_file():