@@ -0,0 +1,520 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU 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 General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+.pragma library
18+
19+var kBackgroundLoaderCode = 'Loader {\n\
20+ id: backgroundLoader; \n\
21+ objectName: "backgroundLoader"; \n\
22+ anchors.fill: parent; \n\
23+ asynchronous: root.asynchronous; \n\
24+ visible: status == Loader.Ready; \n\
25+ sourceComponent: UbuntuShape { \n\
26+ objectName: "background"; \n\
27+ radius: "medium"; \n\
28+ color: getColor(0) || "white"; \n\
29+ gradientColor: getColor(1) || color; \n\
30+ anchors.fill: parent; \n\
31+ image: backgroundImage.source ? backgroundImage : null; \n\
32+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; \n\
33+ property Image backgroundImage: Image { \n\
34+ objectName: "backgroundImage"; \n\
35+ source: { \n\
36+ if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \n\
37+ else if (template && typeof template["card-background"] === "string") return template["card-background"]; \n\
38+ else return ""; \n\
39+ } \n\
40+ } \n\
41+ function getColor(index) { \n\
42+ if (cardData && typeof cardData["background"] === "object" \n\
43+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) { \n\
44+ return cardData["background"]["elements"][index]; \n\
45+ } else if (template && typeof template["card-background"] === "object" \n\
46+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) { \n\
47+ return template["card-background"]["elements"][index]; \n\
48+ } else return undefined; \n\
49+ } \n\
50+ } \n\
51+ }\n';
52+
53+// %1 is used as anchors of artShapeHolder
54+// %2 is used as image width
55+// %3 is used as image height
56+var kArtShapeHolderCode = 'Item { \n\
57+ id: artShapeHolder; \n\
58+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height; \n\
59+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width; \n\
60+ anchors { %1 } \n\
61+ Loader { \n\
62+ id: artShapeLoader; \n\
63+ objectName: "artShapeLoader"; \n\
64+ active: cardData && cardData["art"] || false; \n\
65+ asynchronous: root.asynchronous; \n\
66+ visible: status == Loader.Ready; \n\
67+ sourceComponent: UbuntuShape { \n\
68+ id: artShape; \n\
69+ objectName: "artShape"; \n\
70+ radius: "medium"; \n\
71+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1; \n\
72+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect; \n\
73+ Component.onCompleted: updateWidthHeightBindings(); \n\
74+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings(); \n\
75+ visible: image.status == Image.Ready; \n\
76+ borderSource: artShapeBorderSource; \n\
77+ function updateWidthHeightBindings() { \n\
78+ if (aspectSmallerThanImageAspect) { \n\
79+ width = Qt.binding(function() { return !visible ? 0 : image.width }); \n\
80+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect }); \n\
81+ } else { \n\
82+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect }); \n\
83+ height = Qt.binding(function() { return !visible ? 0 : image.height }); \n\
84+ } \n\
85+ } \n\
86+ image: Image { \n\
87+ objectName: "artImage"; \n\
88+ source: cardData && cardData["art"] || ""; \n\
89+ cache: true; \n\
90+ asynchronous: root.asynchronous; \n\
91+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop; \n\
92+ readonly property real aspect: implicitWidth / implicitHeight; \n\
93+ width: %2; \n\
94+ height: %3; \n\
95+ } \n\
96+ } \n\
97+ } \n\
98+ }\n';
99+
100+var kOverlayLoaderCode = 'Loader { \n\
101+ id: overlayLoader; \n\
102+ anchors { \n\
103+ left: artShapeHolder.left; \n\
104+ right: artShapeHolder.right; \n\
105+ bottom: artShapeHolder.bottom; \n\
106+ } \n\
107+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \n\
108+ asynchronous: root.asynchronous; \n\
109+ visible: showHeader && status == Loader.Ready; \n\
110+ sourceComponent: ShaderEffect { \n\
111+ id: overlay; \n\
112+ height: fixedHeaderHeight != -1 ? fixedHeaderHeight : headerHeight; \n\
113+ opacity: 0.6; \n\
114+ property var source: ShaderEffectSource { \n\
115+ id: shaderSource; \n\
116+ sourceItem: artShapeLoader.item; \n\
117+ onVisibleChanged: if (visible) scheduleUpdate(); \n\
118+ live: false; \n\
119+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height); \n\
120+ } \n\
121+ vertexShader: " \n\
122+ uniform highp mat4 qt_Matrix; \n\
123+ attribute highp vec4 qt_Vertex; \n\
124+ attribute highp vec2 qt_MultiTexCoord0; \n\
125+ varying highp vec2 coord; \n\
126+ void main() { \n\
127+ coord = qt_MultiTexCoord0; \n\
128+ gl_Position = qt_Matrix * qt_Vertex; \n\
129+ }"; \n\
130+ fragmentShader: " \n\
131+ varying highp vec2 coord; \n\
132+ uniform sampler2D source; \n\
133+ uniform lowp float qt_Opacity; \n\
134+ void main() { \n\
135+ lowp vec4 tex = texture2D(source, coord); \n\
136+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity; \n\
137+ }"; \n\
138+ } \n\
139+ }\n';
140+
141+// %1 is used as anchors of row
142+// %2 is used as first child of the row
143+// %3 is used as second child of the row
144+var kHeaderRow2Code = 'Row { \n\
145+ id: row; \n\
146+ objectName: "outerRow"; \n\
147+ property real margins: units.gu(1); \n\
148+ spacing: margins; \n\
149+ anchors { %1 } \n\
150+ anchors.right: parent.right; \n\
151+ anchors.margins: margins;\n\
152+ data: [ %2\n\
153+ ,\n\
154+ %3 \n\
155+ ] \n\
156+ }\n';
157+
158+// %1 is used as anchors of row
159+// %2 is used as first child of the row
160+// %3 is used as second child of the row
161+// %4 is used as third child of the row
162+var kHeaderRow3Code = 'Row { \n\
163+ id: row; \n\
164+ objectName: "outerRow"; \n\
165+ property real margins: units.gu(1); \n\
166+ spacing: margins; \n\
167+ anchors { %1 } \n\
168+ anchors.right: parent.right; \n\
169+ anchors.margins: margins;\n\
170+ data: [ %2\n\
171+ ,\n\
172+ %3 \n\
173+ ,\n\
174+ %4 \n\
175+ ] \n\
176+ }\n';
177+
178+// %1 is used as first child of the column
179+// %2 is used as second child of the column
180+var kHeaderColumnCode = 'Column { \n\
181+ anchors.verticalCenter: parent.verticalCenter; \n\
182+ spacing: units.dp(2); \n\
183+ width: parent.width - x;\n\
184+ data: [ %1\n\
185+ ,\n\
186+ %2 \n\
187+ ] \n\
188+ }\n';
189+
190+// %1 is used as anchors of mascotShapeLoader
191+var kMascotShapeLoaderCode = 'Loader { \n\
192+ id: mascotShapeLoader; \n\
193+ objectName: "mascotShapeLoader"; \n\
194+ asynchronous: root.asynchronous; \n\
195+ active: mascotImage.status === Image.Ready; \n\
196+ visible: showHeader && active && status == Loader.Ready; \n\
197+ width: units.gu(6); \n\
198+ height: units.gu(5.625); \n\
199+ sourceComponent: UbuntuShape { image: mascotImage } \n\
200+ anchors { %1 } \n\
201+ }\n';
202+
203+// %1 is used as anchors of mascotImage
204+// %2 is used as visible of mascotImage
205+var kMascotImageCode = 'Image { \n\
206+ id: mascotImage; \n\
207+ objectName: "mascotImage"; \n\
208+ anchors { %1 } \n\
209+ readonly property int maxSize: Math.max(width, height) * 4; \n\
210+ source: cardData && cardData["mascot"]; \n\
211+ width: units.gu(6); \n\
212+ height: units.gu(5.625); \n\
213+ sourceSize { width: maxSize; height: maxSize } \n\
214+ fillMode: Image.PreserveAspectCrop; \n\
215+ horizontalAlignment: Image.AlignHCenter; \n\
216+ verticalAlignment: Image.AlignVCenter; \n\
217+ visible: %2; \n\
218+ }\n';
219+
220+// %1 is used as anchors of titleLabel
221+// %1 is used as color of titleLabel
222+// %3 is used as extra condition for visible of titleLabel
223+var kTitleLabelCode = 'Label { \n\
224+ id: titleLabel; \n\
225+ objectName: "titleLabel"; \n\
226+ anchors { %1 } \n\
227+ elide: Text.ElideRight; \n\
228+ fontSize: "small"; \n\
229+ wrapMode: Text.Wrap; \n\
230+ maximumLineCount: 2; \n\
231+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \n\
232+ color: %2; \n\
233+ visible: showHeader %3; \n\
234+ text: root.title; \n\
235+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal; \n\
236+ horizontalAlignment: root.headerAlignment; \n\
237+ }\n';
238+
239+// %1 is used as anchors of subtitleLabel
240+// %2 is used as color of subtitleLabel
241+var kSubtitleLabelCode = 'Label { \n\
242+ id: subtitleLabel; \n\
243+ objectName: "subtitleLabel"; \n\
244+ anchors { %1 } \n\
245+ elide: Text.ElideRight; \n\
246+ fontSize: "small"; \n\
247+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \n\
248+ color: %2; \n\
249+ visible: titleLabel.visible && titleLabel.text; \n\
250+ text: cardData && cardData["subtitle"] || ""; \n\
251+ font.weight: Font.Light; \n\
252+ horizontalAlignment: root.headerAlignment; \n\
253+ }\n';
254+
255+// %1 is used as top anchor of summary
256+// %2 is used as topMargin anchor of summary
257+// %3 is used as color of summary
258+var kSummaryLabelCode = 'Label { \n\
259+ id: summary; \n\
260+ objectName: "summaryLabel"; \n\
261+ anchors { \n\
262+ top: %1; \n\
263+ left: parent.left; \n\
264+ right: parent.right; \n\
265+ margins: units.gu(1); \n\
266+ topMargin: %2; \n\
267+ } \n\
268+ wrapMode: Text.Wrap; \n\
269+ maximumLineCount: 5; \n\
270+ elide: Text.ElideRight; \n\
271+ text: cardData && cardData["summary"] || ""; \n\
272+ height: text ? implicitHeight : 0; \n\
273+ fontSize: "small"; \n\
274+ color: %3; \n\
275+ }\n';
276+
277+function cardString(template, components) {
278+ var code;
279+ code = 'AbstractButton { \n\
280+ id: root; \n\
281+ property var template; \n\
282+ property var components; \n\
283+ property var cardData; \n\
284+ property var artShapeBorderSource; \n\
285+ property real fontScale: 1.0; \n\
286+ property int headerAlignment: Text.AlignLeft; \n\
287+ property int fixedHeaderHeight: -1; \n\
288+ property size fixedArtShapeSize: Qt.size(-1, -1); \n\
289+ readonly property string title: cardData && cardData["title"] || ""; \n\
290+ property bool asynchronous: true; \n\
291+ property bool showHeader: true; \n\
292+ implicitWidth: childrenRect.width; \n';
293+
294+ var hasArt = components["art"] && components["art"]["field"] || false;
295+ var hasSummary = components["summary"] || false;
296+ var artAndSummary = hasArt && hasSummary;
297+ var isHorizontal = template["card-layout"] === "horizontal";
298+ var hasBackground = !isHorizontal && (template["card-background"] || components["background"] || artAndSummary);
299+ var hasTitle = components["title"] || false;
300+ var hasMascot = components["mascot"] || false;
301+ var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
302+ var hasSubtitle = components["subtitle"] || false;
303+ var hasHeaderRow = hasMascot && hasTitle;
304+
305+ if (hasBackground) {
306+ code += kBackgroundLoaderCode;
307+ }
308+
309+ if (hasArt) {
310+ code += 'readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);\n';
311+
312+ var widthCode, heightCode;
313+ var anchors;
314+ if (isHorizontal) {
315+ anchors = 'left: parent.left';
316+ if (hasMascot || hasTitle) {
317+ widthCode = 'height * artShape.aspect'
318+ heightCode = 'headerHeight';
319+ } else {
320+ // This side of the else is a bit silly, who wants an horizontal layout without mascot and title?
321+ // So we define a "random" height of the image height + 2 gu for the margins
322+ widthCode = 'height * artShape.aspect'
323+ heightCode = 'units.gu(7.625)';
324+ }
325+ } else {
326+ anchors = 'horizontalCenter: parent.horizontalCenter;';
327+ widthCode = 'root.width'
328+ heightCode = 'width / artShape.aspect';
329+ }
330+
331+ code += kArtShapeHolderCode.arg(anchors).arg(widthCode).arg(heightCode);
332+ } else {
333+ code += 'readonly property size artShapeSize: Qt.size(-1, -1);\n'
334+ }
335+
336+ if (headerAsOverlay) {
337+ code += kOverlayLoaderCode;
338+ }
339+
340+ var headerVerticalAnchors;
341+ if (headerAsOverlay) {
342+ headerVerticalAnchors = 'bottom: artShapeHolder.bottom; \n\
343+ bottomMargin: units.gu(1);\n';
344+ } else {
345+ if (hasArt) {
346+ if (isHorizontal) {
347+ headerVerticalAnchors = 'top: artShapeHolder.top; \n\
348+ topMargin: units.gu(1);\n';
349+ } else {
350+ headerVerticalAnchors = 'top: artShapeHolder.bottom; \n\
351+ topMargin: units.gu(1);\n';
352+ }
353+ } else {
354+ headerVerticalAnchors = 'top: parent.top; \n\
355+ topMargin: units.gu(1);\n';
356+ }
357+ }
358+ var headerLeftAnchor;
359+ var headerLeftAnchorHasMagin = false;
360+ if (isHorizontal && hasArt) {
361+ headerLeftAnchor = 'left: artShapeHolder.right; \n\
362+ leftMargin: units.gu(1);\n';
363+ headerLeftAnchorHasMagin = true;
364+ } else {
365+ headerLeftAnchor = 'left: parent.left;\n';
366+ }
367+
368+ if (hasHeaderRow) {
369+ code += 'readonly property int headerHeight: row.height + row.margins * 2;\n'
370+ } else if (hasMascot) {
371+ code += 'readonly property int headerHeight: mascotImage.height + units.gu(1) * 2;\n'
372+ } else if (hasSubtitle) {
373+ code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2 + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
374+ } else if (hasTitle) {
375+ code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin * 2;\n'
376+ } else {
377+ code += 'readonly property int headerHeight: 0;\n'
378+ }
379+
380+ var mascotShapeCode = "";
381+ var mascotCode = "";
382+ if (hasMascot) {
383+ var useMascotShape = !hasBackground && !headerAsOverlay;
384+ var anchors = "";
385+ if (!hasHeaderRow) {
386+ anchors += headerLeftAnchor;
387+ anchors += headerVerticalAnchors;
388+ if (!headerLeftAnchorHasMagin) {
389+ anchors += 'leftMargin: units.gu(1);\n'
390+ }
391+ } else {
392+ anchors = "verticalCenter: parent.verticalCenter;"
393+ }
394+
395+ if (useMascotShape) {
396+ mascotShapeCode = kMascotShapeLoaderCode.arg(anchors);
397+ }
398+
399+ var mascotImageVisible = useMascotShape ? 'false' : 'showHeader';
400+ mascotCode = kMascotImageCode.arg(anchors).arg(mascotImageVisible);
401+ }
402+
403+ var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"';
404+
405+ var titleSubtitleCode = "";
406+ if (hasTitle) {
407+ var color;
408+ if (headerAsOverlay) {
409+ color = '"white"';
410+ } else if (hasSummary) {
411+ color = 'summary.color';
412+ } else if (hasBackground) {
413+ color = summaryColorWithBackground;
414+ } else {
415+ color = '"grey"';
416+ }
417+
418+ var titleAnchors;
419+ var subtitleAnchors;
420+ if (hasMascot && hasSubtitle) {
421+ // Using row + column
422+ titleAnchors = 'left: parent.left; right: parent.right';
423+ subtitleAnchors = titleAnchors;
424+ } else if (hasMascot) {
425+ // Using row + label
426+ titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
427+ } else {
428+ if (headerAsOverlay) {
429+ // Using anchors to the overlay
430+ titleAnchors = 'left: parent.left; \n\
431+ leftMargin: units.gu(1); \n\
432+ right: parent.right; \n\
433+ top: overlayLoader.top; \n\
434+ topMargin: units.gu(1);\n';
435+ } else {
436+ // Using anchors to the mascot/parent
437+ titleAnchors = "right: parent.right;";
438+ titleAnchors += headerLeftAnchor;
439+ titleAnchors += headerVerticalAnchors;
440+ }
441+ subtitleAnchors = 'left: titleLabel.left; \n\
442+ leftMargin: titleLabel.leftMargin; \n\
443+ right: titleLabel.right; \n\
444+ top: titleLabel.bottom; \n\
445+ topMargin: units.dp(2);\n';
446+ }
447+
448+ var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
449+ var titleCode = kTitleLabelCode.arg(titleAnchors).arg(color).arg(titleLabelVisibleExtra);
450+ var subtitleCode = "";
451+ if (hasSubtitle) {
452+ subtitleCode += kSubtitleLabelCode.arg(subtitleAnchors).arg(color);
453+ }
454+
455+ if (hasMascot && hasSubtitle) {
456+ // If using row + column wrap the code in the column
457+ titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
458+ } else {
459+ titleSubtitleCode = titleCode + subtitleCode;
460+ }
461+ }
462+
463+ if (hasHeaderRow) {
464+ if (mascotShapeCode != "") {
465+ code += kHeaderRow3Code.arg(headerVerticalAnchors + headerLeftAnchor).arg(mascotShapeCode).arg(mascotCode).arg(titleSubtitleCode);
466+ } else {
467+ code += kHeaderRow2Code.arg(headerVerticalAnchors + headerLeftAnchor).arg(mascotCode).arg(titleSubtitleCode);
468+ }
469+ } else {
470+ code += mascotShapeCode + mascotCode + titleSubtitleCode;
471+ }
472+
473+ if (hasSummary) {
474+ var summaryTopAnchor;
475+ if (isHorizontal && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
476+ else if (headerAsOverlay && hasArt) summaryTopAnchor = "artShapeHolder.bottom";
477+ else if (hasHeaderRow) summaryTopAnchor = "row.bottom";
478+ else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
479+ else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
480+ else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
481+ else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
482+ else summaryTopAnchor = "parent.top";
483+
484+ var color;
485+ if (hasBackground) {
486+ color = summaryColorWithBackground;
487+ } else {
488+ color = '"grey"';
489+ }
490+
491+ var summaryTopMargin = (hasMascot || hasSubtitle ? 'anchors.margins' : '0');
492+
493+ code += kSummaryLabelCode.arg(summaryTopAnchor).arg(summaryTopMargin).arg(color);
494+ }
495+
496+ if (hasSummary) {
497+ code += 'implicitHeight: summary.y + summary.height + (summary.text ? units.gu(1) : 0);\n';
498+ } else if (hasHeaderRow) {
499+ code += 'implicitHeight: row.y + row.height + units.gu(1);\n';
500+ } else if (hasMascot) {
501+ code += 'implicitHeight: mascotImage.y + mascotImage.height;\n';
502+ } else if (hasSubtitle) {
503+ code += 'implicitHeight: subtitleLabel.y + subtitleLabel.height + units.gu(1);\n';
504+ } else if (hasTitle) {
505+ code += 'implicitHeight: titleLabel.y + titleLabel.height + units.gu(1);\n';
506+ }
507+ // Close the AbstractButton
508+ code += '}\n';
509+
510+ return code;
511+}
512+
513+function createCardComponent(parent, template, components) {
514+ var imports = 'import QtQuick 2.2; \n\
515+ import Ubuntu.Components 0.1; \n\
516+ import Ubuntu.Thumbnailer 0.1;\n';
517+ var card = cardString(template, components);
518+ var code = imports + 'Component {\n' + card + '}\n';
519+ return Qt.createQmlObject(code, parent, "createCardComponent");
520+}