popupMenu: Refactor focus and key management
With the presence of Clutter.grab(), this behaves differently enough that needs some redoing. The larger difference is what actors are eligible for handling events. In the older code, a PopupMenuManager would ask the grabHelper to capture events from all the stage, and selectively silence events on any actor that is not the currently shown popup menu or the "source" actor for any other popup in the group (i.e. those that would pop up another menu). But we don't want to just silence events, we want to emit the correct set of crossing events when a popup menu is shown or closed, this requires a backing ClutterGrab() on the currently shown menu. Since the presence of a grab also affects the ability to have actors outside the grab area to handle events, the PopupMenuManager now must detect hovering and focus changes to other menu sources by handling events on the grabbed popup itself. Redo the grabbing over Main.pushModal/popModal (i.e. ClutterGrab, plus keyboard focus restoration) and a captured event handler on the currently shown menu, to make PopupMenuManager behave as it is expected with this new kind of grabs. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2045>
This commit is contained in:
parent
63725ef0ef
commit
2709f6c102
@ -7,7 +7,6 @@ const { Atk, Clutter, Gio, GObject, Graphene, Shell, St } = imports.gi;
|
|||||||
const Signals = imports.signals;
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const BoxPointer = imports.ui.boxpointer;
|
const BoxPointer = imports.ui.boxpointer;
|
||||||
const GrabHelper = imports.ui.grabHelper;
|
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Params = imports.misc.params;
|
const Params = imports.misc.params;
|
||||||
|
|
||||||
@ -900,12 +899,10 @@ var PopupMenu = class extends PopupMenuBase {
|
|||||||
return Clutter.EVENT_PROPAGATE;
|
return Clutter.EVENT_PROPAGATE;
|
||||||
|
|
||||||
let symbol = event.get_key_symbol();
|
let symbol = event.get_key_symbol();
|
||||||
|
|
||||||
if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
|
if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
|
||||||
this.toggle();
|
this.toggle();
|
||||||
return Clutter.EVENT_STOP;
|
return Clutter.EVENT_STOP;
|
||||||
} else if (symbol == Clutter.KEY_Escape && this.isOpen) {
|
|
||||||
this.close();
|
|
||||||
return Clutter.EVENT_STOP;
|
|
||||||
} else if (symbol == navKey) {
|
} else if (symbol == navKey) {
|
||||||
if (!this.isOpen)
|
if (!this.isOpen)
|
||||||
this.toggle();
|
this.toggle();
|
||||||
@ -1299,9 +1296,18 @@ class PopupSubMenuMenuItem extends PopupBaseMenuItem {
|
|||||||
*/
|
*/
|
||||||
var PopupMenuManager = class {
|
var PopupMenuManager = class {
|
||||||
constructor(owner, grabParams) {
|
constructor(owner, grabParams) {
|
||||||
grabParams = Params.parse(grabParams,
|
this._grabParams = Params.parse(grabParams,
|
||||||
{ actionMode: Shell.ActionMode.POPUP });
|
{ actionMode: Shell.ActionMode.POPUP });
|
||||||
this._grabHelper = new GrabHelper.GrabHelper(owner, grabParams);
|
global.stage.connect('notify::key-focus', () => {
|
||||||
|
if (!this.activeMenu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let actor = global.stage.get_key_focus();
|
||||||
|
let newMenu = this._findMenuForSource(actor);
|
||||||
|
|
||||||
|
if (newMenu)
|
||||||
|
this._changeMenu(newMenu);
|
||||||
|
});
|
||||||
this._menus = [];
|
this._menus = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1313,20 +1319,9 @@ var PopupMenuManager = class {
|
|||||||
menu,
|
menu,
|
||||||
openStateChangeId: menu.connect('open-state-changed', this._onMenuOpenState.bind(this)),
|
openStateChangeId: menu.connect('open-state-changed', this._onMenuOpenState.bind(this)),
|
||||||
destroyId: menu.connect('destroy', this._onMenuDestroy.bind(this)),
|
destroyId: menu.connect('destroy', this._onMenuDestroy.bind(this)),
|
||||||
enterId: 0,
|
capturedEventId: menu.actor.connect('captured-event', this._onCapturedEvent.bind(this)),
|
||||||
focusInId: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let source = menu.sourceActor;
|
|
||||||
if (source) {
|
|
||||||
this._grabHelper.addActor(source);
|
|
||||||
menudata.enterId = source.connect('enter-event',
|
|
||||||
() => this._onMenuSourceEnter(menu));
|
|
||||||
menudata.focusInId = source.connect('key-focus-in', () => {
|
|
||||||
this._onMenuSourceEnter(menu);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position == undefined)
|
if (position == undefined)
|
||||||
this._menus.push(menudata);
|
this._menus.push(menudata);
|
||||||
else
|
else
|
||||||
@ -1335,7 +1330,7 @@ var PopupMenuManager = class {
|
|||||||
|
|
||||||
removeMenu(menu) {
|
removeMenu(menu) {
|
||||||
if (menu == this.activeMenu)
|
if (menu == this.activeMenu)
|
||||||
this._grabHelper.ungrab({ actor: menu.actor });
|
Main.popModal(menu.actor);
|
||||||
|
|
||||||
let position = this._findMenu(menu);
|
let position = this._findMenu(menu);
|
||||||
if (position == -1) // not a menu we manage
|
if (position == -1) // not a menu we manage
|
||||||
@ -1345,39 +1340,25 @@ var PopupMenuManager = class {
|
|||||||
menu.disconnect(menudata.openStateChangeId);
|
menu.disconnect(menudata.openStateChangeId);
|
||||||
menu.disconnect(menudata.destroyId);
|
menu.disconnect(menudata.destroyId);
|
||||||
|
|
||||||
if (menudata.enterId)
|
|
||||||
menu.sourceActor.disconnect(menudata.enterId);
|
|
||||||
if (menudata.focusInId)
|
|
||||||
menu.sourceActor.disconnect(menudata.focusInId);
|
|
||||||
|
|
||||||
if (menu.sourceActor)
|
|
||||||
this._grabHelper.removeActor(menu.sourceActor);
|
|
||||||
this._menus.splice(position, 1);
|
this._menus.splice(position, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
get activeMenu() {
|
|
||||||
let firstGrab = this._grabHelper.grabStack[0];
|
|
||||||
if (firstGrab)
|
|
||||||
return firstGrab.actor._delegate;
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ignoreRelease() {
|
ignoreRelease() {
|
||||||
return this._grabHelper.ignoreRelease();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMenuOpenState(menu, open) {
|
_onMenuOpenState(menu, open) {
|
||||||
|
if (open && this.activeMenu === menu)
|
||||||
|
return;
|
||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
if (this.activeMenu)
|
if (this.activeMenu)
|
||||||
this.activeMenu.close(BoxPointer.PopupAnimation.FADE);
|
this.activeMenu.close(BoxPointer.PopupAnimation.FADE);
|
||||||
this._grabHelper.grab({
|
Main.pushModal(menu.actor, this._grabParams);
|
||||||
actor: menu.actor,
|
this.activeMenu = menu;
|
||||||
focus: menu.focusActor,
|
|
||||||
onUngrab: isUser => this._closeMenu(isUser, menu),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this._grabHelper.ungrab({ actor: menu.actor });
|
if (this.activeMenu === menu)
|
||||||
|
this.activeMenu = null;
|
||||||
|
Main.popModal(menu.actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1387,17 +1368,45 @@ var PopupMenuManager = class {
|
|||||||
: BoxPointer.PopupAnimation.FULL);
|
: BoxPointer.PopupAnimation.FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMenuSourceEnter(menu) {
|
_onCapturedEvent(actor, event) {
|
||||||
if (!this._grabHelper.grabbed)
|
let menu = actor._delegate;
|
||||||
return Clutter.EVENT_PROPAGATE;
|
if (event.type() === Clutter.EventType.KEY_PRESS) {
|
||||||
|
let symbol = event.get_key_symbol();
|
||||||
|
if (symbol === Clutter.KEY_Down &&
|
||||||
|
global.stage.get_key_focus() === menu.actor) {
|
||||||
|
actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
||||||
|
return Clutter.EVENT_STOP;
|
||||||
|
} else if (symbol === Clutter.KEY_Escape && menu.isOpen) {
|
||||||
|
menu.close(BoxPointer.PopupAnimation.FULL);
|
||||||
|
return Clutter.EVENT_STOP;
|
||||||
|
}
|
||||||
|
} else if (event.type() === Clutter.EventType.ENTER &&
|
||||||
|
(event.get_flags() & Clutter.EventFlags.GRAB_NOTIFY) === 0) {
|
||||||
|
let hoveredMenu = this._findMenuForSource(event.get_source());
|
||||||
|
|
||||||
if (this._grabHelper.isActorGrabbed(menu.actor))
|
if (hoveredMenu && hoveredMenu !== menu)
|
||||||
return Clutter.EVENT_PROPAGATE;
|
this._changeMenu(hoveredMenu);
|
||||||
|
} else if ((event.type() === Clutter.EventType.BUTTON_PRESS ||
|
||||||
|
event.type() === Clutter.EventType.TOUCH_BEGIN) &&
|
||||||
|
!actor.contains(event.get_source())) {
|
||||||
|
menu.close(BoxPointer.PopupAnimation.FULL);
|
||||||
|
}
|
||||||
|
|
||||||
this._changeMenu(menu);
|
|
||||||
return Clutter.EVENT_PROPAGATE;
|
return Clutter.EVENT_PROPAGATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_findMenuForSource(source) {
|
||||||
|
while (source) {
|
||||||
|
let actor = source;
|
||||||
|
const menu = this._menus.map(m => m.menu).find(m => m.sourceActor === actor);
|
||||||
|
if (menu)
|
||||||
|
return menu;
|
||||||
|
source = source.get_parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
_onMenuDestroy(menu) {
|
_onMenuDestroy(menu) {
|
||||||
this.removeMenu(menu);
|
this.removeMenu(menu);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user