@@ -0,0 +1,382 @@
1+
2+.pragma library
3+
4+/*
5+ * Copyright (C) 2013 Canonical, Ltd.
6+ *
7+ * This program is free software; you can redistribute it and/or modify
8+ * it under the terms of the GNU General Public License as published by
9+ * the Free Software Foundation; version 3.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+.pragma library
21+
22+function createCardComponent(parent, template, components, asynchronous) {
23+ var imports = 'import QtQuick 2.2; \
24+ import Ubuntu.Components 0.1; \
25+ import Ubuntu.Thumbnailer 0.1;';
26+ var code;
27+ code = 'AbstractButton { \
28+ clip: true; \
29+ id: root; \
30+ property var template; \
31+ property var components; \
32+ property var cardData; \
33+ property real fontScale: 1.0; \
34+ property int headerAlignment: Text.AlignLeft; \
35+ property size fixedArtShapeSize: Qt.size(-1, -1); \
36+ readonly property string title: cardData && cardData["title"] || ""; \
37+ property bool asynchronous: ' + asynchronous + '; \
38+ property bool showHeader: true; \
39+ implicitWidth: childrenRect.width; ';
40+
41+ var hasArt = components["art"]["field"];
42+ var hasSummary = components["summary"] || false;
43+ var artAndSummary = hasArt && hasSummary;
44+ var isHorizontal = template["card-layout"] === "horizontal";
45+ var hasBackground = !isHorizontal && (template["card-background"] || components["background"] || artAndSummary);
46+ var inOverlay = hasArt && template && template["overlay"] === true;
47+ var hasTitle = components["title"] || false;
48+ var hasMascot = components["mascot"] || false;
49+ var hasSubtitle = components["subtitle"] || false;
50+ var mascotImageHeight = 'units.gu(5.625)';
51+
52+ if (hasBackground) {
53+ code += 'Loader {\
54+ id: backgroundLoader; \
55+ objectName: "backgroundLoader"; \
56+ anchors.fill: parent; \
57+ asynchronous: root.asynchronous; \
58+ visible: status == Loader.Ready; \
59+ sourceComponent: UbuntuShape { \
60+ objectName: "background"; \
61+ radius: "medium"; \
62+ color: getColor(0) || "white"; \
63+ gradientColor: getColor(1) || color; \
64+ anchors.fill: parent; \
65+ image: backgroundImage.source ? backgroundImage : null; \
66+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b; \
67+ property Image backgroundImage: Image { \
68+ objectName: "backgroundImage"; \
69+ source: { \
70+ if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \
71+ else if (template && typeof template["card-background"] === "string") return template["card-background"]; \
72+ else return ""; \
73+ } \
74+ } \
75+ function getColor(index) { \
76+ if (cardData && typeof cardData["background"] === "object" \
77+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) { \
78+ return cardData["background"]["elements"][index]; \
79+ } else if (template && typeof template["card-background"] === "object" \
80+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) { \
81+ return template["card-background"]["elements"][index]; \
82+ } else return undefined; \
83+ } \
84+ } \
85+ }';
86+ }
87+
88+ if (hasArt) {
89+ code += 'readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);'
90+ var imageWidthHeight;
91+ if (isHorizontal) {
92+ if (hasMascot) {
93+ imageWidthHeight = 'width: height * artShape.aspect; \
94+ height: mascotImage.height + 2 * units.gu(1);'
95+ } else {
96+ imageWidthHeight = 'width: height * artShape.aspect; \
97+ height: ' + mascotImageHeight + ' + 2 * units.gu(1);'
98+ }
99+ } else {
100+ imageWidthHeight = 'width: root.width; \
101+ height: width / artShape.aspect;'
102+ }
103+ code += 'Item { \
104+ id: artShapeHolder; \
105+ height: root.fixedArtShapeSize.height != -1 ? root.fixedArtShapeSize.height : artShapeLoader.height; \
106+ width: root.fixedArtShapeSize.width != -1 ? root.fixedArtShapeSize.width : artShapeLoader.width; \
107+ ' + (isHorizontal ? 'anchors.left: parent.left;' : 'anchors.horizontalCenter: parent.horizontalCenter;' ) + '\
108+ Loader { \
109+ id: artShapeLoader; \
110+ objectName: "artShapeLoader"; \
111+ active: cardData && cardData["art"] || false; \
112+ asynchronous: root.asynchronous; \
113+ visible: status == Loader.Ready; \
114+ sourceComponent: UbuntuShape { \
115+ id: artShape; \
116+ radius: "medium"; \
117+ readonly property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1; \
118+ readonly property bool aspectSmallerThanImageAspect: aspect < image.aspect; \
119+ Component.onCompleted: updateWidthHeightBindings(); \
120+ onAspectSmallerThanImageAspectChanged: updateWidthHeightBindings(); \
121+ visible: image.status == Image.Ready; \
122+ function updateWidthHeightBindings() { \
123+ if (aspectSmallerThanImageAspect) { \
124+ width = Qt.binding(function() { return !visible ? 0 : image.width }); \
125+ height = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.height : width / image.aspect }); \
126+ } else { \
127+ width = Qt.binding(function() { return !visible ? 0 : image.fillMode === Image.PreserveAspectCrop ? image.width : height * image.aspect }); \
128+ height = Qt.binding(function() { return !visible ? 0 : image.height }); \
129+ } \
130+ } \
131+ image: Image { \
132+ objectName: "artImage"; \
133+ source: cardData && cardData["art"] || ""; \
134+ cache: true; \
135+ asynchronous: root.asynchronous; \
136+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop; \
137+ readonly property real aspect: implicitWidth / implicitHeight; \
138+ ' + imageWidthHeight + '\
139+ } \
140+ } \
141+ } \
142+ }'
143+ }
144+
145+ if (inOverlay) {
146+ var height;
147+ if (hasMascot) {
148+ var height = 'mascotImage.height + 2 * units.gu(1);';
149+ } else {
150+ var height = mascotImageHeight + ' + 2 * units.gu(1);';
151+ }
152+ code += 'Loader { \
153+ id: overlayLoader; \
154+ anchors { \
155+ left: artShapeHolder.left; \
156+ right: artShapeHolder.right; \
157+ bottom: artShapeHolder.bottom; \
158+ } \
159+ active: artShapeLoader.active && artShapeLoader.item && artShapeLoader.item.image.status === Image.Ready || false; \
160+ asynchronous: root.asynchronous; \
161+ visible: status == Loader.Ready; \
162+ sourceComponent: ShaderEffect { \
163+ id: overlay; \
164+ height: ' + height + ' \
165+ opacity: 0.6; \
166+ property var source: ShaderEffectSource { \
167+ id: shaderSource; \
168+ sourceItem: artShapeLoader.item; \
169+ onVisibleChanged: if (visible) scheduleUpdate(); \
170+ live: false; \
171+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height); \
172+ } \
173+ vertexShader: " \
174+ uniform highp mat4 qt_Matrix; \
175+ attribute highp vec4 qt_Vertex; \
176+ attribute highp vec2 qt_MultiTexCoord0; \
177+ varying highp vec2 coord; \
178+ void main() { \
179+ coord = qt_MultiTexCoord0; \
180+ gl_Position = qt_Matrix * qt_Vertex; \
181+ }"; \
182+ fragmentShader: " \
183+ varying highp vec2 coord; \
184+ uniform sampler2D source; \
185+ uniform lowp float qt_Opacity; \
186+ void main() { \
187+ lowp vec4 tex = texture2D(source, coord); \
188+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity; \
189+ }"; \
190+ } \
191+ }';
192+ }
193+
194+ var headerVerticalAnchors;
195+ if (isHorizontal) {
196+ headerVerticalAnchors = 'anchors.top: artShapeHolder.top; \
197+ anchors.topMargin: units.gu(1);';
198+ } else {
199+ if (inOverlay) {
200+ headerVerticalAnchors = 'anchors.bottom: artShapeHolder.bottom; \
201+ anchors.bottomMargin: units.gu(1);';
202+ } else {
203+ headerVerticalAnchors = 'anchors.top: artShapeHolder.bottom; \
204+ anchors.topMargin: units.gu(1);';
205+ }
206+ }
207+
208+
209+ if (hasMascot) {
210+ var useMascotShape = !hasBackground && !inOverlay;
211+ var anchors = "";
212+ if (isHorizontal) {
213+ anchors += 'anchors.left: artShapeHolder.right; \
214+ anchors.leftMargin: units.gu(1);';
215+ } else {
216+ anchors += 'anchors.left: parent.left; \
217+ anchors.leftMargin: units.gu(1);';
218+ }
219+ anchors += headerVerticalAnchors;
220+
221+ if (useMascotShape) {
222+ code += 'Loader { \
223+ id: mascotShapeLoader; \
224+ objectName: "mascotShapeLoader"; \
225+ asynchronous: root.asynchronous; \
226+ active: mascotImage.status === Image.Ready; \
227+ visible: active && status == Loader.Ready; \
228+ width: units.gu(6); \
229+ height: units.gu(5.625); \
230+ sourceComponent: UbuntuShape { image: mascotImage } \
231+ ' + anchors + '\
232+ }';
233+ }
234+
235+ code += 'Image { \
236+ id: mascotImage; \
237+ objectName: "mascotImage"; \
238+ ' + anchors + '\
239+ readonly property int maxSize: Math.max(width, height) * 4; \
240+ source: cardData && cardData["mascot"]; \
241+ width: units.gu(6); \
242+ height: ' + mascotImageHeight + '; \
243+ sourceSize { width: maxSize; height: maxSize } \
244+ fillMode: Image.PreserveAspectCrop; \
245+ horizontalAlignment: Image.AlignHCenter; \
246+ verticalAlignment: Image.AlignVCenter; \
247+ visible:' + (useMascotShape ? 'false' : 'true') + '; \
248+ }';
249+ }
250+
251+ var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"';
252+
253+ if (hasTitle) {
254+ var color;
255+ if (inOverlay) {
256+ color = '"white"';
257+ } else if (hasSummary) {
258+ color = 'summary.color';
259+ } else if (hasBackground) {
260+ color = summaryColorWithBackground;
261+ } else {
262+ color = '"grey"';
263+ }
264+ var anchors = "anchors.right: parent.right;";
265+ if (hasMascot) {
266+ anchors += 'anchors.left: mascotImage.right; \
267+ anchors.leftMargin: units.gu(1);';
268+ } else {
269+ anchors += 'anchors.left: parent.left; \
270+ anchors.leftMargin: units.gu(1);';
271+ }
272+ anchors += headerVerticalAnchors;
273+
274+ var titleAnchors = "";
275+ var subtitleAnchors = "";
276+ if (hasMascot && hasSubtitle) {
277+ titleAnchors = 'anchors { left: parent.left; right: parent.right }';
278+ code += 'Item { \
279+ ' + anchors + '\
280+ height: mascotImage.height; \
281+ Column { \
282+ anchors.verticalCenter: parent.verticalCenter; \
283+ spacing: units.dp(2); \
284+ width: parent.width - x;';
285+ } else {
286+ titleAnchors = anchors;
287+ subtitleAnchors = 'anchors.left: parent.left; \
288+ anchors.leftMargin: units.gu(1); \
289+ anchors.top: titleLabel.bottom; \
290+ anchors.topMargin: units.dp(2);';
291+ }
292+
293+ code += 'Label { \
294+ id: titleLabel; \
295+ objectName: "titleLabel"; \
296+ ' + titleAnchors + '\
297+ elide: Text.ElideRight; \
298+ fontSize: "small"; \
299+ wrapMode: Text.Wrap; \
300+ maximumLineCount: 2; \
301+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \
302+ color: ' + color + '; \
303+ text: root.title; \
304+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal; \
305+ horizontalAlignment: root.headerAlignment; \
306+ }';
307+
308+ if (hasSubtitle) {
309+ code += 'Label { \
310+ id: subtitleLabel; \
311+ objectName: "subtitleLabel"; \
312+ ' + subtitleAnchors + '\
313+ elide: Text.ElideRight; \
314+ fontSize: "small"; \
315+ wrapMode: Text.Wrap; \
316+ maximumLineCount: 2; \
317+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale); \
318+ color: ' + color + '; \
319+ text: cardData && cardData["subtitle"] || " "; \
320+ font.weight: Font.Light; \
321+ horizontalAlignment: root.headerAlignment; \
322+ }';
323+
324+ // Close Column and Item
325+ if (hasMascot)
326+ code += '} }';
327+ }
328+ }
329+
330+ if (hasSummary) {
331+ var summaryTopAnchor;
332+ if (isHorizontal) summaryTopAnchor = "artShapeHolder.bottom";
333+ else if (hasMascot) summaryTopAnchor = "mascotImage.bottom";
334+ else if (hasSubtitle) summaryTopAnchor = "subtitleLabel.bottom";
335+ else if (hasTitle) summaryTopAnchor = "titleLabel.bottom";
336+ else if (hasArt) summaryTopAnchor = "artShapeHolder.bottom";
337+ else summaryTopAnchor = "parent.top";
338+ var color;
339+ if (hasBackground) {
340+ color = summaryColorWithBackground;
341+ } else {
342+ color = '"grey"';
343+ }
344+ code += 'Label { \
345+ id: summary; \
346+ objectName: "summaryLabel"; \
347+ anchors { \
348+ top: ' + summaryTopAnchor + '; \
349+ left: parent.left; \
350+ right: parent.right; \
351+ margins: backgroundLoader.active ? units.gu(1) : 0; \
352+ topMargin: ' + (hasMascot || hasSubtitle ? 'anchors.margins' : 0) + '; \
353+ } \
354+ wrapMode: Text.Wrap; \
355+ maximumLineCount: 5; \
356+ elide: Text.ElideRight; \
357+ text: cardData && cardData["summary"] || ""; \
358+ height: text ? implicitHeight : 0; \
359+ fontSize: "small"; \
360+ color: ' + color + '; \
361+ }';
362+ }
363+
364+ // Close the AbstractButton
365+ if (hasSummary && hasBackground) {
366+ code += 'implicitHeight: summary.y + summary.height + (summary.text && backgroundLoader.active ? units.gu(1) : 0)';
367+ } else if (hasSummary) {
368+ code += 'implicitHeight: summary.y + summary.height';
369+ } else if (hasMascot) {
370+ code += 'implicitHeight: mascotImage.y + mascotImage.height';
371+ } else if (hasTitle) {
372+ code += 'implicitHeight: titleLabel.y + titleLabel.height';
373+ }
374+ code += '}';
375+
376+
377+
378+ code = imports + 'Component {' + code + '}';
379+// console.log(code)
380+ return Qt.createQmlObject(code, parent, "createCardComponent");
381+}
382+