@@ -0,0 +1,382 @@
5+ * Copyright (C) 2013 Canonical, Ltd.
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.
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.
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/>.
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;';
27+ code = 'AbstractButton { \
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; ';
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)';
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"; \
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"; \
70+ if (cardData && typeof cardData["background"] === "string") return cardData["background"]; \
71+ else if (template && typeof template["card-background"] === "string") return template["card-background"]; \
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; \
89+ code += 'readonly property size artShapeSize: artShapeLoader.item ? Qt.size(artShapeLoader.item.width, artShapeLoader.item.height) : Qt.size(-1, -1);'
90+ var imageWidthHeight;
93+ imageWidthHeight = 'width: height * artShape.aspect; \
94+ height: mascotImage.height + 2 * units.gu(1);'
96+ imageWidthHeight = 'width: height * artShape.aspect; \
97+ height: ' + mascotImageHeight + ' + 2 * units.gu(1);'
100+ imageWidthHeight = 'width: root.width; \
101+ height: width / artShape.aspect;'
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;' ) + '\
109+ id: artShapeLoader; \
110+ objectName: "artShapeLoader"; \
111+ active: cardData && cardData["art"] || false; \
112+ asynchronous: root.asynchronous; \
113+ visible: status == Loader.Ready; \
114+ sourceComponent: UbuntuShape { \
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 }); \
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 }); \
132+ objectName: "artImage"; \
133+ source: cardData && cardData["art"] || ""; \
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 + '\
148+ var height = 'mascotImage.height + 2 * units.gu(1);';
150+ var height = mascotImageHeight + ' + 2 * units.gu(1);';
153+ id: overlayLoader; \
155+ left: artShapeHolder.left; \
156+ right: artShapeHolder.right; \
157+ bottom: artShapeHolder.bottom; \
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 { \
164+ height: ' + height + ' \
166+ property var source: ShaderEffectSource { \
168+ sourceItem: artShapeLoader.item; \
169+ onVisibleChanged: if (visible) scheduleUpdate(); \
171+ sourceRect: Qt.rect(0, artShapeLoader.height - overlay.height, artShapeLoader.width, overlay.height); \
174+ uniform highp mat4 qt_Matrix; \
175+ attribute highp vec4 qt_Vertex; \
176+ attribute highp vec2 qt_MultiTexCoord0; \
177+ varying highp vec2 coord; \
179+ coord = qt_MultiTexCoord0; \
180+ gl_Position = qt_Matrix * qt_Vertex; \
183+ varying highp vec2 coord; \
184+ uniform sampler2D source; \
185+ uniform lowp float qt_Opacity; \
187+ lowp vec4 tex = texture2D(source, coord); \
188+ gl_FragColor = vec4(0, 0, 0, tex.a) * qt_Opacity; \
194+ var headerVerticalAnchors;
196+ headerVerticalAnchors = 'anchors.top: artShapeHolder.top; \
197+ anchors.topMargin: units.gu(1);';
200+ headerVerticalAnchors = 'anchors.bottom: artShapeHolder.bottom; \
201+ anchors.bottomMargin: units.gu(1);';
203+ headerVerticalAnchors = 'anchors.top: artShapeHolder.bottom; \
204+ anchors.topMargin: units.gu(1);';
210+ var useMascotShape = !hasBackground && !inOverlay;
213+ anchors += 'anchors.left: artShapeHolder.right; \
214+ anchors.leftMargin: units.gu(1);';
216+ anchors += 'anchors.left: parent.left; \
217+ anchors.leftMargin: units.gu(1);';
219+ anchors += headerVerticalAnchors;
221+ if (useMascotShape) {
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 } \
237+ objectName: "mascotImage"; \
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') + '; \
251+ var summaryColorWithBackground = 'backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : "grey"';
257+ } else if (hasSummary) {
258+ color = 'summary.color';
259+ } else if (hasBackground) {
260+ color = summaryColorWithBackground;
264+ var anchors = "anchors.right: parent.right;";
266+ anchors += 'anchors.left: mascotImage.right; \
267+ anchors.leftMargin: units.gu(1);';
269+ anchors += 'anchors.left: parent.left; \
270+ anchors.leftMargin: units.gu(1);';
272+ anchors += headerVerticalAnchors;
274+ var titleAnchors = "";
275+ var subtitleAnchors = "";
276+ if (hasMascot && hasSubtitle) {
277+ titleAnchors = 'anchors { left: parent.left; right: parent.right }';
280+ height: mascotImage.height; \
282+ anchors.verticalCenter: parent.verticalCenter; \
283+ spacing: units.dp(2); \
284+ width: parent.width - x;';
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);';
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 + '; \
304+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal; \
305+ horizontalAlignment: root.headerAlignment; \
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; \
324+ // Close Column and Item
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";
339+ if (hasBackground) {
340+ color = summaryColorWithBackground;
346+ objectName: "summaryLabel"; \
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) + '; \
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 + '; \
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';
378+ code = imports + 'Component {' + code + '}';
380+ return Qt.createQmlObject(code, parent, "createCardComponent");