@@ -79,8 +79,15 @@ extern "C" {
7979#endif
8080#endif // __GNUC__
8181
82+ #if defined(__APPLE__) && defined(__clang__)
83+ #pragma clang diagnostic push
84+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
85+ #endif
8286#include " raygui4.5.h"
8387#include " rlgl.h"
88+ #if defined(__APPLE__) && defined(__clang__)
89+ #pragma clang diagnostic pop
90+ #endif
8491
8592#pragma GCC diagnostic pop
8693}
@@ -230,5 +237,205 @@ RLGUIPP_API void SetDefaultFileIcons(const std::unordered_map<std::string_view,
230237RLGUIPP_API bool ModalFileDialog (FileDialogInfo& info, bool * isOpen);
231238RLGUIPP_API bool IsSysKeyDown (); // If macOS same as "IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER)" else "IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)"
232239
240+ namespace PopupMenu {
241+
242+ // ============================================================================
243+ // Configuration - adjust these to match your UI style
244+ // ============================================================================
245+
246+ struct Style {
247+ Color shadow = {0 , 0 , 0 , 80 };
248+ // Dimensions
249+ int itemHeight = 11 ;
250+ int itemPaddingX = 12 ;
251+ int itemPaddingRight = 6 ; // space for submenu arrow
252+ int menuPaddingY = 2 ;
253+ int minWidth = 64 ;
254+ int separatorHeight = 3 ;
255+ int borderWidth = 1 ;
256+ int shadowOffset = 4 ;
257+ int fontSize = 8 ;
258+ int submenuArrowOffset = 10 ;
259+ int iconWidth = 12 ; // reserved space for checkmarks/icons
260+
261+ // Behavior
262+ float submenuOpenDelay = 0 .20f ; // seconds before submenu opens on hover
263+ float submenuCloseDelay = 0 .15f ; // grace period when moving to submenu
264+ };
265+
266+ // ============================================================================
267+ // Menu Item
268+ // ============================================================================
269+
270+ struct Item {
271+ enum class Type { Action, Submenu, Separator, Toggle };
272+
273+ Type type = Type::Action;
274+ std::string label;
275+ std::string shortcut; // optional shortcut hint text (display only)
276+ std::function<void ()> action;
277+ std::vector<Item> children;
278+ bool enabled = true ;
279+ bool * toggleValue = nullptr ; // for toggle items
280+
281+ // Check if this item has a submenu
282+ [[nodiscard]] bool hasSubmenu () const { return !children.empty (); }
283+
284+ // Check if this item can react (can be clicked)
285+ [[nodiscard]] bool canReact () const {
286+ return enabled && type != Type::Separator && !hasSubmenu ();
287+ }
288+ };
289+
290+ // Convenience type alias
291+ using Menu = std::vector<Item>;
292+
293+ // ============================================================================
294+ // Factory functions for creating menu items
295+ // ============================================================================
296+
297+ inline Item Action (std::string label, std::function<void ()> action, std::string shortcut = "") {
298+ return Item{
299+ .type = Item::Type::Action,
300+ .label = std::move (label),
301+ .shortcut = std::move (shortcut),
302+ .action = std::move (action),
303+ .children = {},
304+ .enabled = true ,
305+ .toggleValue = nullptr
306+ };
307+ }
308+
309+ inline Item Submenu (std::string label, std::vector<Item> children) {
310+ return Item{
311+ .type = Item::Type::Submenu,
312+ .label = std::move (label),
313+ .shortcut = {},
314+ .action = nullptr ,
315+ .children = std::move (children),
316+ .enabled = true ,
317+ .toggleValue = nullptr
318+ };
319+ }
320+
321+ inline Item Separator () {
322+ return Item{
323+ .type = Item::Type::Separator,
324+ .label = {},
325+ .shortcut = {},
326+ .action = nullptr ,
327+ .children = {},
328+ .enabled = true ,
329+ .toggleValue = nullptr
330+ };
331+ }
332+
333+ inline Item Toggle (std::string label, bool * value, std::function<void ()> on_change = nullptr, std::string shortcut = "") {
334+ return Item{
335+ .type = Item::Type::Toggle,
336+ .label = std::move (label),
337+ .shortcut = std::move (shortcut),
338+ .action = std::move (on_change),
339+ .children = {},
340+ .enabled = true ,
341+ .toggleValue = value
342+ };
343+ }
344+
345+ inline Item Disabled (Item item) {
346+ item.enabled = false ;
347+ return item;
348+ }
349+
350+ // ============================================================================
351+ // Internal: Open menu state
352+ // ============================================================================
353+
354+ namespace detail {
355+
356+ struct OpenMenu {
357+ Vector2 position{0 , 0 };
358+ Menu const * items = nullptr ;
359+ int hoveredIndex = -1 ;
360+ float hoverTime = 0 .0f ;
361+ int width = 0 ; // cached calculated width
362+ };
363+
364+ } // namespace detail
365+
366+ // ============================================================================
367+ // Popup Menu System
368+ // ============================================================================
369+
370+ class System {
371+ public:
372+ Style style;
373+
374+ // Open a popup menu at the given position
375+ void open (Vector2 position, const Menu& menu);
376+
377+ // Close all menus
378+ void close ();
379+
380+ // Check if any menu is currently open
381+ [[nodiscard]] bool isOpen () const ;
382+
383+ [[nodiscard]] bool isMenuOpen (const Menu& menu) const ;
384+
385+ // Update menu state - call once per frame
386+ // Returns true if a menu action was triggered this frame
387+ bool update (float dt);
388+
389+ // Draw menus - call after drawing your scene (menus draw on top)
390+ void render () const ;
391+
392+ // Get the last triggered action's label (for debugging/logging)
393+ [[nodiscard]] std::optional<std::string> lastAction () const { return _lastTriggeredAction; }
394+
395+ private:
396+ std::vector<detail::OpenMenu> _menuChain;
397+ std::optional<std::string> _lastTriggeredAction;
398+ float _closeGraceTimer = 0 .0f ;
399+ int _closeGraceLevel = -1 ;
400+
401+ // Calculate bounds for a menu panel
402+ [[nodiscard]] Rectangle getMenuBounds (const detail::OpenMenu& menu) const ;
403+
404+ // Calculate the width needed for a menu
405+ [[nodiscard]] int calculateMenuWidth (const Menu& items) const ;
406+
407+ // Calculate the text width for gui font
408+ [[nodiscard]] int calculateTextWidth (const std::string& text) const ;
409+
410+ // Calculate total height of a menu
411+ [[nodiscard]] int calculateMenuHeight (const Menu& items) const ;
412+
413+ // Get item height (separators are shorter)
414+ [[nodiscard]] int getItemHeight (const Item& item) const ;
415+
416+ // Get which item index is at a Y position within a menu, or -1
417+ [[nodiscard]] int getItemAtYPos (const detail::OpenMenu& menu, float y) const ;
418+
419+ // Open a submenu for the given parent level and item index
420+ void openSubmenu (int parent_level, int item_index);
421+
422+ // Trigger an item's action
423+ void triggerItem (const Item& item);
424+
425+ // Draw a single menu panel
426+ void drawMenu (const detail::OpenMenu& menu) const ;
427+
428+ // Draw a single item within a menu
429+ void drawItem (const Item& item, Rectangle bounds, bool hovered) const ;
430+ };
431+
432+ RLGUIPP_API void open (Vector2 position, const Menu& menu);
433+ RLGUIPP_API void close ();
434+ RLGUIPP_API bool isOpen ();
435+ RLGUIPP_API bool isMenuOpen (const Menu& menu);
436+
437+ } // namespace PopupMenu
438+
439+
233440} // namespace gui
234441
0 commit comments