Skip to content

Commit b30d4b4

Browse files
committed
Add placement parameter to menu open methods
Introduces a placement parameter to menu opening functions in C and C++ APIs, allowing context menus to be positioned relative to an anchor (e.g., top, bottom, left, right, and their variants). Updates platform-specific implementations (Linux, macOS, Windows) to handle placement logic, and modifies example code to demonstrate usage. This improves flexibility for UI positioning and enables more precise control over menu display.
1 parent e94700f commit b30d4b4

File tree

9 files changed

+287
-20
lines changed

9 files changed

+287
-20
lines changed

examples/menu_c_example/main.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,8 @@ int main() {
179179

180180
native_point_t point = {100, 100};
181181
native_positioning_strategy_t strategy = native_positioning_strategy_absolute(&point);
182-
if (native_menu_open(menu, strategy)) {
183-
printf("Context menu opened successfully\n");
182+
if (native_menu_open(menu, strategy, NATIVE_PLACEMENT_BOTTOM_START)) {
183+
printf("Context menu opened successfully (BOTTOM_START placement)\n");
184184
} else {
185185
printf("Failed to open context menu (expected in console app)\n");
186186
}

examples/menu_example/main.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,18 @@ int main() {
105105
std::cout << "\n=== Attempting to Open Context Menu ===" << std::endl;
106106
std::cout << "Note: Context menu display may not work in console applications" << std::endl;
107107

108-
// Try to show at screen coordinates (100, 100)
108+
// Try to show at screen coordinates (100, 100) with default placement (BottomStart)
109109
if (menu->Open(PositioningStrategy::Absolute({100, 100}))) {
110-
std::cout << "Context menu opened successfully" << std::endl;
110+
std::cout << "Context menu opened successfully (BottomStart placement)" << std::endl;
111111
} else {
112112
std::cout << "Failed to open context menu (expected in console app)" << std::endl;
113113
}
114114

115+
// Demonstrate placement parameter - open menu above the point
116+
if (menu->Open(PositioningStrategy::Absolute({100, 200}), Placement::Top)) {
117+
std::cout << "Context menu opened successfully (Top placement)" << std::endl;
118+
}
119+
115120
// Demonstrate submenu
116121
std::cout << "\n=== Testing Submenu ===" << std::endl;
117122
auto submenu = std::make_shared<Menu>();

src/capi/geometry_c.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,68 @@ typedef struct {
2525
double width;
2626
double height;
2727
} native_rectangle_t;
28+
29+
/**
30+
* Placement options for positioning UI elements relative to an anchor.
31+
*/
32+
typedef enum {
33+
/**
34+
* Position above the anchor, horizontally centered.
35+
*/
36+
NATIVE_PLACEMENT_TOP,
37+
38+
/**
39+
* Position above the anchor, aligned to the start (left).
40+
*/
41+
NATIVE_PLACEMENT_TOP_START,
42+
43+
/**
44+
* Position above the anchor, aligned to the end (right).
45+
*/
46+
NATIVE_PLACEMENT_TOP_END,
47+
48+
/**
49+
* Position to the right of the anchor, vertically centered.
50+
*/
51+
NATIVE_PLACEMENT_RIGHT,
52+
53+
/**
54+
* Position to the right of the anchor, aligned to the start (top).
55+
*/
56+
NATIVE_PLACEMENT_RIGHT_START,
57+
58+
/**
59+
* Position to the right of the anchor, aligned to the end (bottom).
60+
*/
61+
NATIVE_PLACEMENT_RIGHT_END,
62+
63+
/**
64+
* Position below the anchor, horizontally centered.
65+
*/
66+
NATIVE_PLACEMENT_BOTTOM,
67+
68+
/**
69+
* Position below the anchor, aligned to the start (left).
70+
*/
71+
NATIVE_PLACEMENT_BOTTOM_START,
72+
73+
/**
74+
* Position below the anchor, aligned to the end (right).
75+
*/
76+
NATIVE_PLACEMENT_BOTTOM_END,
77+
78+
/**
79+
* Position to the left of the anchor, vertically centered.
80+
*/
81+
NATIVE_PLACEMENT_LEFT,
82+
83+
/**
84+
* Position to the left of the anchor, aligned to the start (top).
85+
*/
86+
NATIVE_PLACEMENT_LEFT_START,
87+
88+
/**
89+
* Position to the left of the anchor, aligned to the end (bottom).
90+
*/
91+
NATIVE_PLACEMENT_LEFT_END
92+
} native_placement_t;

src/capi/menu_c.cpp

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,14 +838,59 @@ native_menu_item_list_t native_menu_get_all_items(native_menu_t menu) {
838838
}
839839
}
840840

841-
bool native_menu_open(native_menu_t menu, native_positioning_strategy_t strategy) {
841+
bool native_menu_open(native_menu_t menu, native_positioning_strategy_t strategy, native_placement_t placement) {
842842
if (!menu || !strategy)
843843
return false;
844844

845845
try {
846846
auto menu_ptr = static_cast<Menu*>(menu);
847847
auto strategy_ptr = static_cast<PositioningStrategy*>(strategy);
848-
return menu_ptr->Open(*strategy_ptr);
848+
849+
// Convert C placement enum to C++ placement enum
850+
Placement cpp_placement;
851+
switch (placement) {
852+
case NATIVE_PLACEMENT_TOP:
853+
cpp_placement = Placement::Top;
854+
break;
855+
case NATIVE_PLACEMENT_TOP_START:
856+
cpp_placement = Placement::TopStart;
857+
break;
858+
case NATIVE_PLACEMENT_TOP_END:
859+
cpp_placement = Placement::TopEnd;
860+
break;
861+
case NATIVE_PLACEMENT_RIGHT:
862+
cpp_placement = Placement::Right;
863+
break;
864+
case NATIVE_PLACEMENT_RIGHT_START:
865+
cpp_placement = Placement::RightStart;
866+
break;
867+
case NATIVE_PLACEMENT_RIGHT_END:
868+
cpp_placement = Placement::RightEnd;
869+
break;
870+
case NATIVE_PLACEMENT_BOTTOM:
871+
cpp_placement = Placement::Bottom;
872+
break;
873+
case NATIVE_PLACEMENT_BOTTOM_START:
874+
cpp_placement = Placement::BottomStart;
875+
break;
876+
case NATIVE_PLACEMENT_BOTTOM_END:
877+
cpp_placement = Placement::BottomEnd;
878+
break;
879+
case NATIVE_PLACEMENT_LEFT:
880+
cpp_placement = Placement::Left;
881+
break;
882+
case NATIVE_PLACEMENT_LEFT_START:
883+
cpp_placement = Placement::LeftStart;
884+
break;
885+
case NATIVE_PLACEMENT_LEFT_END:
886+
cpp_placement = Placement::LeftEnd;
887+
break;
888+
default:
889+
cpp_placement = Placement::BottomStart;
890+
break;
891+
}
892+
893+
return menu_ptr->Open(*strategy_ptr, cpp_placement);
849894
} catch (...) {
850895
return false;
851896
}

src/capi/menu_c.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <stdbool.h>
44
#include <stddef.h>
55
#include <stdint.h>
6+
#include "geometry_c.h"
67

78
#if _WIN32
89
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
@@ -477,10 +478,12 @@ native_menu_item_list_t native_menu_get_all_items(native_menu_t menu);
477478
* Open the menu as a context menu using the specified positioning strategy
478479
* @param menu The menu
479480
* @param strategy The positioning strategy determining where to display the menu
481+
* @param placement The placement option determining how the menu is positioned
482+
* relative to the reference point (default: NATIVE_PLACEMENT_BOTTOM_START)
480483
* @return true if menu was opened successfully, false otherwise
481484
*/
482485
FFI_PLUGIN_EXPORT
483-
bool native_menu_open(native_menu_t menu, native_positioning_strategy_t strategy);
486+
bool native_menu_open(native_menu_t menu, native_positioning_strategy_t strategy, native_placement_t placement);
484487

485488
/**
486489
* Close the menu if it's currently showing

src/menu.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -650,22 +650,27 @@ class Menu : public EventEmitter<MenuEvent>, public NativeObjectProvider {
650650
* selects an item.
651651
*
652652
* @param strategy The positioning strategy determining where to display the menu
653+
* @param placement The placement option determining how the menu is positioned
654+
* relative to the reference point (default: BottomStart)
653655
* @return true if the menu was successfully opened, false otherwise
654656
*
655657
* @example
656658
* ```cpp
657-
* // Open context menu at cursor position
658-
* menu->Open(PositioningStrategy::CursorPosition());
659+
* // Open context menu at cursor position, below the cursor
660+
* menu->Open(PositioningStrategy::CursorPosition(), Placement::BottomStart);
659661
*
660-
* // Open context menu at specific coordinates
661-
* menu->Open(PositioningStrategy::Absolute({100, 200}));
662+
* // Open context menu at specific coordinates, above and centered
663+
* menu->Open(PositioningStrategy::Absolute({100, 200}), Placement::Top);
662664
*
663-
* // Open context menu relative to a button with offset
665+
* // Open context menu relative to a button with offset, to the right
664666
* Rectangle buttonRect = button->GetBounds();
665-
* menu->Open(PositioningStrategy::Relative(buttonRect, {0, 10}));
667+
* menu->Open(PositioningStrategy::Relative(buttonRect, {0, 10}), Placement::Right);
668+
*
669+
* // Use default placement (BottomStart)
670+
* menu->Open(PositioningStrategy::CursorPosition());
666671
* ```
667672
*/
668-
bool Open(const PositioningStrategy& strategy);
673+
bool Open(const PositioningStrategy& strategy, Placement placement = Placement::BottomStart);
669674

670675
/**
671676
* @brief Programmatically close the menu if it's currently showing.

src/platform/linux/menu_linux.cpp

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ std::vector<std::shared_ptr<MenuItem>> Menu::GetAllItems() const {
395395
return pimpl_->items_;
396396
}
397397

398-
bool Menu::Open(const PositioningStrategy& strategy) {
398+
bool Menu::Open(const PositioningStrategy& strategy, Placement placement) {
399399
if (pimpl_->gtk_menu_) {
400400
gtk_widget_show_all(pimpl_->gtk_menu_);
401401

@@ -426,6 +426,61 @@ bool Menu::Open(const PositioningStrategy& strategy) {
426426
}
427427
}
428428

429+
// Map placement to GDK gravity values
430+
GdkGravity anchor_gravity = GDK_GRAVITY_NORTH_WEST;
431+
GdkGravity menu_gravity = GDK_GRAVITY_NORTH_WEST;
432+
433+
switch (placement) {
434+
case Placement::Top:
435+
anchor_gravity = GDK_GRAVITY_SOUTH;
436+
menu_gravity = GDK_GRAVITY_NORTH;
437+
break;
438+
case Placement::TopStart:
439+
anchor_gravity = GDK_GRAVITY_SOUTH_WEST;
440+
menu_gravity = GDK_GRAVITY_NORTH_WEST;
441+
break;
442+
case Placement::TopEnd:
443+
anchor_gravity = GDK_GRAVITY_SOUTH_EAST;
444+
menu_gravity = GDK_GRAVITY_NORTH_EAST;
445+
break;
446+
case Placement::Right:
447+
anchor_gravity = GDK_GRAVITY_WEST;
448+
menu_gravity = GDK_GRAVITY_EAST;
449+
break;
450+
case Placement::RightStart:
451+
anchor_gravity = GDK_GRAVITY_NORTH_WEST;
452+
menu_gravity = GDK_GRAVITY_NORTH_EAST;
453+
break;
454+
case Placement::RightEnd:
455+
anchor_gravity = GDK_GRAVITY_SOUTH_WEST;
456+
menu_gravity = GDK_GRAVITY_SOUTH_EAST;
457+
break;
458+
case Placement::Bottom:
459+
anchor_gravity = GDK_GRAVITY_NORTH;
460+
menu_gravity = GDK_GRAVITY_SOUTH;
461+
break;
462+
case Placement::BottomStart:
463+
anchor_gravity = GDK_GRAVITY_NORTH_WEST;
464+
menu_gravity = GDK_GRAVITY_SOUTH_WEST;
465+
break;
466+
case Placement::BottomEnd:
467+
anchor_gravity = GDK_GRAVITY_NORTH_EAST;
468+
menu_gravity = GDK_GRAVITY_SOUTH_EAST;
469+
break;
470+
case Placement::Left:
471+
anchor_gravity = GDK_GRAVITY_EAST;
472+
menu_gravity = GDK_GRAVITY_WEST;
473+
break;
474+
case Placement::LeftStart:
475+
anchor_gravity = GDK_GRAVITY_NORTH_EAST;
476+
menu_gravity = GDK_GRAVITY_NORTH_WEST;
477+
break;
478+
case Placement::LeftEnd:
479+
anchor_gravity = GDK_GRAVITY_SOUTH_EAST;
480+
menu_gravity = GDK_GRAVITY_SOUTH_WEST;
481+
break;
482+
}
483+
429484
// Try to position at explicit coordinates if available
430485
if (use_explicit_position) {
431486
GdkWindow* root_window = gdk_get_default_root_window();
@@ -436,7 +491,7 @@ bool Menu::Open(const PositioningStrategy& strategy) {
436491
rect.width = 1;
437492
rect.height = 1;
438493
gtk_menu_popup_at_rect(GTK_MENU(pimpl_->gtk_menu_), root_window, &rect,
439-
GDK_GRAVITY_NORTH_WEST, GDK_GRAVITY_NORTH_WEST, nullptr);
494+
anchor_gravity, menu_gravity, nullptr);
440495
} else {
441496
// Fallback to pointer if root window not available
442497
gtk_menu_popup_at_pointer(GTK_MENU(pimpl_->gtk_menu_), nullptr);

src/platform/macos/menu_macos.mm

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ - (void)menuDidClose:(NSMenu*)menu {
670670
return pimpl_->items_;
671671
}
672672

673-
bool Menu::Open(const PositioningStrategy& strategy) {
673+
bool Menu::Open(const PositioningStrategy& strategy, Placement placement) {
674674
double x = 0, y = 0;
675675

676676
// Determine position based on strategy type
@@ -697,6 +697,70 @@ - (void)menuDidClose:(NSMenu*)menu {
697697
}
698698
}
699699

700+
// Get menu size for placement adjustments
701+
NSSize menu_size = [pimpl_->ns_menu_ size];
702+
double menu_width = menu_size.width;
703+
double menu_height = menu_size.height;
704+
705+
// Adjust position based on placement
706+
switch (placement) {
707+
case Placement::TopStart: // topLeft
708+
x -= menu_width;
709+
y += menu_height;
710+
break;
711+
712+
case Placement::Top: // top center
713+
x -= menu_width / 2.0;
714+
y += menu_height;
715+
break;
716+
717+
case Placement::TopEnd: // topRight
718+
y += menu_height;
719+
break;
720+
721+
case Placement::RightStart: // right top
722+
x += menu_width;
723+
y += menu_height;
724+
break;
725+
726+
case Placement::Right: // right center
727+
x += menu_width;
728+
y -= menu_height / 2.0;
729+
break;
730+
731+
case Placement::RightEnd: // right bottom
732+
x += menu_width;
733+
y -= menu_height;
734+
break;
735+
736+
case Placement::BottomStart: // bottomLeft
737+
x -= menu_width;
738+
break;
739+
740+
case Placement::Bottom: // bottom center
741+
x -= menu_width / 2.0;
742+
break;
743+
744+
case Placement::BottomEnd: // bottomRight
745+
// No adjustment needed
746+
break;
747+
748+
case Placement::LeftStart: // left top
749+
x -= menu_width;
750+
y += menu_height;
751+
break;
752+
753+
case Placement::Left: // left center
754+
x -= menu_width;
755+
y -= menu_height / 2.0;
756+
break;
757+
758+
case Placement::LeftEnd: // left bottom
759+
x -= menu_width;
760+
y -= menu_height;
761+
break;
762+
}
763+
700764
// Get the main window
701765
NSWindow* main_window = [[NSApplication sharedApplication] mainWindow];
702766
if (!main_window) {

0 commit comments

Comments
 (0)