Merge lp:~tpeeters/ubuntu-ui-toolkit/cppHeader into lp:ubuntu-ui-toolkit/staging

Proposed by Tim Peeters
Status: Merged
Approved by: Tim Peeters
Approved revision: 1681
Merged at revision: 1648
Proposed branch: lp:~tpeeters/ubuntu-ui-toolkit/cppHeader
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 908 lines (+775/-7)
10 files modified
components.api (+4/-0)
src/Ubuntu/Components/ListItems/1.2/ListItemHeader.qml (+1/-0)
src/Ubuntu/Components/ListItems/1.3/ListItemHeader.qml (+1/-0)
src/Ubuntu/Components/ListItems/ListItems.pro (+2/-2)
src/Ubuntu/Components/ListItems/qmldir (+3/-3)
src/Ubuntu/Components/plugin/plugin.cpp (+2/-0)
src/Ubuntu/Components/plugin/plugin.pri (+4/-2)
src/Ubuntu/Components/plugin/ucheader.cpp (+328/-0)
src/Ubuntu/Components/plugin/ucheader.h (+76/-0)
tests/unit_x11/tst_components/tst_header.qml (+354/-0)
To merge this branch: bzr merge lp:~tpeeters/ubuntu-ui-toolkit/cppHeader
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Zsombor Egri Approve
Review via email: mp+271347@code.launchpad.net

Commit message

New Header C++ component.

Description of the change

New Header C++ component.

This will be used as the application header in following MRs.

Note that I had to disable the docs of the ListItems.Header by renaming ListItems/1.x/Header.qml to ListItemsHeader.qml and marking the component as deprecated. If I do not do this, The list item header 'hijacks" the properties of Ubuntu.Components.Header defined with \qmlproperty Header::flickable (for example).

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Zsombor Egri (zsombi) wrote :

Nice tests! I see you included some bug guards as well.

I have commented inline, reply on each if you want to keep the inline commenting active. I'll see them inline.

review: Needs Fixing
Revision history for this message
Tim Peeters (tpeeters) wrote :

All the proposed changes done (see inline comments), except the flickable.topMargin PropertyChange and the CPO. I'll push an update later with those two.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Zsombor Egri (zsombi) wrote :

Do you expect to be able to override the Header properties? if not (especially exposed and flickable, but I think all) then please make them FINAL!!!!

Also, see 2 comments inline. ListItem.header tests woudl be welcome!!!

review: Needs Fixing
Revision history for this message
Tim Peeters (tpeeters) wrote :

> Do you expect to be able to override the Header properties? if not (especially
> exposed and flickable, but I think all) then please make them FINAL!!!!
>
> Also, see 2 comments inline. ListItem.header tests woudl be welcome!!!

I made the properties FINAL in r1676.

About the ListItem.header I am not sure what to test. You can have code like this:
Item {
  Header {
    flickable: listView
  }
  ListView {
    id: listView
    anchors.fill: parent
    model: 50
  }
}

here the Header works exactly like with a Flickable, so no additional tests are needed.

Alternatively you can have code like this:

ListView {
    model: 50
    headerPositioning: ListView.PullBackHeader
    header: Component {
        Header { }
    }
}

here, (since you don't set the flickable of the header), the Header does not manipulate its y-value so it is positioned like any Item. What would I have to test there?

The two cases give similar results, but there is a difference when scrolling, and then releasing while the header is partially exposed: in the first case it will either show or hide completely (depending on how much it was exposed), it the second case it will just stay where it is.

Revision history for this message
Zsombor Egri (zsombi) wrote :

Looks good now! Looking forward to see the code removals when we put these in Page :)

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1681. By Tim Peeters

fix init

Revision history for this message
Zsombor Egri (zsombi) wrote :

Still good :)

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote :

CI approved it and jenkins?? set it back to "needs review"? Weird. I re-happroved it.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'components.api'
--- components.api 2015-09-14 11:32:40 +0000
+++ components.api 2015-09-23 11:35:46 +0000
@@ -389,6 +389,10 @@
389 property bool useDeprecatedToolbar389 property bool useDeprecatedToolbar
390Ubuntu.Components.ListItems.Header 1.3: Item390Ubuntu.Components.ListItems.Header 1.3: Item
391 property string text391 property string text
392Ubuntu.Components.Header 1.3: StyledItem
393 property bool exposed
394 property Flickable flickable
395 readonly property bool moving
392Ubuntu.Components.Icon 1.0 0.1: Item396Ubuntu.Components.Icon 1.0 0.1: Item
393 property color color397 property color color
394 property color keyColor398 property color keyColor
395399
=== renamed file 'src/Ubuntu/Components/ListItems/1.2/Header.qml' => 'src/Ubuntu/Components/ListItems/1.2/ListItemHeader.qml'
--- src/Ubuntu/Components/ListItems/1.2/Header.qml 2015-04-30 08:32:44 +0000
+++ src/Ubuntu/Components/ListItems/1.2/ListItemHeader.qml 2015-09-23 11:35:46 +0000
@@ -18,6 +18,7 @@
1818
19/*!19/*!
20 \qmltype Header20 \qmltype Header
21 \deprecated
21 \inqmlmodule Ubuntu.Components.ListItems 1.022 \inqmlmodule Ubuntu.Components.ListItems 1.0
22 \ingroup ubuntu-listitems23 \ingroup ubuntu-listitems
23 \brief Header for grouping list items together24 \brief Header for grouping list items together
2425
=== renamed file 'src/Ubuntu/Components/ListItems/1.3/Header.qml' => 'src/Ubuntu/Components/ListItems/1.3/ListItemHeader.qml'
--- src/Ubuntu/Components/ListItems/1.3/Header.qml 2015-04-29 07:21:29 +0000
+++ src/Ubuntu/Components/ListItems/1.3/ListItemHeader.qml 2015-09-23 11:35:46 +0000
@@ -18,6 +18,7 @@
1818
19/*!19/*!
20 \qmltype Header20 \qmltype Header
21 \deprecated
21 \inqmlmodule Ubuntu.Components.ListItems 1.022 \inqmlmodule Ubuntu.Components.ListItems 1.0
22 \ingroup ubuntu-listitems23 \ingroup ubuntu-listitems
23 \brief Header for grouping list items together24 \brief Header for grouping list items together
2425
=== modified file 'src/Ubuntu/Components/ListItems/ListItems.pro'
--- src/Ubuntu/Components/ListItems/ListItems.pro 2015-05-19 09:23:01 +0000
+++ src/Ubuntu/Components/ListItems/ListItems.pro 2015-09-23 11:35:46 +0000
@@ -6,7 +6,7 @@
6 1.2/Empty.qml \6 1.2/Empty.qml \
7 1.2/Expandable.qml \7 1.2/Expandable.qml \
8 1.2/ExpandablesColumn.qml \8 1.2/ExpandablesColumn.qml \
9 1.2/Header.qml \9 1.2/ListItemHeader.qml \
10 1.2/IconVisual.qml \10 1.2/IconVisual.qml \
11 1.2/ImageWithFallback.qml \11 1.2/ImageWithFallback.qml \
12 1.2/ItemSelector.qml \12 1.2/ItemSelector.qml \
@@ -25,7 +25,7 @@
25 1.3/Empty.qml \25 1.3/Empty.qml \
26 1.3/Expandable.qml \26 1.3/Expandable.qml \
27 1.3/ExpandablesColumn.qml \27 1.3/ExpandablesColumn.qml \
28 1.3/Header.qml \28 1.3/ListItemHeader.qml \
29 1.3/IconVisual.qml \29 1.3/IconVisual.qml \
30 1.3/ImageWithFallback.qml \30 1.3/ImageWithFallback.qml \
31 1.3/ItemSelector.qml \31 1.3/ItemSelector.qml \
3232
=== modified file 'src/Ubuntu/Components/ListItems/qmldir'
--- src/Ubuntu/Components/ListItems/qmldir 2015-05-02 13:27:39 +0000
+++ src/Ubuntu/Components/ListItems/qmldir 2015-09-23 11:35:46 +0000
@@ -3,7 +3,7 @@
3Base 0.1 1.2/Base.qml3Base 0.1 1.2/Base.qml
4Caption 0.1 1.2/Caption.qml4Caption 0.1 1.2/Caption.qml
5Divider 0.1 1.2/Divider.qml5Divider 0.1 1.2/Divider.qml
6Header 0.1 1.2/Header.qml6Header 0.1 1.2/ListItemHeader.qml
7internal IconVisual IconVisual.qml7internal IconVisual IconVisual.qml
8internal LabelVisual LabelVisual.qml8internal LabelVisual LabelVisual.qml
9MultiValue 0.1 1.2/MultiValue.qml9MultiValue 0.1 1.2/MultiValue.qml
@@ -23,7 +23,7 @@
23Base 1.0 1.2/Base.qml23Base 1.0 1.2/Base.qml
24Caption 1.0 1.2/Caption.qml24Caption 1.0 1.2/Caption.qml
25Divider 1.0 1.2/Divider.qml25Divider 1.0 1.2/Divider.qml
26Header 1.0 1.2/Header.qml26Header 1.0 1.2/ListItemHeader.qml
27MultiValue 1.0 1.2/MultiValue.qml27MultiValue 1.0 1.2/MultiValue.qml
28ItemSelector 1.0 1.2/ItemSelector.qml28ItemSelector 1.0 1.2/ItemSelector.qml
29ValueSelector 1.0 1.2/ValueSelector.qml29ValueSelector 1.0 1.2/ValueSelector.qml
@@ -41,7 +41,7 @@
41Base 1.3 1.3/Base.qml41Base 1.3 1.3/Base.qml
42Caption 1.3 1.3/Caption.qml42Caption 1.3 1.3/Caption.qml
43Divider 1.3 1.3/Divider.qml43Divider 1.3 1.3/Divider.qml
44Header 1.3 1.3/Header.qml44Header 1.3 1.3/ListItemHeader.qml
45MultiValue 1.3 1.3/MultiValue.qml45MultiValue 1.3 1.3/MultiValue.qml
46ItemSelector 1.3 1.3/ItemSelector.qml46ItemSelector 1.3 1.3/ItemSelector.qml
47ValueSelector 1.3 1.3/ValueSelector.qml47ValueSelector 1.3 1.3/ValueSelector.qml
4848
=== modified file 'src/Ubuntu/Components/plugin/plugin.cpp'
--- src/Ubuntu/Components/plugin/plugin.cpp 2015-09-21 11:40:22 +0000
+++ src/Ubuntu/Components/plugin/plugin.cpp 2015-09-23 11:35:46 +0000
@@ -66,6 +66,7 @@
66#include "ucactionitem.h"66#include "ucactionitem.h"
67#include "uchaptics.h"67#include "uchaptics.h"
68#include "ucabstractbutton.h"68#include "ucabstractbutton.h"
69#include "ucheader.h"
6970
70#include <sys/types.h>71#include <sys/types.h>
71#include <unistd.h>72#include <unistd.h>
@@ -240,6 +241,7 @@
240 qmlRegisterType<UCProportionalShape>(uri, 1, 3, "ProportionalShape");241 qmlRegisterType<UCProportionalShape>(uri, 1, 3, "ProportionalShape");
241 qmlRegisterType<LiveTimer>(uri, 1, 3, "LiveTimer");242 qmlRegisterType<LiveTimer>(uri, 1, 3, "LiveTimer");
242 qmlRegisterType<UCAbstractButton>(uri, 1, 3, "AbstractButton");243 qmlRegisterType<UCAbstractButton>(uri, 1, 3, "AbstractButton");
244 qmlRegisterType<UCHeader>(uri, 1, 3, "Header");
243}245}
244246
245void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)247void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
246248
=== modified file 'src/Ubuntu/Components/plugin/plugin.pri'
--- src/Ubuntu/Components/plugin/plugin.pri 2015-09-21 11:40:22 +0000
+++ src/Ubuntu/Components/plugin/plugin.pri 2015-09-23 11:35:46 +0000
@@ -84,7 +84,8 @@
84 $$PWD/ucactionitem.h \84 $$PWD/ucactionitem.h \
85 $$PWD/uchaptics.h \85 $$PWD/uchaptics.h \
86 $$PWD/ucabstractbutton.h \86 $$PWD/ucabstractbutton.h \
87 $$PWD/ucthemingextension.h87 $$PWD/ucthemingextension.h \
88 $$PWD/ucheader.h
8889
89SOURCES += $$PWD/plugin.cpp \90SOURCES += $$PWD/plugin.cpp \
90 $$PWD/uctheme.cpp \91 $$PWD/uctheme.cpp \
@@ -141,7 +142,8 @@
141 $$PWD/ucactionitem.cpp \142 $$PWD/ucactionitem.cpp \
142 $$PWD/uchaptics.cpp \143 $$PWD/uchaptics.cpp \
143 $$PWD/ucabstractbutton.cpp \144 $$PWD/ucabstractbutton.cpp \
144 $$PWD/ucthemingextension.cpp145 $$PWD/ucthemingextension.cpp \
146 $$PWD/ucheader.cpp
145147
146# adapters148# adapters
147SOURCES += $$PWD/adapters/alarmsadapter_organizer.cpp149SOURCES += $$PWD/adapters/alarmsadapter_organizer.cpp
148150
=== added file 'src/Ubuntu/Components/plugin/ucheader.cpp'
--- src/Ubuntu/Components/plugin/ucheader.cpp 1970-01-01 00:00:00 +0000
+++ src/Ubuntu/Components/plugin/ucheader.cpp 2015-09-23 11:35:46 +0000
@@ -0,0 +1,328 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "ucheader.h"
18#include <QDebug>
19#include <QtQuick/private/qquickflickable_p.h>
20#include <QtQuick/private/qquickanchors_p.h>
21#include <QtQuick/private/qquickanimation_p.h>
22#include "ucubuntuanimation.h"
23#include "ucunits.h"
24#include "propertychange_p.h"
25
26
27/*!
28 \qmltype Header
29 \instantiates UCHeader
30 \inherits StyledItem
31 \inqmlmodule Ubuntu.Components 1.3
32 \ingroup ubuntu
33 \since Ubuntu.Components 1.3
34 \brief Header bar that can contain the title and controls for the current view.
35
36 The Header can be exposed and hidden by setting the
37 \l exposed property, and when a \l flickable is set, the header will
38 scroll together with the flickable and expose or hide when the
39 Flickable movement ends.
40
41 \qml
42 import QtQuick 2.4
43 import Ubuntu.Components 1.3
44
45 Item {
46 width: units.gu(50)
47 height: units.gu(70)
48
49 Header {
50 id: header
51 width: parent.width
52 height: units.gu(6)
53 z: 1 // ensure the header goes on top of the flickable contents
54 flickable: scrollableContent
55
56 Rectangle {
57 // to visualize the header
58 anchors.fill: parent
59 color: UbuntuColors.blue
60 }
61 }
62
63 Flickable {
64 id: scrollableContent
65 anchors.fill: parent
66 contentHeight: height * 2
67 Label {
68 text: "Flick me"
69 }
70 }
71 }
72 \endqml
73
74 The default z-value is 0, so declare the Header after any Items that it should
75 overlay, or set its z-value to be larger than that of the other Items.
76 The initial y-value is 0, but scrolling the flickable or setting \l exposed to
77 false will change the y-value in the range from -height to 0.
78*/
79
80UCUbuntuAnimation *UCHeader::s_ubuntuAnimation = new UCUbuntuAnimation();
81
82UCHeader::UCHeader(QQuickItem *parent)
83 : UCStyledItemBase(parent)
84 , m_flickable(Q_NULLPTR)
85 , m_showHideAnimation(new QQuickNumberAnimation)
86 , m_flickableTopMarginBackup(Q_NULLPTR)
87 , m_previous_contentY(0)
88 , m_exposed(true)
89 , m_moving(false)
90{
91 m_showHideAnimation->setParent(this);
92 m_showHideAnimation->setTargetObject(this);
93 m_showHideAnimation->setProperty("y");
94 m_showHideAnimation->setEasing(s_ubuntuAnimation->StandardEasing());
95 m_showHideAnimation->setDuration(s_ubuntuAnimation->BriskDuration());
96
97 connect(m_showHideAnimation, SIGNAL(runningChanged(bool)),
98 this, SLOT(_q_showHideAnimationRunningChanged()));
99 connect(this, SIGNAL(heightChanged()), this, SLOT(_q_heightChanged()));
100}
101
102UCHeader::~UCHeader() {
103 if (m_flickable != Q_NULLPTR) {
104 Q_ASSERT(m_flickableTopMarginBackup != Q_NULLPTR);
105 delete m_flickableTopMarginBackup;
106 }
107}
108
109void UCHeader::_q_heightChanged() {
110 updateFlickableMargins();
111 if (m_exposed || (!m_flickable.isNull() && m_flickable->contentY() <= 0.0)) {
112 // Header was exposed before, or the flickable is scrolled up close to
113 // the top so that the header should be visible.
114 show();
115 } else {
116 hide();
117 }
118}
119
120/*!
121 * \qmlproperty Flickable Header::flickable
122 *
123 * When flickable is set, scrolling vertically in the flickable, or setting the
124 * Flickable's y-value will move the header y-position by the same amount as the
125 * flickable content movement. When scrolling the flickable, upon release, the header
126 * will animate to fully exposed or fully hidden state, depending on whether it was
127 * more or less than half exposed when the user stopped moving the flickable.
128 *
129 * When flickable is null, the header can be exposed or
130 * hidden by setting the \l exposed property.
131 *
132 * The topMargin of the flickable will automatically be updated to always match
133 * the height of the header. When changing the flickable, the topMargin of the previous
134 * flickable is set to 0.
135 *
136 * It is permitted to use a ListView as the value of flickable, but this works
137 * well only if the ListView.header property is not set. Alternatively,
138 * a Header component may be used for ListView.header, but in that case the flickable
139 * of the Header must be null.
140 */
141QQuickFlickable* UCHeader::flickable() {
142 return m_flickable.data();
143}
144
145void UCHeader::setFlickable(QQuickFlickable *flickable) {
146 if (m_flickable == flickable) {
147 return;
148 }
149 if (!m_flickable.isNull()) {
150 Q_ASSERT(m_flickableTopMarginBackup != Q_NULLPTR);
151
152 // Finish the current header movement in case the current
153 // flickable is disconnected while scrolling.
154 _q_flickableMovementEnded();
155 m_flickable->disconnect(this);
156
157 delete m_flickableTopMarginBackup; // Restores previous value/binding for topMargin.
158 m_flickableTopMarginBackup = Q_NULLPTR;
159 }
160
161 m_flickable = flickable;
162 Q_EMIT flickableChanged();
163
164 Q_ASSERT(m_flickableTopMarginBackup == Q_NULLPTR);
165 if (!m_flickable.isNull()) {
166 m_flickableTopMarginBackup = new PropertyChange(m_flickable, "topMargin");
167 connect(m_flickable, SIGNAL(contentYChanged()),
168 this, SLOT(_q_scrolledContents()));
169 connect(m_flickable, SIGNAL(movementEnded()),
170 this, SLOT(_q_flickableMovementEnded()));
171 connect(m_flickable, SIGNAL(contentHeightChanged()),
172 this, SLOT(_q_contentHeightChanged()));
173 connect(m_flickable, SIGNAL(interactiveChanged()),
174 this, SLOT(_q_flickableInteractiveChanged()));
175 m_previous_contentY = m_flickable->contentY();
176 updateFlickableMargins();
177 show();
178 }
179}
180
181void UCHeader::updateFlickableMargins() {
182 if (m_flickable.isNull()) {
183 return;
184 }
185 qreal headerHeight = height();
186 qreal previousHeaderHeight = m_flickable->topMargin();
187 if (headerHeight != previousHeaderHeight) {
188 qreal previousContentY = m_flickable->contentY();
189 PropertyChange::setValue(m_flickableTopMarginBackup, headerHeight);
190 // Push down contents when header grows,
191 // pull up contents when header shrinks.
192 m_flickable->setContentY(previousContentY - headerHeight + previousHeaderHeight);
193 }
194}
195
196void UCHeader::show() {
197 if (!m_exposed) {
198 m_exposed = true;
199 Q_EMIT exposedChanged();
200 if (m_showHideAnimation->isRunning()) {
201 // The header was in the process of hiding.
202 m_showHideAnimation->stop();
203 }
204 }
205 if (isComponentComplete()) {
206 m_showHideAnimation->setFrom(y());
207 m_showHideAnimation->setTo(0.0);
208 m_showHideAnimation->start();
209 } else {
210 this->setY(0.0);
211 if (m_moving) {
212 m_moving = false;
213 Q_EMIT movingChanged();
214 }
215 }
216}
217
218void UCHeader::hide() {
219 if (m_exposed) {
220 m_exposed = false;
221 Q_EMIT exposedChanged();
222 if (m_showHideAnimation->isRunning()) {
223 // The header was in the process of showing.
224 m_showHideAnimation->stop();
225 }
226 }
227 if (isComponentComplete()) {
228 m_showHideAnimation->setFrom(y());
229 m_showHideAnimation->setTo(-1.0*height());
230 m_showHideAnimation->start();
231 } else {
232 this->setY(-1.0*height());
233 if (m_moving) {
234 m_moving = false;
235 Q_EMIT movingChanged();
236 }
237 }
238}
239
240void UCHeader::_q_showHideAnimationRunningChanged() {
241 if (!m_showHideAnimation->isRunning()) {
242 // Animation finished.
243 Q_ASSERT(m_moving);
244 m_moving = false;
245 Q_EMIT movingChanged();
246 } else if (!m_moving) {
247 // Animation started.
248 m_moving = true;
249 Q_EMIT movingChanged();
250 } // else: Transition from flickable movement to showHideAnimation running.
251}
252
253/*!
254 * \qmlproperty bool Header::exposed
255 * Exposes and hides the header by animating its y-value between -height and 0
256 * to move it in or out of its parent Item. The value of exposed can be set directly,
257 * or it will be automatically updated when the user exposes or hides the Header
258 * by scrolling the Header's \l flickable.
259 */
260void UCHeader::setExposed(bool exposed) {
261 if (exposed) {
262 show();
263 } else {
264 hide();
265 }
266}
267
268/*!
269 * \qmlproperty bool Header::moving
270 * \readonly
271 * Indicates whether the header is currently moving, either because contentY of
272 * the \l flickable changes (due to user interaction or by setting it directly),
273 * or because the header is animating in or out because the value of \l exposed
274 * was updated.
275 */
276bool UCHeader::moving() {
277 return m_moving;
278}
279
280// Called when moving due to user interaction with the flickable, or by
281// setting m_flickable.contentY programatically.
282void UCHeader::_q_scrolledContents() {
283 Q_ASSERT(!m_flickable.isNull());
284 // Avoid moving the header when rebounding or being dragged over the bounds.
285 if (!m_flickable->isAtYBeginning() && !m_flickable->isAtYEnd()) {
286 qreal dy = m_flickable->contentY() - m_previous_contentY;
287 // Restrict the header y between -height and 0.
288 qreal clampedY = qMin(qMax(-height(), y() - dy), 0.0);
289 setY(clampedY);
290 }
291 m_previous_contentY = m_flickable->contentY();
292 if (!m_moving) {
293 m_moving = true;
294 Q_EMIT movingChanged();
295 }
296 if (!m_flickable->isMoving()) {
297 // m_flickable.contentY was set directly, so no user flicking.
298 _q_flickableMovementEnded();
299 }
300}
301
302void UCHeader::_q_flickableMovementEnded() {
303 Q_ASSERT(!m_flickable.isNull());
304 if ((m_flickable->contentY() < 0)
305 || (y() > -height()/2.0)) {
306 show();
307 } else {
308 hide();
309 }
310}
311
312void UCHeader::_q_contentHeightChanged() {
313 Q_ASSERT(!m_flickable.isNull());
314 if (m_flickable->height() >= m_flickable->contentHeight()) {
315 // The user cannot scroll down to expose the header, so ensure
316 // that it is visible.
317 show();
318 }
319}
320
321void UCHeader::_q_flickableInteractiveChanged() {
322 Q_ASSERT(!m_flickable.isNull());
323 if (!m_flickable->isInteractive()) {
324 // The user cannot scroll down to expose the header, so ensure
325 // that it is visible.
326 show();
327 }
328}
0329
=== added file 'src/Ubuntu/Components/plugin/ucheader.h'
--- src/Ubuntu/Components/plugin/ucheader.h 1970-01-01 00:00:00 +0000
+++ src/Ubuntu/Components/plugin/ucheader.h 2015-09-23 11:35:46 +0000
@@ -0,0 +1,76 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#ifndef UCHEADER_H
18#define UCHEADER_H
19
20#include "ucstyleditembase.h"
21#include <QtCore/QPointer>
22
23class QQuickFlickable;
24class QQuickNumberAnimation;
25class UCUbuntuAnimation;
26class PropertyChange;
27
28class UCHeader : public UCStyledItemBase
29{
30 Q_OBJECT
31 Q_PROPERTY(QQuickFlickable* flickable READ flickable WRITE setFlickable NOTIFY flickableChanged FINAL)
32 Q_PROPERTY(bool exposed MEMBER m_exposed WRITE setExposed NOTIFY exposedChanged FINAL)
33 Q_PROPERTY(bool moving READ moving NOTIFY movingChanged FINAL)
34
35public:
36 explicit UCHeader(QQuickItem *parent = 0);
37 ~UCHeader();
38
39 QQuickFlickable* flickable();
40 void setFlickable(QQuickFlickable* flickable);
41 void setExposed(bool exposed);
42 bool moving();
43
44Q_SIGNALS:
45 void flickableChanged();
46 void exposedChanged();
47 void movingChanged();
48
49protected:
50 void show();
51 void hide();
52
53private Q_SLOTS:
54 void _q_scrolledContents();
55 void _q_showHideAnimationRunningChanged();
56 void _q_flickableMovementEnded();
57 void _q_contentHeightChanged();
58 void _q_flickableInteractiveChanged();
59 void _q_heightChanged();
60
61private:
62 QPointer<QQuickFlickable> m_flickable;
63 QQuickNumberAnimation* m_showHideAnimation;
64 PropertyChange* m_flickableTopMarginBackup;
65
66 qreal m_previous_contentY;
67 bool m_exposed:1;
68 bool m_moving:1;
69
70 // used to set the easing and duration of m_showHideAnimation
71 static UCUbuntuAnimation *s_ubuntuAnimation;
72
73 void updateFlickableMargins();
74};
75
76#endif // UCHEADER_H
077
=== added file 'tests/unit_x11/tst_components/tst_header.qml'
--- tests/unit_x11/tst_components/tst_header.qml 1970-01-01 00:00:00 +0000
+++ tests/unit_x11/tst_components/tst_header.qml 2015-09-23 11:35:46 +0000
@@ -0,0 +1,354 @@
1/*
2 * Copyright (C) 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Ubuntu.Components 1.3
19import Ubuntu.Test 1.0
20
21Item {
22 id: root
23 width: units.gu(50)
24 height: units.gu(70)
25
26 property real initialHeaderHeight: units.gu(6)
27
28 Header {
29 id: header
30 flickable: flickable
31 z:1
32 width: parent.width
33 height: root.initialHeaderHeight
34
35 Rectangle {
36 // to visualize the header
37 anchors.fill: parent
38 color: UbuntuColors.red
39 opacity: 0.5
40 border {
41 color: "black"
42 width: 2
43 }
44 }
45 }
46
47 Flickable {
48 id: flickable
49 anchors.fill: parent
50 contentHeight: height * 2
51
52 Grid {
53 id: switchGrid
54 columns: 2
55 spacing: units.gu(1)
56 anchors {
57 top: parent.top
58 left: parent.left
59 margins: units.gu(5)
60 }
61 Switch {
62 id: lockedSwitch
63 checked: null === header.flickable
64 function trigger() {
65 if (header.flickable) {
66 header.flickable = null;
67 } else {
68 header.flickable = flickable;
69 }
70 }
71 }
72 Label {
73 text: "header locked"
74 }
75 Switch {
76 id: hiddenSwitch
77 checked: header.exposed
78 function trigger() {
79 header.exposed = !header.exposed;
80 }
81 }
82 Label {
83 text: "header exposed"
84 }
85 Item {
86 width: 1
87 height: 1
88 }
89 }
90 Button {
91 id: contentYButton
92 anchors {
93 top: switchGrid.bottom
94 topMargin: units.gu(4)
95 horizontalCenter: parent.horizontalCenter
96 }
97 property real newY: flickable.contentY == 0 ? -header.height : 0
98 onClicked: flickable.contentY = newY;
99 text: "Set contentY to " + newY
100 }
101 Label {
102 anchors {
103 top : contentYButton.bottom
104 horizontalCenter: parent.horizontalCenter
105 topMargin: units.gu(8)
106 }
107 text: "Flick me"
108 }
109 }
110
111 Rectangle {
112 id: reparentTestItem
113 anchors {
114 right: parent.right
115 bottom: parent.bottom
116 }
117 height: units.gu(15)
118 width: parent.width / 2
119 color: "blue"
120
121 Label {
122 anchors {
123 horizontalCenter: parent.horizontalCenter
124 bottom: parent.bottom
125 }
126 text: "Click to reparent"
127 color: "white"
128 }
129
130 MouseArea {
131 anchors.fill: parent
132 onClicked: {
133 if (header.parent === root) {
134 header.parent = reparentTestItem;
135 } else {
136 header.parent = root;
137 }
138 }
139 }
140 }
141
142 Flickable {
143 id: otherFlickable
144 property real initialTopMargin: 123
145 topMargin: initialTopMargin
146 height: units.gu(10)
147 contentHeight: units.gu(20);
148 }
149
150 Header {
151 id: otherHeader
152 }
153
154 UbuntuTestCase {
155 name: "Header"
156 when: windowShown
157 id: testCase
158
159 function initTestCase() {
160 wait_for_exposed(true, "Header is not exposed initially.");
161 compare(otherHeader.flickable, null, "Flickable not null by default.");
162 compare(header.flickable, flickable, "Flickable not properly intialized.");
163 // note: moving may be true briefly due to header height changes, but
164 // it does not change in the initialization after wait_for_exposed() above.
165 compare(header.moving, false, "Header moving initially.");
166 }
167
168 function init() {
169 flickable.contentHeight = 2*flickable.height;
170 flickable.interactive = true;
171 flickable.contentY = -header.height;
172 header.exposed = true;
173 wait_for_exposed(true);
174 }
175
176 function scroll(dy) {
177 var p = centerOf(flickable);
178 // Use mouseWheel to scroll because mouseDrag is very unreliable
179 // and does not properly handle negative values for dy.
180 mouseWheel(flickable, p.x, p.y, 0,dy);
181 }
182
183 function scroll_down() {
184 scroll(-2.0*header.height);
185 }
186
187 function scroll_up() {
188 scroll(header.height);
189 }
190
191 function wait_for_exposed(exposed, errorMessage) {
192 tryCompare(header, "exposed", exposed, 5000, errorMessage);
193 // wait for the animation to finish:
194 tryCompare(header, "moving", false, 5000, "Header still moving?");
195 if (exposed) {
196 compare(header.y, 0, errorMessage +
197 " y-value/exposed mismatch for exposed header!");
198 } else {
199 compare(header.y, -header.height, errorMessage +
200 " y-value/exposed mismatch for hidden header!");
201 }
202 }
203
204 function test_reparent_width() {
205 // test initial header width:
206 compare(header.parent, root);
207 compare(header.width, root.width);
208 compare(header.y, 0);
209
210 // test width update when changing parent:
211 header.parent = reparentTestItem;
212 compare(header.parent, reparentTestItem);
213 compare(header.width, reparentTestItem.width);
214 compare(header.y, 0);
215
216 // test width update when changing width of parent:
217 var old_width = reparentTestItem.width;
218 reparentTestItem.width = units.gu(5);
219 compare(header.width, reparentTestItem.width);
220 compare(header.y, 0);
221
222 // revert to original parent:
223 header.parent = root;
224 compare(header.parent, root);
225 compare(header.width, root.width);
226 compare(header.y, 0);
227 reparentTestItem.width = old_width;
228 }
229
230 function test_height_change() {
231 // first scroll down for this test, the following test repeats
232 // the cases at the top of the flickable.
233 scroll_down();
234 header.height = units.gu(15);
235 wait_for_exposed(true, "Increasing header height hides header.");
236 scroll_down();
237 header.exposed = false;
238 wait_for_exposed(false, "Header with height set does not hide.");
239 header.height = units.gu(2);
240 wait_for_exposed(false, "Decreasing header height exposes it.");
241 header.exposed = true;
242 wait_for_exposed(true, "Header with decreased height does not expose.");
243
244 // revert to initial state
245 header.height = root.initialHeaderHeight;
246 flickable.contentY = -header.height;
247 wait_for_exposed(true, "Setting flickable.contentY hides the header.");
248 }
249
250 function test_height_change_at_top() {
251 // Near the top, changing the header height exposes the header
252 // to avoid the header becoming inaccessible because it cannot
253 // be pulled down.
254 header.exposed = false;
255 wait_for_exposed(false);
256 header.height = units.gu(15);
257 wait_for_exposed(true, "Increasing header height at top hides header.");
258
259 // making the header smaller does not need to expose it, because there is
260 // enough space to pull it down.
261
262 // revert to original state.
263 header.height = root.initialHeaderHeight;
264 flickable.contentY = -header.height;
265 header.exposed = true;
266 wait_for_exposed(true);
267 }
268
269 function test_set_exposed_to_hide_and_show() {
270 header.exposed = false;
271 wait_for_exposed(false, "Cannot hide header by setting visible to false.");
272 header.exposed = true;
273 wait_for_exposed(true, "Cannot show header by setting visible to true.");
274
275 // change the value of exposed twice quickly:
276 header.exposed = false;
277 header.exposed = true;
278 wait_for_exposed(true, "Quickly hiding and showing header does not result in exposed header.");
279
280 // and the other way around:
281 header.exposed = false;
282 wait_for_exposed(false);
283 header.exposed = true;
284 header.exposed = false;
285 wait_for_exposed(false, "Quickly showing and hiding header does not result in hidden header.");
286
287 header.exposed = true;
288 wait_for_exposed(true);
289 }
290
291 function test_scroll_updates_visible() {
292 scroll_down();
293 wait_for_exposed(false, "Scrolling down does not hide header.");
294 scroll_up();
295 wait_for_exposed(true, "Scrolling up does not show header.");
296 }
297
298 function test_flickable_margins() {
299 compare(flickable.topMargin, header.height, "Flickable top margin does not match header height.");
300 header.height = units.gu(15);
301 wait_for_exposed(true, "Increasing header height at top hides header.");
302 compare(flickable.topMargin, header.height, "Updating header height does not update flickable top margin.");
303
304 header.height = root.initialHeaderHeight; // revert
305 wait_for_exposed(true, "Reverting header height at top hides header.");
306 compare(flickable.topMargin, header.height, "Reverting header height does not revert flickable top margin.");
307
308 compare(otherFlickable.topMargin, otherFlickable.initialTopMargin, "Flickable top margin is not initialized properly.");
309 header.flickable = otherFlickable;
310 compare(otherFlickable.topMargin, header.height, "Setting flickable does not update flickable top margin.");
311 compare(flickable.topMargin, 0, "Changing the flickable does not reset the previous flickable top margin to 0.");
312
313 header.flickable = flickable;
314 compare(otherFlickable.topMargin, otherFlickable.initialTopMargin, "Reverting flickable does not restore the other flickable top margin.");
315 compare(flickable.topMargin, header.height, "Reverting flickable breaks flickable top margin.");
316 }
317
318 function test_flickable_contentHeight_bug1156573() {
319 var old_height = flickable.contentHeight;
320 header.exposed = false;
321 wait_for_exposed(false);
322 flickable.contentHeight = flickable.height / 2;
323 wait_for_exposed(true, "Small content height does not expose the header.");
324
325 // revert:
326 flickable.contentHeight = old_height;
327 compare(header.exposed, true, "Reverting flickable content height hides the header.");
328 }
329
330 function test_flickable_interactive() {
331 header.exposed = false;
332 wait_for_exposed(false);
333 flickable.interactive = false;
334 wait_for_exposed(true, "Making flickable not interactive does not expose the header.");
335
336 // revert:
337 flickable.interactive = true;
338 compare(header.exposed, true, "Reverting flickable exposed hides the header.");
339 }
340
341 function test_scroll_disconnected_flickable() {
342 var hy = header.y;
343 header.flickable = null;
344 scroll_down();
345 compare(header.y, hy, "Header scrolls when disconnected flickable scrolls down.");
346 wait_for_exposed(true, "Scrolling down disconnected flickable hides header.");
347 scroll_up();
348 compare(header.y, hy, "Header scrolls when disconnected flickable scrolls up.");
349 wait_for_exposed(true, "Scrolling up disconnected flickable hides header.");
350
351 header.flickable = flickable;
352 }
353 }
354}

Subscribers

People subscribed via source and target branches