Merge lp:~tpeeters/ubuntu-ui-toolkit/panel into lp:ubuntu-ui-toolkit
- panel
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Zsombor Egri |
Approved revision: | 507 |
Merged at revision: | 461 |
Proposed branch: | lp:~tpeeters/ubuntu-ui-toolkit/panel |
Merge into: | lp:ubuntu-ui-toolkit |
Diff against target: |
1218 lines (+753/-142) 10 files modified
CHANGES (+8/-1) demos/Popover.qml (+2/-2) modules/Ubuntu/Components/Panel.qml (+328/-82) modules/Ubuntu/Components/Toolbar.qml (+54/-34) modules/Ubuntu/Components/ToolbarActions.qml (+31/-11) modules/Ubuntu/Components/qmldir (+1/-1) tests/unit/tst_components/tst_panel.qml (+251/-0) tests/unit/tst_components/tst_toolbar.qml (+74/-0) themes/Ambiance/qmltheme/ToolbarButtonDelegate.qml (+0/-3) themes/Ambiance/qmltheme/ToolbarDelegate.qml (+4/-8) |
To merge this branch: | bzr merge lp:~tpeeters/ubuntu-ui-toolkit/panel |
Related bugs: | |
Related blueprints: |
Flexible panel component for custom toolbars
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Andrea Cimitan (community) | Needs Fixing | ||
Tim Peeters | Approve | ||
Florian Boucault (community) | Needs Fixing | ||
Zsombor Egri | Needs Fixing | ||
Review via email: mp+158399@code.launchpad.net |
Commit message
Add a new Panel component that can be swiped in and out from an edge of the window by the user. As opposed to the default toolbar (which inherits from the Panel), a Panel can be attached to any edge of the screen, and the developer is free to define its contents. For standard cases, the automatic toolbar from the MainView and Page can still be used.
API changes:
* CHANGED IN Toolbar: property bool active TO property bool opened
* CHANGED IN Toolbar: property bool lock TO property bool locked
* CHANGED IN ToolbarActions: property bool active TO property bool opened
* CHANGED IN ToolbarActions: property bool lock TO property bool locked
Description of the change
Add a new Panel component that can be swiped in and out from an edge of the window by the user. As opposed to the default toolbar (which inherits from the Panel), a Panel can be attached to any edge of the screen, and the developer is free to define its contents. For standard cases, the automatic toolbar from the MainView and Page can still be used.
API changes:
* CHANGED IN Toolbar: property bool active TO property bool opened
* CHANGED IN Toolbar: property bool lock TO property bool locked
* CHANGED IN ToolbarActions: property bool active TO property bool opened
* CHANGED IN ToolbarActions: property bool lock TO property bool locked
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:448
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : | # |
Ready for review!
After the latest changes, the user can swipe the toolbar out, even on top of toolbar buttons, while standard components such as Sliders and Buttons will fully work and prevent mouse events from passing through.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:455
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Zsombor Egri (zsombi) wrote : | # |
file: ....modules/
Zsombor Egri (zsombi) wrote : | # |
What happens if you add a Panel to a MainView?
Zsombor Egri (zsombi) wrote : | # |
174 + /*! \internal */
175 default property alias contents: bar.data
We agreed once that we document default properties too.
Zsombor Egri (zsombi) wrote : | # |
183 + \li Qt.AlignLeft to swipe in the panel from the left
184 + \li Qt.AlignRight to swipe in the panel from the right
Think about LTR/RTL, thus AlignLeading/
Zsombor Egri (zsombi) wrote : | # |
521 + // v will always be in the range 0..size, where v==0 means spread, v==size means hidden.
522 + property real v: panel.active ? 0 : size
Maybe a better name for "v"?
Zsombor Egri (zsombi) wrote : | # |
568 + // All theming items go into the background because only the children
569 // of the GenericToolbar are being shown/hidden while the toolbar
GenericToolbar-
Zsombor Egri (zsombi) wrote : | # |
So, as theming is moved inside the internal item, there won't be possibility to change the style class of the toolbar, right?
For now it's OK, however we need to come back to this when we will be able to position the delegate freely in a styled item.
Zsombor Egri (zsombi) wrote : | # |
604 - enabled: action && action.enabled
605 + property bool enabled: action && action.enabled
What was the reason to override Item's enabled property?
Zsombor Egri (zsombi) wrote : | # |
Unit tests to guard swipe interactions are missing.
Tim Peeters (tpeeters) wrote : | # |
> What happens if you add a Panel to a MainView?
I made a small change, and now:
- Panels that align left/right/top always work
- Panels that align bottom work if no tools were defined for the toolbar (thus, toolbar is locked in inactive position)
- If there is a toolbar with tools, bottom-aligned panels do not work because the mousearea for swiping the toolbar is on top of them.
Sounds good to me. If you want your own panel at the bottom, do not define tools for the toolbar.
Tim Peeters (tpeeters) wrote : | # |
> 183 + \li Qt.AlignLeft to swipe in the panel from the left
> 184 + \li Qt.AlignRight to swipe in the panel from the right
>
> Think about LTR/RTL, thus AlignLeading/
The developer still needs to set the anchors himself, and align only specifies which direction to swipe inside the edge of the area of the Panel to show/hide the panel. So, when going from LTR to RTL, the anchors would also need to be updated and it would only be confusing if the swiping direction would change when the anchors don't change automatically.
An example of why the anchors are not automatic, is this Panel (it is in the examples from documentation):
Panel {
anchors {
right: parent.right
bottom: parent.bottom
top: parent.top
}
width: parent.width / 2
}
This can be aligned top, bottom or right, which only defines the swiping direction, not the location of the panel when it is active.
Tim Peeters (tpeeters) wrote : | # |
> 604 - enabled: action && action.enabled
> 605 + property bool enabled: action && action.enabled
>
> What was the reason to override Item's enabled property?
I forgot its there. Reverted.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:462
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:463
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:464
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Zsombor Egri (zsombi) wrote : | # |
Found one more: (file:/
This one on Toolbars, One->Two->Three, then swipe to show toolbar, and press "Cancel"
Zsombor Egri (zsombi) wrote : | # |
(file:/
The binding loop it's still there.
Zsombor Egri (zsombi) wrote : | # |
Rev. 458 - I don't necessarily think that we override default property, we define one. "contents" property is not Item's default one.
A better way to say in documentation would be: Default property, holds the content which will be swiped in and out.
Zsombor Egri (zsombi) wrote : | # |
Would it be possible to split test_swipe() in smaller test cases?
Zsombor Egri (zsombi) wrote : | # |
> > 183 + \li Qt.AlignLeft to swipe in the panel from the left
> > 184 + \li Qt.AlignRight to swipe in the panel from the right
> >
> > Think about LTR/RTL, thus AlignLeading/
> suitable...
>
> The developer still needs to set the anchors himself, and align only specifies
> which direction to swipe inside the edge of the area of the Panel to show/hide
> the panel. So, when going from LTR to RTL, the anchors would also need to be
> updated and it would only be confusing if the swiping direction would change
> when the anchors don't change automatically.
>
> An example of why the anchors are not automatic, is this Panel (it is in the
> examples from documentation):
> Panel {
> anchors {
> right: parent.right
> bottom: parent.bottom
> top: parent.top
> }
> width: parent.width / 2
> }
>
> This can be aligned top, bottom or right, which only defines the swiping
> direction, not the location of the panel when it is active.
Well, if you anchor an item to left on LTR, it will be anchored to right on RTL if I'm not mistaken :) so in that sense my comments are right.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:466
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:468
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : | # |
I'm switching between pages in the Toolbars demo like a maniac, but I don't get the binding loop any more.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:472
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:473
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Florian Boucault (fboucault) wrote : | # |
In documentation of Panel:
"Any Items can be placed inside the Panel, but MouseAreas can block mouse events from reaching the panel and thus obstruct the swiping behavior for hiding the panel. As a result, the user cannot start swiping on the buttons in the examples above in order to hide the panel. To remedy this, clicked() signals are forwarded from the panel to its children."
That does not sound right. Why can't the MouseArea inside Panel be placed on top of all the content and refuse mouse events that it does not care about?
Florian Boucault (fboucault) wrote : | # |
Panel.active is not a good name. It is not very clear what it does by just looking at it. Is it a synonym for enabled? No.
Opened/Closed would seem closer to what it actually is.
Florian Boucault (fboucault) wrote : | # |
"Note that when using leading or trailing alignment in combination with LayoutMirroring
Can't that be done automatically?
Florian Boucault (fboucault) wrote : | # |
Panel.contents is an alias and its type must be documented explicitly.
Florian Boucault (fboucault) wrote : | # |
Should we document the default values of the following properties?
- hintSize
- triggerSize
- lock
Florian Boucault (fboucault) wrote : | # |
* The second example in documentation (the one with no theming) is less complex than the first one and should be put first.
* Overall these code examples could be simplified a bit:
- replace the root Rectangle object with an Item
- in the second example, put the Panel at the bottom with an height of units.gu(8), to be identical to the first example (developers understand how to place Items in QML, no need to demonstrate variations of it)
Florian Boucault (fboucault) wrote : | # |
Please check with interested parties if the Panel suits them:
- Webbrowser (oSoMoN)
- Media player (renato)
- Shell (Saviq & Cimi)
Tim Peeters (tpeeters) wrote : | # |
> In documentation of Panel:
>
> "Any Items can be placed inside the Panel, but MouseAreas can block mouse
> events from reaching the panel and thus obstruct the swiping behavior for
> hiding the panel. As a result, the user cannot start swiping on the buttons in
> the examples above in order to hide the panel. To remedy this, clicked()
> signals are forwarded from the panel to its children."
>
> That does not sound right. Why can't the MouseArea inside Panel be placed on
> top of all the content and refuse mouse events that it does not care about?
If the mouseArea accepts pressed events, then the panel cannot detect swipes anymore, and if the mosueArea does not accept pressed events, it cannot detect a click. So the only solution we could think of something in the panel to get a clicked signal, while the user can still swipe the panel is to have the panel itself to handle the mouse events and detect the click on the Item inside it.
The current toolbar can have toolbar buttons where you can swipe and click (that is also supported now), but no components in the toolbar can detect other events (if you place a "normal" button it won't have the pressed event so you cannot see the pressed animation, sliders etc do not work)
Andrea Cimitan (cimi) wrote : | # |
Two small things
- On the panel component, there's a magic number (if (draggingArea.
- We are missing tests for this slow swipes indeed (with a slow drag velocity between -44 and +44), one solution is to do multiple mouse moves without releasing the mouse press, adding few waits. The line not tested is:
panel.state = (bar.position < bar.size / 2) ? "spread" : "";
Tim Peeters (tpeeters) wrote : | # |
I verified on the device that the toolbars in gallery-app and phone-app work exactly as before.
Andrea Cimitan (cimi) : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:474
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : | # |
fyi, a little program I am using for testing:
http://
Tim Peeters (tpeeters) wrote : | # |
> Panel.active is not a good name. It is not very clear what it does by just
> looking at it. Is it a synonym for enabled? No.
> Opened/Closed would seem closer to what it actually is.
This was already used in Toolbar, and is also used in the tools property for pages, so a lot of applications need to be changed if we rename active to opened.
Tim Peeters (tpeeters) wrote : | # |
> > Panel.active is not a good name. It is not very clear what it does by just
> > looking at it. Is it a synonym for enabled? No.
> > Opened/Closed would seem closer to what it actually is.
>
> This was already used in Toolbar, and is also used in the tools property for
> pages, so a lot of applications need to be changed if we rename active to
> opened.
But I do agree with your comment, so I'm not sure what to do in this case.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:475
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:483
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:487
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : | # |
Tested with webbrowser-app. Seems to work well https:/
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:490
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:491
http://
Executed test runs:
UNSTABLE: http://
UNSTABLE: http://
UNSTABLE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:492
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:495
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Andrea Cimitan (cimi) wrote : | # |
It's good enough for the LensBar on the Dash (works), testing this afternoon/tomorrow morning to see how it works on the Launcher, which I believe will be more complicated due to Mouse events required by the Launcher.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:496
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:498
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:499
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:505
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 506. By Tim Peeters
-
print warning when using deprecated properties
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:506
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 507. By Tim Peeters
-
add FIXME
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:507
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'CHANGES' |
2 | --- CHANGES 2013-04-26 20:32:38 +0000 |
3 | +++ CHANGES 2013-04-30 17:34:37 +0000 |
4 | @@ -10,7 +10,14 @@ |
5 | API Changes |
6 | *********** |
7 | |
8 | -* None |
9 | +* CHANGED IN Toolbar: property bool active TO property bool opened |
10 | +* CHANGED IN Toolbar: property bool lock TO property bool locked |
11 | +* CHANGED IN ToolbarActions: property bool active TO property bool opened |
12 | +* CHANGED IN ToolbarActions: property bool lock TO property bool locked |
13 | +* DEPRECATED IN Toolbar: property bool active |
14 | +* DEPRECATED IN Toolbar: property bool lock |
15 | +* DEPRECATED IN ToolbarActions: property bool active |
16 | +* DEPRECATED IN ToolbarActions: property bool lock |
17 | |
18 | Compatibility Breaks |
19 | ******************** |
20 | |
21 | === modified file 'demos/Popover.qml' |
22 | --- demos/Popover.qml 2013-04-26 20:31:16 +0000 |
23 | +++ demos/Popover.qml 2013-04-30 17:34:37 +0000 |
24 | @@ -26,8 +26,8 @@ |
25 | iconSource: "call_icon.png" |
26 | onTriggered: PopupUtils.open(actionSelectionPopover, caller) |
27 | } |
28 | - lock: true |
29 | - active: true |
30 | + locked: true |
31 | + opened: true |
32 | } |
33 | |
34 | TemplateSection { |
35 | |
36 | === renamed file 'modules/Ubuntu/Components/GenericToolbar.qml' => 'modules/Ubuntu/Components/Panel.qml' |
37 | --- modules/Ubuntu/Components/GenericToolbar.qml 2013-04-26 20:31:16 +0000 |
38 | +++ modules/Ubuntu/Components/Panel.qml 2013-04-30 17:34:37 +0000 |
39 | @@ -18,49 +18,193 @@ |
40 | import Ubuntu.Components 0.1 as Toolkit |
41 | |
42 | /*! |
43 | - \internal |
44 | - \qmltype GenericToolbar |
45 | + \qmltype Panel |
46 | \inqmlmodule Ubuntu.Components 0.1 |
47 | \ingroup ubuntu |
48 | + \brief A panel that can be swiped in and out from an edge of the window by the user. |
49 | + For most applications, it is highly recommended to use the \l MainView instead which includes |
50 | + a toolbar at its bottom that can be swiped in or out. |
51 | + |
52 | + Unless your application has very specific needs for a Panel, use a \l MainView with the |
53 | + default toolbar. |
54 | + |
55 | + When using a Panel, do not theme it directly, but theme its contents, because |
56 | + the Panel itself should not have visible elements, since it is |
57 | + in the view (to detect mouse events) even when its contents should be invisible. |
58 | + |
59 | + Set the anchors and/or width/height of the Panel to specify the area that the Panel covers when |
60 | + opened. |
61 | + |
62 | + A black panel that can be swiped in from the lower-right of the window can be created like this: |
63 | + \qml |
64 | + import QtQuick 2.0 |
65 | + import Ubuntu.Components 0.1 |
66 | + |
67 | + Item { |
68 | + width: units.gu(80) |
69 | + height: units.gu(80) |
70 | + |
71 | + Panel { |
72 | + id: panel |
73 | + anchors { |
74 | + right: parent.right |
75 | + bottom: parent.bottom |
76 | + } |
77 | + width: parent.width / 2 |
78 | + height: units.gu(8) |
79 | + |
80 | + Rectangle { |
81 | + anchors.fill: parent |
82 | + color: "black" |
83 | + |
84 | + Button { |
85 | + anchors.centerIn: parent |
86 | + text: "Click" |
87 | + } |
88 | + } |
89 | + } |
90 | + } |
91 | + \endqml |
92 | + |
93 | + A panel that looks like the standard (bottom-aligned) toolbar, but with custom contents, can be created like this: |
94 | + \qml |
95 | + import QtQuick 2.0 |
96 | + import Ubuntu.Components 0.1 |
97 | + |
98 | + Item { |
99 | + width: units.gu(80) |
100 | + height: units.gu(80) |
101 | + |
102 | + Panel { |
103 | + id: panel |
104 | + anchors { |
105 | + left: parent.left |
106 | + right: parent.right |
107 | + bottom: parent.bottom |
108 | + } |
109 | + height: units.gu(8) |
110 | + |
111 | + Item { |
112 | + anchors.fill: parent |
113 | + ItemStyle.class: "toolbar" |
114 | + |
115 | + // two properties used by the toolbar delegate: |
116 | + property bool opened: panel.opened |
117 | + property bool animating: panel.animating |
118 | + |
119 | + Button { |
120 | + anchors.centerIn: parent |
121 | + text: "Click" |
122 | + } |
123 | + } |
124 | + } |
125 | + } |
126 | + \endqml |
127 | + |
128 | + Any Items can be placed inside the Panel, but MouseAreas can block mouse events from reaching |
129 | + the panel and thus obstruct the swiping behavior for hiding the panel. As a result, the user cannot |
130 | + start swiping on the buttons in the examples above in order to hide the panel. To remedy this, clicked() |
131 | + signals are forwarded from the panel to its children. The children's clicked() signal does not have |
132 | + a mouse parameter. Example: |
133 | + \qml |
134 | + import QtQuick 2.0 |
135 | + import Ubuntu.Components 0.1 |
136 | + |
137 | + Rectangle { |
138 | + color: "grey" |
139 | + width: units.gu(40) |
140 | + height: units.gu(40) |
141 | + |
142 | + Panel { |
143 | + id: panel |
144 | + anchors { |
145 | + left: parent.left |
146 | + right: parent.right |
147 | + bottom: parent.bottom |
148 | + } |
149 | + height: units.gu(8) |
150 | + |
151 | + Rectangle { |
152 | + color: "white" |
153 | + anchors.fill: parent |
154 | + Rectangle { |
155 | + width: units.gu(8) |
156 | + height: units.gu(4) |
157 | + anchors.centerIn: parent |
158 | + color: "red" |
159 | + signal clicked() |
160 | + onClicked: print("The red rectangle was clicked"); |
161 | + } |
162 | + } |
163 | + } |
164 | + } |
165 | + \endqml |
166 | + Like this, the red rectangle accepts clicked() events, but the user can still swipe down on top |
167 | + of the rectangle in order to hide the panel. |
168 | */ |
169 | Item { |
170 | - id: bottomBar |
171 | - anchors { |
172 | - left: parent.left |
173 | - right: parent.right |
174 | - bottom: parent.bottom |
175 | - } |
176 | + id: panel |
177 | + |
178 | + /*! |
179 | + Default property, holds the content which will be swiped in and out. |
180 | + \qmlproperty list<Object> contents |
181 | + */ |
182 | default property alias contents: bar.data |
183 | |
184 | /*! |
185 | - When active, the bar is visible, otherwise it is hidden. |
186 | - Use bottom edge swipe up/down to activate/deactivate the bar. |
187 | - The active property is not updated until the swipe gesture is completed. |
188 | + The property defines the alignment of the panel. |
189 | + The implementation supports the following values: |
190 | + \list |
191 | + \li Qt.AlignBottom to swipe in the panel from the bottom (default) |
192 | + \li Qt.AlignTop to swipe in the panel from the top |
193 | + \li Qt.AlignLeft to swipe in the panel from the left |
194 | + \li Qt.AlignRight to swipe in the panel from the right |
195 | + \li Qt.AlignLeading left when layout mirrorring is disabled, right otherwise |
196 | + \li Qt.AlignTrailing right when layout mirroring is disabled, left otherwise |
197 | + \endlist |
198 | + The default value is \b Qt.AlignBottom, and it is not recommended to change the |
199 | + default value because the left, right and top edge are already used for system |
200 | + functions, while the bottom edge is reserved for app-specific functionality |
201 | + such as a default toolbar. The use of Qt.AlignLeading and Qt.AlignTrailing is |
202 | + preferred over Qt.AlignLeft and Qt.AlignRight in order to more easily support |
203 | + right-to-left user interfaces that use LayoutMirroring. |
204 | + */ |
205 | + property int align: Qt.AlignBottom |
206 | + |
207 | + /*! |
208 | + When opened, the panel is visible, otherwise it is hidden. |
209 | + Use edge swipes to open/close the panel. |
210 | + The opened property is not updated until the swipe gesture is completed. |
211 | */ |
212 | - property bool active: false |
213 | - onActiveChanged: { |
214 | - if (active) state = "spread"; |
215 | + property bool opened: false |
216 | + /*! \internal */ |
217 | + onOpenedChanged: { |
218 | + if (opened) state = "spread"; |
219 | else state = ""; |
220 | } |
221 | |
222 | /*! |
223 | - Disable bottom edge swipe to activate/deactivate the toolbar. |
224 | + Disable edge swipe to open/close the panel. False by default. |
225 | */ |
226 | - property bool lock: false |
227 | - onLockChanged: { |
228 | + property bool locked: false |
229 | + /*! \internal */ |
230 | + onLockedChanged: { |
231 | if (state == "hint" || state == "moving") { |
232 | draggingArea.finishMoving(); |
233 | } |
234 | } |
235 | |
236 | /*! |
237 | - How much of the toolbar to show when starting interaction. |
238 | + How much of the panel to show when the user touches the panel's edge. |
239 | + This gives the user a hint that there is a panel hiding at that edge and |
240 | + invites him/her to swipe to show the panel completely. Default value: units.gu(2). |
241 | */ |
242 | - property real hintSize: units.gu(1) |
243 | + property real hintSize: units.gu(2) |
244 | |
245 | /*! |
246 | - The height of the mouse area used to detect edge swipes to |
247 | - activate the toolbar. |
248 | + The size (height for top or bottom-aligned panels, width for left or right-aligned |
249 | + panels) of the mouse area used to detect edge swipes to open the panel, when |
250 | + the panel is not opened. Default value: units.gu(2). |
251 | */ |
252 | property real triggerSize: units.gu(2) |
253 | |
254 | @@ -69,28 +213,28 @@ |
255 | name: "hint" |
256 | PropertyChanges { |
257 | target: bar |
258 | - y: bar.height - bottomBar.hintSize |
259 | + position: bar.size - panel.hintSize |
260 | } |
261 | }, |
262 | State { |
263 | name: "moving" |
264 | PropertyChanges { |
265 | target: bar |
266 | - y: MathUtils.clamp(draggingArea.mouseY - internal.movingDelta, 0, bar.height) |
267 | + position: MathUtils.clamp(draggingArea.mousePosition - internal.movingDelta, 0, bar.size) |
268 | } |
269 | }, |
270 | State { |
271 | name: "spread" |
272 | PropertyChanges { |
273 | target: bar |
274 | - y: 0 |
275 | + position: 0 |
276 | } |
277 | }, |
278 | State { |
279 | name: "" |
280 | PropertyChanges { |
281 | target: bar |
282 | - y: bar.height |
283 | + position: bar.size |
284 | explicit: true |
285 | } |
286 | } |
287 | @@ -99,15 +243,15 @@ |
288 | /*! |
289 | The toolbar is currently not in a stable hidden or visible state. |
290 | */ |
291 | - readonly property bool animating: draggingArea.pressed || (state == "" && bar.y != bar.height) || (state == "spread" && bar.y != 0) |
292 | + readonly property bool animating: draggingArea.pressed || (state == "" && bar.position != bar.size) || (state == "spread" && bar.position != 0) |
293 | |
294 | transitions: [ |
295 | Transition { |
296 | to: "" |
297 | PropertyAnimation { |
298 | target: bar |
299 | - properties: "y" |
300 | - duration: 50 |
301 | + properties: "position" |
302 | + duration: internal.transitionDuration |
303 | easing.type: Easing.OutQuad |
304 | } |
305 | }, |
306 | @@ -115,8 +259,8 @@ |
307 | to: "hint" |
308 | PropertyAnimation { |
309 | target: bar |
310 | - properties: "y" |
311 | - duration: 50 |
312 | + properties: "position" |
313 | + duration: internal.transitionDuration |
314 | easing.type: Easing.OutQuad |
315 | } |
316 | }, |
317 | @@ -124,8 +268,8 @@ |
318 | to: "spread" |
319 | PropertyAnimation { |
320 | target: bar |
321 | - properties: "y" |
322 | - duration: 50 |
323 | + properties: "position" |
324 | + duration: internal.transitionDuration |
325 | easing.type: Easing.OutQuad |
326 | } |
327 | } |
328 | @@ -133,108 +277,210 @@ |
329 | |
330 | QtObject { |
331 | id: internal |
332 | + |
333 | + /*! |
334 | + The duration in milliseconds of sliding in or out transitions when opening, closing, and showing the hint. |
335 | + Default value: 250 |
336 | + // FIXME: Update to use ubuntuFastBeat when animation speeds are added to the SDK. |
337 | + */ |
338 | + property real transitionDuration: 250 |
339 | + |
340 | + |
341 | property string previousState: "" |
342 | property int movingDelta |
343 | |
344 | // Used for recovering the state from before |
345 | // bottomBarVisibilityCommunicator forced the toolbar to hide. |
346 | - property bool savedLock: bottomBar.lock |
347 | - property bool savedActive: bottomBar.active |
348 | + property bool savedlocked: panel.locked |
349 | + property bool savedOpened: panel.opened |
350 | + |
351 | + // Convert from Qt.AlignLeading to Qt.AlignTrailing to Qt.AlignLeft and Qt.AlignRight |
352 | + property int align: { |
353 | + if (panel.align === Qt.AlignLeading) { |
354 | + if (panel.LayoutMirroring.enabled) { |
355 | + return Qt.AlignRight; |
356 | + } else { |
357 | + return Qt.AlignLeft; |
358 | + } |
359 | + } else if (panel.align === Qt.AlignTrailing) { |
360 | + if (panel.LayoutMirroring.enabled) { |
361 | + return Qt.AlignLeft; |
362 | + } else { |
363 | + return Qt.AlignRight; |
364 | + } |
365 | + } else { |
366 | + return panel.align; |
367 | + } |
368 | + } |
369 | + |
370 | + readonly property int orientation: (panel.align === Qt.AlignTop || panel.align === Qt.AlignBottom) |
371 | + ? Qt.Horizontal : Qt.Vertical |
372 | } |
373 | |
374 | Connections { |
375 | + // FIXME: bottomBarVisibilityCommunicator is not the most-suitable name anymore. |
376 | target: bottomBarVisibilityCommunicator |
377 | onForceHiddenChanged: { |
378 | if (bottomBarVisibilityCommunicator.forceHidden) { |
379 | - internal.savedLock = bottomBar.lock; |
380 | - internal.savedActive = bottomBar.active; |
381 | - bottomBar.active = false; |
382 | - bottomBar.lock = true; |
383 | + internal.savedLocked = panel.locked; |
384 | + internal.savedOpened = panel.opened; |
385 | + panel.opened = false; |
386 | + panel.locked = true; |
387 | } else { // don't force hidden |
388 | - bottomBar.lock = internal.savedLock; |
389 | - if (internal.savedLock) bottomBar.active = internal.savedActive; |
390 | - // if the toolbar was locked, do not slide it back in |
391 | - // until the user performs a bottom-edge-swipe. |
392 | + panel.locked = internal.savedlocked; |
393 | + if (internal.savedlocked) panel.opened = internal.savedOpened; |
394 | + // if the panel was locked, do not slide it back in |
395 | + // until the user performs an edge swipe. |
396 | } |
397 | } |
398 | } |
399 | |
400 | + /*! \internal */ |
401 | onStateChanged: { |
402 | if (state == "hint") { |
403 | - internal.movingDelta = bottomBar.hintSize + draggingArea.initialY - bar.height; |
404 | + internal.movingDelta = panel.hintSize + draggingArea.initialPosition - bar.size; |
405 | } else if (state == "moving" && internal.previousState == "spread") { |
406 | - internal.movingDelta = draggingArea.initialY; |
407 | + internal.movingDelta = draggingArea.initialPosition; |
408 | } else if (state == "spread") { |
409 | - bottomBar.active = true; |
410 | + panel.opened = true; |
411 | } else if (state == "") { |
412 | - bottomBar.active = false; |
413 | + panel.opened = false; |
414 | } |
415 | internal.previousState = state; |
416 | } |
417 | |
418 | - Item { |
419 | - id: bar |
420 | - height: parent.height |
421 | - anchors { |
422 | - left: parent.left |
423 | - right: parent.right |
424 | - } |
425 | - |
426 | - y: bottomBar.active ? 0 : height |
427 | - } |
428 | - |
429 | Toolkit.InverseMouseArea { |
430 | anchors.fill: draggingArea |
431 | onClicked: { |
432 | mouse.accepted = false; |
433 | // the mouse click may cause an update |
434 | - // of lock by the clicked Item behind |
435 | - if (!bottomBar.lock) bottomBar.active = false; |
436 | + // of locked by the clicked Item behind |
437 | + if (!panel.locked) panel.opened = false; |
438 | } |
439 | propagateComposedEvents: true |
440 | - visible: bottomBar.lock == false && bottomBar.state == "spread" |
441 | + visible: panel.locked == false && panel.state == "spread" |
442 | } |
443 | |
444 | DraggingArea { |
445 | - orientation: Qt.Vertical |
446 | id: draggingArea |
447 | - anchors { |
448 | - bottom: parent.bottom |
449 | - left: parent.left |
450 | - right: parent.right |
451 | - } |
452 | - height: bottomBar.active ? bar.height + units.gu(1) : toolbar.triggerSize |
453 | + orientation: internal.orientation === Qt.Horizontal ? Qt.Vertical : Qt.Horizontal |
454 | zeroVelocityCounts: true |
455 | - propagateComposedEvents: true |
456 | - visible: !bottomBar.lock |
457 | - |
458 | - property int initialY |
459 | + anchors { |
460 | + top: panel.align === Qt.AlignBottom ? undefined : parent.top |
461 | + bottom: panel.align === Qt.AlignTop ? undefined : parent.bottom |
462 | + left: panel.align === Qt.AlignRight || panel.align === Qt.AlignTrailing ? undefined : parent.left |
463 | + right: panel.align === Qt.AlignLeft || panel.align === Qt.AlignLeading ? undefined : parent.right |
464 | + } |
465 | + height: internal.orientation === Qt.Horizontal ? panel.opened ? bar.size + units.gu(1) : panel.triggerSize : undefined |
466 | + width: internal.orientation === Qt.Vertical ? panel.opened ? bar.size + units.gu(1) : panel.triggerSize : undefined |
467 | + visible: !panel.locked || panel.opened |
468 | + |
469 | + property int mousePosition: getMousePosition() |
470 | + function getMousePosition() { |
471 | + switch (internal.align) { |
472 | + case Qt.AlignLeft: |
473 | + return -mouseX; |
474 | + case Qt.AlignRight: |
475 | + return mouseX; |
476 | + case Qt.AlignBottom: |
477 | + return mouseY; |
478 | + case Qt.AlignTop: |
479 | + return -mouseY; |
480 | + } |
481 | + } |
482 | + |
483 | + // set in onPressed, reset when entering "moving" state |
484 | + property Item pressedItem: null |
485 | + |
486 | + // find the first child with a clicked property: |
487 | + function getClickableItem(mouse) { |
488 | + var item = bar; // contains the children |
489 | + while (item && !item.hasOwnProperty("clicked")) { |
490 | + var coords = mapToItem(item, mouse.x, mouse.y); |
491 | + // FIXME: When using a ListView the highlight may be |
492 | + // returned instead of the Item that you are looking for |
493 | + item = item.childAt(coords.x, coords.y); |
494 | + } |
495 | + return item; // will be null if no item has clicked() signal. |
496 | + } |
497 | + |
498 | + // forward clicked events to any child Item with a clicked() signal, not |
499 | + // just MouseAreas since MouseAreas would block swiping of the panel. |
500 | + // This must also happen when the panel is locked, so the DraggingArea is |
501 | + // never disabled, and other signal handlers will return when panel.locked is true. |
502 | + onClicked: { |
503 | + if (pressedItem && pressedItem === getClickableItem(mouse)) { |
504 | + // Click event positioned at the Item where the user first pressed |
505 | + pressedItem.clicked(); |
506 | + } |
507 | + } |
508 | + |
509 | + property int initialPosition |
510 | onPressed: { |
511 | - initialY = mouseY; |
512 | - if (bottomBar.state == "") bottomBar.state = "hint"; |
513 | - else bottomBar.state = "moving"; |
514 | + pressedItem = getClickableItem(mouse) |
515 | + if (panel.locked) return; |
516 | + initialPosition = getMousePosition(); |
517 | + if (panel.state == "") panel.state = "hint"; |
518 | } |
519 | |
520 | onPositionChanged: { |
521 | - if (bottomBar.state == "hint" && mouseY < initialY) { |
522 | - bottomBar.state = "moving"; |
523 | + if (panel.locked) return; |
524 | + if (panel.state == "hint" && mousePosition < initialPosition) { |
525 | + panel.state = "moving"; |
526 | + pressedItem = null; |
527 | + } else if (panel.state == "spread" && mousePosition > initialPosition) { |
528 | + panel.state = "moving"; |
529 | + pressedItem = null; |
530 | } |
531 | } |
532 | |
533 | - onReleased: finishMoving() |
534 | + onReleased: { |
535 | + if (panel.locked) return; |
536 | + finishMoving(); |
537 | + } |
538 | // Mouse cursor moving out of the window while pressed on desktop |
539 | - onCanceled: finishMoving() |
540 | + onCanceled: { |
541 | + if (panel.locked) return; |
542 | + finishMoving(); |
543 | + } |
544 | |
545 | - // FIXME: Make all parameters below themable. |
546 | + // FIXME: Make all parameters below themable and resolution-independent. |
547 | // The value of 44 was copied from the Launcher. |
548 | function finishMoving() { |
549 | if (draggingArea.dragVelocity < -44) { |
550 | - bottomBar.state = "spread"; |
551 | + if (internal.align === Qt.AlignBottom || internal.align === Qt.AlignRight) { |
552 | + panel.state = "spread"; |
553 | + } else { |
554 | + panel.state = ""; |
555 | + } |
556 | } else if (draggingArea.dragVelocity > 44) { |
557 | - bottomBar.state = ""; |
558 | + if (internal.align === Qt.AlignBottom || internal.align === Qt.AlignRight) { |
559 | + panel.state = ""; |
560 | + } else { |
561 | + panel.state = "spread"; |
562 | + } |
563 | } else { |
564 | - bottomBar.state = (bar.y < bar.height / 2) ? "spread" : ""; |
565 | + panel.state = (bar.position < bar.size / 2) ? "spread" : ""; |
566 | } |
567 | } |
568 | } |
569 | + |
570 | + Item { |
571 | + id: bar |
572 | + height: parent.height |
573 | + width: parent.width |
574 | + anchors { |
575 | + left: internal.orientation === Qt.Horizontal ? parent.left : undefined |
576 | + right: internal.orientation === Qt.Horizontal ? parent.right : undefined |
577 | + top: internal.orientation === Qt.Vertical ? parent.top : undefined |
578 | + bottom: internal.orientation === Qt.Vertical ? parent.bottom : undefined |
579 | + } |
580 | + |
581 | + property real size: internal.orientation === Qt.Horizontal ? height : width |
582 | + //position will always be in the range 0..size, where position==0 means spread, position==size means hidden. |
583 | + property real position: panel.opened ? 0 : size |
584 | + |
585 | + y: internal.align === Qt.AlignTop ? -position : internal.align === Qt.AlignBottom ? position : 0 |
586 | + x: internal.align === Qt.AlignLeft ? -position : internal.align === Qt.AlignRight ? position : 0 |
587 | + } |
588 | } |
589 | |
590 | === modified file 'modules/Ubuntu/Components/Toolbar.qml' |
591 | --- modules/Ubuntu/Components/Toolbar.qml 2013-04-26 20:31:16 +0000 |
592 | +++ modules/Ubuntu/Components/Toolbar.qml 2013-04-30 17:34:37 +0000 |
593 | @@ -29,13 +29,34 @@ |
594 | \brief Application toolbar. This class is not exposed because it will |
595 | be automatically added when a Page defines tools. |
596 | */ |
597 | -GenericToolbar { |
598 | +Panel { |
599 | id: toolbar |
600 | - Theming.ItemStyle.class: "toolbar" |
601 | - |
602 | + anchors { |
603 | + left: parent.left |
604 | + right: parent.right |
605 | + bottom: parent.bottom |
606 | + } |
607 | height: background.height |
608 | - hintSize: units.gu(2) |
609 | - triggerSize: units.gu(2) |
610 | + |
611 | + /*! |
612 | + \deprecated |
613 | + Use property bool opened instead. |
614 | + */ |
615 | + property bool active |
616 | + onActiveChanged: { |
617 | + print("Toolbar.active property is DEPRECATED. Use opened instead."); |
618 | + toolbar.opened = active; |
619 | + } |
620 | + |
621 | + /* |
622 | + \deprecated |
623 | + Use property locked instead. |
624 | + */ |
625 | + property bool lock |
626 | + onLockChanged: { |
627 | + print("Toolbar.lock property is DEPRECATED. Use locked instead."); |
628 | + toolbar.locked = lock; |
629 | + } |
630 | |
631 | /*! |
632 | \preliminary |
633 | @@ -43,42 +64,45 @@ |
634 | */ |
635 | property ToolbarActions tools: null |
636 | onToolsChanged: { |
637 | - if (tools && tools.active && tools.lock) { |
638 | + if (tools && tools.opened && tools.locked) { |
639 | // toolbar is locked in visible state. |
640 | internal.visibleTools = tools; |
641 | - active = true; |
642 | - } else if (!active && !animating) { |
643 | + opened = true; |
644 | + } else if (!opened && !animating) { |
645 | // toolbar is invisible |
646 | internal.visibleTools = tools; |
647 | } else { |
648 | - active = false; |
649 | + opened = false; |
650 | // internal.visibleTools will be updated |
651 | // when the hide animation is finished |
652 | } |
653 | } |
654 | |
655 | - // if tools is not specified, lock the toolbar in inactive position |
656 | - lock: tools ? tools.lock : true |
657 | + // if tools is not specified, lock the toolbar in closed position |
658 | + locked: tools ? tools.locked : true |
659 | |
660 | Connections { |
661 | target: tools |
662 | - onActiveChanged: toolbar.active = tools.active; |
663 | + onOpenedChanged: toolbar.opened = tools.opened; |
664 | + onLockedChanged: toolbar.locked = tools.locked; |
665 | } |
666 | - onActiveChanged: if (tools) tools.active = toolbar.active |
667 | + onOpenedChanged: if (tools) tools.opened = toolbar.opened |
668 | + onLockedChanged: if (tools) tools.locked = toolbar.locked |
669 | QtObject { |
670 | id: internal |
671 | property ToolbarActions visibleTools: tools |
672 | } |
673 | |
674 | onAnimatingChanged: { |
675 | - if (!animating && !active) { |
676 | + if (!animating && !opened) { |
677 | internal.visibleTools = toolbar.tools; |
678 | } |
679 | } |
680 | |
681 | Item { |
682 | - // All visual items go into the background because only the children |
683 | - // of the GenericToolbar are being shown/hidden while the toolbar |
684 | + // FIXME: |
685 | + // All theming items go into the background because only the children |
686 | + // of the Panel are being shown/hidden while the toolbar |
687 | // itself may stay in place. |
688 | id: background |
689 | anchors { |
690 | @@ -88,29 +112,24 @@ |
691 | } |
692 | height: units.gu(8) |
693 | |
694 | - Theming.ItemStyle.style: toolbar.Theming.ItemStyle.style |
695 | - Theming.ItemStyle.delegate: toolbar.Theming.ItemStyle.delegate |
696 | - |
697 | - MouseArea { |
698 | - // don't let mouse events go through the toolbar |
699 | - anchors.fill: parent |
700 | - // FIXME: Bug in qml? Without onClicked below, this MouseArea |
701 | - // seems disabled. |
702 | - onClicked: { } |
703 | - } |
704 | + Theming.ItemStyle.class: "toolbar" |
705 | + // The values of opened and animated properties are used in the delegate |
706 | + property bool opened: toolbar.opened |
707 | + property bool animating: toolbar.animating |
708 | } |
709 | |
710 | Component { |
711 | id: toolButtonComponent |
712 | - Button { |
713 | + Item { |
714 | id: toolButton |
715 | Theming.ItemStyle.class: "toolbar-button" |
716 | - text: action && action.text ? action.text : "" |
717 | - iconSource: action && action.iconSource ? action.iconSource : "" |
718 | + property string text: action && action.text ? action.text : "" |
719 | + property url iconSource: action && action.iconSource ? action.iconSource : "" |
720 | + signal clicked() |
721 | onClicked: action.triggered(toolButton) |
722 | enabled: action && action.enabled |
723 | visible: action && action.visible |
724 | - width: visible ? implicitWidth : 0 |
725 | + width: units.gu(5) |
726 | height: toolbar.height |
727 | } |
728 | } |
729 | @@ -126,11 +145,13 @@ |
730 | } |
731 | onStatusChanged: { |
732 | if (item && status == Loader.Ready && action && action.itemHint) { |
733 | - if (item.hasOwnProperty("clicked")) item.clicked.connect(action.triggered); |
734 | - if (item.hasOwnProperty("accepted")) item.accepted.connect(action.triggered); |
735 | - if (item.hasOwnProperty("triggered")) item.accepted.connect(action.triggered); |
736 | + if (item.hasOwnProperty("clicked")) item.clicked.connect(backButton.itemTriggered); |
737 | + if (item.hasOwnProperty("accepted")) item.accepted.connect(backButton.itemTriggered); |
738 | + if (item.hasOwnProperty("triggered")) item.accepted.connect(backButton.itemTtriggered); |
739 | } |
740 | } |
741 | + signal itemTriggered() |
742 | + onItemTriggered: action.triggered(item) |
743 | } |
744 | |
745 | Row { |
746 | @@ -141,7 +162,6 @@ |
747 | top: parent.top |
748 | rightMargin: units.gu(2) |
749 | } |
750 | - width: childrenRect.width |
751 | spacing: units.gu(1) |
752 | |
753 | Repeater { |
754 | |
755 | === modified file 'modules/Ubuntu/Components/ToolbarActions.qml' |
756 | --- modules/Ubuntu/Components/ToolbarActions.qml 2013-04-26 20:31:16 +0000 |
757 | +++ modules/Ubuntu/Components/ToolbarActions.qml 2013-04-30 17:34:37 +0000 |
758 | @@ -30,7 +30,7 @@ |
759 | When a \l Page is used inside a \l Tabs or \l PageStack, the toolbar will automatically show |
760 | the tools of the active \l Page. When the active \l Page inside the \l Tabs or \l PageStack |
761 | is updated by changing the selected \l Tab or by pushing/popping a \l Page on the \l PageStack, |
762 | - the toolbar will automatically hide, except if the new active \l Page has the \l lock property set. |
763 | + the toolbar will automatically hide, except if the new active \l Page has the \l locked property set. |
764 | |
765 | \qml |
766 | import QtQuick 2.0 |
767 | @@ -61,8 +61,8 @@ |
768 | text: "cancel" |
769 | } |
770 | } |
771 | - lock: true |
772 | - active: true |
773 | + locked: true |
774 | + opened: true |
775 | } |
776 | } |
777 | } |
778 | @@ -98,16 +98,36 @@ |
779 | property Item __pageStack: null |
780 | |
781 | /*! |
782 | - The toolbar is active |
783 | - */ |
784 | - property bool active: false |
785 | - |
786 | - /*! |
787 | - The toolbar cannot be made active or inactive by bottom-edge swipes. |
788 | + The toolbar is opened |
789 | + */ |
790 | + property bool opened: false |
791 | + |
792 | + /*! |
793 | + \deprecated |
794 | + Use property opened instead. |
795 | + */ |
796 | + property bool active |
797 | + onActiveChanged: { |
798 | + print("ToolbarActions.active property is DEPRECATED. Use opened instead."); |
799 | + toolbarActions.opened = active; |
800 | + } |
801 | + |
802 | + /*! |
803 | + \deprecated |
804 | + Use property locked instead. |
805 | + */ |
806 | + property bool lock: toolbarActions.locked |
807 | + onLockChanged: { |
808 | + print("ToolbarActions.lock property is DEPRECATED. Use locked instead."); |
809 | + toolbarActions.locked = lock; |
810 | + } |
811 | + |
812 | + /*! |
813 | + The toolbar cannot be opened/closed by bottom-edge swipes. |
814 | If the ToolbarActions contains no visible actions, it is automatically |
815 | - locked (in inactive state). |
816 | + locked (in closed state). |
817 | */ |
818 | - property bool lock: !toolbarActions.__hasVisibleActions() |
819 | + property bool locked: !toolbarActions.__hasVisibleActions() |
820 | |
821 | /*! |
822 | \internal |
823 | |
824 | === modified file 'modules/Ubuntu/Components/qmldir' |
825 | --- modules/Ubuntu/Components/qmldir 2013-04-26 20:31:16 +0000 |
826 | +++ modules/Ubuntu/Components/qmldir 2013-04-30 17:34:37 +0000 |
827 | @@ -5,7 +5,7 @@ |
828 | ToolbarActions 0.1 ToolbarActions.qml |
829 | MainView 0.1 MainView.qml |
830 | Button 0.1 Button.qml |
831 | -internal GenericToolbar GenericToolbar.qml |
832 | +Panel 0.1 Panel.qml |
833 | internal DraggingArea DraggingArea.qml |
834 | Tabs 0.1 Tabs.qml |
835 | Tab 0.1 Tab.qml |
836 | |
837 | === added file 'tests/unit/tst_components/tst_panel.qml' |
838 | --- tests/unit/tst_components/tst_panel.qml 1970-01-01 00:00:00 +0000 |
839 | +++ tests/unit/tst_components/tst_panel.qml 2013-04-30 17:34:37 +0000 |
840 | @@ -0,0 +1,251 @@ |
841 | +/* |
842 | + * Copyright 2012 Canonical Ltd. |
843 | + * |
844 | + * This program is free software; you can redistribute it and/or modify |
845 | + * it under the terms of the GNU Lesser General Public License as published by |
846 | + * the Free Software Foundation; version 3. |
847 | + * |
848 | + * This program is distributed in the hope that it will be useful, |
849 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
850 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
851 | + * GNU Lesser General Public License for more details. |
852 | + * |
853 | + * You should have received a copy of the GNU Lesser General Public License |
854 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
855 | + */ |
856 | + |
857 | +import QtQuick 2.0 |
858 | +import QtTest 1.0 |
859 | +import Ubuntu.Components 0.1 |
860 | + |
861 | +Item { |
862 | + width: 200 |
863 | + height: 200 |
864 | + id: root |
865 | + |
866 | + Panel { |
867 | + id: panel |
868 | + anchors { |
869 | + bottom: parent.bottom |
870 | + left: parent.left |
871 | + right: parent.right |
872 | + } |
873 | + height: parent.height / 2 |
874 | + } |
875 | + |
876 | + TestCase { |
877 | + id: testCase |
878 | + name: "PanelAPI" |
879 | + when: windowShown |
880 | + |
881 | + function initTestCase() { |
882 | + compare(panel.align, Qt.AlignBottom, "Panel initially aligned at bottom"); |
883 | + compare(panel.opened, false, "Panel initially not opened"); |
884 | + compare(panel.locked, false, "Panel initially not locked"); |
885 | + compare(panel.hintSize, units.gu(2), "Default hintSize is 2 grid units"); |
886 | + compare(panel.triggerSize, units.gu(2), "Default triggerSize is 2 grid units"); |
887 | + } |
888 | + |
889 | + function test_align() { |
890 | + panel.align = Qt.AlignTop; |
891 | + compare(panel.align, Qt.AlignTop, "Can set align to top"); |
892 | + panel.align = Qt.AlignLeft; |
893 | + compare(panel.align, Qt.AlignLeft, "Can set align to left"); |
894 | + panel.align = Qt.AlignRight; |
895 | + compare(panel.align, Qt.AlignRight, "Can set align to right"); |
896 | + panel.align = Qt.AlignBottom; |
897 | + compare(panel.align, Qt.AlignBottom, "Can set align to bottom"); |
898 | + } |
899 | + |
900 | + function test_opened() { |
901 | + panel.opened = true; |
902 | + compare(panel.opened, true, "Can set opened"); |
903 | + panel.opened = false; |
904 | + compare(panel.opened, false, "Can unset opened"); |
905 | + } |
906 | + |
907 | + function test_locked() { |
908 | + panel.locked = true; |
909 | + compare(panel.locked, true, "Can set locked"); |
910 | + panel.locked = false; |
911 | + compare(panel.locked, false, "Can unset locked"); |
912 | + } |
913 | + |
914 | + function test_hintSize() { |
915 | + panel.hintSize = units.gu(1); |
916 | + compare(panel.hintSize, units.gu(1), "Can set hintSize"); |
917 | + panel.hintSize = units.gu(2); |
918 | + } |
919 | + |
920 | + function test_triggerSize() { |
921 | + panel.triggerSize = units.gu(5); |
922 | + compare(panel.triggerSize, units.gu(5), "Can set triggerSize"); |
923 | + panel.triggerSize = units.gu(2); |
924 | + } |
925 | + |
926 | + function test_swipeBottomPanel() { |
927 | + // swipe bottom-aligned panel in and out |
928 | + swipeTests.slowMouseMove = false; |
929 | + swipeTests.swipeUpDown(); |
930 | + swipeTests.slowMouseMove = true; |
931 | + swipeTests.swipeUpDown(); |
932 | + swipeTests.slowMouseMove = false; |
933 | + } |
934 | + |
935 | + function test_swipeLeftPanel() { |
936 | + // swipe a left-aligned panel in and out |
937 | + panel.align = Qt.AlignLeft; |
938 | + swipeTests.slowMouseMove = false; |
939 | + swipeTests.swipeRightLeft(); |
940 | + swipeTests.slowMouseMove = true; |
941 | + swipeTests.swipeRightLeft(); |
942 | + swipeTests.slowMouseMove = false; |
943 | + panel.align = Qt.AlignBottom; |
944 | + } |
945 | + |
946 | + function test_swipeRightPanel() { |
947 | + // swipe right-aligned panel in and out |
948 | + panel.align = Qt.AlignRight; |
949 | + swipeTests.slowMouseMove = false; |
950 | + swipeTests.swipeLeftRight(); |
951 | + swipeTests.slowMouseMove = true; |
952 | + swipeTests.swipeLeftRight(); |
953 | + swipeTests.slowMouseMove = false; |
954 | + panel.align = Qt.AlignBottom; |
955 | + } |
956 | + |
957 | + function test_swipeLeadingPanel() { |
958 | + // swipe leading-aligned panel in and out |
959 | + panel.align = Qt.AlignLeading; |
960 | + swipeTests.swipeRightLeft(); |
961 | + panel.LayoutMirroring.enabled = true; |
962 | + panel.LayoutMirroring.childrenInherit = true; |
963 | + swipeTests.slowMouseMove = false; |
964 | + swipeTests.swipeLeftRight(); |
965 | + swipeTests.slowMouseMove = true; |
966 | + swipeTests.swipeLeftRight(); |
967 | + swipeTests.slowMouseMove = false; |
968 | + panel.LayoutMirroring.enabled = false; |
969 | + panel.align = Qt.AlignBottom; |
970 | + } |
971 | + |
972 | + function test_swipeTrailingPanel() { |
973 | + // swipe trailing-aligned panel in and out |
974 | + panel.align = Qt.AlignTrailing; |
975 | + swipeTests.swipeLeftRight(); |
976 | + panel.LayoutMirroring.enabled = true; |
977 | + panel.LayoutMirroring.childrenInherit = true; |
978 | + swipeTests.slowMouseMove = false; |
979 | + swipeTests.swipeRightLeft(); |
980 | + swipeTests.slowMouseMove = true; |
981 | + swipeTests.swipeRightLeft(); |
982 | + swipeTests.slowMouseMove = false; |
983 | + panel.LayoutMirroring.enabled = false; |
984 | + panel.align = Qt.AlignBottom; |
985 | + } |
986 | + |
987 | + function test_swipeTopPanel() { |
988 | + // swipe top-aligned panel in and out |
989 | + panel.anchors.top = root.top; |
990 | + panel.anchors.bottom = undefined; |
991 | + panel.align = Qt.AlignTop; |
992 | + |
993 | + swipeTests.slowMouseMove = false; |
994 | + swipeTests.swipeDownUp(); |
995 | + swipeTests.slowMouseMove = true; |
996 | + swipeTests.swipeDownUp(); |
997 | + swipeTests.slowMouseMove = false; |
998 | + |
999 | + // revert to original state |
1000 | + panel.anchors.bottom = root.bottom; |
1001 | + panel.anchors.top = undefined; |
1002 | + panel.align = Qt.AlignBottom; |
1003 | + } |
1004 | + |
1005 | + QtObject { |
1006 | + id: swipeTests |
1007 | + |
1008 | + // waiting time in ms after move, to simulate slow drag vs fast swipe |
1009 | + property int moveDelay: slowMouseMove ? 400 : 0 |
1010 | + property bool slowMouseMove: false |
1011 | + |
1012 | + function swipeUpDown() { |
1013 | + testCase.compare(panel.opened, false, "Panel initially not opened") |
1014 | + var x = root.width / 2; var y = root.height - 1; |
1015 | + var dx = 0; |
1016 | + var dy = -panel.height / 2; |
1017 | + testCase.mousePress(root, x, y, Qt.LeftButton); |
1018 | + testCase.mouseMove(root, x + dx, y + dy, moveDelay); |
1019 | + testCase.mouseRelease(root, x + dx, y + dy, Qt.LeftButton); |
1020 | + testCase.compare(panel.opened, true, "Panel activated by swiping up (delay: "+moveDelay+")") |
1021 | + x = panel.width / 2; |
1022 | + y = 10; |
1023 | + testCase.mousePress(panel, x, y, Qt.LeftButton); |
1024 | + testCase.mouseMove(panel, x - dx, y - dy, moveDelay); |
1025 | + testCase.mouseRelease(panel, x - dx, y - dy, Qt.LeftButton); |
1026 | + testCase.compare(panel.opened, false, "Panel deactivated by swiping down (delay: "+moveDelay+")") |
1027 | + } |
1028 | + |
1029 | + function swipeRightLeft() { |
1030 | + testCase.compare(panel.opened, false, "Panel initially not opened") |
1031 | + var x = 1; |
1032 | + var y = 3 * root.height / 4; |
1033 | + var dx = panel.width / 2; |
1034 | + var dy = 0; |
1035 | + testCase.mousePress(root, x, y, Qt.LeftButton); |
1036 | + testCase.mouseMove(root, x + dx, y + dy, moveDelay); |
1037 | + testCase.mouseRelease(root, x + dx, y + dy, Qt.Leftbutton); |
1038 | + testCase.compare(panel.opened, true, "Left-aligned panel activated by swiping to the right (delay: "+moveDelay+")"); |
1039 | + x = 3 * panel.width / 4; |
1040 | + y = panel.height / 2; |
1041 | + testCase.mousePress(panel, x, y, Qt.LeftButton); |
1042 | + testCase.mouseMove(root, x - dx, y - dy, moveDelay); |
1043 | + testCase.mouseRelease(panel, x - dx, y - dy, Qt.LeftButton); |
1044 | + testCase.compare(panel.opened, false, "Left-aligned panel deactivated by swiping to the left (delay: "+moveDelay+""); |
1045 | + } |
1046 | + |
1047 | + function swipeLeftRight() { |
1048 | + testCase.compare(panel.opened, false, "Panel initially not opened") |
1049 | + var x = root.width - 1; |
1050 | + var y = 3 * root.height / 4; |
1051 | + var dx = -panel.width / 2; |
1052 | + var dy = 0; |
1053 | + testCase.mousePress(root, x, y, Qt.LeftButton); |
1054 | + testCase.mouseMove(root, x + dx, y + dy, moveDelay); |
1055 | + testCase.mouseRelease(root, x + dx, y + dy, Qt.Leftbutton); |
1056 | + testCase.compare(panel.opened, true, "Right-aligned panel activated by swiping to the left (delay: "+moveDelay+""); |
1057 | + x = 1; |
1058 | + y = panel.height / 2; |
1059 | + testCase.mousePress(panel, x, y, Qt.LeftButton); |
1060 | + testCase.mouseMove(panel, -dx, -dy, moveDelay); |
1061 | + testCase.mouseRelease(panel, x - dx, y - dy, Qt.LeftButton); |
1062 | + testCase.compare(panel.opened, false, "Right-aligned panel deactivating by swiping to the right (delay: "+moveDelay+""); |
1063 | + } |
1064 | + |
1065 | + function swipeDownUp() { |
1066 | + testCase.compare(panel.opened, false, "Panel initially not opened") |
1067 | + var x = root.width / 2; |
1068 | + var y = 1; |
1069 | + var dx = 0; |
1070 | + var dy = panel.height / 2; |
1071 | + testCase.mousePress(root, x, y, Qt.LeftButton); |
1072 | + testCase.mouseMove(root, x + dx, y + dy, moveDelay); |
1073 | + testCase.mouseRelease(root, x + dx, y + dy, Qt.LeftButton); |
1074 | + testCase.compare(panel.opened, true, "Top-aligned panel activated by swiping down (delay: "+moveDelay+""); |
1075 | + x = panel.width / 2; |
1076 | + y = panel.height - 1; |
1077 | + testCase.mousePress(panel, x, y, Qt.LeftButton); |
1078 | + testCase.mouseMove(panel, x - dx, y - dy, moveDelay); |
1079 | + testCase.mouseRelease(panel, x - dx, y - dy, Qt.LeftButton); |
1080 | + testCase.compare(panel.opened, false, "Top-aligned panel deactivated by swiping up (delay: "+moveDelay+""); |
1081 | + } |
1082 | + |
1083 | + function test_clickToDeactivate() { |
1084 | + panel.opened = true; |
1085 | + compare(panel.opened && panel.align === Qt.AlignBottom, true, "Panel is opened and bottom-aligned"); |
1086 | + mouseClick(root, root.width / 2, 5, Qt.LeftButton); |
1087 | + compare(panel.opened, false, "Panel is deactivated by clicking in the view outside of the panel"); |
1088 | + } |
1089 | + } |
1090 | + } |
1091 | +} |
1092 | |
1093 | === added file 'tests/unit/tst_components/tst_toolbar.qml' |
1094 | --- tests/unit/tst_components/tst_toolbar.qml 1970-01-01 00:00:00 +0000 |
1095 | +++ tests/unit/tst_components/tst_toolbar.qml 2013-04-30 17:34:37 +0000 |
1096 | @@ -0,0 +1,74 @@ |
1097 | +/* |
1098 | + * Copyright 2012 Canonical Ltd. |
1099 | + * |
1100 | + * This program is free software; you can redistribute it and/or modify |
1101 | + * it under the terms of the GNU Lesser General Public License as published by |
1102 | + * the Free Software Foundation; version 3. |
1103 | + * |
1104 | + * This program is distributed in the hope that it will be useful, |
1105 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1106 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1107 | + * GNU Lesser General Public License for more details. |
1108 | + * |
1109 | + * You should have received a copy of the GNU Lesser General Public License |
1110 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1111 | + */ |
1112 | + |
1113 | +import QtQuick 2.0 |
1114 | +import QtTest 1.0 |
1115 | +import Ubuntu.Components 0.1 |
1116 | + |
1117 | +Item { |
1118 | + width: 200 |
1119 | + height: 200 |
1120 | + |
1121 | + MainView { |
1122 | + anchors.fill: parent |
1123 | + id: mainView |
1124 | + Page { |
1125 | + id: page |
1126 | + tools: ToolbarActions { |
1127 | + id: toolbarActions |
1128 | + Action { |
1129 | + text: "action1" |
1130 | + } |
1131 | + } |
1132 | + } |
1133 | + } |
1134 | + |
1135 | + TestCase { |
1136 | + name: "ToolbarAPI" |
1137 | + when: windowShown |
1138 | + |
1139 | + function initTestCase() { |
1140 | + compare(page.tools, toolbarActions, "Page tools are set initially"); |
1141 | + compare(mainView.toolbar.tools, page.tools, "Toolbar tools are set to page tools initially"); |
1142 | + compare(mainView.toolbar.tools.opened, false, "Toolbar is closed initially"); |
1143 | + compare(mainView.toolbar.tools.locked, false, "Toolbar is initially not locked"); |
1144 | + } |
1145 | + |
1146 | + function test_opened() { |
1147 | + compare(mainView.toolbar.tools.opened, false, "Toolbar initially closed"); |
1148 | + mainView.toolbar.opened = true; |
1149 | + compare(mainView.toolbar.opened, true, "Toolbar can be made opened"); |
1150 | + mainView.toolbar.opened = false; |
1151 | + compare(mainView.toolbar.opened, false, "Toolbar can be made closed"); |
1152 | + page.tools.opened = true; |
1153 | + compare(mainView.toolbar.opened, true, "Toolbar can be made opened by setting page.tools.opened"); |
1154 | + page.tools.opened = false; |
1155 | + compare(mainView.toolbar.opened, false, "Toolbar can be made closed by setting page.tools.opened to false"); |
1156 | + } |
1157 | + |
1158 | + function test_locked() { |
1159 | + compare(mainView.toolbar.tools.locked, false, "Toolbar initially not locked"); |
1160 | + mainView.toolbar.locked = true; |
1161 | + compare(mainView.toolbar.locked, true, "Toolbar can be locked"); |
1162 | + mainView.toolbar.locked = false; |
1163 | + compare(mainView.toolbar.locked, false, "Toolbar can be unlocked"); |
1164 | + page.tools.locked = true; |
1165 | + compare(mainView.toolbar.locked, true, "Toolbar can be locked by setting page.tools.locked"); |
1166 | + page.tools.locked = false; |
1167 | + compare(mainView.toolbar.locked, false, "Toolbar can be unlocked by setting page.tools.locked to false"); |
1168 | + } |
1169 | + } |
1170 | +} |
1171 | |
1172 | === modified file 'themes/Ambiance/qmltheme/ToolbarButtonDelegate.qml' |
1173 | --- themes/Ambiance/qmltheme/ToolbarButtonDelegate.qml 2013-04-26 20:31:16 +0000 |
1174 | +++ themes/Ambiance/qmltheme/ToolbarButtonDelegate.qml 2013-04-30 17:34:37 +0000 |
1175 | @@ -25,9 +25,6 @@ |
1176 | |
1177 | anchors.fill: parent |
1178 | |
1179 | - implicitWidth: units.gu(5) |
1180 | - implicitHeight: units.gu(5) |
1181 | - |
1182 | Item { |
1183 | anchors.centerIn: parent |
1184 | height: icon.height + label.height + label.anchors.topMargin |
1185 | |
1186 | === modified file 'themes/Ambiance/qmltheme/ToolbarDelegate.qml' |
1187 | --- themes/Ambiance/qmltheme/ToolbarDelegate.qml 2013-04-26 20:31:16 +0000 |
1188 | +++ themes/Ambiance/qmltheme/ToolbarDelegate.qml 2013-04-30 17:34:37 +0000 |
1189 | @@ -23,17 +23,13 @@ |
1190 | property real barOpacity |
1191 | |
1192 | anchors.fill: parent |
1193 | + property alias contentItem: background |
1194 | |
1195 | Rectangle { |
1196 | id: background |
1197 | - anchors { |
1198 | - left: parent.left |
1199 | - right: parent.right |
1200 | - bottom: parent.bottom |
1201 | - } |
1202 | - height: parent.height //- dropshadow.height |
1203 | + anchors.fill: parent |
1204 | color: visuals.color |
1205 | - opacity: barOpacity |
1206 | + opacity: visuals.barOpacity |
1207 | } |
1208 | |
1209 | Image { |
1210 | @@ -44,7 +40,7 @@ |
1211 | bottom: background.top |
1212 | } |
1213 | source: Qt.resolvedUrl("artwork/toolbar_dropshadow.png") |
1214 | - opacity: item.state == "" ? 0.0 : 0.5 |
1215 | + opacity: item.active || item.animating ? 0.5 : 0.0 |
1216 | Behavior on opacity { |
1217 | NumberAnimation { duration: 50; easing.type: Easing.OutQuad } |
1218 | } |
PASSED: Continuous integration, rev:447 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/1009/ jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/./build= panda-pbuilder, distribution= quantal, flavor= armhf/1009 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/./build= pbuilder- master, distribution= quantal, flavor= amd64/1009 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/./build= pbuilder- master, distribution= quantal, flavor= i386/1009
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ ubuntu- ui-toolkit- ci/1009/ rebuild
http://