on xenial help sign in
Menu layout fixes
commit6c699ede554b00d9ad4c2c0b24fa38cee29b7d80 authorNick Dedekind <nick.dedekind@canonical.com> dateWed 18 Jan 2017, 09:48 (10 years ago) parent6c5022c
3 files changed+175−123
qml/ApplicationMenus/MenuPopup.qml+157−113 tests/qmltests/ApplicationMenuDataLoader.qml+13−5 tests/qmltests/ApplicationMenus/tst_MenuBar.qml+5−5
qml/ApplicationMenus/MenuPopup.qml+157−113
@@ -26,18 +26,18 @@ UbuntuShape {
2626 objectName: "menu"
2727 backgroundColor: theme.palette.normal.overlay
2828
29− property alias unityMenuModel: listView.model
29+ property alias unityMenuModel: repeater.model
3030
3131 readonly property real __ajustedMinimumHeight: {
3232 if (listView.contentHeight > __minimumHeight) {
33− return units.gu(30);
33+ return __minimumHeight;
3434 }
3535 return Math.max(listView.contentHeight, units.gu(2));
3636 }
3737
3838 readonly property real __minimumWidth: units.gu(20)
39− readonly property real __minimumHeight: units.gu(30)
4039 readonly property real __maximumWidth: Screen.width * 0.7
40+ readonly property real __minimumHeight: units.gu(30)
4141 readonly property real __maximumHeight: Screen.height * 0.7
4242
4343 function show() {
@@ -63,13 +63,13 @@ UbuntuShape {
6363 d.dismissAll();
6464 }
6565
66− implicitWidth: container.width
67− implicitHeight: MathUtils.clamp(listView.contentHeight, __ajustedMinimumHeight, __maximumHeight)
66+ implicitWidth: focusScope.width
67+ implicitHeight: focusScope.height
6868
6969 MenuNavigator {
7070 id: d
7171 objectName: "d"
72− itemView: listView
72+ itemView: repeater
7373
7474 property Item currentItem: null
7575 property Item hoveredItem: null
@@ -86,13 +86,26 @@ UbuntuShape {
8686 }
8787
8888 onSelect: {
89− currentItem = listView.itemAt(index);
89+ currentItem = repeater.itemAt(index);
90+ if (currentItem) {
91+ if (currentItem.y < listView.contentY) {
92+ listView.contentY = currentItem.y;
93+ } else if (currentItem.y + currentItem.height > listView.contentY + listView.height) {
94+ listView.contentY = currentItem.y + currentItem.height - listView.height;
95+ }
96+ }
9097 }
9198 }
9299
100+ MouseArea {
101+ // Eat events.
102+ anchors.fill: parent
103+ }
104+
93105 Item {
94106 id: focusScope
95− anchors.fill: parent
107+ width: container.width
108+ height: container.height
96109 focus: visible
97110
98111 Keys.onUpPressed: d.selectPrevious(d.currentIndex)
@@ -108,17 +121,17 @@ UbuntuShape {
108121 id: container
109122 objectName: "container"
110123
111− width: listView.contentWidth
112− height: parent.height
124+ height: MathUtils.clamp(listView.contentHeight, __ajustedMinimumHeight, __maximumHeight)
125+ width: menuColumn.width
113126 spacing: 0
114127
115− // FIXME use ListView.header - tried but was flaky with positionViewAtIndex.
128+ // Header - scroll up
116129 Item {
117− Layout.fillWidth: true;
118− Layout.maximumHeight: units.gu(3)
119− Layout.minimumHeight: units.gu(3)
130+ Layout.fillWidth: true
131+ height: units.gu(3)
120132 visible: listView.contentHeight > root.height
121133 enabled: !listView.atYBeginning
134+ z: 1
122135
123136 Rectangle {
124137 color: enabled ? theme.palette.normal.overlayText :
@@ -143,47 +156,35 @@ UbuntuShape {
143156 MouseArea {
144157 anchors.fill: parent
145158 onPressed: {
146− var index = listView.indexAt(0, listView.contentY);
147− listView.positionViewAtIndex(index-1, ListView.Beginning);
159+ var item = menuColumn.childAt(0, listView.contentY);
160+ if (item) {
161+ var previousItem = item;
162+ do {
163+ previousItem = repeater.itemAt(previousItem.__ownIndex-1);
164+ if (!previousItem) {
165+ listView.contentY = 0;
166+ return;
167+ }
168+ } while (previousItem.__isSeparator);
169+
170+ listView.contentY = previousItem.y
171+ }
148172 }
149173 }
150174 }
151175
152− ListView {
176+ // Menu Items
177+ Flickable {
153178 id: listView
154− objectName: "listView"
155− Layout.fillHeight: true
156− Layout.fillWidth: true
157− contentWidth: MathUtils.clamp(contentItem.childrenRect.width,
158− __minimumWidth,
159− __maximumWidth)
160−
161− orientation: Qt.Vertical
162− interactive: contentHeight > height
163179 clip: interactive
164− highlightFollowsCurrentItem: false
165−
166− highlight: Rectangle {
167− color: "transparent"
168− border.width: units.dp(1)
169− border.color: UbuntuColors.orange
170− z: 1
171180
172− width: listView.width
173− height: d.currentItem ? d.currentItem.height : 0
174− y: d.currentItem ? d.currentItem.y : 0
175− visible: d.currentItem
176− }
177−
178− function itemAt(index) {
179− if (index > count || index < 0) return null;
180− currentIndex = index;
181− return currentItem;
182− }
181+ Layout.fillHeight: true
182+ Layout.fillWidth: true
183+ contentHeight: menuColumn.height
184+ interactive: height < contentHeight
183185
184186 MouseArea {
185− id: menuMouseArea
186− anchors.fill: listView
187+ anchors.fill: parent
187188 hoverEnabled: true
188189 propagateComposedEvents: true // propogate events so we send clicks to children.
189190 z: 1 // on top so we override any other hovers
@@ -195,7 +196,7 @@ UbuntuShape {
195196
196197 if (!d.hoveredItem || !d.currentItem ||
197198 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
198− d.hoveredItem = listView.itemAt(listView.indexAt(pos.x, pos.y));
199+ d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
199200 if (!d.hoveredItem || !d.hoveredItem.enabled)
200201 return false;
201202 d.currentItem = d.hoveredItem;
@@ -216,89 +217,121 @@ UbuntuShape {
216217 }
217218 }
218219
219− delegate: Loader {
220− id: loader
221− objectName: root.objectName + "-item" + __ownIndex
220+ ColumnLayout {
221+ id: menuColumn
222+ spacing: 0
222223
223− property int __ownIndex: index
224+ Repeater {
225+ id: repeater
224226
225− width: root.width
226− enabled: model.isSeparator ? false : model.sensitive
227+ Loader {
228+ id: loader
229+ objectName: root.objectName + "-item" + __ownIndex
227230
228− sourceComponent: {
229− if (model.isSeparator) {
230− return separatorComponent;
231− }
232− return menuItemComponent;
233− }
231+ property int __ownIndex: index
232+ property bool __isSeparator: model.isSeparator
234233
235− property Item popup: null
236−
237− Component {
238− id: menuItemComponent
239− MenuItem {
240− id: menuItem
241− menuData: model
242− objectName: loader.objectName + "-actionItem"
243−
244− action.onTriggered: {
245− d.currentItem = loader;
246−
247− if (hasSubmenu) {
248− if (!popup) {
249− var model = root.unityMenuModel.submenu(__ownIndex);
250− popup = submenuComponent.createObject(focusScope, {
251− objectName: loader.objectName + "-",
252− unityMenuModel: model,
253− x: Qt.binding(function() { return root.width }),
254− y: Qt.binding(function() { return loader.y })
255− });
256− } else if (popup) {
257− popup.visible = true;
258− }
259− popup.retreat.connect(function() {
260− popup.destroy();
261− popup = null;
262− menuItem.forceActiveFocus();
263− })
264− } else {
265− root.unityMenuModel.activate(__ownIndex);
234+ enabled: __isSeparator ? false : model.sensitive
235+
236+ sourceComponent: {
237+ if (model.isSeparator) {
238+ return separatorComponent;
266239 }
240+ return menuItemComponent;
267241 }
268242
269− Connections {
270− target: d
271− onCurrentIndexChanged: {
272− if (popup && d.currentIndex != __ownIndex) {
273− popup.visible = false;
243+ property Item popup: null
244+
245+ Layout.fillWidth: true
246+
247+ Component {
248+ id: menuItemComponent
249+ MenuItem {
250+ id: menuItem
251+ menuData: model
252+ objectName: loader.objectName + "-actionItem"
253+
254+ width: MathUtils.clamp(implicitWidth, __minimumWidth, __maximumWidth)
255+
256+ action.onTriggered: {
257+ d.currentItem = loader;
258+
259+ if (hasSubmenu) {
260+ if (!popup) {
261+ var model = root.unityMenuModel.submenu(__ownIndex);
262+ popup = submenuComponent.createObject(focusScope, {
263+ objectName: loader.objectName + "-",
264+ unityMenuModel: model,
265+ x: Qt.binding(function() { return root.width }),
266+ y: Qt.binding(function() {
267+ var dummy = listView.contentY; // force a recalc on contentY change.
268+ return mapToItem(container, 0, y).y;
269+ })
270+ });
271+ } else if (popup) {
272+ popup.visible = true;
273+ }
274+ popup.retreat.connect(function() {
275+ popup.destroy();
276+ popup = null;
277+ menuItem.forceActiveFocus();
278+ })
279+ } else {
280+ root.unityMenuModel.activate(__ownIndex);
281+ }
274282 }
275− }
276− onDismissAll: {
277− if (popup) {
278− popup.destroy();
279− popup = null;
283+
284+ Connections {
285+ target: d
286+ onCurrentIndexChanged: {
287+ if (popup && d.currentIndex != __ownIndex) {
288+ popup.visible = false;
289+ }
290+ }
291+ onDismissAll: {
292+ if (popup) {
293+ popup.destroy();
294+ popup = null;
295+ }
296+ }
280297 }
281298 }
282299 }
283− }
284− }
285300
286− Component {
287− id: separatorComponent
288− ListItems.ThinDivider {
289− objectName: loader.objectName + "-separator"
301+ Component {
302+ id: separatorComponent
303+ ListItems.ThinDivider {
304+ objectName: loader.objectName + "-separator"
305+ implicitHeight: units.dp(2)
306+ }
307+ }
290308 }
309+
291310 }
292311 }
293− } // ListView
294312
295− // FIXME use ListView.footer - tried but was flaky with positionViewAtIndex.
313+ // Highlight
314+ Rectangle {
315+ color: "transparent"
316+ border.width: units.dp(1)
317+ border.color: UbuntuColors.orange
318+ z: 1
319+
320+ width: listView.width
321+ height: d.currentItem ? d.currentItem.height : 0
322+ y: d.currentItem ? d.currentItem.y : 0
323+ visible: d.currentItem
324+ }
325+
326+ } // Flickable
327+
328+ // Header - scroll down
296329 Item {
297− Layout.fillWidth: true;
298− Layout.maximumHeight: units.gu(3)
299− Layout.minimumHeight: units.gu(3)
330+ Layout.fillWidth: true
331+ height: units.gu(3)
300332 visible: listView.contentHeight > root.height
301333 enabled: !listView.atYEnd
334+ z: 1
302335
303336 Rectangle {
304337 color: enabled ? theme.palette.normal.overlayText :
@@ -323,8 +356,19 @@ UbuntuShape {
323356 MouseArea {
324357 anchors.fill: parent
325358 onPressed: {
326− var index = listView.indexAt(0, listView.contentY);
327− listView.positionViewAtIndex(index+1, ListView.Beginning);
359+ var item = menuColumn.childAt(0, listView.contentY + listView.height);
360+ if (item) {
361+ var nextItem = item;
362+ do {
363+ nextItem = repeater.itemAt(nextItem.__ownIndex+1);
364+ if (!nextItem) {
365+ listView.contentY = listView.contentHeight - listView.height;
366+ return;
367+ }
368+ } while (nextItem.__isSeparator);
369+
370+ listView.contentY = nextItem.y - listView.height
371+ }
328372 }
329373 }
330374 }
tests/qmltests/ApplicationMenuDataLoader.qml+13−5
@@ -10,6 +10,7 @@ Object {
1010
1111 Connections {
1212 id: sMgrHandler
13+ target: null
1314 onSurfaceCreated: {
1415 var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
1516
@@ -25,21 +26,28 @@ Object {
2526 var data = [];
2627 if (root === undefined) root = true;
2728
28− if (prefix === undefined) prefix = "menu"
29−
3029 for (var i = 0; i < length; i++) {
3130
31+ var menuName = prefix;
32+ if (menuName === undefined) {
33+ var chars = Math.random() * 20;
34+ menuName = "";
35+ for (var x = 0; x < chars; x++) {
36+ menuName += String.fromCharCode((Math.random() * 26) + 65);
37+ }
38+ }
39+
3240 var menuCode = String.fromCharCode(i+65);
3341
3442 var isSeparator = !root && separatorInterval > 0 && ((i+1) % separatorInterval == 0);
3543 var row = {
3644 "rowData": { // 1
37− "label": prefix + "&" + menuCode,
45+ "label": menuName + "&" + menuCode,
3846 "sensitive": true,
3947 "isSeparator": isSeparator,
4048 "icon": "",
4149 "ext": {},
42− "action": prefix + menuCode,
50+ "action": menuName + menuCode,
4351 "actionState": {},
4452 "isCheck": false,
4553 "isRadio": false,
@@ -49,7 +57,7 @@ Object {
4957 }
5058 var isSubmenu = root === undefined || root === true || (submenuInterval > 0 && ((i+1) % submenuInterval == 0));
5159 if (isSubmenu && !isSeparator && depth > 1) {
52− row["submenu"] = generateTestData(length, depth-1, submenuInterval, separatorInterval, prefix + menuCode + ".", false);
60+ row["submenu"] = generateTestData(length, depth-1, submenuInterval, separatorInterval,prefix, false);
5361 }
5462 data[i] = row;
5563 }
tests/qmltests/ApplicationMenus/tst_MenuBar.qml+5−5
@@ -65,7 +65,7 @@ Item {
6565
6666 unityMenuModel: UnityMenuModel {
6767 id: menuBackend
68− modelData: appMenuData.generateTestData(7,5,2,3,"menu")
68+ modelData: appMenuData.generateTestData(17,5,2,3)
6969 }
7070 }
7171 }
@@ -83,12 +83,12 @@ Item {
8383
8484 function init() {
8585 menuBar.dismiss();
86− menuBackend.modelData = appMenuData.generateTestData(5,5,2,3,"menu")
86+ menuBackend.modelData = appMenuData.generateTestData(5,5,2,3)
8787 activatedSpy.clear();
8888 }
8989
9090 function test_mouseNavigation() {
91− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
91+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
9292 wait(50) // wait for row to build
9393 var priv = findInvisibleChild(menuBar, "d");
9494
@@ -114,7 +114,7 @@ Item {
114114 }
115115
116116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
117− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
117+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
118118 var priv = findInvisibleChild(menuBar, "d");
119119
120120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -139,7 +139,7 @@ Item {
139139 }
140140
141141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
142− menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
142+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);
143143 var priv = findInvisibleChild(menuBar, "d");
144144
145145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);