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