Wayland: Improved implementation of menu windows

This commit is contained in:
ManoloFLTK 2022-12-29 09:47:02 +01:00
parent 6ada45f1f2
commit 222b2ea2e8
4 changed files with 280 additions and 124 deletions

View File

@ -106,19 +106,25 @@ const Fl_Menu_Item* Fl_Menu_Item::next(int n) const {
static const Fl_Menu_* button=0; static const Fl_Menu_* button=0;
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
class menuwindow;
// utility class covering both menuwindow and menutitle
class window_with_items : public Fl_Menu_Window { class window_with_items : public Fl_Menu_Window {
public: protected:
const Fl_Menu_Item* menu;
window_with_items(int X, int Y, int W, int H, const Fl_Menu_Item *m) : window_with_items(int X, int Y, int W, int H, const Fl_Menu_Item *m) :
Fl_Menu_Window(X, Y, W, H, 0) { menu = m; } Fl_Menu_Window(X, Y, W, H, 0) { menu = m; }
public:
const Fl_Menu_Item* menu;
virtual menuwindow* as_menuwindow() { return NULL; }
}; };
// tiny window for title of menu: // tiny window for title of menu:
class menutitle : public window_with_items { class menutitle : public window_with_items {
void draw() FL_OVERRIDE; void draw() FL_OVERRIDE;
public: 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*);
~menutitle();
}; };
// each vertical menu has one of these: // each vertical menu has one of these:
@ -148,6 +154,9 @@ public:
void autoscroll(int); void autoscroll(int);
void position(int x, int y); void position(int x, int y);
int is_inside(int x, int y); int is_inside(int x, int y);
menuwindow* as_menuwindow() FL_OVERRIDE { return this; }
int menubartitle;
menuwindow *origin;
}; };
Fl_Window *menuwindow::parent_ = NULL; Fl_Window *menuwindow::parent_ = NULL;
@ -158,14 +167,57 @@ Fl_Window *menuwindow::parent_ = NULL;
\{ \{
*/ */
/** The Fl_Window from which currently displayed popups originate. */
Fl_Window *Fl_Window_Driver::menu_parent() { Fl_Window *Fl_Window_Driver::menu_parent() {
return menuwindow::parent_; return menuwindow::parent_;
} }
const Fl_Menu_Item *Fl_Window_Driver::current_menu() { /** Accessor to the "origin" member variable of class menuwindow.
if (!pWindow->menu_window()) return NULL; Variable origin is not NULL when 2 menuwindow's occur, one being a submenu of the other;
return ((window_with_items*)pWindow)->menu; it links the menuwindow at right to the one at left. */
Fl_Window *Fl_Window_Driver::menu_leftorigin(Fl_Window *win) {
if (!win->menu_window()) return NULL;
menuwindow *mwin = ((window_with_items*)win)->as_menuwindow();
return (mwin ? mwin->origin : NULL);
} }
/** Accessor to the "title" member variable of class menuwindow */
Fl_Window *Fl_Window_Driver::menu_title(Fl_Window *win) {
if (!win->menu_window()) return NULL;
menuwindow *mwin = ((window_with_items*)win)->as_menuwindow();
return (mwin ? mwin->title : NULL);
}
/** Accessor to the "itemheight" member variable of class menuwindow */
int Fl_Window_Driver::menu_itemheight(Fl_Window *win) {
if (!win->menu_window()) return 0;
menuwindow *mwin = ((window_with_items*)win)->as_menuwindow();
return (mwin ? mwin->itemheight : 0);
}
/** Accessor to the "menubartitle" member variable of class menuwindow */
int Fl_Window_Driver::menu_bartitle(Fl_Window *win) {
if (!win->menu_window()) return 0;
menuwindow *mwin = ((window_with_items*)win)->as_menuwindow();
return (mwin ? mwin->menubartitle : 0);
}
/** Accessor to the "selected" member variable of class menuwindow */
int Fl_Window_Driver::menu_selected(Fl_Window *win) {
if (!win->menu_window()) return 0;
menuwindow *mwin = ((window_with_items*)win)->as_menuwindow();
return (mwin ? mwin->selected : -1);
}
/** 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) {
menutitle *t = (menutitle*)old;
menutitle *win = new menutitle(t->x(), Y, t->w(), t->h(), t->menu);
t->extra = win;
return win;
}
/** /**
\} \}
\endcond \endcond
@ -290,6 +342,11 @@ menutitle::menutitle(int X, int Y, int W, int H, const Fl_Menu_Item* L) :
set_modal(); set_modal();
clear_border(); clear_border();
set_menu_window(); set_menu_window();
extra = NULL;
}
menutitle::~menutitle() {
delete extra;
} }
menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp, menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
@ -299,6 +356,8 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp,
{ {
int scr_x, scr_y, scr_w, scr_h; int scr_x, scr_y, scr_w, scr_h;
int tx = X, ty = Y; int tx = X, ty = Y;
menubartitle = menubar_title;
origin = NULL;
Fl_Window_Driver::driver(this)->menu_window_area(scr_x, scr_y, scr_w, scr_h); Fl_Window_Driver::driver(this)->menu_window_area(scr_x, scr_y, scr_w, scr_h);
if (!right_edge || right_edge > scr_x+scr_w) right_edge = scr_x+scr_w; if (!right_edge || right_edge > scr_x+scr_w) right_edge = scr_x+scr_w;
@ -1006,6 +1065,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
if (initial_item) { // bring up submenu containing initial item: if (initial_item) { // bring up submenu containing initial item:
menuwindow* n = new menuwindow(menutable,X,Y,W,H,initial_item,title,0,0,cw.x()); menuwindow* n = new menuwindow(menutable,X,Y,W,H,initial_item,title,0,0,cw.x());
pp.p[pp.nummenus++] = n; pp.p[pp.nummenus++] = n;
if (pp.nummenus >= 2) pp.p[pp.nummenus-1]->origin = pp.p[pp.nummenus-2];
// move all earlier menus to line up with this new one: // move all earlier menus to line up with this new one:
if (n->selected>=0) { if (n->selected>=0) {
int dy = n->y()-nY; int dy = n->y()-nY;
@ -1032,6 +1092,9 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown(
pp.p[pp.nummenus++]= new menuwindow(menutable, nX, nY, pp.p[pp.nummenus++]= new menuwindow(menutable, nX, nY,
title?1:0, 0, 0, title, 0, menubar, title?1:0, 0, 0, title, 0, menubar,
(title ? 0 : cw.x()) ); (title ? 0 : cw.x()) );
if (pp.nummenus >= 2 && pp.p[pp.nummenus-2]->itemheight) {
pp.p[pp.nummenus-1]->origin = pp.p[pp.nummenus-2];
}
} }
} else { // !m->submenu(): } else { // !m->submenu():
while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus]; while (pp.nummenus > pp.menu_number+1) delete pp.p[--pp.nummenus];

View File

@ -187,11 +187,18 @@ public:
static inline Fl_Window_Driver* driver(const Fl_Window *win) {return win->pWindowDriver;} static inline Fl_Window_Driver* driver(const Fl_Window *win) {return win->pWindowDriver;}
// --- support for menu windows // --- support for menu windows
// the default implementation of next 2 members is most probably enough // The default implementation of next 2 virtual members is enough if the
// position of a window in a screen is known. Next static members may be useful
// when that's not the case, as with Wayland.
virtual void reposition_menu_window(int x, int y); 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); 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();
const Fl_Menu_Item *current_menu(); 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);
virtual fl_uintptr_t os_id() { return 0; } virtual fl_uintptr_t os_id() { return 0; }
}; };

View File

@ -947,7 +947,7 @@ static struct wl_output_listener output_listener = {
static void registry_handle_global(void *user_data, struct wl_registry *wl_registry, static void registry_handle_global(void *user_data, struct wl_registry *wl_registry,
uint32_t id, const char *interface, uint32_t version) { uint32_t id, const char *interface, uint32_t version) {
//fprintf(stderr, "interface=%s\n", interface); //fprintf(stderr, "interface=%s version=%u\n", interface, version);
Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
if (strcmp(interface, "wl_compositor") == 0) { if (strcmp(interface, "wl_compositor") == 0) {
if (version < 4) { if (version < 4) {

View File

@ -453,7 +453,7 @@ void Fl_Wayland_Window_Driver::hide() {
wld_win->xdg_surface = NULL; wld_win->xdg_surface = NULL;
} else { } else {
if (wld_win->kind == POPUP && wld_win->xdg_popup) { if (wld_win->kind == POPUP && wld_win->xdg_popup) {
popup_done(wld_win, wld_win->xdg_popup); popup_done(xdg_popup_get_user_data(wld_win->xdg_popup), wld_win->xdg_popup);
wld_win->xdg_popup = NULL; wld_win->xdg_popup = NULL;
} }
if (wld_win->kind == UNFRAMED && wld_win->xdg_toplevel) { if (wld_win->kind == UNFRAMED && wld_win->xdg_toplevel) {
@ -882,45 +882,61 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = {
}; };
struct win_positioner {
struct wld_window *window;
int x, y;
};
static void popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) { static void popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) {
struct wld_window *window = (struct wld_window*)data; struct win_positioner *win_pos = (struct win_positioner *)data;
struct wld_window *window = win_pos->window;
//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; 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
window->state = (y - win_pos->y);
} 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.
// A better way would be to move the menutitle up.
// A way to do that is probably xdg_popup reposition but requires version 3
// and xdg_popup_get_version(new_window->xdg_popup) --> 1 with Mutter
Fl_Window *menutitle = Fl_Window_Driver::menu_title(window->fl_win);
int Y = menutitle->y() - (win_pos->y - y);
if (Y > - menutitle->h()) { // not possible if higher than parent window top
Fl_Window *new_menutitle = Fl_Window_Driver::extra_menutitle(menutitle, Y);
new_menutitle->show();
new_menutitle->wait_for_expose();
}
}
} }
#define USE_GRAB_POPUP 0
#if USE_GRAB_POPUP // nearly OK except that menutitle window does not always show
static Fl_Window *mem_parent = NULL;
static struct xdg_popup *mem_grabbing_popup = NULL; static struct xdg_popup *mem_grabbing_popup = NULL;
static void nothing_popup(void *, struct xdg_popup *) {}
#endif
static void popup_done(void *data, struct xdg_popup *xdg_popup) { static void popup_done(void *data, struct xdg_popup *xdg_popup) {
//fprintf(stderr, "popup_done: popup=%p \n", xdg_popup); struct win_positioner *win_pos = (struct win_positioner *)data;
#if USE_GRAB_POPUP struct wld_window *window = win_pos->window;
if (mem_grabbing_popup == xdg_popup) { //fprintf(stderr, "popup_done: popup=%p data=%p xid=%p fl_win=%p\n", xdg_popup, data, window, window->fl_win);
Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
libdecor_frame_popup_ungrab(fl_wl_xid(mem_parent)->frame, scr_driver->get_seat_name());
mem_grabbing_popup = NULL;
mem_parent = NULL;
}
#endif
xdg_popup_destroy(xdg_popup); xdg_popup_destroy(xdg_popup);
delete win_pos;
// The sway compositor calls popup_done directly and hides the menu // The sway compositor calls popup_done directly and hides the menu
// when the app looses focus. // when the app looses focus.
// Thus, we hide the window so FLTK and Wayland are in matching states. // Thus, we hide the window so FLTK and Wayland are in matching states.
struct wld_window *window = (struct wld_window*)data;
window->xdg_popup = NULL; window->xdg_popup = NULL;
window->fl_win->hide(); window->fl_win->hide();
if (mem_grabbing_popup == xdg_popup) {
mem_grabbing_popup = NULL;
}
} }
static const struct xdg_popup_listener popup_listener = { static const struct xdg_popup_listener popup_listener = {
.configure = popup_configure, .configure = popup_configure,
#if USE_GRAB_POPUP
.popup_done = nothing_popup,
#else
.popup_done = popup_done, .popup_done = popup_done,
#endif
}; };
bool Fl_Wayland_Window_Driver::in_flush = false; bool Fl_Wayland_Window_Driver::in_flush = false;
@ -953,40 +969,135 @@ static const char *get_prog_name() {
/* Implementation note about menu windows under Wayland. /* Implementation note about menu windows under Wayland.
Wayland offers a way to position popup windows such as menu windows using constraints Wayland offers a way to position popup windows such as menu windows using constraints.
but hides the position of the window inside the display. Each popup is located relatively Each popup is located relatively to a parent window which can be a popup itself and
to a parent window which can be a popup itself and MUST overlap or at least touch this parent. MUST overlap or at least touch this parent.
FLTK computes the adequate position of a menu window in the display and then maps it Constraints determine how a popup is positioned relatively to a defined area (called
at that position. the anchor rectangle) of its parent popup/window and what happens when this position
These 2 logics are quite different. would place the popup partly outside the display.
In contrast, FLTK computes the adequate positions of menu windows in the display using
knowledge about the display size and the location of the window in the display, and then
maps them at these positions.
These 2 logics are quite different because Wayland hides the position of windows inside the
display, whereas FLTK uses the location of windows inside the display to position popups.
Let's call "source window" the non-popup window above which all popups are mapped.
The approach implemented here is two-fold. The approach implemented here is two-fold.
1) If a menu window is not taller than the display and contains no submenu, use Wayland 1) If a menu window is not taller than the display, use Wayland constraints to position it.
logic to position it. The benefit is that window menus become authorized to lay outside The benefit is that popups will not expand beyond display limits. The current
the parent window but Wayland will not make them run beyond display limits. implementation is constrained by the fact that the first constructed popup must overlap
We avoid submenu-containing popups because these could lead to or touch the source window. Other popups are placed below, above, or at right
locate the future submenu outside its parent window, which Wayland forbids. of a previous popup which allows them to expand outside the source window, while constraints
We avoid very tall menu windows because navigating with FLTK inside them would require to know ensure they won't extend outside the display.
what part of them is visible which Wayland hides. 2) A menu window taller than the display is initially mapped with the constraint to
2) Otherwise, have FLTK compute the menu position under the constraint that its active item begin at the top border of the display. This allows FLTK to know the distance between
must be inside the menu-containing window. This constraint ensures Wayland will accept this the source window and the display top. FLTK can later reposition the same tall popup,
position because the required overlap is satisfied. without the constraint not to go beyond the display top, at the exact position so that
Function use_wayland_menu_positioning() below determines wether 1) or 2) is used for any the desired series of menu items appear in the visible part of the tall popup.
window menu. The result of this function is stored in the state member of the menu window's
struct wld_window for fast re-use. In case 1) above, the values that represent the display bounds are given very
large values. That's done by member function Fl_Wayland_Window_Driver::menu_window_area().
Consequently, FLTK computes an initial layout of future popups relatively to
the source window as if it was mapped on an infinitely large display. Then, the location
of the first popup to be mapped is modified if necessary so it overlaps or touches the
source window. Finally, other popups are located using Wayland logic below or to the
right of previous popups. Wayland constraints mechanism allows to prevent these popups
from expanding beyond display limits. It also allows a popup tentatively placed below
a previous one to be flipped above it if that prevents the popup from expanding beyond
display limits. This is used to unfold menu bar menus below or above the menu bar.
After each popup is created and scheduled for being mapped on display by function
process_menu_or_tooltip(), makeWindow() calls wl_display_roundtrip() so its constrained
position is known before computing the position of the next popup. This ensures each
popup is correctly placed relatively to its parent.
Consider a menutitle window and a menuwindow expected to map just below the menutitle.
Wayland constraints sometimes push the menuwindow up in the display to prevent its bottom
from expanding outside the display. Consequently, the menutitle is hidden by the
menuwindow above it. The callbak function popup_configure() allows FLTK to detect this
situation because the asked and effective window positions differ. Function
Fl_Window_Driver::extra_menutitle() is used to create an additional menutitle window
with the same size and content as the hidden menutitle and to map it just above
the menuwindow so it becomes visible.
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
where the popup is mapped relatively to an anchor point in the source window.
The difference between the asked window position and the effective position is stored
in the state member variable of the tall popup's struct wld_window. This information
allows FLTK to compute the distance between the source window top and the display top border.
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.
*/ */
//returns true if win is a menuwindow without submenu and is not taller than display
static bool use_wayland_menu_positioning(Fl_Window *win, Fl_Window *parent_win) { static void process_menu_or_tooltip(struct wld_window *new_window) {
if (!win->menu_window()) return true; // a menu window or tooltip
int XX, YY, WW, HH; new_window->kind = Fl_Wayland_Window_Driver::POPUP;
Fl::screen_xywh(XX, YY, WW, HH, parent_win->screen_num()); Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
if (win->h() > HH) return false; Fl_Window *pWindow = new_window->fl_win;
const Fl_Menu_Item *m = Fl_Window_Driver::driver(win)->current_menu(); new_window->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, new_window->wl_surface);
while (m->label()) { xdg_surface_add_listener(new_window->xdg_surface, &xdg_surface_listener, new_window);
if (m->flags & (FL_SUBMENU | FL_SUBMENU_POINTER)) return false; struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base);
m = m->next(); //xdg_positioner_get_version(positioner) <== gives 1 under Debian
struct win_positioner *win_pos = new struct win_positioner;
win_pos->window = new_window;
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);
} }
return true; Fl_Widget *target = (pWindow->tooltip_window() ? Fl_Tooltip::current() : NULL);
if (!target) target = Fl_Window_Driver::menu_parent();
if (!target) target = Fl::belowmouse();
if (!target) target = Fl::first_window();
Fl_Window *parent_win = target->top_window();
while (parent_win && parent_win->menu_window()) parent_win = Fl::next_window(parent_win);
struct wld_window * parent_xid = fl_wl_xid(menu_origin ? menu_origin : parent_win);
struct xdg_surface *parent_xdg = parent_xid->xdg_surface;
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)) {
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();
} else {
int 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;
popup_y -= menu_origin->y() * f;
}
if (!Fl_Window_Driver::menu_title(pWindow) && !Fl_Window_Driver::menu_bartitle(pWindow) && !Fl_Window_Driver::menu_leftorigin(pWindow)) {
// prevent first popup from going above the permissible source window
popup_y = fl_max(popup_y, - pWindow->h() * f);
}
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;
}
xdg_positioner_set_size(positioner, pWindow->w() * f , pWindow->h() * f );
xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT);
xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
// prevent menuwindow from expanding beyond display limits
int constraint = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X |
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
if (Fl_Window_Driver::menu_bartitle(pWindow) && !Fl_Window_Driver::menu_leftorigin(pWindow)) {
constraint |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y;
}
xdg_positioner_set_constraint_adjustment(positioner, constraint);
new_window->xdg_popup = xdg_surface_get_popup(new_window->xdg_surface, parent_xdg, positioner);
//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);
if (!mem_grabbing_popup) {
mem_grabbing_popup = new_window->xdg_popup;
//xdg_popup_grab(new_window->xdg_popup, scr_driver->get_wl_seat(), scr_driver->get_serial());
//libdecor_frame_popup_grab(parent_xid->frame, scr_driver->get_seat_name());
}
wl_surface_commit(new_window->wl_surface);
} }
@ -1023,46 +1134,7 @@ Fl_X *Fl_Wayland_Window_Driver::makeWindow()
} }
if (pWindow->menu_window() || pWindow->tooltip_window()) { // a menu window or tooltip if (pWindow->menu_window() || pWindow->tooltip_window()) { // a menu window or tooltip
new_window->kind = POPUP; process_menu_or_tooltip(new_window);
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
Fl_Widget *target = (pWindow->tooltip_window() ?
Fl_Tooltip::current() : Fl_Window_Driver::menu_parent() );
if (!target) target = Fl::belowmouse();
if (!target) target = Fl::first_window();
Fl_Window *parent_win = target->top_window();
while (parent_win && parent_win->menu_window()) parent_win = Fl::next_window(parent_win);
struct wld_window * parent_xid = fl_wl_xid(parent_win);
struct xdg_surface *parent_xdg = parent_xid->xdg_surface;
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());
int popup_x = pWindow->x() * f, popup_y = pWindow->y() * f;
if (parent_xid->kind == 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);
xdg_positioner_set_size(positioner, pWindow->w() * f , pWindow->h() * f );
xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
new_window->state = use_wayland_menu_positioning(pWindow, parent_win);
if (new_window->state) {
// prevent menuwindow from expanding beyond display limits
xdg_positioner_set_constraint_adjustment(positioner,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
}
new_window->xdg_popup = xdg_surface_get_popup(new_window->xdg_surface, parent_xdg, positioner);
xdg_positioner_destroy(positioner);
xdg_popup_add_listener(new_window->xdg_popup, &popup_listener, new_window);
#if USE_GRAB_POPUP
if (!mem_grabbing_popup) {
mem_parent = parent_win;
mem_grabbing_popup = new_window->xdg_popup;
xdg_popup_grab(new_window->xdg_popup, scr_driver->get_wl_seat(), scr_driver->get_serial());
libdecor_frame_popup_grab(parent_xid->frame, scr_driver->get_seat_name());
}
#endif
wl_surface_commit(new_window->wl_surface);
} else if ( pWindow->border() && !pWindow->parent() ) { // a decorated window } else if ( pWindow->border() && !pWindow->parent() ) { // a decorated window
new_window->kind = DECORATED; new_window->kind = DECORATED;
@ -1146,7 +1218,8 @@ Fl_X *Fl_Wayland_Window_Driver::makeWindow()
pWindow->handle(Fl::e_number = FL_SHOW); // get child windows to appear pWindow->handle(Fl::e_number = FL_SHOW); // get child windows to appear
Fl::e_number = old_event; Fl::e_number = old_event;
pWindow->redraw(); pWindow->redraw();
// make sure each popup is mapped with its constraints before mapping next popup
if (pWindow->menu_window()) wl_display_roundtrip(Fl_Wayland_Screen_Driver::wl_display);
return xp; return xp;
} }
@ -1549,19 +1622,16 @@ void Fl_Wayland_Window_Driver::subRect(cairo_rectangle_int_t *r) {
void Fl_Wayland_Window_Driver::reposition_menu_window(int x, int y) { void Fl_Wayland_Window_Driver::reposition_menu_window(int x, int y) {
if (y == pWindow->y()) return;
struct wld_window * xid_menu = fl_wl_xid(pWindow); struct wld_window * xid_menu = fl_wl_xid(pWindow);
if (y == pWindow->y() && y >= 0) return; //printf("reposition %dx%d[cur=%d] menu->state=%d\n", x, y, pWindow->y(), xid_menu->state);
int true_y = y;
int y_offset = 0;
if (y < 0) {
y_offset = y-1;
y = 1;
}
//printf("should move menuwindow to %d y_offset=%d y=%d\n", true_y, y_offset, pWindow->y());
struct xdg_popup *old_popup = xid_menu->xdg_popup; struct xdg_popup *old_popup = xid_menu->xdg_popup;
struct xdg_surface *old_xdg = xid_menu->xdg_surface; struct xdg_surface *old_xdg = xid_menu->xdg_surface;
struct wl_surface *old_surface = xid_menu->wl_surface; struct wl_surface *old_surface = xid_menu->wl_surface;
// menu_origin will be the parent of the processed menu window
Fl_Window *menu_origin = Fl_Window_Driver::menu_title(pWindow);
if (!menu_origin) menu_origin = Fl_Window_Driver::menu_leftorigin(pWindow);
if (!menu_origin) menu_origin = Fl_Window_Driver::menu_parent();
// create a new popup at position (x,y) and display it above the current one // create a new popup at position (x,y) and display it above the current one
Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
xid_menu->wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor); xid_menu->wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor);
@ -1569,41 +1639,57 @@ void Fl_Wayland_Window_Driver::reposition_menu_window(int x, int y) {
xid_menu->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, xid_menu->wl_surface); xid_menu->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, xid_menu->wl_surface);
xdg_surface_add_listener(xid_menu->xdg_surface, &xdg_surface_listener, xid_menu); xdg_surface_add_listener(xid_menu->xdg_surface, &xdg_surface_listener, xid_menu);
struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base); struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base);
struct wld_window * parent_xid = fl_wl_xid(Fl_Window_Driver::menu_parent()); struct wld_window * parent_xid = fl_wl_xid(menu_origin);
float f = Fl::screen_scale(Fl_Window_Driver::menu_parent()->screen_num()); float f = Fl::screen_scale(Fl_Window_Driver::menu_parent()->screen_num());
int popup_x = x * f, popup_y = y * f; int popup_x = x * f, popup_y = y * f + xid_menu->state;
if (menu_origin->menu_window()) {
popup_x -= menu_origin->x() * f;
popup_y -= menu_origin->y() * f;
}
if (parent_xid->kind == DECORATED) if (parent_xid->kind == DECORATED)
libdecor_frame_translate_coordinate(parent_xid->frame, popup_x, popup_y, &popup_x, &popup_y); 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); xdg_positioner_set_anchor_rect(positioner, popup_x, popup_y, 1, 1);
xdg_positioner_set_size(positioner, pWindow->w() * f , pWindow->h() * f ); xdg_positioner_set_size(positioner, pWindow->w() * f , pWindow->h() * f );
xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
if (y_offset) xdg_positioner_set_offset(positioner, 0, y_offset * f); xdg_positioner_set_constraint_adjustment(positioner, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X);
xid_menu->xdg_popup = xdg_surface_get_popup(xid_menu->xdg_surface, parent_xid->xdg_surface, positioner); xid_menu->xdg_popup = xdg_surface_get_popup(xid_menu->xdg_surface, parent_xid->xdg_surface, positioner);
xdg_positioner_destroy(positioner); xdg_positioner_destroy(positioner);
xdg_popup_add_listener(xid_menu->xdg_popup, &popup_listener, xid_menu); struct win_positioner *win_pos = new struct win_positioner;
win_pos->window = xid_menu;
win_pos->x = popup_x;
win_pos->y = popup_y;
xdg_popup_add_listener(xid_menu->xdg_popup, &popup_listener, win_pos);
wl_surface_commit(xid_menu->wl_surface); wl_surface_commit(xid_menu->wl_surface);
wl_display_roundtrip(Fl_Wayland_Screen_Driver::wl_display); wl_display_roundtrip(Fl_Wayland_Screen_Driver::wl_display); // necessary with sway
// delete the previous popup // delete the previous popup
struct win_positioner *old_win_pos = (struct win_positioner*)xdg_popup_get_user_data(old_popup);
xdg_popup_destroy(old_popup); xdg_popup_destroy(old_popup);
delete old_win_pos;
xdg_surface_destroy(old_xdg); xdg_surface_destroy(old_xdg);
wl_surface_destroy(old_surface); wl_surface_destroy(old_surface);
wl_display_roundtrip(Fl_Wayland_Screen_Driver::wl_display); this->y(y);
this->y(true_y);
} }
void Fl_Wayland_Window_Driver::menu_window_area(int &X, int &Y, int &W, int &H, int nscreen) { 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(); Fl_Window *parent = Fl_Window_Driver::menu_parent();
if (parent) { if (parent) {
struct wld_window *xid = fl_wl_xid(pWindow); int XX, YY, WW, HH;
bool condition = xid ? xid->state : use_wayland_menu_positioning(pWindow, parent); Fl::screen_xywh(XX, YY, WW, HH, parent->screen_num());
if (!condition) { if (pWindow->menu_window() && pWindow->h() > HH) {
// keep active menu part inside parent window // tall menu: set top (Y) and bottom (Y+H) bounds relatively to reference window
X = parent->x(); int ih = Fl_Window_Driver::menu_itemheight(pWindow);
Y = parent->y(); X = -50000;
W = parent->w(); W = 1000000;
H = parent->h(); H = HH - 2 * ih;
Fl_Window *origin = Fl_Window_Driver::menu_leftorigin(pWindow);
if (origin) { // has left parent
int selected = fl_max(Fl_Window_Driver::menu_selected(origin), 0);
Y = origin->y() + (selected + 0.5) * ih;
} else {
Y = 1.5 * ih;
}
} else { // position the menu window by wayland constraints } else { // position the menu window by wayland constraints
X = -50000; X = -50000;
Y = -50000; Y = -50000;