@@ -22,6 +22,7 @@ import "../Components/IconUtil.js" as IconUtil
2222
2323ScopeView {
2424 id: scopeView
25+ property alias previewShown: previewLoader.onScreen
2526
2627 onIsCurrentChanged: {
2728 pageHeader.resetSearch();
@@ -47,6 +48,8 @@ ScopeView {
4748 id: categoryView
4849 anchors.fill: parent
4950 model: scopeView.categories
51+ forceNoClip: previewLoader.onScreen
52+
5053 onAtYEndChanged: if (atYEnd) endReached()
5154 onMovingChanged: if (moving && atYEnd) endReached()
5255
@@ -75,7 +78,32 @@ ScopeView {
7578 text: title
7679 imageWidth: units.gu(11)
7780 imageHeight: units.gu(16)
81+
7882 source: IconUtil.from_gicon(icon)
83+
84+ MouseArea {
85+ anchors {
86+ fill: parent
87+ }
88+ onClicked: {
89+ mouse.accepted = true
90+ effect.positionPx = mapToItem(categoryView, 0, 0).y
91+ scopeView.scope.activate(uri, icon,
92+ category, 0,
93+ mimetype, title,
94+ comment, dndUri,
95+ metadata)
96+ }
97+ onPressAndHold: {
98+ mouse.accepted = true
99+ effect.positionPx = mapToItem(categoryView, 0, 0).y
100+ scopeView.scope.preview(uri, icon,
101+ category, 0,
102+ mimetype, title,
103+ comment, dndUri,
104+ metadata)
105+ }
106+ }
79107 }
80108 }
81109 }
@@ -93,4 +121,95 @@ ScopeView {
93121 searchEntryEnabled: true
94122 }
95123 }
124+
125+ OpenEffect {
126+ id: effect
127+ anchors {
128+ fill: parent
129+ bottomMargin: -bottomOverflow
130+ }
131+ sourceItem: categoryView
132+
133+ enabled: gap > 0.0
134+
135+ topGapPx: (1 - gap) * positionPx
136+ topOpacity: (1 - gap * 1.2)
137+ bottomGapPx: positionPx + gap * (targetBottomGapPx - positionPx)
138+ bottomOverflow: units.gu(20)
139+ bottomOpacity: 1 - (gap * 0.8)
140+
141+ property int targetBottomGapPx: height - units.gu(8) - bottomOverflow
142+ property real gap: previewLoader.open ? 1.0 : 0.0
143+
144+ Behavior on gap {
145+ NumberAnimation {
146+ duration: 200
147+ easing.type: Easing.InOutQuad
148+ onRunningChanged: {
149+ if (!previewLoader.open && !running) {
150+ previewLoader.onScreen = false
151+ }
152+ }
153+ }
154+ }
155+ }
156+
157+ Connections {
158+ target: scopeView.scope
159+ onPreviewReady: {
160+ previewLoader.previewData = preview
161+ previewLoader.open = true
162+ }
163+ }
164+
165+ PreviewDelegateMapper {
166+ id: previewDelegateMapper
167+ }
168+
169+ Connections {
170+ ignoreUnknownSignals: true
171+ target: previewLoader.valid ? previewLoader.item : null
172+ onClose: {
173+ previewLoader.open = false
174+ }
175+ }
176+
177+ Loader {
178+ id: previewLoader
179+ property var previewData
180+ height: effect.bottomGapPx - effect.topGapPx
181+ anchors {
182+ top: parent.top
183+ topMargin: effect.topGapPx
184+ left: parent.left
185+ right: parent.right
186+ }
187+ source: onScreen ? previewDelegateMapper.map(previewLoader.previewData.rendererName) : ""
188+
189+ property bool open: false
190+ property bool onScreen: false
191+ property bool valid: item !== null
192+
193+ onOpenChanged: {
194+ if (open) {
195+ onScreen = true
196+ }
197+ }
198+
199+ onLoaded: {
200+ item.previewData = previewLoader.previewData
201+ }
202+ }
203+
204+ // TODO: Move as InverseMouseArea to DashPreview
205+ MouseArea {
206+ enabled: previewLoader.onScreen
207+ anchors {
208+ fill: parent
209+ topMargin: effect.bottomGapPx
210+ }
211+ onClicked: {
212+ previewLoader.open = false;
213+ }
214+ }
96215}