~ubuntu-branches/ubuntu/natty/unity-2d/natty

« back to all changes in this revision

Viewing changes to launcher/LauncherItem.qml

  • Committer: Bazaar Package Importer
  • Author(s): Oliver Grawert
  • Date: 2011-02-24 13:45:27 UTC
  • mfrom: (1.1.4 upstream)
  • Revision ID: james.westby@ubuntu.com-20110224134527-d2lk3qu3sxe9a8z0
Tags: 3.6.0-0ubuntu1
New Upstream version

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import Qt 4.7
2
 
 
3
 
/* Item displaying a launcher item.
4
 
 
5
 
   It contains:
6
 
    - a generic bordered background image
7
 
    - an icon representing the item
8
 
    - a text describing the item
9
 
 
10
 
   When an application is launched, the border changes appearance.
11
 
   It supports mouse hover by changing the appearance of the background image.
12
 
 
13
 
   The 'icon' property holds the source of the image to load as an icon.
14
 
   The 'label' property holds the text to display.
15
 
   The 'running' property is a boolean indicating whether or not the
16
 
   application is launched.
17
 
 
18
 
   The 'clicked' signal is emitted upon clicking on the item.
 
2
import Unity2d 1.0
 
3
 
 
4
/* This component represents a single "tile" in the launcher and the surrounding
 
5
   indicator icons.
 
6
 
 
7
   The tile is square in size, with a side determined by the 'tileSize' property,
 
8
   and rounded borders.
 
9
   It is composed by a colored background layer, an icon (with 'icon' as source),
 
10
   and a layer on top that provides a "shine" effect.
 
11
   The main color of the background layer may be calculated based on the icon color
 
12
   or may be fixed (depending on the 'backgroundFromIcon' property).
 
13
 
 
14
   There's also an additional layer which contains only the outline of the tile
 
15
   that is only appearing during the launching animation (when the 'launching' property is
 
16
   true). During this animation the background fades out and the outline fades in,
 
17
   giving a "pulsing" appearance to the tile.
 
18
 
 
19
   Around the tile we may have on the left a number of "pips" between zero and three.
 
20
   Pips are small icons used to indicate how many windows we have open for the current tile
 
21
   (based on the 'windowCount' property).
 
22
   The rule is: if there's only one window, we just display an arrow. If there are
 
23
   two we display 2 pips, if there are 3 or more display 3 pips.
 
24
 
 
25
   On the right of the tile there's an arrow that appears if the tile is currently 'active'.
 
26
 
 
27
   Additionally, when the tile is marked as 'urgent' it will start an animation where the
 
28
   rotation is changed so that it appears to be "shaking".
19
29
*/
20
30
Item {
21
 
    id: launcherItem
22
 
 
 
31
    id: item
 
32
    /* The object name is used by the launcher view to find the current
 
33
       launcher item under the mouse cursor during a drag’n’drop event. */
 
34
    objectName: "launcherItem"
 
35
 
 
36
    anchors.horizontalCenter: parent.horizontalCenter
 
37
    /* Manually add some padding to compensate for the spacing
 
38
       of the ListView being set to 0 to work around
 
39
       http://bugreports.qt.nokia.com/browse/QTBUG-17622. */
 
40
    property int padding: 5
 
41
    height: tileSize + padding
 
42
 
 
43
    property int tileSize
 
44
    property string desktopFile: ""
23
45
    property alias icon: icon.source
24
 
    property alias label: label.text
25
46
    property bool running: false
26
47
    property bool active: false
27
48
    property bool urgent: false
28
49
    property bool launching: false
29
50
 
 
51
    property int counter: 0
 
52
    property bool counterVisible: false
 
53
    property real progress: 0.0
 
54
    property bool progressBarVisible: false
 
55
    property alias emblem: emblemIcon.source
 
56
    property bool emblemVisible: false
 
57
 
 
58
    property bool backgroundFromIcon
 
59
    property color defaultBackgroundColor: "#333333"
 
60
 
 
61
    property alias shortcutVisible: shortcut.visible
 
62
    property alias shortcutText: shortcutText.text
 
63
 
 
64
    property int pips: 0
 
65
    property string pipSource: engineBaseUrl + "artwork/launcher_" +
 
66
                               ((pips <= 1) ? "arrow" : "pip") + "_ltr.png"
 
67
    function getPipOffset(index) {
 
68
        /* Pips need to always be centered, regardless if they are an even or odd
 
69
           number. The following simple conditional code works and is less
 
70
           convoluted than a generic formula. It's ok since we always work with at
 
71
           most three pips anyway. */
 
72
        if (pips == 1) return 0;
 
73
        if (pips == 2) return (index == 0) ? -2 : +2
 
74
        else return (index == 0) ? 0 : (index == 1) ? -4 : +4
 
75
    }
 
76
 
30
77
    signal clicked(variant mouse)
31
78
    signal entered
32
79
    signal exited
33
80
 
34
 
    Keys.onPressed: {
35
 
        if (event.key == Qt.Key_Return) {
36
 
            clicked()
37
 
            event.accepted = true;
38
 
        }
39
 
    }
40
 
 
41
 
    Image {
42
 
        id: shadow
43
 
 
44
 
        source: "artwork/shadow.png"
45
 
        asynchronous: true
46
 
    }
47
 
 
48
 
    Image {
49
 
        id: glow
50
 
 
51
 
        anchors.horizontalCenter: parent.horizontalCenter
52
 
        anchors.verticalCenter: parent.verticalCenter
53
 
 
54
 
        source: "artwork/glow.png"
55
 
        asynchronous: true
56
 
        opacity: 0.0
57
 
 
58
 
        SequentialAnimation on opacity {
59
 
            loops: Animation.Infinite
60
 
            alwaysRunToEnd: true
61
 
            running: launching
62
 
            NumberAnimation { to: 1.0; duration: 1000; easing.type: Easing.InOutQuad }
63
 
            NumberAnimation { to: 0.0; duration: 1000; easing.type: Easing.InOutQuad }
64
 
        }
65
 
    }
66
 
 
67
81
    Item {
68
 
        id: container
69
 
 
70
 
        width: 50
71
 
        height: 50
72
 
        anchors.horizontalCenter: parent.horizontalCenter
73
 
        anchors.verticalCenter: parent.verticalCenter
74
 
 
75
 
        MouseArea {
76
 
            id: mouse
77
 
 
78
 
            acceptedButtons: Qt.LeftButton | Qt.RightButton
79
 
            hoverEnabled: true
80
 
            anchors.fill: parent
81
 
            onClicked: launcherItem.clicked(mouse)
82
 
            onEntered: launcherItem.entered()
83
 
            onExited: launcherItem.exited()
84
 
        }
85
 
 
86
 
        Rectangle {
87
 
            id: background
88
 
 
89
 
            opacity: mouse.containsMouse ? 1.0 : 0.9
90
 
            anchors.fill: parent
91
 
            anchors.margins: 1
92
 
            anchors.horizontalCenter: parent.horizontalCenter
93
 
            anchors.verticalCenter: parent.verticalCenter
94
 
            smooth: true
95
 
            color: if(icon.source != "")
96
 
                       return launcherView.iconAverageColor(icon.source,
97
 
                                                            Qt.size(icon.width, icon.height))
98
 
            radius: 5
99
 
        }
100
 
 
101
 
        Image {
102
 
            id: icon
103
 
 
104
 
            width: 32
105
 
            height: 32
106
 
            anchors.horizontalCenter: parent.horizontalCenter
107
 
            anchors.verticalCenter: parent.verticalCenter
108
 
            fillMode: Image.PreserveAspectFit
109
 
            sourceSize.width: width
110
 
            sourceSize.height: height
111
 
            smooth: true
112
 
 
113
 
            asynchronous: true
114
 
            opacity: status == Image.Ready ? 1 : 0
115
 
            Behavior on opacity {NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}
116
 
        }
117
 
 
118
 
        Image {
119
 
            id: foreground
120
 
 
121
 
            anchors.fill: parent
122
 
            anchors.horizontalCenter: parent.horizontalCenter
123
 
            anchors.verticalCenter: parent.verticalCenter
124
 
            fillMode: Image.PreserveAspectFit
125
 
            sourceSize.width: width
126
 
            sourceSize.height: height
127
 
            smooth: true
128
 
 
129
 
            source: "/usr/share/unity/themes/prism_icon_foreground.png"
130
 
 
131
 
            asynchronous: true
132
 
            opacity: status == Image.Ready ? 1 : 0
133
 
            Behavior on opacity {NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}
134
 
        }
135
 
 
136
 
        SequentialAnimation {
137
 
            id: nudging
138
 
            running: urgent
 
82
        /* The actual item, reparented so its y coordinate can be animated. */
 
83
        id: looseItem
 
84
        parent: launcher
 
85
        width: item.width
 
86
        height: item.height
 
87
        x: item.x
 
88
        /* item.parent is the delegate, and its parent is the LauncherList */
 
89
        y: item.parent.parent.y - item.parent.parent.contentY + item.y
 
90
        z: item.parent.parent.itemZ
 
91
 
 
92
        /* This is the arrow shown at the right of the tile when the application is
 
93
           the active one */
 
94
        Image {
 
95
            anchors.right: parent.right
 
96
            anchors.verticalCenter: parent.verticalCenter
 
97
            source: "image://blended/%1color=%2alpha=%3"
 
98
                  .arg(engineBaseUrl + "artwork/launcher_arrow_rtl.png")
 
99
                  .arg("lightgrey")
 
100
                  .arg(1.0)
 
101
 
 
102
            /* This extra shift is necessary (as is for the pips below)
 
103
               since we are vertically centering in a parent with even height, so
 
104
               there's one pixel offset that need to be assigned arbitrarily.
 
105
               Unity chose to add it, QML to subtract it. So we adjust for that. */
 
106
            transform: Translate { y: 1 }
 
107
 
 
108
            visible: active && (looseItem.state != "beingDragged")
 
109
        }
 
110
 
 
111
        /* This is the area on the left of the tile where the pips/arrow end up.
 
112
 
 
113
           I'd rather use a Column here, but the pip images have an halo
 
114
           around them, so they are pretty tall and would mess up the column.
 
115
           As a workaround I center all of them, then shift up or down
 
116
           depending on the index. */
 
117
        Repeater {
 
118
            model: item.pips
 
119
            delegate: Image {
 
120
                /* FIXME: It seems that when the image is created (or re-used) by the Repeater
 
121
                   for a moment it doesn't have any parent, and therefore warnings are
 
122
                   printed for the following two anchor assignements. This fixes the
 
123
                   problem, but I'm not sure if it should happen in the first place. */
 
124
                anchors.left: (parent) ? parent.left : undefined
 
125
                anchors.verticalCenter: (parent) ? parent.verticalCenter : undefined
 
126
 
 
127
                source: "image://blended/%1color=%2alpha=%3"
 
128
                        .arg(pipSource).arg("lightgrey").arg(1.0)
 
129
 
 
130
                transform: Translate { y: getPipOffset(index) + 1 }
 
131
 
 
132
                visible: looseItem.state != "beingDragged"
 
133
            }
 
134
        }
 
135
 
 
136
        /* This is the for centering the actual tile in the launcher */
 
137
        Item {
 
138
            id: tile
 
139
            width: item.tileSize
 
140
            height: item.tileSize
 
141
            anchors.centerIn: parent
 
142
 
 
143
            /* This is the image providing the background image. The
 
144
               color blended with this image is obtained from the color of the icon when it's
 
145
               loaded.
 
146
               While the application is launching, this will fade out and in. */
 
147
            Image {
 
148
                id: tileBackground
 
149
                property color color: defaultBackgroundColor
 
150
                anchors.fill: parent
 
151
 
 
152
                SequentialAnimation on opacity {
 
153
                    NumberAnimation { to: 0.0; duration: 1000; easing.type: Easing.InOutQuad }
 
154
                    NumberAnimation { to: 1.0; duration: 1000; easing.type: Easing.InOutQuad }
 
155
 
 
156
                    loops: Animation.Infinite
 
157
                    alwaysRunToEnd: true
 
158
                    running: launching
 
159
                }
 
160
 
 
161
                sourceSize.width: item.tileSize
 
162
                sourceSize.height: item.tileSize
 
163
                source: "image://blended/%1color=%2alpha=%3"
 
164
                        .arg(engineBaseUrl + "artwork/round_corner_54x54.png")
 
165
                        .arg(color.toString().replace("#", ""))
 
166
                        .arg(1.0)
 
167
            }
 
168
 
 
169
            /* This image appears only while launching, and pulses in and out in counterpoint
 
170
               to the background, so that the outline of the tile is always visible. */
 
171
            Image {
 
172
                id: tileOutline
 
173
                anchors.fill: parent
 
174
 
 
175
                sourceSize.width: item.tileSize
 
176
                sourceSize.height: item.tileSize
 
177
                source: "artwork/round_outline_54x54.png"
 
178
 
 
179
                opacity: 0
 
180
 
 
181
                SequentialAnimation on opacity {
 
182
                    NumberAnimation { to: 1.0; duration: 1000; easing.type: Easing.InOutQuad }
 
183
                    NumberAnimation { to: 0.0; duration: 1000; easing.type: Easing.InOutQuad }
 
184
 
 
185
                    loops: Animation.Infinite
 
186
                    alwaysRunToEnd: true
 
187
                    running: launching
 
188
                }
 
189
            }
 
190
 
 
191
            /* This is just the main icon of the tile */
 
192
            Image {
 
193
                id: icon
 
194
                anchors.centerIn: parent
 
195
 
 
196
                sourceSize.width: 48
 
197
                sourceSize.height: 48
 
198
 
 
199
                /* Whenever one of the parameters used in calculating the background color of
 
200
                   the icon changes, recalculate its value */
 
201
                onWidthChanged: updateColors()
 
202
                onHeightChanged: updateColors()
 
203
                onSourceChanged: updateColors()
 
204
 
 
205
                function updateColors() {
 
206
                    if (!item.backgroundFromIcon) return;
 
207
 
 
208
                    var colors = launcherView.getColorsFromIcon(icon.source, icon.sourceSize)
 
209
                    if (colors && colors.length > 0) tileBackground.color = colors[0]
 
210
                }
 
211
            }
 
212
 
 
213
            /* This just adds some shiny effect to the tile */
 
214
            Image {
 
215
                id: tileShine
 
216
                anchors.fill: parent
 
217
 
 
218
                source: "artwork/round_shine_54x54.png"
 
219
                sourceSize.width: item.tileSize
 
220
                sourceSize.height: item.tileSize
 
221
            }
 
222
 
 
223
            Rectangle {
 
224
                id: counter
 
225
                height: 16 - border.width
 
226
                width: 32
 
227
                // Using anchors the item will be 1 pixel off with respect to Unity
 
228
                y: 1
 
229
                x: 1
 
230
                radius: height / 2 - 1
 
231
                border.width: 2
 
232
                border.color: "white"
 
233
                color: "#595959"
 
234
                visible: launcherItem.counterVisible
 
235
 
 
236
                Text {
 
237
                    anchors.centerIn: parent
 
238
                    font.pixelSize: parent.height - 3
 
239
                    width: parent.width - 5
 
240
                    elide: Text.ElideRight
 
241
                    horizontalAlignment: Text.AlignHCenter
 
242
                    color: "white"
 
243
                    text: launcherItem.counter
 
244
                }
 
245
            }
 
246
 
 
247
            Image {
 
248
                id: progressBar
 
249
                source: "artwork/progress_bar_trough.png"
 
250
                anchors.verticalCenter: parent.verticalCenter
 
251
                anchors.left: parent.left
 
252
                width: tile.width
 
253
                state: launcherItem.progressBarVisible ? "" : "hidden"
 
254
 
 
255
                Image {
 
256
                    id: progressFill
 
257
                    source: "artwork/progress_bar_fill.png"
 
258
                    anchors.verticalCenter: parent.verticalCenter
 
259
                    x: 6
 
260
                    width: sourceSize.width * launcherItem.progress
 
261
 
 
262
                    Behavior on width {
 
263
                       NumberAnimation { duration: 200; easing.type: Easing.InOutSine }
 
264
                    }
 
265
                }
 
266
 
 
267
                Behavior on width {
 
268
                    NumberAnimation { duration: 200; easing.type: Easing.InOutSine }
 
269
                }
 
270
 
 
271
                states: State {
 
272
                    name: "hidden"
 
273
                    PropertyChanges {
 
274
                        target: progressBar
 
275
                        width: 0
 
276
                    }
 
277
                    // This, combined with anchors.left: parent.left in the default state
 
278
                    // makes the bar seem to come in from the left and go away at the right
 
279
                    AnchorChanges {
 
280
                        target: progressBar
 
281
                        anchors.left: undefined
 
282
                        anchors.right: tile.right
 
283
                    }
 
284
                }
 
285
            }
 
286
 
 
287
            Rectangle {
 
288
                id: shortcut
 
289
                anchors.centerIn: parent
 
290
                color: "#B3000000" // 0.7 opacity on black
 
291
                radius: 2
 
292
                width: 22
 
293
                height: 22
 
294
 
 
295
                Text {
 
296
                    id: shortcutText
 
297
                    anchors.centerIn: parent
 
298
                    color: "white"
 
299
                }
 
300
            }
 
301
 
 
302
            Image {
 
303
                id: emblemIcon
 
304
                anchors.left: parent.left
 
305
                anchors.top: parent.top
 
306
                visible: launcherItem.emblemVisible && !counter.visible
 
307
            }
 
308
 
 
309
 
 
310
            /* The entire tile will "shake" when the window is marked as "urgent", to attract
 
311
               the user's attention */
139
312
            SequentialAnimation {
140
 
                loops: 30
141
 
                NumberAnimation { target: container; property: "rotation"; to: 15; duration: 150 }
142
 
                NumberAnimation { target: container; property: "rotation"; to: -15; duration: 150 }
143
 
            }
144
 
            NumberAnimation { target: container; property: "rotation"; to: 0; duration: 75 }
145
 
        }
146
 
 
147
 
        NumberAnimation {
148
 
            id: end_nudging
149
 
            running: !urgent
150
 
            target: container
151
 
            property: "rotation"
152
 
            to: 0
153
 
            duration: 75
154
 
        }
155
 
 
156
 
    }
157
 
 
158
 
    Image {
159
 
        id: running_arrow
160
 
 
161
 
        z: -1
162
 
        width: sourceSize.width
163
 
        height: sourceSize.height
164
 
        anchors.rightMargin: -2
165
 
        anchors.right: container.left
166
 
        anchors.verticalCenter: container.verticalCenter
167
 
        opacity: running ? 1.0 : 0.0
168
 
        source: urgent ? "/usr/share/unity/themes/application-running-notify.png" : "/usr/share/unity/themes/application-running.png"
169
 
 
170
 
        Behavior on opacity {NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}
171
 
    }
172
 
 
173
 
    Image {
174
 
        id: active_arrow
175
 
 
176
 
        z: -1
177
 
        width: sourceSize.width
178
 
        height: sourceSize.height
179
 
        anchors.leftMargin: -2
180
 
        anchors.left: container.right
181
 
        anchors.verticalCenter: container.verticalCenter
182
 
        opacity: active ? 1.0 : 0.0
183
 
        source: "/usr/share/unity/themes/application-selected.png"
184
 
 
185
 
        Behavior on opacity {NumberAnimation {duration: 200; easing.type: Easing.InOutQuad}}
186
 
    }
187
 
 
188
 
    Text {
189
 
        id: label
190
 
 
191
 
        font.pointSize: 10
192
 
        wrapMode: Text.WordWrap
193
 
        horizontalAlignment: Text.AlignHCenter
194
 
        anchors.top: parent.bottom
195
 
        anchors.topMargin: 7
196
 
        anchors.right: parent.right
197
 
        anchors.bottom: parent.bottom
198
 
        anchors.left: parent.left
199
 
        font.underline: parent.focus
 
313
                running: urgent
 
314
                alwaysRunToEnd: true
 
315
 
 
316
                SequentialAnimation {
 
317
                    loops: 30
 
318
                    NumberAnimation { target: tile; property: "rotation"; to: 15; duration: 150 }
 
319
                    NumberAnimation { target: tile; property: "rotation"; to: -15; duration: 150 }
 
320
                }
 
321
                NumberAnimation { target: tile; property: "rotation"; to: 0; duration: 75 }
 
322
            }
 
323
 
 
324
            MouseArea {
 
325
                id: mouse
 
326
                anchors.fill: parent
 
327
 
 
328
                hoverEnabled: true
 
329
                acceptedButtons: Qt.LeftButton | Qt.RightButton
 
330
                onClicked: item.clicked(mouse)
 
331
                onEntered: item.entered()
 
332
                onExited: item.exited()
 
333
            }
 
334
        }
 
335
 
 
336
        states: State {
 
337
            name: "beingDragged"
 
338
            when: (dnd.currentId != "") && (dnd.currentId == item.desktopFile)
 
339
            PropertyChanges {
 
340
                target: looseItem
 
341
                /* item.parent is the delegate, and its parent is the LauncherList */
 
342
                y: dnd.listCoordinates.y - item.parent.parent.contentY - tile.height / 2
 
343
                /* When dragging an item, stack it on top of all its siblings */
 
344
                z: 1
 
345
            }
 
346
        }
 
347
        Behavior on y {
 
348
            enabled: (looseItem.state != "beingDragged") && !item.parent.parent.moving && !item.parent.parent.autoScrolling
 
349
            NumberAnimation {
 
350
                duration: 400
 
351
                easing.type: Easing.OutBack
 
352
            }
 
353
        }
200
354
    }
201
355
}