Skip to content

Commit f3c4ad7

Browse files
committed
Refactor Linux menu positioning logic
Simplifies and restructures the menu positioning code in menu_linux.cpp for clarity and maintainability. Updates PositioningStrategy to use GetContentBounds instead of GetBounds for relative window positioning.
1 parent 371f10d commit f3c4ad7

File tree

2 files changed

+126
-195
lines changed

2 files changed

+126
-195
lines changed

src/platform/linux/menu_linux.cpp

Lines changed: 125 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -655,211 +655,143 @@ bool Menu::Open(const PositioningStrategy& strategy, Placement placement) {
655655
return data.result;
656656
}
657657

658-
if (pimpl_->gtk_menu_) {
659-
gtk_widget_show_all(pimpl_->gtk_menu_);
660-
661-
// Try to get GdkWindow from strategy if it's Relative type with a window
662-
GdkWindow* gdk_window = nullptr;
663-
GdkRectangle rectangle;
664-
bool use_explicit_position = false;
665-
666-
// Determine position based on strategy type
667-
switch (strategy.GetType()) {
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;
674-
use_explicit_position = true;
675-
// Use root window for absolute positioning
676-
gdk_window = gdk_get_default_root_window();
677-
break;
678-
}
658+
if (!pimpl_->gtk_menu_) {
659+
return false;
660+
}
679661

680-
case PositioningStrategy::Type::CursorPosition: {
681-
// Will use gtk_menu_popup_at_pointer or mouse position
682-
use_explicit_position = false;
683-
break;
684-
}
662+
gtk_widget_show_all(pimpl_->gtk_menu_);
685663

686-
case PositioningStrategy::Type::Relative: {
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-
}
758-
break;
759-
}
664+
// Get GdkWindow from relative window if available, otherwise use root window
665+
GdkWindow* gdk_window = nullptr;
666+
const Window* relative_window = strategy.GetRelativeWindow();
667+
if (relative_window) {
668+
void* native_obj = relative_window->GetNativeObject();
669+
if (native_obj) {
670+
gdk_window = static_cast<GdkWindow*>(native_obj);
760671
}
672+
}
673+
if (!gdk_window) {
674+
gdk_window = gdk_get_default_root_window();
675+
}
676+
if (!gdk_window) {
677+
// No window available (e.g., Wayland without root window) → cannot show
678+
return false;
679+
}
761680

762-
// Map placement to GDK gravity values
763-
GdkGravity anchor_gravity = GDK_GRAVITY_NORTH_WEST;
764-
GdkGravity menu_gravity = GDK_GRAVITY_NORTH_WEST;
765-
766-
switch (placement) {
767-
case Placement::Top:
768-
anchor_gravity = GDK_GRAVITY_SOUTH;
769-
menu_gravity = GDK_GRAVITY_NORTH;
770-
break;
771-
case Placement::TopStart:
772-
anchor_gravity = GDK_GRAVITY_SOUTH_WEST;
773-
menu_gravity = GDK_GRAVITY_NORTH_WEST;
774-
break;
775-
case Placement::TopEnd:
776-
anchor_gravity = GDK_GRAVITY_SOUTH_EAST;
777-
menu_gravity = GDK_GRAVITY_NORTH_EAST;
778-
break;
779-
case Placement::Right:
780-
anchor_gravity = GDK_GRAVITY_WEST;
781-
menu_gravity = GDK_GRAVITY_EAST;
782-
break;
783-
case Placement::RightStart:
784-
anchor_gravity = GDK_GRAVITY_NORTH_WEST;
785-
menu_gravity = GDK_GRAVITY_NORTH_EAST;
786-
break;
787-
case Placement::RightEnd:
788-
anchor_gravity = GDK_GRAVITY_SOUTH_WEST;
789-
menu_gravity = GDK_GRAVITY_SOUTH_EAST;
790-
break;
791-
case Placement::Bottom:
792-
anchor_gravity = GDK_GRAVITY_NORTH;
793-
menu_gravity = GDK_GRAVITY_SOUTH;
794-
break;
795-
case Placement::BottomStart:
796-
anchor_gravity = GDK_GRAVITY_NORTH_WEST;
797-
menu_gravity = GDK_GRAVITY_SOUTH_WEST;
798-
break;
799-
case Placement::BottomEnd:
800-
anchor_gravity = GDK_GRAVITY_NORTH_EAST;
801-
menu_gravity = GDK_GRAVITY_SOUTH_EAST;
802-
break;
803-
case Placement::Left:
804-
anchor_gravity = GDK_GRAVITY_EAST;
805-
menu_gravity = GDK_GRAVITY_WEST;
806-
break;
807-
case Placement::LeftStart:
808-
anchor_gravity = GDK_GRAVITY_NORTH_EAST;
809-
menu_gravity = GDK_GRAVITY_NORTH_WEST;
810-
break;
811-
case Placement::LeftEnd:
812-
anchor_gravity = GDK_GRAVITY_SOUTH_EAST;
813-
menu_gravity = GDK_GRAVITY_SOUTH_WEST;
814-
break;
815-
}
681+
GdkRectangle rectangle;
816682

817-
// Position menu
818-
if (use_explicit_position) {
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) {
824-
// No root window (e.g., Wayland) and no parent to anchor to → cannot show
825-
return false;
826-
}
827-
gtk_menu_popup_at_rect(GTK_MENU(pimpl_->gtk_menu_), gdk_window, &rectangle,
828-
anchor_gravity, menu_gravity, nullptr);
829-
return true;
830-
} else {
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;
683+
// Calculate position based on strategy type
684+
if (strategy.GetType() == PositioningStrategy::Type::CursorPosition) {
685+
// Use mouse position
686+
GdkDevice* mouse_device;
838687
#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);
688+
GdkSeat* seat = gdk_display_get_default_seat(gdk_display_get_default());
689+
mouse_device = gdk_seat_get_pointer(seat);
841690
#else
842-
GdkDeviceManager* devman =
843-
gdk_display_get_device_manager(gdk_display_get_default());
844-
mouse_device = gdk_device_manager_get_client_pointer(devman);
691+
GdkDeviceManager* devman =
692+
gdk_display_get_device_manager(gdk_display_get_default());
693+
mouse_device = gdk_device_manager_get_client_pointer(devman);
845694
#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;
695+
int x, y;
696+
gdk_window_get_device_position(gdk_window, mouse_device, &x, &y, nullptr);
697+
rectangle.x = x;
698+
rectangle.y = y;
699+
rectangle.width = 1;
700+
rectangle.height = 1;
701+
} else {
702+
// Absolute or Relative positioning
703+
Point position;
704+
705+
if (strategy.GetType() == PositioningStrategy::Type::Absolute) {
706+
position = strategy.GetAbsolutePosition();
707+
// For absolute positioning, use root window coordinates directly
708+
rectangle.x = static_cast<int>(position.x);
709+
rectangle.y = static_cast<int>(position.y);
710+
} else {
711+
// Relative positioning
712+
Rectangle rect = strategy.GetRelativeRectangle();
713+
Point offset = strategy.GetRelativeOffset();
714+
position = Point{rect.x + offset.x, rect.y + offset.y};
715+
716+
// If we have a relative window, adjust for frame extents and title bar
717+
if (relative_window && relative_window->GetNativeObject()) {
718+
GdkRectangle frame_rectangle;
719+
gdk_window_get_frame_extents(gdk_window, &frame_rectangle);
720+
721+
// Get title bar height from GtkWindow
722+
int title_bar_height = 0;
723+
GtkWindow* gtk_window = nullptr;
724+
GList* toplevels = gtk_window_list_toplevels();
725+
for (GList* l = toplevels; l != nullptr; l = l->next) {
726+
GtkWindow* candidate = GTK_WINDOW(l->data);
727+
GdkWindow* candidate_gdk = gtk_widget_get_window(GTK_WIDGET(candidate));
728+
if (candidate_gdk == gdk_window) {
729+
gtk_window = candidate;
730+
break;
731+
}
855732
}
733+
g_list_free(toplevels);
734+
735+
if (gtk_window) {
736+
GtkWidget* titlebar = gtk_window_get_titlebar(gtk_window);
737+
if (titlebar) {
738+
title_bar_height = gtk_widget_get_allocated_height(titlebar);
739+
}
740+
}
741+
742+
rectangle.x = static_cast<int>(position.x - frame_rectangle.x);
743+
rectangle.y = static_cast<int>(position.y - frame_rectangle.y + title_bar_height);
744+
} else {
745+
// Relative to rectangle (no window) - use root window coordinates
746+
rectangle.x = static_cast<int>(position.x);
747+
rectangle.y = static_cast<int>(position.y);
856748
}
857-
// Fallback: Let GTK automatically use the current event
858-
gtk_menu_popup_at_pointer(GTK_MENU(pimpl_->gtk_menu_), nullptr);
859-
return true;
860749
}
750+
751+
// Set rectangle dimensions
752+
rectangle.width = 1;
753+
rectangle.height = 1;
861754
}
862-
return false;
755+
756+
// Map placement to GDK gravity (menu anchor)
757+
GdkGravity menu_anchor = GDK_GRAVITY_NORTH_WEST;
758+
759+
switch (placement) {
760+
case Placement::TopStart:
761+
case Placement::Top:
762+
menu_anchor = GDK_GRAVITY_SOUTH_WEST;
763+
break;
764+
case Placement::TopEnd:
765+
menu_anchor = GDK_GRAVITY_SOUTH_EAST;
766+
break;
767+
case Placement::BottomStart:
768+
case Placement::Bottom:
769+
menu_anchor = GDK_GRAVITY_NORTH_WEST;
770+
break;
771+
case Placement::BottomEnd:
772+
menu_anchor = GDK_GRAVITY_NORTH_EAST;
773+
break;
774+
case Placement::LeftStart:
775+
case Placement::Left:
776+
menu_anchor = GDK_GRAVITY_NORTH_EAST;
777+
break;
778+
case Placement::LeftEnd:
779+
menu_anchor = GDK_GRAVITY_SOUTH_EAST;
780+
break;
781+
case Placement::RightStart:
782+
case Placement::Right:
783+
menu_anchor = GDK_GRAVITY_NORTH_WEST;
784+
break;
785+
case Placement::RightEnd:
786+
menu_anchor = GDK_GRAVITY_SOUTH_WEST;
787+
break;
788+
}
789+
790+
// Position menu using gtk_menu_popup_at_rect
791+
gtk_menu_popup_at_rect(GTK_MENU(pimpl_->gtk_menu_), gdk_window, &rectangle,
792+
GDK_GRAVITY_NORTH_WEST, menu_anchor, nullptr);
793+
794+
return true;
863795
}
864796

865797
bool Menu::Close() {

src/positioning_strategy.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ PositioningStrategy PositioningStrategy::Relative(const Window& window, const Po
3434

3535
Rectangle PositioningStrategy::GetRelativeRectangle() const {
3636
if (relative_window_) {
37-
return relative_window_->GetBounds();
37+
return relative_window_->GetContentBounds();
3838
}
3939
return relative_rect_;
4040
}
4141

4242
} // namespace nativeapi
43-

0 commit comments

Comments
 (0)