on xenial help sign in
Make the menus and submenus do not go outside the screen when popping out

Approved by: Michael Zanetti, Unity8 CI Bot

commit0edaf2c95aa2b8476d84d7357cb3838a07a27ab7 authorAlbert Astals Cid <albert.astals@canonical.com> dateThu 16 Feb 2017, 13:41 (10 years ago) parent46dea6a4ffc752
5 files changed+174−17
qml/ApplicationMenus/ApplicationMenusLimits.qml+24 qml/ApplicationMenus/MenuBar.qml+2−2 qml/ApplicationMenus/MenuPopup.qml+47−15 qml/ApplicationMenus/qmldir+1 tests/qmltests/Stage/tst_DesktopStage.qml+100
qml/ApplicationMenus/ApplicationMenusLimits.qmlnew file+24
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (C) 2017 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+pragma Singleton
18+import QtQuick 2.4
19+import QtQuick.Window 2.2
20+
21+QtObject {
22+ property real screenWidth: Screen.width
23+ property real screenHeight: Screen.height
24+}
qml/ApplicationMenus/MenuBar.qml+2−2
@@ -149,8 +149,8 @@ Item {
149149 Component {
150150 id: menuComponent
151151 MenuPopup {
152− x: visualItem.x - units.gu(1)
153− anchors.top: parent.bottom
152+ desiredX: visualItem.x - units.gu(1)
153+ desiredY: parent.height
154154 unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
155155
156156 Component.onCompleted: reset();
qml/ApplicationMenus/MenuPopup.qml+47−15
@@ -16,10 +16,10 @@
1616
1717import QtQuick 2.4
1818import QtQuick.Layouts 1.1
19−import QtQuick.Window 2.2
2019import Ubuntu.Components 1.3
2120import Ubuntu.Components.ListItems 1.3 as ListItems
2221import "../Components"
22+import "."
2323
2424UbuntuShape {
2525 id: root
@@ -28,6 +28,38 @@ UbuntuShape {
2828
2929 signal childActivated()
3030
31+ // true for submenus that need to show on the other side of their parent
32+ // if they don't fit when growing right
33+ property bool substractWidth: false
34+
35+ property real desiredX
36+ x: {
37+ var dummy = visible; // force recalc when shown/hidden
38+ var parentTopLeft = parent.mapToItem(null, 0, 0);
39+ var farX = ApplicationMenusLimits.screenWidth;
40+ if (parentTopLeft.x + width + desiredX <= farX) {
41+ return desiredX;
42+ } else {
43+ if (substractWidth) {
44+ return -width;
45+ } else {
46+ return farX - parentTopLeft.x - width;
47+ }
48+ }
49+ }
50+
51+ property real desiredY
52+ y: {
53+ var dummy = visible; // force recalc when shown/hidden
54+ var parentTopLeft = parent.mapToItem(null, 0, 0);
55+ var bottomY = ApplicationMenusLimits.screenHeight;
56+ if (parentTopLeft.y + height + desiredY <= bottomY) {
57+ return desiredY;
58+ } else {
59+ return bottomY - parentTopLeft.y - height;
60+ }
61+ }
62+
3163 property alias unityMenuModel: repeater.model
3264
3365 function show() {
@@ -66,9 +98,9 @@ UbuntuShape {
6698 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
6799
68100 property real __minimumWidth: units.gu(20)
69− property real __maximumWidth: Screen.width * 0.7
101+ property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
70102 property real __minimumHeight: units.gu(2)
71− property real __maximumHeight: Screen.height * 0.7
103+ property real __maximumHeight: ApplicationMenusLimits.screenHeight * 0.7
72104
73105 signal dismissAll()
74106
@@ -259,8 +291,9 @@ UbuntuShape {
259291 popup = submenuComponent.createObject(focusScope, {
260292 objectName: loader.objectName + "-",
261293 unityMenuModel: model,
262− x: Qt.binding(function() { return root.width }),
263− y: Qt.binding(function() {
294+ substractWidth: true,
295+ desiredX: Qt.binding(function() { return root.width }),
296+ desiredY: Qt.binding(function() {
264297 var dummy = listView.contentY; // force a recalc on contentY change.
265298 return mapToItem(container, 0, y).y;
266299 })
@@ -383,20 +416,19 @@ UbuntuShape {
383416 id: submenuLoader
384417 source: "MenuPopup.qml"
385418
419+ property real desiredX
420+ property real desiredY
421+ property bool substractWidth
386422 property var unityMenuModel: null
387423 signal retreat()
388424 signal childActivated()
389425
390− Binding {
391− target: item
392− property: "unityMenuModel"
393− value: submenuLoader.unityMenuModel
394− }
395−
396− Binding {
397− target: item
398− property: "objectName"
399− value: submenuLoader.objectName + "menu"
426+ onLoaded: {
427+ item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
428+ item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
429+ item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
430+ item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
431+ item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
400432 }
401433
402434 Keys.onLeftPressed: retreat()
qml/ApplicationMenus/qmldirnew file+1
@@ -0,0 +1 @@
1+singleton ApplicationMenusLimits 0.1 ApplicationMenusLimits.qml
tests/qmltests/Stage/tst_DesktopStage.qml+100
@@ -29,6 +29,7 @@ import ".." // For EdgeBarrierControls
2929import "../../../qml/Stage"
3030import "../../../qml/Components"
3131import "../../../qml/Components/PanelState"
32+import "../../../qml/ApplicationMenus"
3233
3334Item {
3435 id: root
@@ -44,6 +45,8 @@ Item {
4445 }
4546
4647 Component.onCompleted: {
48+ ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
49+ ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
4750 QuickUtils.keyboardAttached = true;
4851 theme.name = "Ubuntu.Components.Themes.SuruDark";
4952 resetGeometry();
@@ -830,5 +833,102 @@ Item {
830833 mouseRelease(decoration);
831834 tryCompare(Mir, "cursorName", "");
832835 }
836+
837+ function test_menuPositioning_data() {
838+ return [
839+ {tag: "good",
840+ windowPosition: Qt.point(units.gu(10), units.gu(10))
841+ },
842+ {tag: "collides right",
843+ windowPosition: Qt.point(units.gu(100), units.gu(10)),
844+ minimumXDifference: units.gu(8)
845+ },
846+ {tag: "collides bottom",
847+ windowPosition: Qt.point(units.gu(10), units.gu(80)),
848+ minimumYDifference: units.gu(7)
849+ },
850+ ]
851+ }
852+
853+ function test_menuPositioning(data) {
854+ var appDelegate = startApplication("dialer-app");
855+ appDelegate.windowedX = data.windowPosition.x;
856+ appDelegate.windowedY = data.windowPosition.y;
857+
858+ var menuItem = findChild(appDelegate, "menuBar-item3");
859+ menuItem.show();
860+
861+ var menu = findChild(appDelegate, "menuBar-item3-menu");
862+ tryCompare(menu, "visible", true);
863+
864+ var normalPositioningX = menuItem.x - units.gu(1);
865+ var normalPositioningY = menuItem.height;
866+
867+ // We do this fuzzy checking because otherwise we would be duplicating the code
868+ // that calculates the coordinates and any bug it may have, what we want is really
869+ // to check that on collision with the border the menu is shifted substantially
870+ if (data.minimumXDifference) {
871+ verify(menu.x < normalPositioningX - data.minimumXDifference);
872+ } else {
873+ compare(menu.x, normalPositioningX);
874+ }
875+
876+ if (data.minimumYDifference) {
877+ verify(menu.y < normalPositioningY - data.minimumYDifference);
878+ } else {
879+ compare(menu.y, normalPositioningY);
880+ }
881+ }
882+
883+ function test_submenuPositioning_data() {
884+ return [
885+ {tag: "good",
886+ windowPosition: Qt.point(units.gu(10), units.gu(10))
887+ },
888+ {tag: "collides right",
889+ windowPosition: Qt.point(units.gu(100), units.gu(10)),
890+ minimumXDifference: units.gu(35)
891+ },
892+ {tag: "collides bottom",
893+ windowPosition: Qt.point(units.gu(10), units.gu(80)),
894+ minimumYDifference: units.gu(8)
895+ },
896+ ]
897+ }
898+
899+ function test_submenuPositioning(data) {
900+ var appDelegate = startApplication("dialer-app");
901+ appDelegate.windowedX = data.windowPosition.x;
902+ appDelegate.windowedY = data.windowPosition.y;
903+
904+ var menuItem = findChild(appDelegate, "menuBar-item3");
905+ menuItem.show();
906+
907+ var menu = findChild(appDelegate, "menuBar-item3-menu");
908+ menuItem = findChild(menu, "menuBar-item3-menu-item3-actionItem");
909+ tryCompare(menuItem, "visible", true);
910+ mouseMove(menuItem);
911+ mouseClick(menuItem);
912+
913+ menu = findChild(appDelegate, "menuBar-item3-menu-item3-menu");
914+
915+ var normalPositioningX = menuItem.width;
916+ var normalPositioningY = menuItem.parent.y;
917+
918+ // We do this fuzzy checking because otherwise we would be duplicating the code
919+ // that calculates the coordinates and any bug it may have, what we want is really
920+ // to check that on collision with the border the menu is shifted substantially
921+ if (data.minimumXDifference) {
922+ verify(menu.x < normalPositioningX - data.minimumXDifference);
923+ } else {
924+ compare(menu.x, normalPositioningX);
925+ }
926+
927+ if (data.minimumYDifference) {
928+ verify(menu.y < normalPositioningY - data.minimumYDifference);
929+ } else {
930+ compare(menu.y, normalPositioningY);
931+ }
932+ }
833933 }
834934}