From 9210e3efbf789a15bef1d9e84b335a4c43c0e25d Mon Sep 17 00:00:00 2001 From: ManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com> Date: Wed, 11 Jan 2023 17:40:38 +0100 Subject: [PATCH] Wayland: Further improved implementation of menu windows Tall menus now work also as single popup window and show the correct selected item. Groups of popups with a menutitle, an associated menuwindow, and possibly submenus are constructed around the menuwindow, the menutitle being a child popup of the menuwindow. This positions these popup groups better than before. --- src/Fl_Menu.cxx | 33 +++++- src/Fl_Window_Driver.H | 4 +- .../Wayland/Fl_Wayland_Window_Driver.cxx | 104 ++++++++++++++---- 3 files changed, 114 insertions(+), 27 deletions(-) diff --git a/src/Fl_Menu.cxx b/src/Fl_Menu.cxx index 4f6966bb0..37e8b9226 100644 --- a/src/Fl_Menu.cxx +++ b/src/Fl_Menu.cxx @@ -123,8 +123,9 @@ class menutitle : public window_with_items { void draw() FL_OVERRIDE; public: menutitle *extra; // additional menutitle window when the 1st one is covered by a menuwindow - menutitle(int X, int Y, int W, int H, const Fl_Menu_Item*); + menutitle(int X, int Y, int W, int H, const Fl_Menu_Item*, bool menubar = false); ~menutitle(); + bool in_menubar; }; // each vertical menu has one of these: @@ -136,6 +137,7 @@ class menuwindow : public window_with_items { int handle_part1(int); int handle_part2(int e, int ret); static Fl_Window *parent_; + static int display_height_; public: menutitle* title; int handle(int) FL_OVERRIDE; @@ -160,6 +162,7 @@ public: }; Fl_Window *menuwindow::parent_ = NULL; +int menuwindow::display_height_ = 0; /** \cond DriverDev @@ -167,8 +170,10 @@ Fl_Window *menuwindow::parent_ = NULL; \{ */ -/** The Fl_Window from which currently displayed popups originate. */ -Fl_Window *Fl_Window_Driver::menu_parent() { +/** The Fl_Window from which currently displayed popups originate. + Optionally, gives also the height of the display containing this window */ +Fl_Window *Fl_Window_Driver::menu_parent(int *display_height) { + if (display_height) *display_height = menuwindow::display_height_; return menuwindow::parent_; } @@ -209,6 +214,13 @@ int Fl_Window_Driver::menu_selected(Fl_Window *win) { return (mwin ? mwin->selected : -1); } +/** Returns whether win is a non-menubar menutitle */ +bool Fl_Window_Driver::is_floating_title(Fl_Window *win) { + if (!win->menu_window()) return false; + Fl_Window *mwin = ((window_with_items*)win)->as_menuwindow(); + return !mwin && !((menutitle*)win)->in_menubar; +} + /** Create a menutitle window with same content and size as another one and another ordinate. */ Fl_Window *Fl_Window_Driver::extra_menutitle(Fl_Window *old, int Y) { @@ -336,13 +348,14 @@ void Fl_Menu_Item::draw(int x, int y, int w, int h, const Fl_Menu_* m, fl_draw_shortcut = 0; } -menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L) : +menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L, bool inbar) : window_with_items(X, Y, W, H, L) { end(); set_modal(); clear_border(); set_menu_window(); extra = NULL; + in_menubar = inbar; } menutitle::~menutitle() { @@ -476,7 +489,7 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp, if (menubar_title) { int dy = Fl::box_dy(button->box())+1; int ht = button->h()-dy*2; - title = new menutitle(tx, ty-ht-dy, Wtitle, ht, t); + title = new menutitle(tx, ty-ht-dy, Wtitle, ht, t, true); } else { int dy = 2; int ht = Htitle+2*BW+3; @@ -682,6 +695,14 @@ struct menustate { }; static menustate* p=0; +void Fl_Window_Driver::scroll_to_selected_item(Fl_Window *win) { + if (!p || !win->menu_window()) return; + menuwindow *mwin = ((window_with_items*)win)->as_menuwindow(); + if (mwin && p->item_number > 0) { + mwin->autoscroll(p->item_number); + } +} + // return 1 if the coordinates are inside any of the menuwindows int menustate::is_inside(int mx, int my) { int i; @@ -972,6 +993,8 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown( Y += Fl::event_y_root()-Fl::event_y(); menuwindow::parent_ = Fl::first_window(); } + int XX, YY, WW; + Fl::screen_xywh(XX, YY, WW, menuwindow::display_height_, menuwindow::parent_->screen_num()); menuwindow mw(this, X, Y, W, H, initial_item, title, menubar); Fl::grab(mw); menustate pp; p = &pp; diff --git a/src/Fl_Window_Driver.H b/src/Fl_Window_Driver.H index bf0be9fd6..860f29568 100644 --- a/src/Fl_Window_Driver.H +++ b/src/Fl_Window_Driver.H @@ -192,13 +192,15 @@ public: // when that's not the case, as with Wayland. virtual void reposition_menu_window(int x, int y); virtual void menu_window_area(int &X, int &Y, int &W, int &H, int nscreen = -1); - static Fl_Window *menu_parent(); + static Fl_Window *menu_parent(int *display_height = NULL); static Fl_Window *menu_leftorigin(Fl_Window*); static Fl_Window *menu_title(Fl_Window*); static int menu_itemheight(Fl_Window*); static int menu_bartitle(Fl_Window*); static int menu_selected(Fl_Window*); static Fl_Window *extra_menutitle(Fl_Window *old, int Y); + static bool is_floating_title(Fl_Window *); + static void scroll_to_selected_item(Fl_Window *); virtual fl_uintptr_t os_id() { return 0; } }; diff --git a/src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx index 6c86aba0c..6a40cec3c 100644 --- a/src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx +++ b/src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx @@ -885,6 +885,7 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = { struct win_positioner { struct wld_window *window; int x, y; + Fl_Window *child_popup; }; @@ -894,10 +895,12 @@ static void popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, //printf("popup_configure %p asked:%dx%d got:%dx%d\n",window->fl_win, win_pos->x,win_pos->y, x,y); //fprintf(stderr, "popup_configure: popup=%p data=%p xid=%p fl_win=%p\n", xdg_popup, data, window, window->fl_win); Fl_Window_Driver::driver(window->fl_win)->wait_for_expose_value = 0; - int XX, YY, WW, HH; - Fl::screen_xywh(XX, YY, WW, HH, window->fl_win->screen_num()); - if (window->fl_win->h() > HH && y < win_pos->y) { // A menu taller than the display + int HH; + Fl_Window_Driver::menu_parent(&HH); + if (window->fl_win->h() > HH && y != win_pos->y) { // A menu taller than the display window->state = (y - win_pos->y); + // make selected item visible, if there's one + Fl_Window_Driver::scroll_to_selected_item(window->fl_win); } else if (Fl_Window_Driver::menu_title(window->fl_win) && y < win_pos->y) { // A menuwindow below a menutitle has been placed higher to avoid display bottom. // The workaround here creates an extra menutitle above the menuwindow. @@ -922,6 +925,7 @@ static void popup_done(void *data, struct xdg_popup *xdg_popup) { struct win_positioner *win_pos = (struct win_positioner *)data; struct wld_window *window = win_pos->window; //fprintf(stderr, "popup_done: popup=%p data=%p xid=%p fl_win=%p\n", xdg_popup, data, window, window->fl_win); + if (win_pos->child_popup) win_pos->child_popup->hide(); xdg_popup_destroy(xdg_popup); delete win_pos; // The sway compositor calls popup_done directly and hides the menu @@ -1017,6 +1021,15 @@ static const char *get_prog_name() { with the same size and content as the hidden menutitle and to map it just above the menuwindow so it becomes visible. + Groups of popups that begin with a menutitle, the associated menuwindow, and optionally + a submenu window and that don't belong to an Fl_Menu_Bar are processed differently: + the menuwindow is mapped first, and the menutitle is mapped second above it as a child popup. + Fl_Window_Driver::is_floating_title() detects when such a menutitle is created, + global variable previous_floatingtitle is assigned the value of this menutitle, and + the menutitle is mapped only after the menuwindow has been mapped, as a child of it. + This positions the popup group in the display better in relation to where the popup + was created. + In case 2) above, a tall popup is mapped with XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y which puts its top at the display top border. The Wayland system then calls the popup_configure() callback function with the x,y coordinates of the top left corner @@ -1027,24 +1040,35 @@ static const char *get_prog_name() { Function Fl_Wayland_Window_Driver::menu_window_area() sets the top of the display to a value such that function Fl_Wayland_Window_Driver::reposition_menu_window(), called by menuwindow::autoscroll(int n), ensures that menu item #n is visible. + Fl_Wayland_Window_Driver::scroll_to_selected_item() scrolls the tall popup so its selected + item, when there's one, is visible. */ +// A menutitle to be mapped later as the child of a menuwindow +static Fl_Window *previous_floatingtitle = NULL; +static int tall_popup = 0; // to support tall menu buttons -static void process_menu_or_tooltip(struct wld_window *new_window) { +static bool process_menu_or_tooltip(struct wld_window *new_window) { // a menu window or tooltip new_window->kind = Fl_Wayland_Window_Driver::POPUP; Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); Fl_Window *pWindow = new_window->fl_win; new_window->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, new_window->wl_surface); xdg_surface_add_listener(new_window->xdg_surface, &xdg_surface_listener, new_window); - struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base); - //xdg_positioner_get_version(positioner) <== gives 1 under Debian - struct win_positioner *win_pos = new struct win_positioner; - win_pos->window = new_window; + if (Fl_Window_Driver::is_floating_title(pWindow)) { + previous_floatingtitle = pWindow; + return true; + } + tall_popup = 0; + if (pWindow->menu_window() && !Fl_Window_Driver::menu_title(pWindow)) { + int HH; + Fl_Window_Driver::menu_parent(&HH); + if (pWindow->h() > HH) tall_popup = 1; + } Fl_Window *menu_origin = NULL; if (pWindow->menu_window()) { menu_origin = Fl_Window_Driver::menu_leftorigin(pWindow); - if (!menu_origin ) menu_origin = Fl_Window_Driver::menu_title(pWindow); + if (!menu_origin && !previous_floatingtitle) menu_origin = Fl_Window_Driver::menu_title(pWindow); } Fl_Widget *target = (pWindow->tooltip_window() ? Fl_Tooltip::current() : NULL); if (!target) target = Fl_Window_Driver::menu_parent(); @@ -1057,12 +1081,15 @@ static void process_menu_or_tooltip(struct wld_window *new_window) { float f = Fl::screen_scale(parent_win->screen_num()); //fprintf(stderr, "menu parent_win=%p pos:%dx%d size:%dx%d\n", parent_win, pWindow->x(), pWindow->y(), pWindow->w(), pWindow->h()); //printf("window=%p menutitle=%p bartitle=%d leftorigin=%p y=%d\n", pWindow, Fl_Window_Driver::menu_title(pWindow), Fl_Window_Driver::menu_bartitle(pWindow), Fl_Window_Driver::menu_leftorigin(pWindow), pWindow->y()); - if (Fl_Window_Driver::menu_title(pWindow)) { + struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base); + //xdg_positioner_get_version(positioner) <== gives 1 under Debian and Sway + int popup_x, popup_y; + if (Fl_Window_Driver::menu_title(pWindow) && Fl_Window_Driver::menu_bartitle(pWindow)) { xdg_positioner_set_anchor_rect(positioner, 0, 0, Fl_Window_Driver::menu_title(pWindow)->w(), Fl_Window_Driver::menu_title(pWindow)->h()); - win_pos->x = 0; - win_pos->y = Fl_Window_Driver::menu_title(pWindow)->h(); + popup_x = 0; + popup_y = Fl_Window_Driver::menu_title(pWindow)->h(); } else { - int popup_x = pWindow->x() * f, popup_y = pWindow->y() * f; + popup_x = pWindow->x() * f, popup_y = pWindow->y() * f; if (popup_x + pWindow->w() * f < 0) popup_x = - pWindow->w() * f; if (menu_origin) { popup_x -= menu_origin->x() * f; @@ -1075,8 +1102,7 @@ static void process_menu_or_tooltip(struct wld_window *new_window) { if (parent_xid->kind == Fl_Wayland_Window_Driver::DECORATED) libdecor_frame_translate_coordinate(parent_xid->frame, popup_x, popup_y, &popup_x, &popup_y); xdg_positioner_set_anchor_rect(positioner, popup_x, popup_y, 1, 1); - win_pos->x = popup_x; - win_pos->y = popup_y + 1; + popup_y++; } xdg_positioner_set_size(positioner, pWindow->w() * f , pWindow->h() * f ); xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT); @@ -1089,6 +1115,11 @@ static void process_menu_or_tooltip(struct wld_window *new_window) { } xdg_positioner_set_constraint_adjustment(positioner, constraint); new_window->xdg_popup = xdg_surface_get_popup(new_window->xdg_surface, parent_xdg, positioner); + struct win_positioner *win_pos = new struct win_positioner; + win_pos->window = new_window; + win_pos->x = popup_x; + win_pos->y = popup_y; + win_pos->child_popup = NULL; //printf("create xdg_popup=%p data=%p xid=%p fl_win=%p\n",new_window->xdg_popup,win_pos,new_window,new_window->fl_win); xdg_positioner_destroy(positioner); xdg_popup_add_listener(new_window->xdg_popup, &popup_listener, win_pos); @@ -1098,12 +1129,14 @@ static void process_menu_or_tooltip(struct wld_window *new_window) { //libdecor_frame_popup_grab(parent_xid->frame, scr_driver->get_seat_name()); } wl_surface_commit(new_window->wl_surface); + return false; } void Fl_Wayland_Window_Driver::makeWindow() { struct wld_window *new_window; + bool is_floatingtitle; Fl_Wayland_Screen_Driver::output *output; wait_for_expose_value = 1; @@ -1134,7 +1167,7 @@ void Fl_Wayland_Window_Driver::makeWindow() } if (pWindow->menu_window() || pWindow->tooltip_window()) { // a menu window or tooltip - process_menu_or_tooltip(new_window); + is_floatingtitle = process_menu_or_tooltip(new_window); } else if ( pWindow->border() && !pWindow->parent() ) { // a decorated window new_window->kind = DECORATED; @@ -1219,7 +1252,31 @@ void Fl_Wayland_Window_Driver::makeWindow() Fl::e_number = old_event; pWindow->redraw(); // make sure each popup is mapped with its constraints before mapping next popup - if (pWindow->menu_window()) pWindow->wait_for_expose(); + if (pWindow->menu_window() && !is_floatingtitle) { + pWindow->wait_for_expose(); + if (previous_floatingtitle) { // map the menutitle popup now as child of pWindow + struct wld_window *xid = fl_wl_xid(previous_floatingtitle); + struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base); + xdg_positioner_set_anchor_rect(positioner, 0, 0, 1, 1); + float f = Fl::screen_scale(Fl_Window_Driver::menu_parent()->screen_num()); + xdg_positioner_set_size(positioner, previous_floatingtitle->w() * f , previous_floatingtitle->h() * f ); + xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); + xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_TOP_RIGHT); + xid->xdg_popup = xdg_surface_get_popup(xid->xdg_surface, new_window->xdg_surface, positioner); + xdg_positioner_destroy(positioner); + struct win_positioner *win_pos = new struct win_positioner; + win_pos->window = xid; + win_pos->x = 0; + win_pos->y = 0; + xdg_popup_add_listener(xid->xdg_popup, &popup_listener, win_pos); + wl_surface_commit(xid->wl_surface); + previous_floatingtitle->wait_for_expose(); + struct win_positioner *parent_win_pos = + (struct win_positioner*)xdg_popup_get_user_data(new_window->xdg_popup); + parent_win_pos->child_popup = previous_floatingtitle; + previous_floatingtitle = NULL; + } + } } Fl_Wayland_Window_Driver::type_for_resize_window_between_screens Fl_Wayland_Window_Driver::data_for_resize_window_between_screens_ = {0, false}; @@ -1658,6 +1715,7 @@ void Fl_Wayland_Window_Driver::reposition_menu_window(int x, int y) { win_pos->window = xid_menu; win_pos->x = popup_x; win_pos->y = popup_y; + win_pos->child_popup = NULL; xdg_popup_add_listener(xid_menu->xdg_popup, &popup_listener, win_pos); wl_surface_commit(xid_menu->wl_surface); wl_display_roundtrip(Fl_Wayland_Screen_Driver::wl_display); // necessary with sway @@ -1672,10 +1730,9 @@ void Fl_Wayland_Window_Driver::reposition_menu_window(int x, int y) { void Fl_Wayland_Window_Driver::menu_window_area(int &X, int &Y, int &W, int &H, int nscreen) { - Fl_Window *parent = Fl_Window_Driver::menu_parent(); + int HH; + Fl_Window *parent = Fl_Window_Driver::menu_parent(&HH); if (parent) { - int XX, YY, WW, HH; - Fl::screen_xywh(XX, YY, WW, HH, parent->screen_num()); if (pWindow->menu_window() && pWindow->h() > HH) { // tall menu: set top (Y) and bottom (Y+H) bounds relatively to reference window int ih = Fl_Window_Driver::menu_itemheight(pWindow); @@ -1686,9 +1743,14 @@ void Fl_Wayland_Window_Driver::menu_window_area(int &X, int &Y, int &W, int &H, if (origin) { // has left parent int selected = fl_max(Fl_Window_Driver::menu_selected(origin), 0); Y = origin->y() + (selected + 0.5) * ih; - } else { + } else if (!Fl_Window_Driver::menu_bartitle(pWindow)) { // tall menu button + static int y_offset = 0; + if (tall_popup == 1) y_offset = pWindow->y()- ih; + Y = 1.5 * ih + y_offset; + } else { // has a menutitle Y = 1.5 * ih; } + tall_popup++; } else { // position the menu window by wayland constraints X = -50000; Y = -50000;