on xenial help sign in
Add attribute UI.
c667d99Ying-Chun Liu
plugins/Dash/CardAttributesGrid.qmlnew file+44
@@ -0,0 +1,44 @@
1+import QtQuick 2.2
2+import QtQuick.Layouts 1.1
3+import Ubuntu.Components 1.1
4+import Ubuntu.Settings.Components 0.1
5+
6+GridLayout {
7+ id: grid
8+ anchors {
9+ left: parent.left;
10+ right: parent.right;
11+ }
12+ columns: 2 + repeater.count % 2
13+ property alias model: repeater.model
14+ Repeater {
15+ id: repeater
16+ delegate: Row {
17+ spacing: units.gu(0.5)
18+ readonly property int column: index % grid.columns;
19+ Layout.alignment: {
20+ if (column == 0) return Qt.AlignLeft;
21+ if (column == grid.columns - 1 || index == repeater.count - 1) return Qt.AlignRight;
22+ if (column == 1) return Qt.AlignHCenter;
23+ }
24+ Layout.columnSpan: index == repeater.count - 1 && grid.columns == 3 && column == 1 ? 2 : 1
25+ Layout.maximumWidth: Math.max(icon.width, label.x + label.implicitWidth)
26+ Layout.fillWidth: true
27+ StatusIcon {
28+ id: icon
29+ height: units.gu(2)
30+ sets: ["actions", "status", "apps"]
31+ source: "icon" in modelData ? modelData["icon"] : ""
32+ }
33+ Label {
34+ id: label
35+ width: parent.width - x
36+ anchors.verticalCenter: parent.verticalCenter
37+ text: "value" in modelData ? modelData["value"] : "";
38+ elide: Text.ElideRight
39+ maximumLineCount: 1
40+ font.weight: "style" in modelData && modelData["style"] == "highlighted" ? Font.DemiBold : Font.Light
41+ }
42+ }
43+ }
44+}
plugins/Dash/CardCreator.js+45−2
@@ -193,6 +193,21 @@ var kHeaderColumnCode = 'Column { \n\
193 ] \n\
193 ] \n\
194 }\n';
194 }\n';
195
195
196+// multiple column version of kHeaderColumnCode.
197+function kHeaderColumnCodeGenerator() {
198+ var headerColumnCodeTemplate = 'Column { \n\
199+ anchors.verticalCenter: parent.verticalCenter; \n\
200+ spacing: units.dp(2); \n\
201+ width: parent.width - x;\n\
202+ data: [ \n\
203+ %1 \n\
204+ ]\n\
205+ }\n';
206+ var args = Array.prototype.slice.call(arguments);
207+ var code = headerColumnCodeTemplate.arg(args.join(',\n'));
208+ return code;
209+}
210+
196// %1 is used as anchors of mascotShapeLoader
211// %1 is used as anchors of mascotShapeLoader
197var kMascotShapeLoaderCode = 'Loader { \n\
212var kMascotShapeLoaderCode = 'Loader { \n\
198 id: mascotShapeLoader; \n\
213 id: mascotShapeLoader; \n\
@@ -224,7 +239,7 @@ var kMascotImageCode = 'Image { \n\
224 }\n';
239 }\n';
225
240
226// %1 is used as anchors of titleLabel
241// %1 is used as anchors of titleLabel
227−// %1 is used as color of titleLabel
242+// %2 is used as color of titleLabel
228// %3 is used as extra condition for visible of titleLabel
243// %3 is used as extra condition for visible of titleLabel
229var kTitleLabelCode = 'Label { \n\
244var kTitleLabelCode = 'Label { \n\
230 id: titleLabel; \n\
245 id: titleLabel; \n\
@@ -258,6 +273,15 @@ var kSubtitleLabelCode = 'Label { \n\
258 horizontalAlignment: root.headerAlignment; \n\
273 horizontalAlignment: root.headerAlignment; \n\
259 }\n';
274 }\n';
260
275
276+// %1 is used as anchors of attributesRow
277+var kAttributesRowCode = 'CardAttributesGrid { \n\
278+ id: attributesRow; \n\
279+ objectName: "attributesRow"; \n\
280+ height: units.gu(1.5); \n\
281+ anchors { %1 } \n\
282+ model: cardData["attributes"] \n\
283+ }\n';
284+
261// %1 is used as top anchor of summary
285// %1 is used as top anchor of summary
262// %2 is used as topMargin anchor of summary
286// %2 is used as topMargin anchor of summary
263// %3 is used as color of summary
287// %3 is used as color of summary
@@ -308,6 +332,7 @@ function cardString(template, components) {
308 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
332 var headerAsOverlay = hasArt && template && template["overlay"] === true && (hasTitle || hasMascot);
309 var hasSubtitle = hasTitle && components["subtitle"] || false;
333 var hasSubtitle = hasTitle && components["subtitle"] || false;
310 var hasHeaderRow = hasMascot && hasTitle;
334 var hasHeaderRow = hasMascot && hasTitle;
335+ var hasAttributes = components["attributes"] || false;
311
336
312 if (hasBackground) {
337 if (hasBackground) {
313 code += kBackgroundLoaderCode;
338 code += kBackgroundLoaderCode;
@@ -377,6 +402,8 @@ function cardString(template, components) {
377 code += 'readonly property int headerHeight: row.height;\n'
402 code += 'readonly property int headerHeight: row.height;\n'
378 } else if (hasMascot) {
403 } else if (hasMascot) {
379 code += 'readonly property int headerHeight: mascotImage.height;\n'
404 code += 'readonly property int headerHeight: mascotImage.height;\n'
405+ } else if (hasAttributes) {
406+ code += 'readonly property int headerHeight: titleLabel.height + titleLabel.anchors.topMargin + subtitleLabel.height + subtitleLabel.anchors.topMargin + attributesRow.height + attributesRow.anchors.topMargin;\n'
380 } else if (hasSubtitle) {
407 } else if (hasSubtitle) {
381 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
408 code += 'readonly property int headerHeight: titleLabel.height + subtitleLabel.height + subtitleLabel.anchors.topMargin;\n'
382 } else if (hasTitle) {
409 } else if (hasTitle) {
@@ -425,10 +452,12 @@ function cardString(template, components) {
425
452
426 var titleAnchors;
453 var titleAnchors;
427 var subtitleAnchors;
454 var subtitleAnchors;
455+ var attributesAnchors;
428 if (hasMascot && hasSubtitle) {
456 if (hasMascot && hasSubtitle) {
429 // Using row + column
457 // Using row + column
430 titleAnchors = 'left: parent.left; right: parent.right';
458 titleAnchors = 'left: parent.left; right: parent.right';
431 subtitleAnchors = titleAnchors;
459 subtitleAnchors = titleAnchors;
460+ attributesAnchors = subtitleAnchors;
432 } else if (hasMascot) {
461 } else if (hasMascot) {
433 // Using row + label
462 // Using row + label
434 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
463 titleAnchors = 'verticalCenter: parent.verticalCenter;\n'
@@ -454,6 +483,11 @@ function cardString(template, components) {
454 right: titleLabel.right; \n\
483 right: titleLabel.right; \n\
455 top: titleLabel.bottom; \n\
484 top: titleLabel.bottom; \n\
456 topMargin: units.dp(2);\n';
485 topMargin: units.dp(2);\n';
486+ attributesAnchors = 'left: subtitleLabel.left; \n\
487+ leftMargin: subtitleLabel.leftMargin; \n\
488+ right: subtitleLabel.right; \n\
489+ top: subtitleLabel.bottom; \n\
490+ topMargin: units.dp(2);\n';
457 }
491 }
458
492
459 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
493 var titleLabelVisibleExtra = (headerAsOverlay ? '&& overlayLoader.active': '');
@@ -466,8 +500,16 @@ function cardString(template, components) {
466 if (hasMascot && hasSubtitle) {
500 if (hasMascot && hasSubtitle) {
467 // If using row + column wrap the code in the column
501 // If using row + column wrap the code in the column
468 titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
502 titleSubtitleCode = kHeaderColumnCode.arg(titleCode).arg(subtitleCode);
503+ if (hasAttributes) {
504+ var attributesCode = kAttributesRowCode.arg(attributesAnchors);
505+ titleSubtitleCode = kHeaderColumnCodeGenerator(titleCode, subtitleCode, attributesCode);
506+ }
469 } else {
507 } else {
470 titleSubtitleCode = titleCode + subtitleCode;
508 titleSubtitleCode = titleCode + subtitleCode;
509+ if (hasAttributes) {
510+ var attributesCode = kAttributesRowCode.arg(attributesAnchors);
511+ titleSubtitleCode = titleSubtitleCode + attributesCode;
512+ }
471 }
513 }
472 }
514 }
473
515
@@ -526,7 +568,8 @@ function cardString(template, components) {
526function createCardComponent(parent, template, components) {
568function createCardComponent(parent, template, components) {
527 var imports = 'import QtQuick 2.2; \n\
569 var imports = 'import QtQuick 2.2; \n\
528 import Ubuntu.Components 0.1; \n\
570 import Ubuntu.Components 0.1; \n\
529− import Ubuntu.Thumbnailer 0.1;\n';
571+ import Ubuntu.Thumbnailer 0.1;\n\
572+ import Dash 0.1;\n';
530 var card = cardString(template, components);
573 var card = cardString(template, components);
531 var code = imports + 'Component {\n' + card + '}\n';
574 var code = imports + 'Component {\n' + card + '}\n';
532 return Qt.createQmlObject(code, parent, "createCardComponent");
575 return Qt.createQmlObject(code, parent, "createCardComponent");
plugins/Dash/qmldir+1
@@ -3,3 +3,4 @@ plugin Dash-qml
3typeinfo Dash.qmltypes
3typeinfo Dash.qmltypes
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
4singleton CardCreatorCache 0.1 CardCreatorCache.qml
5ScopeStyle 0.1 ScopeStyle.qml
5ScopeStyle 0.1 ScopeStyle.qml
6+CardAttributesGrid 0.1 CardAttributesGrid.qml
tests/plugins/Dash/CMakeLists.txt+1
@@ -79,3 +79,4 @@ set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
79set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
79set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "LC_ALL=C")
80add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
80add_qml_test(. ScopeStyle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
81add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
81add_qml_test(. ListViewWithPageHeaderQML IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
82+add_qml_test(. CardAttributesGrid IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
tests/plugins/Dash/cardcreator/7.resnew file+128
@@ -0,0 +1,128 @@
1+AbstractButton {
2+ id: root;
3+ property var template;
4+ property var components;
5+ property var cardData;
6+ property var artShapeBorderSource: undefined;
7+ property real fontScale: 1.0;
8+ property var scopeStyle: null;
9+ property int headerAlignment: Text.AlignLeft;
10+ property int fixedHeaderHeight: -1;
11+ property size fixedArtShapeSize: Qt.size(-1, -1);
12+ readonly property string title: cardData && cardData["title"] || "";
13+ property bool asynchronous: true;
14+ property bool showHeader: true;
15+ implicitWidth: childrenRect.width;
16+Loader {
17+ id: backgroundLoader;
18+ objectName: "backgroundLoader";
19+ anchors.fill: parent;
20+ asynchronous: root.asynchronous;
21+ visible: status == Loader.Ready;
22+ sourceComponent: UbuntuShape {
23+ objectName: "background";
24+ radius: "medium";
25+ color: getColor(0) || "white";
26+ gradientColor: getColor(1) || color;
27+ anchors.fill: parent;
28+ image: backgroundImage.source ? backgroundImage : null;
29+ property real luminance: 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
30+ property Image backgroundImage: Image {
31+ objectName: "backgroundImage";
32+ source: {
33+ if (cardData && typeof cardData["background"] === "string") return cardData["background"];
34+ else if (template && typeof template["card-background"] === "string") return template["card-background"];
35+ else return "";
36+ }
37+ }
38+ function getColor(index) {
39+ if (cardData && typeof cardData["background"] === "object"
40+ && (cardData["background"]["type"] === "color" || cardData["background"]["type"] === "gradient")) {
41+ return cardData["background"]["elements"][index];
42+ } else if (template && typeof template["card-background"] === "object"
43+ && (template["card-background"]["type"] === "color" || template["card-background"]["type"] === "gradient")) {
44+ return template["card-background"]["elements"][index];
45+ } else return undefined;
46+ }
47+ }
48+ }
49+readonly property size artShapeSize: Qt.size(-1, -1);
50+readonly property int headerHeight: row.height;
51+Row {
52+ id: row;
53+ objectName: "outerRow";
54+ property real margins: units.gu(1);
55+ spacing: margins;
56+ height: root.fixedHeaderHeight != -1 ? root.fixedHeaderHeight : implicitHeight;
57+ anchors { top: parent.top;
58+ topMargin: units.gu(1);
59+left: parent.left;
60+ }
61+ anchors.right: parent.right;
62+ anchors.margins: margins;
63+ data: [ Image {
64+ id: mascotImage;
65+ objectName: "mascotImage";
66+ anchors { verticalCenter: parent.verticalCenter; }
67+ readonly property int maxSize: Math.max(width, height) * 4;
68+ source: cardData && cardData["mascot"];
69+ width: units.gu(6);
70+ height: units.gu(5.625);
71+ sourceSize { width: maxSize; height: maxSize }
72+ fillMode: Image.PreserveAspectCrop;
73+ horizontalAlignment: Image.AlignHCenter;
74+ verticalAlignment: Image.AlignVCenter;
75+ visible: showHeader;
76+ }
77+
78+ ,
79+ Column {
80+ anchors.verticalCenter: parent.verticalCenter;
81+ spacing: units.dp(2);
82+ width: parent.width - x;
83+ data: [
84+ Label {
85+ id: titleLabel;
86+ objectName: "titleLabel";
87+ anchors { left: parent.left; right: parent.right }
88+ elide: Text.ElideRight;
89+ fontSize: "small";
90+ wrapMode: Text.Wrap;
91+ maximumLineCount: 2;
92+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
93+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
94+ visible: showHeader ;
95+ text: root.title;
96+ font.weight: components && components["subtitle"] ? Font.DemiBold : Font.Normal;
97+ horizontalAlignment: root.headerAlignment;
98+ }
99+,
100+Label {
101+ id: subtitleLabel;
102+ objectName: "subtitleLabel";
103+ anchors { left: parent.left; right: parent.right }
104+ elide: Text.ElideRight;
105+ fontSize: "small";
106+ font.pixelSize: Math.round(FontUtils.sizeToPixels(fontSize) * fontScale);
107+ color: backgroundLoader.active && backgroundLoader.item && backgroundLoader.item.luminance < 0.7 ? "white" : (root.scopeStyle ? root.scopeStyle.foreground : "grey");
108+ visible: titleLabel.visible && titleLabel.text;
109+ text: cardData && cardData["subtitle"] || "";
110+ font.weight: Font.Light;
111+ horizontalAlignment: root.headerAlignment;
112+ }
113+,
114+CardAttributesGrid {
115+ id: attributesRow;
116+ objectName: "attributesRow";
117+ height: units.gu(1.5);
118+ anchors { left: parent.left; right: parent.right }
119+ model: cardData["attributes"]
120+ }
121+
122+ ]
123+ }
124+
125+ ]
126+ }
127+implicitHeight: row.y + row.height + units.gu(1);
128+}
tests/plugins/Dash/cardcreator/7.tstnew file+3
@@ -0,0 +1,3 @@
1+template: {"card-background":{"elements":["#E9E9E9"],"type":"color"},"card-layout":"vertical","card-size":"medium","category-layout":"grid","collapsed-rows":2}
2+components: {"art":{"aspect-ratio":1,"fill-mode":"crop"},"background":{"field":"background"},"mascot":{"field":"icon"},"subtitle":{"field":"author"},"title":{"field":"title"},"attributes":[{"text":"text1","icon":"search"},{"text":"text2","icon":"cancel"}]}
3+result: 7.res
tests/plugins/Dash/tst_CardAttributesGrid.qmlnew file+51
@@ -0,0 +1,51 @@
1+/*
2+ * Copyright 2013 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+import QtQuick 2.0
18+import QtTest 1.0
19+import Dash 0.1
20+
21+Item {
22+ width: units.gu(40)
23+ height: units.gu(30)
24+
25+ property var testData: [
26+ [{"value":"text1"},{"value":"text2"}],
27+ [{"value":"text1"},{"value":"text2"},{"value":"text3"}]
28+ ]
29+
30+ CardAttributesGrid {
31+ id: cardAttributesGrid
32+ model: testData[0]
33+ }
34+
35+ TestCase {
36+ name: "CardAttributesGridTest"
37+ when: windowShown
38+
39+ function init() {
40+ }
41+
42+ function test_columns_data() {
43+ return testData;
44+ }
45+
46+ function test_columns(data) {
47+ cardAttributesGrid.model = data;
48+ compare(cardAttributesGrid.columns, data.length);
49+ }
50+ }
51+}
tests/qmltests/Dash/CardHelpers.js+5−3
@@ -16,7 +16,7 @@
16
16
17.pragma library
17.pragma library
18
18
19−var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary", "attributes"]
20
20
21var defaultLayout = ' \
21var defaultLayout = ' \
22{ \
22{ \
@@ -48,7 +48,8 @@ var defaultLayout = ' \
48 "empty": "image://theme/rating-star-empty" \
48 "empty": "image://theme/rating-star-empty" \
49 }, \
49 }, \
50 "alt-rating": null, \
50 "alt-rating": null, \
51− "summary": null \
51+ "summary": null, \
52+ "attributes": null \
52 }, \
53 }, \
53 "resources": {} \
54 "resources": {} \
54}'
55}'
@@ -59,7 +60,8 @@ var fullMapping = ' \
59 "art": "art", \
60 "art": "art", \
60 "subtitle": "subtitle", \
61 "subtitle": "subtitle", \
61 "mascot": "mascot", \
62 "mascot": "mascot", \
62− "summary": "summary" \
63+ "summary": "summary", \
64+ "attributes": "attributes" \
63}'
65}'
64
66
65
67
tests/qmltests/Dash/tst_Card.qml+2−1
@@ -33,7 +33,8 @@ Rectangle {
33 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
33 "mascot": "../../../tests/qmltests/Dash/artwork/avatar.png",
34 "title": "foo",
34 "title": "foo",
35 "subtitle": "bar",
35 "subtitle": "bar",
36− "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
36+ "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
37+ "attributes": [{"value":"text1"},{"value":"text2"}]
37 }'
38 }'
38
39
39 property var cardsModel: [
40 property var cardsModel: [