Skip to content

Commit 371f10d

Browse files
committed
Improve menu positioning for Relative and Cursor strategies
Enhances menu positioning on Linux by using the associated window's GdkWindow for Relative and CursorPosition strategies when available. Adds PositioningStrategy::GetRelativeWindow() accessor and refines coordinate calculations for more accurate menu placement, including title bar height adjustment and mouse position retrieval.
1 parent 94cb774 commit 371f10d

File tree

2 files changed

+130
-24
lines changed

2 files changed

+130
-24
lines changed

src/platform/linux/menu_linux.cpp

Lines changed: 122 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "../../image.h"
1313
#include "../../menu.h"
1414
#include "../../menu_event.h"
15+
#include "../../window.h"
1516

1617
namespace nativeapi {
1718

@@ -657,29 +658,103 @@ bool Menu::Open(const PositioningStrategy& strategy, Placement placement) {
657658
if (pimpl_->gtk_menu_) {
658659
gtk_widget_show_all(pimpl_->gtk_menu_);
659660

660-
double x = 0, y = 0;
661+
// Try to get GdkWindow from strategy if it's Relative type with a window
662+
GdkWindow* gdk_window = nullptr;
663+
GdkRectangle rectangle;
661664
bool use_explicit_position = false;
662665

663666
// Determine position based on strategy type
664667
switch (strategy.GetType()) {
665-
case PositioningStrategy::Type::Absolute:
666-
x = strategy.GetAbsolutePosition().x;
667-
y = strategy.GetAbsolutePosition().y;
668+
case PositioningStrategy::Type::Absolute: {
669+
Point abs_pos = strategy.GetAbsolutePosition();
670+
rectangle.x = static_cast<int>(abs_pos.x);
671+
rectangle.y = static_cast<int>(abs_pos.y);
672+
rectangle.width = 1;
673+
rectangle.height = 1;
668674
use_explicit_position = true;
675+
// Use root window for absolute positioning
676+
gdk_window = gdk_get_default_root_window();
669677
break;
678+
}
670679

671-
case PositioningStrategy::Type::CursorPosition:
672-
// Will use gtk_menu_popup_at_pointer
680+
case PositioningStrategy::Type::CursorPosition: {
681+
// Will use gtk_menu_popup_at_pointer or mouse position
673682
use_explicit_position = false;
674683
break;
684+
}
675685

676686
case PositioningStrategy::Type::Relative: {
677-
Rectangle rect = strategy.GetRelativeRectangle();
678-
Point offset = strategy.GetRelativeOffset();
679-
// Position at top-left corner of rectangle plus offset
680-
x = rect.x + offset.x;
681-
y = rect.y + offset.y;
682-
use_explicit_position = true;
687+
const Window* relative_window = strategy.GetRelativeWindow();
688+
if (relative_window) {
689+
// Strategy was created with a Window - use its GdkWindow
690+
void* native_obj = relative_window->GetNativeObject();
691+
if (native_obj) {
692+
gdk_window = static_cast<GdkWindow*>(native_obj);
693+
694+
// Get window frame extents for accurate positioning
695+
GdkRectangle frame_rectangle;
696+
gdk_window_get_frame_extents(gdk_window, &frame_rectangle);
697+
698+
// Get window position
699+
Point window_pos = relative_window->GetPosition();
700+
701+
// Try to get GtkWindow for title bar height calculation
702+
int title_bar_height = 0;
703+
GtkWindow* gtk_window = nullptr;
704+
// Find GtkWindow from GdkWindow by iterating through all toplevel windows
705+
GList* toplevels = gtk_window_list_toplevels();
706+
for (GList* l = toplevels; l != nullptr; l = l->next) {
707+
GtkWindow* candidate = GTK_WINDOW(l->data);
708+
GdkWindow* candidate_gdk = gtk_widget_get_window(GTK_WIDGET(candidate));
709+
if (candidate_gdk == gdk_window) {
710+
gtk_window = candidate;
711+
break;
712+
}
713+
}
714+
g_list_free(toplevels);
715+
716+
if (gtk_window) {
717+
GtkWidget* titlebar = gtk_window_get_titlebar(gtk_window);
718+
if (titlebar) {
719+
title_bar_height = gtk_widget_get_allocated_height(titlebar);
720+
}
721+
}
722+
723+
// Get relative rectangle and offset
724+
Rectangle rect = strategy.GetRelativeRectangle();
725+
Point offset = strategy.GetRelativeOffset();
726+
727+
// Calculate position: relative position (already in screen coordinates from GetRelativeRectangle)
728+
// + offset, then adjust for frame extents and title bar
729+
// Note: GetRelativeRectangle() returns window bounds in screen coordinates,
730+
// so we need to add the offset and adjust for frame/titlebar
731+
rectangle.x = static_cast<int>(rect.x + offset.x - frame_rectangle.x);
732+
rectangle.y = static_cast<int>(rect.y + offset.y - frame_rectangle.y + title_bar_height);
733+
rectangle.width = 1;
734+
rectangle.height = 1;
735+
use_explicit_position = true;
736+
} else {
737+
// Fallback: use root window
738+
gdk_window = gdk_get_default_root_window();
739+
Rectangle rect = strategy.GetRelativeRectangle();
740+
Point offset = strategy.GetRelativeOffset();
741+
rectangle.x = static_cast<int>(rect.x + offset.x);
742+
rectangle.y = static_cast<int>(rect.y + offset.y);
743+
rectangle.width = 1;
744+
rectangle.height = 1;
745+
use_explicit_position = true;
746+
}
747+
} else {
748+
// Strategy was created with a Rectangle - use root window
749+
Rectangle rect = strategy.GetRelativeRectangle();
750+
Point offset = strategy.GetRelativeOffset();
751+
rectangle.x = static_cast<int>(rect.x + offset.x);
752+
rectangle.y = static_cast<int>(rect.y + offset.y);
753+
rectangle.width = 1;
754+
rectangle.height = 1;
755+
use_explicit_position = true;
756+
gdk_window = gdk_get_default_root_window();
757+
}
683758
break;
684759
}
685760
}
@@ -739,24 +814,47 @@ bool Menu::Open(const PositioningStrategy& strategy, Placement placement) {
739814
break;
740815
}
741816

742-
// Try to position at explicit coordinates if available
817+
// Position menu
743818
if (use_explicit_position) {
744-
GdkWindow* root_window = gdk_get_default_root_window();
745-
if (!root_window) {
819+
if (!gdk_window) {
820+
// Fallback to root window if no window available
821+
gdk_window = gdk_get_default_root_window();
822+
}
823+
if (!gdk_window) {
746824
// No root window (e.g., Wayland) and no parent to anchor to → cannot show
747825
return false;
748826
}
749-
750-
GdkRectangle rect;
751-
rect.x = static_cast<int>(x);
752-
rect.y = static_cast<int>(y);
753-
rect.width = 1;
754-
rect.height = 1;
755-
gtk_menu_popup_at_rect(GTK_MENU(pimpl_->gtk_menu_), root_window, &rect, anchor_gravity,
756-
menu_gravity, nullptr);
827+
gtk_menu_popup_at_rect(GTK_MENU(pimpl_->gtk_menu_), gdk_window, &rectangle,
828+
anchor_gravity, menu_gravity, nullptr);
757829
return true;
758830
} else {
759-
// Let GTK automatically use the current event
831+
// CursorPosition: Try to get mouse position relative to a window if available
832+
const Window* relative_window = strategy.GetRelativeWindow();
833+
if (relative_window) {
834+
void* native_obj = relative_window->GetNativeObject();
835+
if (native_obj) {
836+
GdkWindow* target_window = static_cast<GdkWindow*>(native_obj);
837+
GdkDevice* mouse_device;
838+
#if GTK_CHECK_VERSION(3, 20, 0)
839+
GdkSeat* seat = gdk_display_get_default_seat(gdk_display_get_default());
840+
mouse_device = gdk_seat_get_pointer(seat);
841+
#else
842+
GdkDeviceManager* devman =
843+
gdk_display_get_device_manager(gdk_display_get_default());
844+
mouse_device = gdk_device_manager_get_client_pointer(devman);
845+
#endif
846+
int x, y;
847+
gdk_window_get_device_position(target_window, mouse_device, &x, &y, nullptr);
848+
rectangle.x = x;
849+
rectangle.y = y;
850+
rectangle.width = 1;
851+
rectangle.height = 1;
852+
gtk_menu_popup_at_rect(GTK_MENU(pimpl_->gtk_menu_), target_window, &rectangle,
853+
anchor_gravity, menu_gravity, nullptr);
854+
return true;
855+
}
856+
}
857+
// Fallback: Let GTK automatically use the current event
760858
gtk_menu_popup_at_pointer(GTK_MENU(pimpl_->gtk_menu_), nullptr);
761859
return true;
762860
}

src/positioning_strategy.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ class PositioningStrategy {
156156
*/
157157
Point GetRelativeOffset() const { return relative_offset_; }
158158

159+
/**
160+
* @brief Get the relative window (for Relative type created with Window).
161+
*
162+
* @return Pointer to the Window, or nullptr if not set
163+
* @note Only valid when GetType() == Type::Relative and strategy was created with a Window
164+
*/
165+
const Window* GetRelativeWindow() const { return relative_window_; }
166+
159167
private:
160168
/**
161169
* @brief Private constructor - use static factory methods instead.

0 commit comments

Comments
 (0)