Wayland: Improved implementation of menu windows
This commit is contained in:
parent
6ada45f1f2
commit
222b2ea2e8
@ -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];
|
||||||
|
@ -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; }
|
||||||
};
|
};
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user