2 * Copyright (C) 2016 Canonical, Ltd.
3 * Copyright (C) 2020-2021 UBports Foundation
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import Ubuntu.Components 1.3
20 import Unity.Launcher 0.1
22 import "../Components"
23 import Qt.labs.settings 1.0
25 import AccountsService 0.1
26 import QtGraphicalEffects 1.0
31 property int panelWidth: 0
32 readonly property bool moving: (appList && appList.moving) ? true : false
33 readonly property Item searchTextField: searchField
34 readonly property real delegateWidth: units.gu(10)
35 property url background
36 property alias backgroundSourceSize: background.sourceSize
37 property bool staticBlurEnabled : true
39 property var fullyOpen: x === 0
40 property var fullyClosed: x === -width
42 signal applicationSelected(string appId)
44 // Request that the Drawer is opened fully, if it was partially closed then
46 signal openRequested()
48 // Request that the Drawer (and maybe its parent) is hidden, normally if
49 // the Drawer has been dragged away.
50 signal hideRequested()
52 property bool allowSlidingAnimation: false
53 property bool draggingHorizontally: false
54 property int dragDistance: 0
56 property var hadFocus: false
57 property var oldSelectionStart: null
58 property var oldSelectionEnd: null
61 onRightMarginChanged: refocusInputAfterUserLetsGo()
64 Behavior on anchors.rightMargin {
65 enabled: allowSlidingAnimation && !draggingHorizontally
68 easing.type: Easing.OutCubic
72 onDraggingHorizontallyChanged: {
73 // See refocusInputAfterUserLetsGo()
74 if (draggingHorizontally) {
75 hadFocus = searchField.focus;
76 oldSelectionStart = searchField.selectionStart;
77 oldSelectionEnd = searchField.selectionEnd;
78 searchField.focus = false;
80 if (x < -units.gu(10)) {
85 refocusInputAfterUserLetsGo();
89 Keys.onEscapePressed: {
93 onDragDistanceChanged: {
94 anchors.rightMargin = Math.max(-drawer.width, anchors.rightMargin + dragDistance);
97 function resetOldFocus() {
99 oldSelectionStart = null;
100 oldSelectionEnd = null;
103 function refocusInputAfterUserLetsGo() {
104 if (!draggingHorizontally) {
105 if (fullyOpen && hadFocus) {
106 searchField.focus = hadFocus;
107 searchField.select(oldSelectionStart, oldSelectionEnd);
108 } else if (fullyOpen || fullyClosed) {
113 searchField.text = "";
114 appList.currentIndex = 0;
115 searchField.focus = false;
116 appList.focus = false;
121 function focusInput() {
122 searchField.selectAll();
123 searchField.focus = true;
126 function unFocusInput() {
127 searchField.focus = false;
131 if (event.text.trim() !== "") {
133 searchField.text = event.text;
139 appList.focus = true;
145 // Catch all presses here in case the navigation lets something through
146 // We never want to end up in the launcher with focus
147 event.accepted = true;
153 acceptedButtons: Qt.AllButtons
154 onWheel: wheel.accepted = true
163 objectName: "drawerBackground"
164 visible: staticBlurEnabled
165 enabled: staticBlurEnabled
167 source: root.background
171 anchors.fill: background
172 visible: staticBlurEnabled
173 enabled: staticBlurEnabled
179 // Images with fastblur can't use opacity, so we'll put this on top
181 anchors.fill: background
182 visible: staticBlurEnabled
183 enabled: staticBlurEnabled
190 objectName: "drawerHandle"
194 bottom: parent.bottom
200 handle.active = true;
204 var diff = oldX - mouseX;
205 root.draggingHorizontally |= diff > units.gu(2);
206 if (!root.draggingHorizontally) {
209 root.dragDistance += diff;
216 root.draggingHorizontally = false;
217 handle.active = false;
218 root.dragDistance = 0;
224 active: parent.pressed
232 AppDrawerProxyModel {
234 source: appDrawerModel
235 filterString: searchField.displayText
236 sortBy: AppDrawerProxyModel.SortByAToZ
243 right: drawerHandle.left
245 bottom: parent.bottom
246 leftMargin: root.panelWidth
250 id: searchFieldContainer
252 anchors { left: parent.left; top: parent.top; right: parent.right; margins: units.gu(1) }
256 objectName: "searchField"
257 inputMethodHints: Qt.ImhNoPredictiveText; //workaround to get the clear button enabled without the need of a space char event or change in focus
262 bottom: parent.bottom
264 placeholderText: i18n.tr("Search…")
267 KeyNavigation.down: appList
270 if (searchField.displayText != "" && appList) {
271 // In case there is no currentItem (it might have been filtered away) lets reset it to the first item
272 if (!appList.currentItem) {
273 appList.currentIndex = 0;
275 root.applicationSelected(appList.getFirstAppId());
283 objectName: "drawerAppList"
287 top: searchFieldContainer.bottom
288 bottom: parent.bottom
290 height: rows * delegateHeight
293 model: sortProxyModel
294 delegateWidth: root.delegateWidth
295 delegateHeight: units.gu(11)
296 delegate: drawerDelegateComponent
297 onDraggingVerticallyChanged: {
298 if (draggingVertically) {
303 refreshing: appDrawerModel.refreshing
305 appDrawerModel.refresh();
311 id: drawerDelegateComponent
314 width: GridView.view.cellWidth
316 objectName: "drawerItem_" + model.appId
318 readonly property bool focused: index === GridView.view.currentIndex && GridView.view.activeFocus
320 onClicked: root.applicationSelected(model.appId)
322 if (model.appId.includes(".")) { // Open OpenStore page if app is a click
323 var splitAppId = model.appId.split("_");
324 Qt.openUrlExternally("https://open-store.io/app/" + model.appId.replace("_" + splitAppId[splitAppId.length-1],"") + "/");
327 z: loader.active ? 1 : 0
331 anchors.horizontalCenter: parent.horizontalCenter
332 height: childrenRect.height
338 height: 7.5 / 8 * width
339 anchors.horizontalCenter: parent.horizontalCenter
341 borderSource: 'undefined'
345 sourceSize.width: appIcon.width
348 sourceFillMode: UbuntuShape.PreserveAspectCrop
351 styleName: "FocusShape"
354 visible: drawerDelegate.focused
355 radius: units.gu(2.55)
364 anchors.horizontalCenter: parent.horizontalCenter
365 horizontalAlignment: Text.AlignHCenter
367 wrapMode: Text.WordWrap
369 elide: Text.ElideRight
376 aux = label.width / 2 - item.width / 2;
377 var containerXMap = mapToItem(contentContainer, aux, 0).x
378 if (containerXMap < 0) {
379 aux = aux - containerXMap;
382 if (containerXMap + item.width > contentContainer.width) {
383 aux = aux - (containerXMap + item.width - contentContainer.width);
389 active: label.truncated && (drawerDelegate.hovered || drawerDelegate.focused)
390 sourceComponent: Rectangle {
391 color: UbuntuColors.jet
392 width: fullLabel.contentWidth + units.gu(1)
393 height: fullLabel.height + units.gu(1)
397 width: Math.min(root.delegateWidth * 2, implicitWidth)
399 horizontalAlignment: Text.AlignHCenter
401 elide: Text.ElideRight
402 anchors.centerIn: parent