├── AUTHORS.md ├── LICENSE ├── README.md ├── examples ├── app │ ├── DialogsPage.qml │ ├── app.pro │ ├── app.qmlproject │ ├── deployment.pri │ ├── icons │ │ ├── apps.svg │ │ ├── arrow_back.svg │ │ ├── done.svg │ │ ├── hamburger.svg │ │ └── more_vert.svg │ ├── main.cpp │ ├── main.qml │ └── qml.qrc └── examples.pro ├── modules ├── Mut │ ├── AppBar.qml │ ├── ApplicationWindow.qml │ ├── Card.qml │ ├── DebugOutline.qml │ ├── Device.qml │ ├── Dialog.qml │ ├── Divider.qml │ ├── Icon.qml │ ├── Layouts │ │ ├── ConditionalLayout.qml │ │ ├── DeviceLayout.qml │ │ ├── OrientationLayout.qml │ │ └── qmldir │ ├── ListItems │ │ ├── DoubleLineItem.qml │ │ ├── ListView.qml │ │ ├── SingleLineItem.qml │ │ ├── Tile.qml │ │ └── qmldir │ ├── NavDrawer.qml │ ├── Page.qml │ ├── Paper.qml │ ├── Popup.qml │ ├── Scrim.qml │ ├── Styles │ │ ├── ActionButtonStyle.qml │ │ ├── BaseButtonStyle.qml │ │ ├── BaseTextFieldStyle.qml │ │ ├── CheckBoxStyle.qml │ │ ├── FlatButtonStyle.qml │ │ ├── FloatingTextFieldStyle.qml │ │ ├── NormalTextFieldStyle.qml │ │ ├── ProgressBarStyle.qml │ │ ├── RaisedButtonStyle.qml │ │ ├── SwitchStyle.qml │ │ ├── ToolBarStyle.qml │ │ ├── ToolButtonStyle.qml │ │ └── qmldir │ ├── Surface.qml │ ├── TextField.qml │ ├── Themes │ │ ├── ButtonPalette.qml │ │ ├── CheckBoxPalette.qml │ │ ├── ElementPalette.qml │ │ ├── Palette.qml │ │ ├── SwitchPalette.qml │ │ ├── TextFieldPalette.qml │ │ ├── Theme.qml │ │ └── qmldir │ ├── ToolBar.qml │ ├── qmldir │ └── units.qml └── modules.pro ├── mut.pro └── tests ├── test.svg ├── tests.cpp ├── tests.pro ├── tests.qrc ├── tst_appbar.qml ├── tst_application_window.qml ├── tst_button_style.qml ├── tst_card.qml ├── tst_conditional_layout.qml ├── tst_device_layout.qml ├── tst_divider.qml ├── tst_icon.qml ├── tst_navdrawer.qml ├── tst_orientation_layout.qml ├── tst_page.qml ├── tst_paper.qml ├── tst_scrim.qml ├── tst_single_line_item.qml ├── tst_theme.qml ├── tst_tile.qml ├── tst_toolbar.qml └── tst_units.qml /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Authors 2 | ------- 3 | 4 | * Federico Frenguelli 5 | * Emanuele Palazzetti 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Evonove 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MUT 2 | 3 | **M**UT **U**i **T**oolkit is a Qt/QML components collection, based on Google [Material Design specifications][1]. 4 | Using a theme centric approach you can improve the look and feel of your applications using the default material theme, or 5 | you can apply any customization according to your application brand. 6 | 7 | [1]: http://www.google.it/design/spec/material-design/introduction.html 8 | 9 | ## Implemented components 10 | 11 | At the moment, the following high-level components are provided: 12 | 13 | * [standard and floating buttons][2] 14 | * [cards][3] 15 | * [dialogs][4] 16 | * [list views with primary and secondary actions][5] 17 | * [menus][6] 18 | * [standard and floating labels textfield][7] 19 | * [actions toolbar][8] 20 | 21 | Any missing component will be released soon according to our roadmap. 22 | 23 | [2]: http://www.google.it/design/spec/components/buttons.html 24 | [3]: http://www.google.it/design/spec/components/cards.html 25 | [4]: http://www.google.it/design/spec/components/dialogs.html 26 | [5]: http://www.google.it/design/spec/components/lists.html 27 | [6]: http://www.google.it/design/spec/components/menus.html 28 | [7]: http://www.google.it/design/spec/components/text-fields.html 29 | [8]: http://www.google.it/design/spec/components/toolbars.html 30 | 31 | ## Roadmap 32 | 33 | * provide a high-level documentation for material components APIs 34 | * provide a step-by-step tutorial that shows how to install and configure MUT 35 | * provide a low-level documentation for material components (such as Tiles) for major customizations 36 | * provide all missing material components 37 | 38 | The project is currently in alpha stage and will be available for **release late 2015 - early 2016**. 39 | 40 | ## License 41 | 42 | MUT Ui Toolkit is released under the terms of the **MIT license**. Full details in `LICENSE` file. 43 | -------------------------------------------------------------------------------- /examples/app/DialogsPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 as Q 3 | import Mut 0.1 4 | import Mut.Styles 0.1 5 | 6 | Page { 7 | id: root 8 | title: qsTr("Dialogs") 9 | 10 | Paper { 11 | anchors.fill: parent 12 | 13 | Q.Button { 14 | text: "open dialog" 15 | style: RaisedButtonStyle {} 16 | onClicked: dialogRoot.open() 17 | } 18 | } 19 | 20 | Dialog { 21 | id: dialogRoot 22 | 23 | dialogComponent: Card { 24 | implicitWidth: __column.implicitWidth + Units.dp(48) 25 | implicitHeight: __column.implicitHeight + Units.dp(24) 26 | 27 | Column { 28 | id: __column 29 | anchors { 30 | fill: parent 31 | topMargin: Units.dp(24) 32 | leftMargin: Units.dp(24) 33 | rightMargin: Units.dp(24) 34 | } 35 | Text { 36 | text: qsTr("This is a dialog example") 37 | } 38 | TextField { 39 | id: nameField 40 | placeholderText: qsTr("Enter some text") 41 | style: NormalTextFieldStyle {} 42 | } 43 | 44 | Row { 45 | Q.Button { 46 | text: qsTr("ok") 47 | style: FlatButtonStyle {} 48 | onClicked: dialogRoot.close() 49 | } 50 | Q.Button { 51 | text: qsTr("cancel") 52 | style: FlatButtonStyle {} 53 | onClicked: dialogRoot.close() 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/app/app.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick widgets svg 4 | 5 | SOURCES += main.cpp 6 | 7 | RESOURCES += qml.qrc 8 | 9 | include(deployment.pri) 10 | 11 | QML_IMPORT_PATH += $$OUT_PWD/../../modules 12 | -------------------------------------------------------------------------------- /examples/app/app.qmlproject: -------------------------------------------------------------------------------- 1 | /* File generated by Qt Creator */ 2 | 3 | import QmlProject 1.1 4 | 5 | Project { 6 | mainFile: "main.qml" 7 | 8 | /* Include .qml, .js, and image files from current directory and subdirectories */ 9 | QmlFiles { 10 | directory: "." 11 | } 12 | JavaScriptFiles { 13 | directory: "." 14 | } 15 | ImageFiles { 16 | directory: "." 17 | } 18 | /* List of plugin directories passed to QML runtime */ 19 | importPaths: [ "../../modules" ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/app/deployment.pri: -------------------------------------------------------------------------------- 1 | android-no-sdk { 2 | target.path = /data/user/qt 3 | export(target.path) 4 | INSTALLS += target 5 | } else:android { 6 | x86 { 7 | target.path = /libs/x86 8 | } else: armeabi-v7a { 9 | target.path = /libs/armeabi-v7a 10 | } else { 11 | target.path = /libs/armeabi 12 | } 13 | export(target.path) 14 | INSTALLS += target 15 | } else:unix { 16 | isEmpty(target.path) { 17 | qnx { 18 | target.path = /tmp/$${TARGET}/bin 19 | } else { 20 | target.path = /opt/$${TARGET}/bin 21 | } 22 | export(target.path) 23 | } 24 | INSTALLS += target 25 | } 26 | 27 | export(INSTALLS) 28 | -------------------------------------------------------------------------------- /examples/app/icons/apps.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /examples/app/icons/arrow_back.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /examples/app/icons/done.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /examples/app/icons/hamburger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/app/icons/more_vert.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /examples/app/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication app(argc, argv); 7 | QQmlApplicationEngine engine; 8 | engine.addImportPath(QApplication::applicationDirPath() + QStringLiteral("/../../modules")); 9 | engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); 10 | 11 | return app.exec(); 12 | } 13 | -------------------------------------------------------------------------------- /examples/app/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtQuick.Layouts 1.1 4 | 5 | import Mut 0.1 6 | import Mut.Styles 0.1 7 | import Mut.Themes 0.1 8 | import Mut.Layouts 0.1 9 | import Mut.ListItems 0.1 10 | 11 | 12 | ApplicationWindow { 13 | id: app 14 | 15 | title: qsTr("Mut App") 16 | visible: true 17 | 18 | pageStack.initialItem: page1 19 | pageStack.onCurrentItemChanged: drawer.close() 20 | pageStack.focus: !drawer.opened 21 | 22 | Component.onCompleted: { 23 | Theme.imageAssetsPath = "qrc:///icons/"; 24 | } 25 | 26 | property list actions: [ 27 | Action { 28 | text: "Item 1" 29 | iconSource: Qt.resolvedUrl("./icons/apps.svg") 30 | onTriggered: { 31 | console.log("NavDrawer action 1"); 32 | } 33 | }, 34 | Action { 35 | text: "Item 2" 36 | iconSource: Qt.resolvedUrl("./icons/hamburger.svg") 37 | onTriggered: { 38 | console.log("NavDrawer action 2"); 39 | } 40 | }, 41 | Action { 42 | text: "Dialogs" 43 | iconSource: Qt.resolvedUrl("./icons/hamburger.svg") 44 | onTriggered: pageStack.push(Qt.resolvedUrl("DialogsPage.qml")) 45 | } 46 | ] 47 | 48 | NavDrawer { 49 | id: drawer 50 | focus: drawer.opened 51 | 52 | ListView { 53 | anchors.fill: parent 54 | model: app.actions 55 | 56 | delegate: SingleLineItem { 57 | text: model.text 58 | primaryAction: modelData 59 | } 60 | } 61 | } 62 | 63 | Component { 64 | id: page1 65 | 66 | Page { 67 | title: qsTr("NavDrawer") 68 | 69 | navAction: Action { 70 | iconSource: Qt.resolvedUrl("./icons/hamburger.svg") 71 | onTriggered: { 72 | drawer.toggle(); 73 | } 74 | } 75 | 76 | actions: [ 77 | Action { 78 | iconSource: Qt.resolvedUrl("./icons/apps.svg") 79 | }, 80 | Action { 81 | iconSource: Qt.resolvedUrl("./icons/more_vert.svg") 82 | onTriggered: { 83 | push(page2); 84 | } 85 | }, 86 | Action { 87 | iconSource: Qt.resolvedUrl("./icons/more_vert.svg") 88 | onTriggered: { 89 | push(page2a); 90 | } 91 | }, 92 | Action { 93 | iconSource: Qt.resolvedUrl("./icons/more_vert.svg") 94 | onTriggered: { 95 | push(page3); 96 | } 97 | }, 98 | Action { 99 | iconSource: Qt.resolvedUrl("./icons/more_vert.svg") 100 | onTriggered: { 101 | push(page4); 102 | } 103 | } 104 | ] 105 | } 106 | } 107 | 108 | Component { 109 | id: page2 110 | 111 | Page { 112 | title: qsTr("ListViews") 113 | 114 | Paper { 115 | anchors.fill: parent 116 | 117 | ListView { 118 | anchors.fill: parent 119 | model: 5 120 | 121 | delegate: SingleLineItem { 122 | text: "Single line item" 123 | primaryAction: Action { 124 | iconSource: Qt.resolvedUrl("./icons/apps.svg") 125 | onTriggered: { 126 | console.log("primary") 127 | } 128 | } 129 | secondaryAction: Action { 130 | iconSource: Qt.resolvedUrl("./icons/apps.svg") 131 | onTriggered: { 132 | console.log("secondary") 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | Component { 142 | id: page2a 143 | 144 | Page { 145 | title: qsTr("Double line items") 146 | 147 | Paper { 148 | anchors.fill: parent 149 | 150 | ListView { 151 | anchors.fill: parent 152 | model: 5 153 | 154 | delegate: DoubleLineItem { 155 | primaryText: "TEEXT" 156 | secondaryText: "teeeext really i'm a text and i will text you till the text " 157 | primaryAction: Action { 158 | iconSource: Qt.resolvedUrl("./icons/apps.svg") 159 | onTriggered: console.log("Primary action") 160 | } 161 | secondaryAction: Action { 162 | iconSource: Qt.resolvedUrl("./icons/apps.svg") 163 | onTriggered: console.log("Secondary action") 164 | } 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | Component { 172 | id: page3 173 | 174 | Page { 175 | title: qsTr("Components") 176 | 177 | Item { 178 | anchors.fill: parent 179 | anchors.margins: Units.dp(16) 180 | ColumnLayout { 181 | anchors.centerIn: parent 182 | spacing: Units.dp(24) 183 | 184 | TextField { 185 | placeholderText: "Enter your username" 186 | style: NormalTextFieldStyle {} 187 | } 188 | 189 | TextField { 190 | placeholderText: "Enter your password" 191 | hintText: "This is an hint" 192 | hasError: text === "error" 193 | 194 | style: FloatingTextFieldStyle {} 195 | } 196 | 197 | // flat button 198 | Button { 199 | text: qsTr("excellent") 200 | style: FlatButtonStyle {} 201 | onClicked: { 202 | pop(); 203 | } 204 | } 205 | 206 | // raised button 207 | Button { 208 | text: qsTr("pupp") 209 | style: RaisedButtonStyle {} 210 | } 211 | // raised button 212 | Button { 213 | text: qsTr("foobar") 214 | style: RaisedButtonStyle {} 215 | enabled: false 216 | } 217 | 218 | Button { 219 | iconSource: Qt.resolvedUrl("./icons/apps.svg") 220 | style: ActionButtonStyle {} 221 | } 222 | Switch { 223 | style: SwitchStyle {} 224 | } 225 | Switch { 226 | enabled: false 227 | style: SwitchStyle {} 228 | } 229 | CheckBox { 230 | style: CheckBoxStyle {} 231 | text: "Enabled" 232 | } 233 | CheckBox { 234 | style: CheckBoxStyle {} 235 | enabled: false 236 | text: "Disabled" 237 | } 238 | CheckBox { 239 | style: CheckBoxStyle {} 240 | enabled: false 241 | checked: true 242 | text: "Disabled checked" 243 | } 244 | Rectangle { 245 | width: Units.dp(48); height: Units.dp(48) 246 | color: "blue" 247 | Icon { 248 | image.source: Qt.resolvedUrl("./icons/apps.svg") 249 | } 250 | } 251 | Icon { 252 | image.source: Qt.resolvedUrl("./icons/apps.svg") 253 | colorOverlay: 'red' 254 | } 255 | Paper { 256 | width: Units.dp(200); height: Units.dp(100) 257 | elevation: 2 258 | 259 | Surface { 260 | anchors.fill: parent 261 | } 262 | } 263 | Card { 264 | width: Units.dp(200); height: Units.dp(200) 265 | } 266 | } 267 | } 268 | } 269 | } 270 | 271 | Component { 272 | id: page4 273 | 274 | Page { 275 | id: layoutPage 276 | title: qsTr("Layouting") 277 | 278 | property int model: 3 279 | 280 | Component { 281 | id: layout_landscape 282 | 283 | RowLayout { 284 | anchors.margins: 20 285 | 286 | Repeater { 287 | model: layoutPage.model 288 | Rectangle { 289 | Layout.fillWidth: true 290 | Layout.fillHeight: true 291 | color: "#5d5b59" 292 | 293 | Label { 294 | anchors.centerIn: parent 295 | text: "I'm a box!" 296 | color: "white" 297 | } 298 | } 299 | } 300 | } 301 | } 302 | 303 | Component { 304 | id: layout_portrait 305 | 306 | ColumnLayout { 307 | anchors.margins: 20 308 | 309 | Repeater { 310 | model: layoutPage.model 311 | 312 | Rectangle { 313 | Layout.fillWidth: true 314 | Layout.fillHeight: true 315 | color: "#5d5b59" 316 | 317 | Label { 318 | anchors.centerIn: parent 319 | text: "I'm a box!" 320 | color: "white" 321 | } 322 | } 323 | } 324 | } 325 | } 326 | 327 | ConditionalLayout { 328 | anchors.fill: parent 329 | when: app.width > 600 ? 1 : 0 330 | 331 | layouts: [ 332 | layout_portrait, 333 | layout_landscape 334 | ] 335 | } 336 | } 337 | } 338 | } 339 | 340 | -------------------------------------------------------------------------------- /examples/app/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | DialogsPage.qml 5 | icons/arrow_back.svg 6 | icons/apps.svg 7 | icons/more_vert.svg 8 | icons/hamburger.svg 9 | icons/done.svg 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/examples.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS += app 3 | -------------------------------------------------------------------------------- /modules/Mut/AppBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.3 4 | import QtQuick.Window 2.2 5 | 6 | import Mut 0.1 7 | import Mut.Styles 0.1 8 | import Mut.Themes 0.1 9 | 10 | 11 | ToolBar { 12 | id: root 13 | 14 | property Page page 15 | 16 | property Action defaultNavAction: null 17 | 18 | implicitHeight: { 19 | if (Device.size >= Device.sizeLarge) { 20 | Units.dp(64); 21 | } else if (Device.primaryOrientation == Qt.PortraitOrientation) { 22 | Units.dp(56); 23 | } else { 24 | Units.dp(48); 25 | } 26 | } 27 | 28 | elevation: 2 29 | fillWidth: true 30 | 31 | RowLayout { 32 | width: parent.width 33 | anchors.verticalCenter: parent.verticalCenter 34 | spacing: Units.dp(1) 35 | Button { 36 | action: page && page.navAction ? page.navAction : root.defaultNavAction 37 | style: ToolButtonStyle {} 38 | } 39 | Text { 40 | id: title 41 | text: page ? page.title : "" 42 | elide: Text.ElideRight 43 | color: Theme.p.textPrimary 44 | font { 45 | pixelSize: Units.sp(20) 46 | weight: Font.DemiBold 47 | } 48 | Layout.fillWidth: true 49 | } 50 | 51 | Repeater { 52 | model: page ? page.actions : null 53 | Button { 54 | action: modelData 55 | style: ToolButtonStyle {} 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/Mut/ApplicationWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 as Controls 3 | import QtQuick.Window 2.2 4 | 5 | import Mut 0.1 6 | import Mut.Themes 0.1 7 | 8 | /*! \qmltype ApplicationWindow 9 | \inqmlmodule Mut 0.1 10 | \ingroup applicationwindow 11 | 12 | \brief Provides a top-level application window. 13 | 14 | ApplicationWindow extends \l {QtQuick.Controls::ApplicationWindow} that adds 15 | convenience for sizing items and components. 16 | 17 | \qml 18 | import QtQuick 2.4 19 | import Mut 0.1 20 | 21 | ApplicationWindow { 22 | title: "Application Name" 23 | 24 | width: Units.dp(800) 25 | height: Units.dp(600) 26 | } 27 | \endqml 28 | */ 29 | Controls.ApplicationWindow { 30 | id: app 31 | 32 | width: 800 33 | height: 600 34 | 35 | color: Theme.p.background 36 | 37 | /*! 38 | \qmlproperty AppBar ApplicationWindow::appBar 39 | 40 | This property holds the \l AppBar. 41 | 42 | By default, this value is set and the page property is bound to currentItem 43 | of the page stack. 44 | 45 | Note that the default toolBar property is bound to the appBar so developers 46 | should only interact with the appBar property. 47 | */ 48 | property AppBar appBar: AppBar { 49 | id: appBar 50 | page: __stack.currentItem 51 | } 52 | 53 | toolBar: appBar 54 | 55 | property alias pageStack: __stack 56 | 57 | Controls.StackView { 58 | id: __stack 59 | anchors.fill: parent 60 | 61 | Keys.onReleased: { 62 | if (event.key === Qt.Key_Back || event.key === Qt.Key_Escape) { 63 | if (__stack.depth > 1) { 64 | __stack.pop(); 65 | event.accepted = true; 66 | } 67 | } 68 | } 69 | } 70 | 71 | Component.onCompleted: { 72 | /* FIXME: both Units and Device should be implemented at C++ 73 | level. It's easier to bind and change values from QScreen */ 74 | Units.pixelDensity = Qt.binding(function() { return Screen.pixelDensity; }); 75 | initDevice(); 76 | } 77 | 78 | function initDevice() { 79 | Device.primaryOrientation = Qt.binding(function () { return Screen.primaryOrientation; }); 80 | Device.size = Qt.binding(function() { 81 | var dpWidth = Units.pxToDp(Screen.width); 82 | var dpHeight = Units.pxToDp(Screen.height); 83 | 84 | var minLength = Math.min(dpWidth, dpHeight); 85 | var maxLength = Math.max(dpWidth, dpHeight); 86 | 87 | // set the device size 88 | if (maxLength >= "960" && minLength >= "720") 89 | return Device.sizeXLarge; 90 | else if (maxLength >= "640" && minLength >= "480") 91 | return Device.sizeLarge; 92 | else if (maxLength >= "470" && minLength >= "320") 93 | return Device.sizeNormal; 94 | else // if (maxLength >= "426" && minLength >= "320") 95 | return Device.sizeSmall; 96 | }); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /modules/Mut/Card.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | import Mut 0.1 4 | 5 | Paper { 6 | elevation: 1 7 | radius: Units.dp(2) 8 | } 9 | -------------------------------------------------------------------------------- /modules/Mut/DebugOutline.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | Rectangle { 4 | id: dbg 5 | anchors.fill: parent 6 | color: "transparent" 7 | border.color: "red" 8 | } 9 | -------------------------------------------------------------------------------- /modules/Mut/Device.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import QtQuick 2.4 4 | import QtQuick.Window 2.2 5 | 6 | import Mut 0.1 7 | 8 | Item { 9 | id: device 10 | 11 | property int size 12 | property int density 13 | property int primaryOrientation 14 | 15 | readonly property int sizeSmall: 0 16 | readonly property int sizeNormal: 1 17 | readonly property int sizeLarge: 2 18 | readonly property int sizeXLarge: 3 19 | 20 | readonly property int densityLDPI: 120 21 | readonly property int densityMDPI: 160 22 | readonly property int densityHDPI: 240 23 | readonly property int densityXHDPI: 320 24 | readonly property int densityXXHDPI: 480 25 | readonly property int densityXXXHDPI: 640 26 | } 27 | -------------------------------------------------------------------------------- /modules/Mut/Dialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Window 2.2 3 | 4 | Item { 5 | id: root 6 | property Item __lastFocus 7 | property Item __dialogInstance 8 | 9 | function open() { 10 | __lastFocus = Window.activeFocusItem; 11 | __dialogInstance = __panel.createObject(Window.contentItem, {"focus": "true"}); 12 | } 13 | 14 | function close() { 15 | __lastFocus.forceActiveFocus(); 16 | __dialogInstance.destroy(); 17 | } 18 | 19 | property Component __panel: Component { 20 | Scrim { 21 | id: __dialogRoot 22 | anchors.fill: parent 23 | 24 | onClicked: root.close() 25 | 26 | Loader { 27 | anchors.centerIn: parent 28 | sourceComponent: root.dialogComponent 29 | focus: true 30 | } 31 | 32 | Keys.onReleased: { 33 | if (event.key === Qt.Key_Back || event.key === Qt.Key_Escape) { 34 | root.close(); 35 | event.accepted = true; 36 | } 37 | } 38 | } 39 | } 40 | 41 | property Component dialogComponent: null 42 | } 43 | 44 | -------------------------------------------------------------------------------- /modules/Mut/Divider.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | import Mut 0.1 4 | import Mut.Themes 0.1 5 | 6 | Rectangle { 7 | id: root 8 | 9 | height: Units.dp(1) 10 | color: Theme.p.divider 11 | } 12 | -------------------------------------------------------------------------------- /modules/Mut/Icon.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtGraphicalEffects 1.0 3 | 4 | import Mut 0.1 5 | 6 | Item { 7 | implicitWidth: __image.implicitWidth 8 | implicitHeight: __image.implicitHeight 9 | 10 | property alias image: __image 11 | property color colorOverlay: "transparent" 12 | 13 | Image { 14 | id: __image 15 | smooth: false 16 | sourceSize { 17 | height: Units.dp(24) 18 | width: Units.dp(24) 19 | } 20 | anchors.centerIn: parent 21 | 22 | visible: false 23 | } 24 | 25 | ColorOverlay { 26 | id: __overlay 27 | anchors.fill: image 28 | source: image 29 | cached: true 30 | 31 | color: Qt.rgba(colorOverlay.r, colorOverlay.g, colorOverlay.b) 32 | opacity: colorOverlay.a 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /modules/Mut/Layouts/ConditionalLayout.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | /*! \qmltype ConditionalLayout 4 | \inqmlmodule Mut.Layout 0.1 5 | 6 | \brief Provides a layout that could change dinamically. 7 | 8 | ConditionalLayout uses a \l {QtQuick::Loader} that loads a layout 9 | dinamically according to the following properties: 10 | * `layouts`: contains a list of possible layouts that should be defined as a {QtQuick::Component}. 11 | * `when`: is a condition that should return the `layouts` index for the chosen layout 12 | 13 | ConditionalLayout provides a fallback system that chooses the nearest available layout 14 | so if the `when` property is set to 5 but `layouts` contains only 3 layouts, the 15 | rightmost element of the array will be used. 16 | 17 | \qml 18 | import QtQuick 2.4 19 | import Mut 0.1 20 | import Mut.Layouts 0.1 21 | 22 | Component { 23 | id: first_layout 24 | RowLayout { 25 | ... 26 | } 27 | } 28 | 29 | Component { 30 | id: second_layout 31 | ColumnLayout { 32 | ... 33 | } 34 | } 35 | 36 | ConditionalLayout { 37 | when: aReallyLongCondition ? 0 : 1 38 | layouts: [ 39 | first_layout, 40 | second_layout 41 | ] 42 | 43 | } 44 | \endqml 45 | */ 46 | FocusScope { 47 | id: root 48 | 49 | property list layouts 50 | property int when: -1 51 | 52 | property alias activeComponent: __layoutLoader.sourceComponent 53 | property alias activeLayout: __layoutLoader.item 54 | 55 | Loader { 56 | id: __layoutLoader 57 | anchors.fill: parent 58 | active: root.when >= 0 59 | sourceComponent: layoutSelection() 60 | } 61 | 62 | function layoutSelection () { 63 | var index = root.when; 64 | var layout; 65 | 66 | // manages layouts fallback 67 | while (typeof root.layouts[index] === 'undefined' && index > -1) { 68 | index -= 1; 69 | } 70 | 71 | // chooses the most similar layout available, 72 | // 'empty' otherwise (no layout will be loaded) 73 | if (index === -1) { 74 | layout = undefined; 75 | } else { 76 | layout = root.layouts[index]; 77 | } 78 | 79 | return layout; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /modules/Mut/Layouts/DeviceLayout.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | import Mut 0.1 4 | import Mut.Layouts 0.1 5 | 6 | /*! \qmltype DeviceLayout 7 | \inqmlmodule Mut.Layout 0.1 8 | 9 | \brief Provides a layout that changes according to device screen size. 10 | Screen size values are defined in \l {Mut::Device} singleton 11 | having values in the range [0, 3]. 12 | 13 | DeviceLayout makes use of the \l {Mut.Layouts::ConditionalLayout} 14 | fallback system so if only `sizeSmall` and `sizeNormal` layouts are defined 15 | but the current device requires a `sizeXLarge` layout, `sizeNormal` layout 16 | will be used. 17 | 18 | \qml 19 | import QtQuick 2.4 20 | import Mut 0.1 21 | import Mut.Layouts 0.1 22 | 23 | Component { 24 | id: small_layout 25 | ColumnLayout { 26 | ... 27 | } 28 | } 29 | 30 | Component { 31 | id: normal_layout 32 | ColumnLayout { 33 | ... 34 | } 35 | } 36 | 37 | Component { 38 | id: large_layout 39 | RowLayout { 40 | ... 41 | } 42 | } 43 | 44 | DeviceLayout { 45 | layouts: [ 46 | small_layout, 47 | normal_layout, 48 | large_layout 49 | ] 50 | 51 | } 52 | \endqml 53 | */ 54 | ConditionalLayout { 55 | when: Device.size 56 | } 57 | -------------------------------------------------------------------------------- /modules/Mut/Layouts/OrientationLayout.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | import Mut 0.1 4 | import Mut.Layouts 0.1 5 | 6 | /*! \qmltype OrientationLayout 7 | \inqmlmodule Mut.Layout 0.1 8 | 9 | \brief Provides a layout that changes according to device orientation. 10 | 11 | \qml 12 | import QtQuick 2.4 13 | import Mut 0.1 14 | import Mut.Layouts 0.1 15 | 16 | Component { 17 | id: portrait_layout 18 | ColumnLayout { 19 | ... 20 | } 21 | } 22 | 23 | Component { 24 | id: landscape_layout 25 | RowLayout { 26 | ... 27 | } 28 | } 29 | 30 | OrientationLayout { 31 | layouts: [ 32 | portrait_layout, 33 | landscape_layout 34 | ] 35 | 36 | } 37 | \endqml 38 | */ 39 | ConditionalLayout { 40 | when: Device.primaryOrientation === Qt.PortraitOrientation ? 0 : 1 41 | } 42 | -------------------------------------------------------------------------------- /modules/Mut/Layouts/qmldir: -------------------------------------------------------------------------------- 1 | module Mut.Layouts 2 | ConditionalLayout 0.1 ConditionalLayout.qml 3 | OrientationLayout 0.1 OrientationLayout.qml 4 | DeviceLayout 0.1 DeviceLayout.qml 5 | -------------------------------------------------------------------------------- /modules/Mut/ListItems/DoubleLineItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | 4 | import Mut 0.1 5 | import Mut.Themes 0.1 6 | import Mut.Styles 0.1 7 | 8 | Tile { 9 | id: root 10 | height: Units.dp(72) 11 | 12 | property string primaryText 13 | property string secondaryText 14 | property Action primaryAction 15 | property Action secondaryAction 16 | 17 | primaryComponent: Item { 18 | anchors.fill: parent 19 | Icon { 20 | id: __icon 21 | visible: root.primaryAction 22 | 23 | anchors { 24 | verticalCenter: parent.verticalCenter 25 | left: parent.left 26 | } 27 | 28 | image.source: root.primaryAction ? root.primaryAction.iconSource : "" 29 | colorOverlay: Theme.p.dark.icon 30 | } 31 | 32 | Column { 33 | anchors.verticalCenter: parent.verticalCenter 34 | width: secondaryAction ? parent.width - Units.dp(48) : parent.width 35 | 36 | Text { 37 | id: __primaryText 38 | elide: Text.ElideRight 39 | width: parent.width 40 | 41 | text: root.primaryText 42 | font.pixelSize: Units.sp(16) 43 | } 44 | 45 | Text { 46 | id: __secondaryText 47 | color: Theme.p.textSecondary 48 | width: parent.width 49 | elide: Text.ElideRight 50 | 51 | text: root.secondaryText 52 | font.pixelSize: Units.dp(14) 53 | } 54 | } 55 | } 56 | 57 | secondaryComponent: Button { 58 | visible: secondaryAction 59 | enabled: secondaryAction 60 | 61 | action: secondaryAction 62 | style: ToolButtonStyle { 63 | palette: Theme.p.flatButton 64 | } 65 | } 66 | 67 | Surface { 68 | anchors.fill: parent 69 | anchors.right: secondaryComponent.left 70 | onClicked: primaryAction.trigger() 71 | enabled: root.primaryAction 72 | visible: enabled 73 | } 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /modules/Mut/ListItems/ListView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 as Q 2 | 3 | import Mut 0.1 4 | import Mut.Styles 0.1 5 | import Mut.Themes 0.1 6 | 7 | /*! \qmltype ListView 8 | \inqmlmodule Mut.ListItems 0.1 9 | 10 | \brief Provides a generic ListView component compliant to material design guidelines. 11 | 12 | ListView extends \l {QtQuick::ListView} 13 | */ 14 | Q.ListView { 15 | id: root 16 | 17 | header: Q.Item { 18 | height: Units.dp(8) 19 | } 20 | 21 | footer: Q.Item { 22 | height: Units.dp(8) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/Mut/ListItems/SingleLineItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | 4 | import Mut 0.1 5 | import Mut.Styles 0.1 6 | import Mut.Themes 0.1 7 | 8 | /*! \qmltype SingleLineItem 9 | \inqmlmodule Mut.ListItems 0.1 10 | 11 | \brief Provides the single-line list component. 12 | 13 | SingleLineItem implements the text only item type, providing a Text 14 | component as a `primaryComponent` and a Button as a supplemental action. 15 | Exposed properties are: 16 | * `text`: is the `primaryComponent` text 17 | * `primaryAction`: defines the triggered action when users click the line; it 18 | should be a Qt.Action 19 | * `secondaryAction`: defines the icon and the triggered action for the 20 | supplemental action; it should be a Qt.Action 21 | 22 | SingleLineItem includes a material `Surface` area, which `onClicked` handler is 23 | bounded to `primaryAction.trigger` method. 24 | 25 | \qml 26 | import QtQuick 2.4 27 | import QtQuick.Controls 1.3 28 | 29 | import Mut 0.1 30 | import Mut.ListItems 0.1 31 | 32 | ListView { 33 | anchors.fill: parent 34 | model: 5 35 | 36 | delegate: SingleLineItem { 37 | text: "Single line item" 38 | primaryAction: Action { 39 | iconSource: Qt.resolvedUrl("./icons/folder.svg") 40 | onTriggered: { 41 | console.log("Primary action") 42 | } 43 | } 44 | secondaryAction: Action { 45 | iconSource: Qt.resolvedUrl("./icons/star.svg") 46 | onTriggered: { 47 | console.log("Secondary action") 48 | } 49 | } 50 | } 51 | } 52 | \endqml 53 | */ 54 | Tile { 55 | id: root 56 | 57 | property string text 58 | property Action primaryAction 59 | property Action secondaryAction 60 | 61 | primaryComponent: Item { 62 | Row { 63 | anchors.fill: parent 64 | 65 | spacing: Units.dp(16) 66 | Icon { 67 | id: __icon 68 | visible: root.primaryAction 69 | 70 | anchors.verticalCenter: parent.verticalCenter 71 | image.source: root.primaryAction.iconSource 72 | colorOverlay: Theme.p.dark.icon 73 | } 74 | 75 | Text { 76 | id: __text 77 | 78 | anchors.verticalCenter: parent.verticalCenter 79 | width: parent.width - __icon.width 80 | text: root.text 81 | elide: Text.ElideRight 82 | font.pixelSize: Units.sp(16) 83 | } 84 | } 85 | } 86 | 87 | secondaryComponent: Button { 88 | visible: secondaryAction 89 | enabled: secondaryAction 90 | 91 | action: secondaryAction 92 | style: ToolButtonStyle { 93 | palette: Theme.p.flatButton 94 | } 95 | } 96 | 97 | Surface { 98 | anchors.fill: parent 99 | onClicked: primaryAction.trigger() 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /modules/Mut/ListItems/Tile.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | import Mut 0.1 4 | import Mut.Themes 0.1 5 | 6 | /*! \qmltype Tile 7 | \inqmlmodule Mut.ListItems 0.1 8 | 9 | \brief Provides the Tile component used in material lists. 10 | 11 | Tile component uses two \l {QtQuick::Loader} to render a primary component 12 | and a secondary component. Exposed properties are: 13 | * `backgroundColor`: defines the Tile background color 14 | * `primaryComponent`: is the Loader sourceComponent 15 | * `secondaryComponent`: is the Loader sourceComponent 16 | 17 | The primary component owns the majority of line space, while the secondary 18 | component should be considered as a right button that provides a 19 | supplemental action. 20 | 21 | Tile is a base component required to realize Lists Components such as 22 | `SingleLineItem`, `TwoLineItem`, `ThreeLineItem` 23 | 24 | \qml 25 | import QtQuick 2.4 26 | import QtQuick.Controls 1.3 27 | 28 | import Mut 0.1 29 | import Mut.Styles 0.1 30 | import Mut.Themes 0.1 31 | 32 | Tile { 33 | id: root 34 | 35 | primaryComponent: Text { 36 | text: "Hello world!" 37 | font.pixelSize: Units.sp(16) 38 | } 39 | 40 | secondaryComponent: Button { 41 | action: secondaryAction 42 | style: ToolButtonStyle { 43 | palette: Theme.p.flatButton 44 | } 45 | } 46 | 47 | Surface { 48 | anchors.fill: parent 49 | onClicked: "Are you clicking me?!" 50 | } 51 | } 52 | \endqml 53 | */ 54 | FocusScope { 55 | id: tile 56 | 57 | implicitHeight: Units.dp(48) 58 | implicitWidth: parent.width 59 | 60 | property alias backgroundColor: background.color 61 | property alias primaryItem: __primary.item 62 | property alias secondaryItem: __secondary.item 63 | property Component primaryComponent 64 | property Component secondaryComponent 65 | 66 | Rectangle { 67 | id: background 68 | anchors.fill: parent 69 | 70 | color: "transparent" 71 | } 72 | 73 | Loader { 74 | id: __primary 75 | 76 | sourceComponent: primaryComponent 77 | 78 | anchors { 79 | left: parent.left 80 | right: __secondary.sourceComponent ? __secondary.left : parent.right 81 | leftMargin: Units.dp(16) 82 | rightMargin: Units.dp(16) 83 | verticalCenter: parent.verticalCenter 84 | } 85 | } 86 | 87 | Loader { 88 | id: __secondary 89 | width: Units.dp(48) 90 | 91 | sourceComponent: secondaryComponent 92 | 93 | anchors { 94 | verticalCenter: parent.verticalCenter 95 | right: parent.right 96 | leftMargin: Units.dp(16) 97 | rightMargin: Units.dp(16) 98 | } 99 | 100 | z: 1 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /modules/Mut/ListItems/qmldir: -------------------------------------------------------------------------------- 1 | module Mut.ListItems 2 | ListView 0.1 ListView.qml 3 | Tile 0.1 Tile.qml 4 | SingleLineItem 0.1 SingleLineItem.qml 5 | DoubleLineItem 0.1 DoubleLineItem.qml 6 | -------------------------------------------------------------------------------- /modules/Mut/NavDrawer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | import Mut 0.1 4 | 5 | /*! \qmltype NavDrawer 6 | \inqmlmodule Mut 0.1 7 | 8 | \brief Provides a `NavDrawer` that uses a `Scrim` to prevent any interaction with the components below 9 | 10 | `NavDrawer` component makes use of a \l {Mut::Scrim} as an overlay 11 | for all underlying components and uses a \l {Mut::Paper} as a main 12 | container for all `NavDrawer` children. 13 | 14 | As a default behavior, it provides two states: 15 | * `DRAWER_CLOSED`: the drawer `x` property is set to the far left, while the 16 | `Scrim` component has 0.0 opacity 17 | * `DRAWER_OPENED`: the drawer is visible to users, while the `Scrim` component 18 | has 1.0 opacity. The `Scrim` \l {QtQuick::MouseArea} catches keyboard and 19 | mouse inputs, preventing any interaction with the components below 20 | 21 | Even if a `NavDrawer` is flexible and can contain any kind of component, the 22 | Material Design specifications suggest following the keylines and metrics 23 | for lists. 24 | 25 | \qml 26 | import QtQuick 2.4 27 | import QtQuick.Controls 1.3 28 | 29 | import Mut 0.1 30 | import Mut.ListItems 0.1 31 | 32 | ApplicationWindow { 33 | id: app 34 | 35 | property list actions: [ 36 | Action { 37 | text: "Item 1" 38 | iconSource: Qt.resolvedUrl("./icon1.svg") 39 | onTriggered: { 40 | console.log("NavDrawer action 1"); 41 | } 42 | }, 43 | Action { 44 | text: "Item 2" 45 | iconSource: Qt.resolvedUrl("./icon2.svg") 46 | onTriggered: { 47 | console.log("NavDrawer action 2"); 48 | } 49 | } 50 | ] 51 | 52 | NavDrawer { 53 | id: drawer 54 | 55 | ListView { 56 | anchors.fill: parent 57 | model: app.actions 58 | 59 | delegate: SingleLineItem { 60 | text: model.text 61 | primaryAction: modelData 62 | } 63 | } 64 | } 65 | } 66 | \endqml 67 | */ 68 | FocusScope { 69 | id: drawer 70 | state: "DRAWER_CLOSED" 71 | 72 | signal drawerOpened() 73 | signal drawerClosed() 74 | 75 | default property alias data: __paper.data 76 | property bool opened: state === "DRAWER_OPENED" 77 | 78 | implicitWidth: parent.width 79 | implicitHeight: parent.height 80 | 81 | Keys.onReleased: { 82 | if (event.key === Qt.Key_Back || event.key === Qt.Key_Escape) { 83 | if (drawer.opened) { 84 | drawer.close(); 85 | event.accepted = true; 86 | } 87 | } 88 | } 89 | 90 | Scrim { 91 | id: __scrim 92 | 93 | anchors.fill: parent 94 | onClicked: drawer.close() 95 | } 96 | 97 | Paper { 98 | id: __paper 99 | 100 | implicitHeight: parent.height 101 | implicitWidth: Units.dp(320) 102 | 103 | elevation: 2 104 | } 105 | 106 | function open() { 107 | drawer.state = "DRAWER_OPENED"; 108 | drawerOpened(); 109 | } 110 | 111 | function close() { 112 | drawer.state = "DRAWER_CLOSED"; 113 | drawerClosed(); 114 | } 115 | 116 | function toggle() { 117 | if (drawer.opened) { 118 | drawer.close(); 119 | } else { 120 | drawer.open(); 121 | } 122 | } 123 | 124 | states: [ 125 | State { 126 | name: "DRAWER_OPEN" 127 | PropertyChanges { target: __paper; x: 0; } 128 | PropertyChanges { target: __scrim; opacity: 1.0; } 129 | }, 130 | State { 131 | name: "DRAWER_CLOSED" 132 | PropertyChanges { target: __paper; x: Units.dp(-320); } 133 | PropertyChanges { target: __scrim; opacity: 0.0; } 134 | } 135 | ] 136 | 137 | transitions: [ 138 | Transition { 139 | to: "*" 140 | NumberAnimation { target: __paper; properties: "x"; duration: 450; easing.type: Easing.OutCubic; } 141 | NumberAnimation { target: __scrim; properties: "opacity"; duration: 100; easing.type: Easing.Linear; } 142 | } 143 | ] 144 | } 145 | -------------------------------------------------------------------------------- /modules/Mut/Page.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import Mut 0.1 4 | import Mut.Themes 0.1 5 | 6 | FocusScope { 7 | id: root 8 | 9 | property string title 10 | property list actions 11 | 12 | property var navHandler: function () { root.pop(); }; 13 | property Action navAction: Action { 14 | iconSource: Theme.image("arrow_back.svg") 15 | onTriggered: root.navHandler() 16 | } 17 | 18 | /*! Push a new component into the stack */ 19 | function push(item) { 20 | return Stack.view.push(item); 21 | } 22 | 23 | /*! Pop this page from the stack */ 24 | function pop() { 25 | if (Stack.view.currentItem === root) 26 | return Stack.view.pop(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/Mut/Paper.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtGraphicalEffects 1.0 3 | import Mut 0.1 4 | 5 | /*! \qmltype Paper 6 | \inqmlmodule Mut 7 | 8 | \brief Provides a generic sheet of paper. 9 | 10 | Paper is the base building block of all the components 11 | of the application. 12 | */ 13 | FocusScope { 14 | id: paper 15 | 16 | /*! \qmlproperty color Paper::backgroundColor 17 | 18 | This property holds the background color of the 19 | sheet of paper. 20 | */ 21 | property alias backgroundColor: background.color 22 | property alias radius: background.radius 23 | 24 | /*! \qmlproperty real Paper::elevation 25 | 26 | This property holds the elevation level of the sheet 27 | paper and determines the appearance of its shadow. 28 | */ 29 | property real elevation: 0 30 | 31 | property bool fillWidth: false 32 | property bool fillHeight: false 33 | 34 | property bool circular: false 35 | 36 | /*! \internal */ 37 | property bool __hasShadow: paper.backgroundColor.a != 0 && elevation > 0 38 | 39 | /*! \internal */ 40 | property real __effectWidth: paper.width + Units.dp(10) * paper.fillWidth 41 | /*! \internal */ 42 | property real __effectHeight: paper.height + Units.dp(20) * paper.fillHeight 43 | 44 | RectangularGlow { 45 | visible: paper.__hasShadow && paper.circular 46 | //width: paper.__effectWidth; height: paper.__effectHeight 47 | width: paper.__effectWidth * .95; height: paper.__effectHeight * .95 48 | 49 | anchors.centerIn: parent 50 | anchors.verticalCenterOffset: Units.dp(paper.elevation * 1.5) 51 | 52 | glowRadius: Units.dp(0.75) 53 | opacity: 0.6 54 | spread: 0.7 55 | color: "black" 56 | cornerRadius: paper.radius 57 | } 58 | 59 | RectangularGlow { 60 | id: topEffect 61 | visible: paper.__hasShadow && !paper.circular 62 | width: paper.__effectWidth; height: paper.__effectHeight 63 | anchors.centerIn: parent 64 | anchors.verticalCenterOffset: Units.dp(paper.elevation * 3) 65 | glowRadius: Units.dp(paper.elevation * 3) 66 | spread: 0.05 67 | color: "black" 68 | opacity: 0.08 + (0.04 * paper.elevation) 69 | cornerRadius: paper.radius + glowRadius * 2.5 70 | } 71 | 72 | RectangularGlow { 73 | id: bottomEffect 74 | visible: paper.__hasShadow && !paper.circular 75 | width: paper.__effectWidth; height: paper.__effectHeight 76 | anchors.centerIn: parent 77 | anchors.verticalCenterOffset: Units.dp(paper.elevation * 2.5) 78 | glowRadius: Units.dp(paper.elevation) 79 | spread: 0.05 80 | color: "black" 81 | opacity: 0.25 - (0.01 * paper.elevation) 82 | cornerRadius: paper.radius + glowRadius * 2.5 83 | } 84 | 85 | Rectangle { 86 | id: background 87 | anchors.fill: parent 88 | color: "white" 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /modules/Mut/Popup.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Window 2.2 3 | 4 | Item { 5 | id: root 6 | property Item __lastFocus 7 | property Item __popupInstance 8 | 9 | function open() { 10 | __lastFocus = Window.activeFocusItem; 11 | __popupInstance = root.content.createObject(Window.contentItem, {"focus": "true"}); 12 | } 13 | 14 | function close() { 15 | __lastFocus.forceActiveFocus(); 16 | __popupInstance.destroy(); 17 | } 18 | 19 | property Component content: null 20 | } 21 | 22 | -------------------------------------------------------------------------------- /modules/Mut/Scrim.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | /*! \qmltype Scrim 4 | \inqmlmodule Mut 0.1 5 | 6 | \brief Provides a Scrim that creates an opacity overlay over all components 7 | 8 | Scrim implements an overlay over all components. By default, its opacity 9 | controls the `visible` and `enabled` properties so that when the component 10 | opacity is 0.0, the Scrim is disabled and not visible in the QML hierarchy. 11 | On the other hand, when its opacity is 1.0, the Scrim is enabled and its 12 | `hoverEnabled: true` property prevents any interaction with components 13 | below. 14 | 15 | To use this component, remember to set its `width`, `height` or `anchors` 16 | properties. 17 | 18 | \qml 19 | import QtQuick 2.4 20 | 21 | import Mut 0.1 22 | 23 | FocusScope { 24 | Scrim { 25 | anchors.fill: parent 26 | onClicked: console.log("Scrim is clicked!") 27 | } 28 | } 29 | \endqml 30 | */ 31 | FocusScope { 32 | id: root 33 | visible: root.opacity != 0.0 34 | enabled: root.opacity != 0.0 35 | 36 | property alias background: __background 37 | 38 | signal clicked() 39 | 40 | MouseArea { 41 | id: __mouse 42 | anchors.fill: parent 43 | hoverEnabled: true 44 | onClicked: root.clicked() 45 | } 46 | 47 | Rectangle { 48 | id: __background 49 | color: "#000000" 50 | opacity: 0.54 51 | anchors.fill: parent 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /modules/Mut/Styles/ActionButtonStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Mut 0.1 3 | import Mut.Styles 0.1 4 | import Mut.Themes 0.1 5 | 6 | BaseButtonStyle { 7 | id: root 8 | elevation: 1 9 | palette: Theme.p.actionButton 10 | 11 | background: Paper { 12 | implicitWidth: Units.dp(56) 13 | implicitHeight: Units.dp(56) 14 | radius: Units.dp(28) 15 | circular: true 16 | 17 | elevation: control.enabled ? root.elevation : 0 18 | backgroundColor: root.backgroundColor 19 | } 20 | label: Icon { 21 | image.source: control.iconSource 22 | colorOverlay: root.iconColor 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/Mut/Styles/BaseButtonStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls.Styles 1.3 as Styles 3 | 4 | import Mut 0.1 5 | import Mut.Themes 0.1 6 | 7 | Styles.ButtonStyle { 8 | id: root 9 | 10 | property real elevation 11 | 12 | property ButtonPalette palette 13 | 14 | /*! \internal */ 15 | property color backgroundColor: { 16 | if (!control.enabled) 17 | root.palette.disabled; 18 | else if (control.pressed) 19 | root.palette.pressed; 20 | else if (control.hovered) 21 | root.palette.hovered; 22 | else 23 | root.palette.normal; 24 | } 25 | 26 | property color iconColor: { 27 | control.enabled ? root.palette.icon : root.palette.textDisabled 28 | } 29 | 30 | /*! \internal */ 31 | property color textColor: { 32 | control.enabled ? root.palette.text : root.palette.textDisabled 33 | } 34 | 35 | padding { 36 | left: Units.dp(12) 37 | right: Units.dp(12) 38 | top: 0 39 | bottom: 0 40 | } 41 | background: Item { 42 | id: background 43 | implicitWidth: Units.dp(88) 44 | implicitHeight: Units.dp(48) 45 | 46 | Paper { 47 | implicitWidth: background.width 48 | implicitHeight: Units.dp(36) 49 | anchors.centerIn: parent 50 | radius: Units.dp(1) 51 | elevation: control.enabled ? root.elevation : 0 52 | backgroundColor: root.backgroundColor 53 | } 54 | } 55 | label: Item { 56 | implicitWidth: row.implicitWidth 57 | implicitHeight: row.implicitHeight 58 | 59 | Row { 60 | id: row 61 | anchors.centerIn: parent 62 | spacing: 2 63 | 64 | Icon { 65 | id: icon 66 | anchors.verticalCenter: parent.verticalCenter 67 | 68 | image.source: control.iconSource 69 | colorOverlay: root.iconColor 70 | } 71 | 72 | Text { 73 | id: text 74 | anchors.verticalCenter: parent.verticalCenter 75 | text: control.text 76 | 77 | color: root.textColor 78 | font { 79 | capitalization: Font.AllUppercase 80 | pixelSize: Units.sp(14) 81 | weight: Font.DemiBold 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /modules/Mut/Styles/BaseTextFieldStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.3 4 | import QtQuick.Controls.Styles 1.3 as Styles 5 | 6 | import Mut 0.1 7 | import Mut.Themes 0.1 8 | 9 | Styles.TextFieldStyle { 10 | id: root 11 | 12 | property TextFieldPalette palette 13 | 14 | property bool floatingLabel: false 15 | 16 | // exposing internal properties 17 | property color textColor: palette.text 18 | property color normalColor: palette.normal 19 | property color pressedColor: palette.pressed 20 | property color errorColor: palette.error 21 | property color hintColor: palette.hint 22 | property color placeholderTextColor: "transparent" 23 | 24 | padding { 25 | left: 0 26 | right: 0 27 | top: Units.dp(16) 28 | bottom: Units.dp(8) 29 | } 30 | 31 | font.pixelSize: Units.sp(16) 32 | 33 | background: Item { 34 | id: __background 35 | implicitWidth: Units.dp(188) 36 | implicitHeight: Units.dp(48) 37 | 38 | Rectangle { 39 | id: underline 40 | height: control.activeFocus ? Units.dp(2) : Units.dp(1) 41 | 42 | color: control.hasError ? root.errorColor : control.activeFocus ? root.pressedColor : root.normalColor 43 | 44 | anchors { 45 | left: parent.left 46 | right: parent.right 47 | bottom: parent.bottom 48 | } 49 | 50 | Behavior on height { 51 | NumberAnimation { duration: 200 } 52 | } 53 | 54 | Behavior on color { 55 | ColorAnimation { duration: 200 } 56 | } 57 | } 58 | 59 | Label { 60 | id: __placeholder 61 | 62 | text: control.placeholderText 63 | 64 | anchors { 65 | verticalCenter: parent.verticalCenter 66 | margins: -Units.dp(8) 67 | } 68 | 69 | font.pixelSize: Units.sp(16) 70 | 71 | color: control.hasError ? root.errorColor : floatingLabel && control.text && control.activeFocus ? root.pressedColor : root.normalColor 72 | 73 | states: [ 74 | State { 75 | name: "floating" 76 | when: control.displayText.length > 0 && root.floatingLabel 77 | 78 | AnchorChanges { 79 | target: __placeholder 80 | anchors { 81 | verticalCenter: undefined 82 | top: parent.top 83 | } 84 | } 85 | 86 | PropertyChanges { 87 | target: __placeholder 88 | font.pixelSize: Units.dp(12) 89 | } 90 | }, 91 | State { 92 | name: "hidden" 93 | when: control.displayText.length > 0 && !root.floatingLabel 94 | 95 | PropertyChanges { 96 | target: __placeholder 97 | visible: false 98 | } 99 | } 100 | ] 101 | 102 | transitions: [ 103 | Transition { 104 | id: transition 105 | enabled: false 106 | 107 | AnchorAnimation { 108 | duration: 200 109 | } 110 | 111 | NumberAnimation { 112 | duration: 200 113 | property: "font.pixelSize" 114 | } 115 | } 116 | ] 117 | 118 | Behavior on color { 119 | ColorAnimation { duration: 200 } 120 | } 121 | 122 | Component.onCompleted: transition.enabled = true 123 | } 124 | 125 | RowLayout { 126 | anchors { 127 | left: parent.left 128 | right: parent.right 129 | top: underline.top 130 | topMargin: Units.dp(4) 131 | } 132 | 133 | Label { 134 | id: __hint 135 | visible: control.hintText 136 | text: control.hintText 137 | 138 | font.pixelSize: Units.dp(12) 139 | color: control.hasError ? root.errorColor : root.hintColor 140 | 141 | Behavior on color { 142 | ColorAnimation { duration: 200 } 143 | } 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /modules/Mut/Styles/CheckBoxStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.5 2 | import QtQuick.Controls.Styles 1.3 3 | import QtQuick.Controls 1.3 4 | import Mut 0.1 5 | import Mut.Themes 0.1 6 | 7 | CheckBoxStyle { 8 | id: root 9 | 10 | spacing: 0 11 | property CheckBoxPalette palette: Theme.p.checkBox 12 | property color backgroundColor: { 13 | control.checked ? 14 | control.enabled ? palette.backgroundSelected : palette.backgroundSelectedDisabled 15 | : palette.backgroundUnselected 16 | } 17 | 18 | property color borderColor: { 19 | control.checked ? palette.backgroundUnselected : 20 | control.enabled ? palette.borderEnabled : palette.borderDisabled 21 | } 22 | 23 | label: Rectangle { 24 | id: labelRectangle 25 | height: Units.dp(48) 26 | 27 | Text { 28 | id: text 29 | anchors.verticalCenter: parent.verticalCenter 30 | text: control.text 31 | 32 | MouseArea { 33 | anchors.fill: parent 34 | onClicked: control.checked = !control.checked 35 | } 36 | } 37 | } 38 | 39 | indicator: Item { 40 | id: parentRect 41 | 42 | implicitWidth: Units.dp(48) 43 | implicitHeight: Units.dp(48) 44 | 45 | Rectangle { 46 | id: indicatorRect 47 | 48 | color: root.backgroundColor 49 | 50 | anchors.centerIn: parent 51 | 52 | border.width: Units.dp(2) 53 | border.color: root.borderColor 54 | 55 | width: Units.dp(18) 56 | height: Units.dp(18) 57 | radius: Units.dp(2) 58 | 59 | Behavior on color { 60 | ColorAnimation { 61 | easing.type: Easing.InOutQuad 62 | duration: 200 63 | } 64 | } 65 | 66 | Icon { 67 | anchors.centerIn: parent 68 | image.source: Theme.image("check.svg") 69 | image.visible: true 70 | image.smooth: true 71 | image.opacity: control.checked ? 1 : 0 72 | image.width: Units.dp(18) 73 | image.height: Units.dp(18) 74 | colorOverlay: root.palette.iconOverlay 75 | } 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /modules/Mut/Styles/FlatButtonStyle.qml: -------------------------------------------------------------------------------- 1 | import Mut 0.1 2 | import Mut.Themes 0.1 3 | 4 | BaseButtonStyle { 5 | elevation: 0 6 | 7 | palette: Theme.p.flatButton 8 | } 9 | -------------------------------------------------------------------------------- /modules/Mut/Styles/FloatingTextFieldStyle.qml: -------------------------------------------------------------------------------- 1 | import Mut 0.1 2 | import Mut.Themes 0.1 3 | 4 | BaseTextFieldStyle { 5 | palette: Theme.p.textField 6 | 7 | floatingLabel: true 8 | } 9 | -------------------------------------------------------------------------------- /modules/Mut/Styles/NormalTextFieldStyle.qml: -------------------------------------------------------------------------------- 1 | import Mut 0.1 2 | import Mut.Themes 0.1 3 | 4 | BaseTextFieldStyle { 5 | palette: Theme.p.textField 6 | } 7 | -------------------------------------------------------------------------------- /modules/Mut/Styles/ProgressBarStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls.Styles 1.3 as Styles 3 | import Mut 0.1 4 | import Mut.Themes 0.1 5 | 6 | Styles.ProgressBarStyle { 7 | id: progressBarStyle 8 | 9 | property color color: Theme.p.primary 10 | 11 | background: Rectangle { 12 | implicitWidth: Units.dp(200) 13 | implicitHeight: Units.dp(4) 14 | color: progressBarStyle.color 15 | opacity: 0.2 16 | } 17 | 18 | progress: Rectangle { 19 | color: control.indeterminate ? "transparent" : progressBarStyle.color 20 | 21 | Rectangle { 22 | id: __progress 23 | visible: control.indeterminate 24 | property int end: 0 25 | width: end - x; 26 | height: parent.height 27 | color: progressBarStyle.color 28 | ParallelAnimation { 29 | running: control.width && control.indeterminate 30 | SequentialAnimation { 31 | loops: Animation.Infinite 32 | PauseAnimation { 33 | duration: 500 34 | } 35 | 36 | NumberAnimation{ 37 | target: __progress; property: "x" 38 | from: 0; to: control.width 39 | duration: 2000 40 | easing.type: Easing.InCubic 41 | } 42 | } 43 | SequentialAnimation { 44 | loops: Animation.Infinite 45 | NumberAnimation { 46 | target: __progress; property: "end" 47 | from: 0; to: control.width 48 | duration: 2000 49 | } 50 | PauseAnimation { 51 | duration: 500 52 | } 53 | ScriptAction { 54 | script: __progress.x = 0 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /modules/Mut/Styles/RaisedButtonStyle.qml: -------------------------------------------------------------------------------- 1 | import Mut 0.1 2 | import Mut.Themes 0.1 3 | 4 | BaseButtonStyle { 5 | elevation: 1 6 | 7 | palette: Theme.p.raisedButton 8 | } 9 | -------------------------------------------------------------------------------- /modules/Mut/Styles/SwitchStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls.Styles 1.3 3 | import Mut 0.1 4 | import Mut.Themes 0.1 5 | 6 | SwitchStyle { 7 | id: style 8 | 9 | property SwitchPalette palette: Theme.p.switchToggle 10 | 11 | property color thumbColor: { 12 | control.enabled ? 13 | control.checked ? palette.thumbOn : palette.thumbOff 14 | : palette.thumbDisabled 15 | } 16 | property color trackColor: { 17 | control.enabled ? 18 | control.checked ? palette.trackOn : palette.trackOff 19 | : palette.trackDisabled 20 | } 21 | 22 | handle: Item { 23 | width: Units.dp(24) 24 | height: Units.dp(24) 25 | Paper { 26 | anchors.centerIn: parent 27 | width: control.pressed ? Units.dp(56) : Units.dp(24) 28 | height: width 29 | 30 | radius: width/2 31 | 32 | backgroundColor: style.trackColor 33 | opacity: control.pressed ? 0.4 : 0 34 | 35 | Behavior on width { 36 | NumberAnimation {duration: 500} 37 | } 38 | Behavior on opacity { 39 | NumberAnimation {duration: 500} 40 | } 41 | } 42 | Paper { 43 | anchors.centerIn: parent 44 | width: Units.dp(24) 45 | height: Units.dp(24) 46 | radius: width/2 47 | circular: true 48 | 49 | elevation: 1 50 | backgroundColor: style.thumbColor 51 | } 52 | } 53 | 54 | groove: Item { 55 | width: Units.dp(42) 56 | height: Units.dp(24) 57 | 58 | Rectangle { 59 | anchors.centerIn: parent 60 | 61 | width: parent.width - Units.dp(2) 62 | height: Units.dp(16) 63 | 64 | radius: height/2 65 | 66 | color: style.trackColor 67 | 68 | Behavior on color { 69 | ColorAnimation {duration: 200} 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /modules/Mut/Styles/ToolBarStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Window 2.2 2 | import QtQuick.Controls.Styles 1.3 as Styles 3 | 4 | import Mut 0.1 5 | 6 | 7 | Styles.ToolBarStyle { 8 | id: style 9 | 10 | padding { 11 | // FIXME: adjust paddings depending on device size 12 | left: Units.dp(4) 13 | right: Units.dp(4) 14 | top: 0 15 | bottom: 0 16 | } 17 | background: Paper { 18 | backgroundColor: control.backgroundColor 19 | 20 | fillWidth: control.fillWidth 21 | elevation: control.elevation 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /modules/Mut/Styles/ToolButtonStyle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Mut 0.1 3 | import Mut.Styles 0.1 4 | import Mut.Themes 0.1 5 | 6 | BaseButtonStyle { 7 | id: root 8 | palette: Theme.p.toolButton 9 | 10 | background: Paper { 11 | implicitHeight: Units.dp(48) 12 | 13 | backgroundColor: root.backgroundColor 14 | } 15 | label: Icon { 16 | image.source: control.iconSource 17 | colorOverlay: root.iconColor 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/Mut/Styles/qmldir: -------------------------------------------------------------------------------- 1 | module Mut.Styles 2 | ActionButtonStyle 0.1 ActionButtonStyle.qml 3 | BaseButtonStyle 0.1 BaseButtonStyle.qml 4 | FlatButtonStyle 0.1 FlatButtonStyle.qml 5 | ProgressBarStyle 0.1 ProgressBarStyle.qml 6 | RaisedButtonStyle 0.1 RaisedButtonStyle.qml 7 | ToolBarStyle 0.1 ToolBarStyle.qml 8 | ToolButtonStyle 0.1 ToolButtonStyle.qml 9 | BaseTextFieldStyle 0.1 BaseTextFieldStyle.qml 10 | NormalTextFieldStyle 0.1 NormalTextFieldStyle.qml 11 | FloatingTextFieldStyle 0.1 FloatingTextFieldStyle.qml 12 | SwitchStyle 0.1 SwitchStyle.qml 13 | CheckBoxStyle 0.1 CheckBoxStyle.qml 14 | -------------------------------------------------------------------------------- /modules/Mut/Surface.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import Mut 0.1 3 | 4 | MouseArea { 5 | id: surface 6 | clip: true 7 | 8 | onPressed: { 9 | ripple.startX = mouse.x; 10 | ripple.startY = mouse.y; 11 | ripple.opacity = 1; 12 | } 13 | onCanceled: { 14 | ripple.opacity = 0; 15 | ripple.radius = 0; 16 | } 17 | onReleased: { 18 | ripple.opacity = 0; 19 | ripple.radius = 0; 20 | } 21 | 22 | Rectangle { 23 | id: background 24 | anchors.fill: parent 25 | color: Qt.rgba(0, 0, 0, 0.05) 26 | opacity: surface.pressed 27 | 28 | Behavior on opacity { 29 | NumberAnimation {} 30 | } 31 | } 32 | Rectangle { 33 | id: ripple 34 | property int startX; 35 | property int startY; 36 | 37 | x: startX - radius; y: startY - radius 38 | width: radius*2; height: radius*2 39 | radius: 0 40 | 41 | color: Qt.rgba(0, 0, 0, 0.05) 42 | opacity: 0 43 | 44 | Behavior on opacity { 45 | NumberAnimation {} 46 | } 47 | SmoothedAnimation on radius { 48 | to: Math.max(surface.width, surface.height) 49 | running: surface.pressed 50 | velocity: Units.dp(100) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /modules/Mut/TextField.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 as Controls 3 | 4 | Controls.TextField { 5 | id: textField 6 | 7 | property string hintText 8 | property bool hasError 9 | } 10 | -------------------------------------------------------------------------------- /modules/Mut/Themes/ButtonPalette.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | QtObject { 4 | id: palette 5 | 6 | property color normal 7 | property color hovered 8 | property color pressed 9 | property color disabled 10 | property color icon 11 | property color text 12 | property color textDisabled 13 | } 14 | -------------------------------------------------------------------------------- /modules/Mut/Themes/CheckBoxPalette.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | QtObject { 4 | id: checkbox 5 | 6 | property color backgroundSelected 7 | property color backgroundUnselected 8 | property color backgroundSelectedDisabled 9 | 10 | property color borderEnabled 11 | property color borderDisabled 12 | 13 | property color iconOverlay 14 | } 15 | 16 | -------------------------------------------------------------------------------- /modules/Mut/Themes/ElementPalette.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | 4 | QtObject { 5 | property color text 6 | property color secondaryText 7 | property color icon 8 | property color hintText 9 | property color disabled 10 | property color divider 11 | } 12 | -------------------------------------------------------------------------------- /modules/Mut/Themes/Palette.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | QtObject { 4 | id: palette 5 | 6 | /*! \qmlproperty color Palette::background 7 | 8 | Color that matches (as closely as possible) the window background. 9 | */ 10 | property color background 11 | 12 | /*! \qmlproperty color Palette::primary 13 | 14 | The primary branding color for the app. 15 | By default, this is the color applied to the action bar background. 16 | */ 17 | property color primary 18 | property color primaryLight 19 | property color primaryDark 20 | 21 | /*! \qmlproperty color Palette::accent 22 | 23 | Bright complement to the primary branding color. 24 | */ 25 | property color accent 26 | property color accentLight 27 | property color accentDark 28 | 29 | /*! \qmlproperty color Palette::textPrimary 30 | 31 | The most prominent text color. 32 | */ 33 | property color textPrimary 34 | 35 | /*! \qmlproperty color Palette::textSecondary 36 | 37 | Secondary text color. 38 | */ 39 | property color textSecondary 40 | } 41 | -------------------------------------------------------------------------------- /modules/Mut/Themes/SwitchPalette.qml: -------------------------------------------------------------------------------- 1 | 2 | import QtQuick 2.4 3 | 4 | QtObject { 5 | id: palette 6 | 7 | property color thumbOn 8 | property color thumbOff 9 | property color thumbDisabled 10 | property color trackOn 11 | property color trackOff 12 | property color trackDisabled 13 | } 14 | -------------------------------------------------------------------------------- /modules/Mut/Themes/TextFieldPalette.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | 3 | QtObject { 4 | property color text 5 | property color placeholder 6 | property color normal 7 | property color pressed 8 | property color hint 9 | property color error 10 | property color disabled 11 | } 12 | -------------------------------------------------------------------------------- /modules/Mut/Themes/Theme.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import QtQuick 2.4 4 | 5 | 6 | QtObject { 7 | id: root 8 | 9 | property url imageAssetsPath 10 | 11 | /*! This is property allows the access to the general 12 | palette and other component-specific palettes. 13 | */ 14 | property Palette p: Palette { 15 | id: palette 16 | 17 | background: "#eeeeee" 18 | 19 | primary: "#3f51b5" 20 | primaryLight: "#3949ab" 21 | primaryDark: "#303f9f" 22 | 23 | accent: "#ff4081" 24 | accentLight: "#f50057" 25 | accentDark: "#c51162" 26 | 27 | textPrimary: palette.light.text 28 | textSecondary: palette.dark.secondaryText 29 | 30 | property ElementPalette light: ElementPalette { 31 | text: "#ffffff" 32 | icon: "#ffffff" 33 | secondaryText: Qt.rgba(1, 1, 1, 0.7) 34 | hintText: Qt.rgba(1, 1, 1, 0.3) 35 | disabled: Qt.rgba(1, 1, 1, 0.3) 36 | divider: Qt.rgba(1, 1, 1, 0.12) 37 | } 38 | 39 | property ElementPalette dark: ElementPalette { 40 | text: Qt.rgba(0, 0, 0, 0.87) 41 | icon: Qt.rgba(0, 0, 0, 0.54) 42 | secondaryText: Qt.rgba(0, 0, 0, 0.54) 43 | hintText: Qt.rgba(0, 0, 0, 0.26) 44 | disabled: Qt.rgba(0, 0, 0, 0.26) 45 | divider: Qt.rgba(0, 0, 0, 0.12) 46 | } 47 | 48 | property color divider: palette.dark.divider 49 | 50 | property ButtonPalette flatButton: ButtonPalette { 51 | normal: "transparent" 52 | hovered: "#33999999" 53 | pressed: "#66999999" 54 | disabled: "transparent" 55 | icon: palette.dark.icon 56 | text: palette.dark.text 57 | textDisabled: palette.dark.disabled 58 | } 59 | 60 | property ButtonPalette raisedButton: ButtonPalette { 61 | normal: palette.primary 62 | hovered: palette.primaryLight 63 | pressed: palette.primaryDark 64 | disabled: "#1f000000" 65 | icon: palette.light.icon 66 | text: palette.light.text 67 | textDisabled: palette.dark.disabled 68 | } 69 | 70 | property ButtonPalette toolButton: ButtonPalette { 71 | normal: "transparent" 72 | hovered: "#33999999" 73 | pressed: "#66999999" 74 | disabled: "transparent" 75 | icon: palette.light.icon 76 | text: palette.light.text 77 | textDisabled: palette.light.disabled 78 | } 79 | 80 | property ButtonPalette actionButton: ButtonPalette { 81 | normal: palette.accent 82 | hovered: palette.accentLight 83 | pressed: palette.accentDark 84 | disabled: "#1f000000" 85 | icon: palette.light.icon 86 | text: palette.light.text 87 | textDisabled: palette.light.disabled 88 | } 89 | 90 | property TextFieldPalette textField: TextFieldPalette { 91 | text: palette.dark.text 92 | placeholder: palette.dark.hintText 93 | normal: palette.dark.hintText 94 | pressed: palette.primary 95 | hint: palette.dark.hintText 96 | error: "#f44336" 97 | disabled: palette.dark.disabled 98 | } 99 | 100 | property SwitchPalette switchToggle: SwitchPalette { 101 | thumbOn: palette.accent 102 | thumbOff: "#fafafa" 103 | thumbDisabled: "#bdbdbd" 104 | 105 | trackOn: Qt.rgba(thumbOn.r, thumbOn.g, thumbOn.b, 0.5) 106 | trackOff: palette.dark.disabled 107 | trackDisabled: palette.dark.divider 108 | } 109 | 110 | property CheckBoxPalette checkBox: CheckBoxPalette { 111 | backgroundSelected: palette.accent 112 | backgroundUnselected: "transparent" 113 | backgroundSelectedDisabled: palette.dark.disabled 114 | 115 | borderEnabled: palette.dark.secondaryText 116 | borderDisabled: palette.dark.disabled 117 | 118 | iconOverlay: palette.light.icon 119 | } 120 | } 121 | 122 | function image(name) { 123 | if (root.imageAssetsPath == "") { 124 | return ""; 125 | } 126 | return Qt.resolvedUrl(root.imageAssetsPath + name); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /modules/Mut/Themes/qmldir: -------------------------------------------------------------------------------- 1 | module Mut.Themes 2 | CheckBoxPalette 0.1 CheckBoxPalette.qml 3 | ButtonPalette 0.1 ButtonPalette.qml 4 | ElementPalette 0.1 ElementPalette.qml 5 | TextFieldPalette 0.1 TextFieldPalette.qml 6 | SwitchPalette 0.1 SwitchPalette.qml 7 | Palette 0.1 Palette.qml 8 | singleton Theme 0.1 Theme.qml 9 | -------------------------------------------------------------------------------- /modules/Mut/ToolBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick.Controls 1.2 as Controls 2 | import Mut 0.1 3 | import Mut.Styles 0.1 4 | import Mut.Themes 0.1 5 | 6 | /*! \qmltype ToolBar 7 | \inqmlmodule Mut 0.1 8 | \ingroup toolbar 9 | 10 | \brief Provides a generic toolbar component. 11 | 12 | ToolBar extends \l {QtQuick.Controls::ToolBar} 13 | */ 14 | Controls.ToolBar { 15 | id: toolbar 16 | 17 | /*! \qmlproperty color ToolBar::backgroundColor 18 | 19 | The background color for the toolbar. 20 | */ 21 | property color backgroundColor: Theme.p.primary 22 | 23 | /*! \qmlproperty color ToolBar::elevation 24 | 25 | The elevation level for the toolbar. 26 | Default elevation is 0. 27 | */ 28 | property real elevation: 0 29 | 30 | property bool fillWidth: false 31 | 32 | style: ToolBarStyle {} 33 | } 34 | 35 | -------------------------------------------------------------------------------- /modules/Mut/qmldir: -------------------------------------------------------------------------------- 1 | module Mut 2 | AppBar 0.1 AppBar.qml 3 | ApplicationWindow 0.1 ApplicationWindow.qml 4 | DebugOutline 0.1 DebugOutline.qml 5 | Divider 0.1 Divider.qml 6 | Dialog 0.1 Dialog.qml 7 | singleton Device 0.1 Device.qml 8 | Icon 0.1 Icon.qml 9 | Page 0.1 Page.qml 10 | Paper 0.1 Paper.qml 11 | Popup 0.1 Popup.qml 12 | Card 0.1 Card.qml 13 | Surface 0.1 Surface.qml 14 | ToolBar 0.1 ToolBar.qml 15 | Scrim 0.1 Scrim.qml 16 | NavDrawer 0.1 NavDrawer.qml 17 | TextField 0.1 TextField.qml 18 | singleton Units 0.1 units.qml 19 | -------------------------------------------------------------------------------- /modules/Mut/units.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | 3 | import QtQuick 2.4 4 | import QtQuick.Window 2.2 5 | 6 | QtObject { 7 | id: units 8 | 9 | /*! Global font scale factor */ 10 | property real fontScale: 1.0; 11 | 12 | /*! System pixel density. Usually this is Screen.pixelDensity */ 13 | property real pixelDensity; 14 | 15 | function factor() { 16 | return (pixelDensity*25.4)/160; 17 | } 18 | 19 | /*! Method to compute component size using device independent pixels */ 20 | function dp(number) { 21 | return Math.round(number*((pixelDensity*25.4)/160)); 22 | } 23 | 24 | /*! Method to compute device independent pixels from real pixels */ 25 | function pxToDp(number) { 26 | return number / Math.round(((pixelDensity*25.4)/160)); 27 | } 28 | 29 | /*! Method to compute font sizes using device independent pixels 30 | and a global font scaling factor. 31 | */ 32 | function sp(number) { 33 | return dp(number) * units.fontScale; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/modules.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | 3 | deployment.files += \ 4 | qmldir \ 5 | *.qml \ 6 | Mut 7 | 8 | INSTALLS += deployment 9 | OTHER_FILES += $$deployment.files 10 | 11 | isEmpty(DESTDIR) { 12 | DESTDIR = $$OUT_PWD 13 | } 14 | 15 | # does not work when template is subdirs 16 | !equals(_PRO_FILE_PWD_, $$OUT_PWD) { 17 | copy_modules.target = mymodule 18 | win32 { 19 | modulePath = $$PWD/Mut 20 | destPath = $$DESTDIR/Mut 21 | copy_modules.commands = \ 22 | $(COPY_DIR) $$replace(modulePath, \/, \\) $$replace(destPath, \/, \\) 23 | } else { 24 | copy_modules.commands = $(COPY_DIR) $$PWD/Mut $$DESTDIR 25 | } 26 | 27 | QMAKE_EXTRA_TARGETS += copy_modules 28 | POST_TARGETDEPS += $$copy_modules.target 29 | } 30 | 31 | -------------------------------------------------------------------------------- /mut.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS = modules 3 | 4 | !no-mut-examples { 5 | SUBDIRS += examples 6 | examples.depends = modules 7 | } 8 | 9 | !no-mut-tests { 10 | SUBDIRS += tests 11 | tests.depends = modules 12 | } 13 | 14 | -------------------------------------------------------------------------------- /tests/test.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | QUICK_TEST_MAIN(Mut) 3 | -------------------------------------------------------------------------------- /tests/tests.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET = tst_mut 3 | CONFIG += qmltestcase 4 | SOURCES += tests.cpp 5 | 6 | IMPORTPATH = $$PWD/../modules 7 | 8 | RESOURCES += tests.qrc 9 | 10 | OTHER_FILES += \ 11 | tst_appbar.qml \ 12 | tst_application_window \ 13 | tst_button_style.qml \ 14 | tst_conditional_layout.qml \ 15 | tst_device_layout.qml \ 16 | tst_divider.qml \ 17 | tst_icon.qml \ 18 | tst_orientation_layout.qml \ 19 | tst_page.qml \ 20 | tst_paper.qml \ 21 | tst_theme.qml \ 22 | tst_toolbar.qml \ 23 | tst_units.qml 24 | 25 | -------------------------------------------------------------------------------- /tests/tests.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | test.svg 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/tst_appbar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | import Mut.Themes 0.1 6 | 7 | TestCase { 8 | id: testCase 9 | name: "Tests_AppBar" 10 | 11 | function initTestCase() { 12 | Units.pixelDensity = 5.0; 13 | } 14 | 15 | /* tests that the default value of the properties are correct */ 16 | function test_defaults() { 17 | var tmp = Qt.createQmlObject( 18 | 'import Mut 0.1; AppBar {id: appbar1}', 19 | testCase, ''); 20 | 21 | compare(tmp.defaultNavAction, null); 22 | compare(tmp.elevation, 2); 23 | compare(tmp.fillWidth, true); 24 | tmp.destroy(); 25 | } 26 | 27 | /* tests that appbar is resized properly when device size and 28 | screen orientation changes. 29 | */ 30 | function test_implicitHeight() { 31 | var tmp = Qt.createQmlObject( 32 | 'import Mut 0.1; AppBar {id: appbar1}', 33 | testCase, ''); 34 | 35 | Device.size = Device.sizeXLarge; 36 | compare(tmp.implicitHeight, Units.dp(64)); 37 | 38 | Device.size = Device.sizeNormal; 39 | Device.primaryOrientation = Qt.PortraitOrientation; 40 | compare(tmp.implicitHeight, Units.dp(56)); 41 | 42 | Device.primaryOrientation = Qt.LandscapeOrientation; 43 | compare(tmp.implicitHeight, Units.dp(48)); 44 | 45 | tmp.destroy(); 46 | } 47 | 48 | function test_defaultNavAction() { 49 | var tmp = Qt.createQmlObject( 50 | 'import QtQuick.Controls 1.3; import Mut 0.1;' + 51 | 'AppBar {id: appbar1; defaultNavAction: Action {}}', 52 | testCase, ''); 53 | tmp.destroy(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/tst_application_window.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | 6 | Item { 7 | id: container 8 | width: 300; height: 300 9 | 10 | Page { 11 | id: page1 12 | } 13 | 14 | Page { 15 | id: page2 16 | } 17 | 18 | ApplicationWindow { 19 | id: app 20 | } 21 | 22 | TestCase { 23 | id: testCase 24 | name: "Tests_ApplicationWindow" 25 | when: windowShown 26 | width: 300; height: 300 27 | 28 | function initTestCase() { 29 | Units.pixelDensity = 5.0; 30 | } 31 | 32 | /* tests that Device singleton is initialized when ApplicationWindow 33 | loading is completed 34 | */ 35 | function test_deviceIsInitialized() { 36 | verify(Device.primaryOrientation !== 0, "primaryOrientation not set"); 37 | verify(Device.size !== 0, "device size not set"); 38 | } 39 | 40 | /* tests that the ESC button pops the last page */ 41 | function test_esc_button() { 42 | app.pageStack.push(page1); 43 | app.pageStack.push(page2); 44 | 45 | keyClick(Qt.Key_Escape); 46 | // TODO: this test doesn't work; there is a problem with the ApplicationWindow focus 47 | // compare(app.pageStack.currentItem, page1); 48 | 49 | app.pageStack.clear(); 50 | } 51 | 52 | /* tests that the back button pops the last page */ 53 | function test_back_button() { 54 | app.pageStack.push(page1); 55 | app.pageStack.push(page2); 56 | 57 | keyClick(Qt.Key_Back); 58 | // TODO: this test doesn't work; there is a problem with the ApplicationWindow focus 59 | // compare(app.pageStack.currentItem, page1); 60 | 61 | app.pageStack.clear(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/tst_button_style.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtTest 1.0 4 | 5 | import Mut 0.1 6 | import Mut.Styles 0.1 7 | import Mut.Themes 0.1 8 | 9 | Item { 10 | id: container 11 | width: 300; height: 300 12 | 13 | Button { 14 | id: button 15 | style: BaseButtonStyle { 16 | id: style 17 | elevation: 1 18 | palette: ButtonPalette { 19 | normal: "#ff0000" 20 | hovered: "#00ff00" 21 | pressed: "#0000ff" 22 | disabled: "#aaaaaa" 23 | text: "#ffffff" 24 | textDisabled: "#eeeeee" 25 | 26 | } 27 | } 28 | } 29 | 30 | TestCase { 31 | id: testCase 32 | name: "Tests_ButtonStyle" 33 | when: windowShown 34 | width: 300; height: 300 35 | 36 | function initTestCase() { 37 | Units.pixelDensity = 5; 38 | } 39 | 40 | /* tests default elevation */ 41 | function test_defaultElevation() { 42 | compare(button.__style.elevation, 1); 43 | } 44 | 45 | /* tests colors on normal state */ 46 | function test_defaultColors() { 47 | compare(button.__style.backgroundColor, "#ff0000"); 48 | compare(button.__style.textColor, "#ffffff"); 49 | } 50 | 51 | /* tests colors on pressed state */ 52 | function test_pressedColors() { 53 | mousePress(button); 54 | compare(button.__style.backgroundColor, "#0000ff"); 55 | mouseRelease(button); 56 | } 57 | 58 | /* tests colors on hovered state */ 59 | function test_hoveredColors() { 60 | mouseMove(button); 61 | compare(button.__style.backgroundColor, "#00ff00"); 62 | } 63 | 64 | /* tests colors on disabled state */ 65 | function test_disabledColors() { 66 | button.enabled = false; 67 | compare(button.__style.backgroundColor, "#aaaaaa"); 68 | compare(button.__style.textColor, "#eeeeee"); 69 | button.enabled = true; 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /tests/tst_card.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtTest 1.0 4 | 5 | import Mut 0.1 6 | 7 | Item { 8 | id: container 9 | width: 800; height: 600 10 | 11 | Card { 12 | id: card 13 | width: Units.dp(200); height: Units.dp(200) 14 | } 15 | 16 | TestCase { 17 | id: testCase 18 | name: "Tests_Card" 19 | when: windowShown 20 | width: 800; height: 600 21 | 22 | function initTestCase() { 23 | Units.pixelDensity = 5; 24 | } 25 | 26 | /* it should have a radius and an elevation */ 27 | function test_state_default() { 28 | compare(card.elevation, 1); 29 | compare(card.radius, Units.dp(2)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/tst_conditional_layout.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | import Mut.Layouts 0.1 6 | 7 | Item { 8 | id: container 9 | width: 300; height: 300 10 | 11 | Component { 12 | id: first_layout 13 | 14 | Item { 15 | Rectangle { width: 80; height: 80; border.width: 1 } 16 | } 17 | } 18 | 19 | Component { 20 | id: second_layout 21 | 22 | Item { 23 | Rectangle { width: 80; height: 80; border.width: 1 } 24 | } 25 | } 26 | 27 | ConditionalLayout { 28 | id: layout 29 | } 30 | 31 | TestCase { 32 | id: testCase 33 | name: "Tests_ConditionalLayout" 34 | when: windowShown 35 | width: 300; height: 300 36 | 37 | function initTestCase() { 38 | Units.pixelDensity = 5; 39 | } 40 | 41 | /* it shouldn't draw any component */ 42 | function test_no_components() { 43 | layout.layouts = [] 44 | layout.when = -1 45 | compare(layout.activeComponent, null); 46 | } 47 | 48 | /* it should draw the first layout */ 49 | function test_one_layout() { 50 | layout.layouts = [first_layout] 51 | layout.when = 0 52 | compare(layout.activeComponent, first_layout); 53 | } 54 | 55 | /* it should draw the first layout and the second when 'when' property changes */ 56 | function test_layout_switching() { 57 | layout.layouts = [first_layout, second_layout] 58 | layout.when = 0 59 | compare(layout.activeComponent, first_layout); 60 | layout.when = 1 61 | compare(layout.activeComponent, second_layout); 62 | } 63 | 64 | /* it should fallback to a previous layout if the chosen is not available */ 65 | function test_layout_fallback() { 66 | layout.layouts = [first_layout, second_layout] 67 | layout.when = 4 68 | compare(layout.activeComponent, second_layout); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/tst_device_layout.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | import Mut.Layouts 0.1 6 | 7 | Item { 8 | id: container 9 | width: 300; height: 300 10 | 11 | Component { 12 | id: small 13 | 14 | Item { 15 | Rectangle { width: 80; height: 80; border.width: 1 } 16 | } 17 | } 18 | 19 | Component { 20 | id: normal 21 | 22 | Item { 23 | Rectangle { width: 80; height: 80; border.width: 1 } 24 | } 25 | } 26 | 27 | Component { 28 | id: large 29 | 30 | Item { 31 | Rectangle { width: 80; height: 80; border.width: 1 } 32 | } 33 | } 34 | 35 | DeviceLayout { 36 | id: layout 37 | } 38 | 39 | TestCase { 40 | id: testCase 41 | name: "Tests_DeviceLayout" 42 | when: windowShown 43 | width: 300; height: 300 44 | 45 | function initTestCase() { 46 | Units.pixelDensity = 5; 47 | layout.layouts = [small, normal, large] 48 | } 49 | 50 | /* it should use the right layout according to device size */ 51 | function test_size_layout() { 52 | Device.size = Device.sizeSmall; 53 | compare(layout.activeComponent, small); 54 | Device.size = Device.sizeNormal; 55 | compare(layout.activeComponent, normal); 56 | Device.size = Device.sizeLarge; 57 | compare(layout.activeComponent, large); 58 | } 59 | 60 | /* it should use the layout that best fits the device size */ 61 | function test_size_fallback_layout() { 62 | Device.size = Device.sizeXLarge; 63 | compare(layout.activeComponent, large); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/tst_divider.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | import Mut.Themes 0.1 6 | 7 | TestCase { 8 | id: testCase 9 | name: "Tests_Divider" 10 | 11 | function initTestCase() { 12 | Units.pixelDensity = 5.0; 13 | } 14 | 15 | /* tests that the default value of the properties are correct */ 16 | function test_defaults() { 17 | var tmp = Qt.createQmlObject( 18 | 'import Mut 0.1; Divider {id: divider1}', 19 | testCase, ''); 20 | compare(tmp.height, Units.dp(1)); 21 | Theme.p.divider = "blue"; 22 | compare(tmp.color, "#0000ff"); 23 | tmp.destroy(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/tst_icon.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | 6 | Item { 7 | id: container 8 | width: 300; height: 300 9 | 10 | TestCase { 11 | id: testCase 12 | name: "Tests_Icon" 13 | when: windowShown 14 | 15 | function initTestCase() { 16 | Units.pixelDensity = 5.0; 17 | } 18 | 19 | /* tests that the default value of the properties are correct */ 20 | function test_defaults() { 21 | var tmp = Qt.createQmlObject( 22 | 'import QtQuick 2.4; import Mut 0.1;' + 23 | 'Icon {id: icon1; image.source: Qt.resolvedUrl("test.svg")}', 24 | testCase, ''); 25 | compare(tmp.image.visible, false); 26 | tmp.destroy(); 27 | } 28 | 29 | function benchmark_overlayicon_transparent() { 30 | var tmp = Qt.createQmlObject( 31 | 'import QtQuick 2.4; import Mut 0.1;' + 32 | 'Icon {id: icon2; image.source: Qt.resolvedUrl("test.svg")}', 33 | testCase, ''); 34 | tmp.destroy(); 35 | } 36 | function benchmark_overlayicon_solid() { 37 | var tmp = Qt.createQmlObject( 38 | 'import QtQuick 2.4; import Mut 0.1;' + 39 | 'Icon {id: icon3; colorOverlay: "#ff0000";' + 40 | 'image.source: Qt.resolvedUrl("test.svg")}', 41 | testCase, ''); 42 | tmp.destroy(); 43 | } 44 | function benchmark_normal_image() { 45 | var tmp = Qt.createQmlObject( 46 | 'import QtQuick 2.4; import Mut 0.1;' + 47 | 'Image {' + 48 | 'sourceSize {height: Units.dp(24); width: Units.dp(24)}' + 49 | 'anchors.centerIn: parent; smooth: false;' + 50 | 'source: Qt.resolvedUrl("test.svg")}', 51 | testCase, ''); 52 | tmp.destroy(); 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tests/tst_navdrawer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtTest 1.0 4 | 5 | import Mut 0.1 6 | 7 | Item { 8 | id: container 9 | width: 800; height: 600 10 | 11 | NavDrawer { 12 | id: drawer 13 | focus: true 14 | } 15 | 16 | TestCase { 17 | id: testCase 18 | name: "Tests_NavDrawer" 19 | when: windowShown 20 | width: 800; height: 600 21 | 22 | function initTestCase() { 23 | Units.pixelDensity = 5; 24 | } 25 | 26 | /* it should be closed as default */ 27 | function test_state_default() { 28 | compare(drawer.state, "DRAWER_CLOSED"); 29 | } 30 | 31 | /* it should change the current state when open() is called */ 32 | function test_state_open() { 33 | drawer.open(); 34 | compare(drawer.state, "DRAWER_OPENED"); 35 | drawer.state = "DRAWER_CLOSED"; 36 | } 37 | 38 | /* it should change the current state when close() is called */ 39 | function test_state_close() { 40 | drawer.state = "DRAWER_OPENED"; 41 | drawer.close(); 42 | compare(drawer.state, "DRAWER_CLOSED"); 43 | } 44 | 45 | /* it should expose a toggle() state that opens and closes the component */ 46 | function test_state_toggle() { 47 | drawer.toggle(); 48 | compare(drawer.state, "DRAWER_OPENED"); 49 | drawer.toggle(); 50 | compare(drawer.state, "DRAWER_CLOSED"); 51 | } 52 | 53 | /* it should close the component when the Scrim is clicked */ 54 | function test_close_on_scrim_click() { 55 | drawer.state = "DRAWER_OPENED"; 56 | 57 | // transitions require this wait otherwise the Scrim is not ready to 58 | // accept events 59 | wait(150); 60 | 61 | mouseClick(container); 62 | compare(drawer.state, "DRAWER_CLOSED"); 63 | } 64 | 65 | /* it should expose two status getters */ 66 | function test_state_getters() { 67 | drawer.state = "DRAWER_OPENED"; 68 | verify(drawer.opened); 69 | drawer.state = "DRAWER_CLOSED"; 70 | verify(!drawer.opened); 71 | } 72 | 73 | /* the ESC button should close the drawer */ 74 | function test_esc_button() { 75 | drawer.state = "DRAWER_OPENED"; 76 | keyClick(Qt.Key_Escape); 77 | compare(drawer.state, "DRAWER_CLOSED"); 78 | } 79 | 80 | /* the mobile back button should close the drawer */ 81 | function test_back_button() { 82 | drawer.state = "DRAWER_OPENED"; 83 | keyClick(Qt.Key_Back); 84 | compare(drawer.state, "DRAWER_CLOSED"); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/tst_orientation_layout.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | import Mut.Layouts 0.1 6 | 7 | Item { 8 | id: container 9 | width: 300; height: 300 10 | 11 | Component { 12 | id: portrait 13 | 14 | Item { 15 | Rectangle { width: 80; height: 80; border.width: 1 } 16 | } 17 | } 18 | 19 | Component { 20 | id: landscape 21 | 22 | Item { 23 | Rectangle { width: 80; height: 80; border.width: 1 } 24 | } 25 | } 26 | 27 | OrientationLayout { 28 | id: layout 29 | } 30 | 31 | TestCase { 32 | id: testCase 33 | name: "Tests_OrientationLayout" 34 | when: windowShown 35 | width: 300; height: 300 36 | 37 | function initTestCase() { 38 | Units.pixelDensity = 5; 39 | layout.layouts = [portrait, landscape] 40 | } 41 | 42 | /* it should use the portrait layout when in portrait mode */ 43 | function test_portrait_layout() { 44 | Device.primaryOrientation = Qt.PortraitOrientation 45 | compare(layout.activeComponent, portrait); 46 | } 47 | 48 | /* it should use the landscape layout when in landscape mode */ 49 | function test_landscape_layout() { 50 | Device.primaryOrientation = Qt.LandscapeOrientation 51 | compare(layout.activeComponent, landscape); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/tst_page.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtTest 1.0 4 | 5 | import Mut 0.1 6 | 7 | Item { 8 | width: Units.dp(100); height: Units.dp(200) 9 | StackView { 10 | id: stackView 11 | anchors.fill: parent 12 | } 13 | TestCase { 14 | id: testCase 15 | name: "Tests_Page" 16 | when: windowShown 17 | 18 | function initTestCase() { 19 | Units.pixelDensity = 5.0; 20 | } 21 | 22 | /* tests the api to push and pop pages */ 23 | function test_pagePushPop() { 24 | var page = Qt.createQmlObject( 25 | 'import Mut 0.1; Page {id: page1}', 26 | testCase, ''); 27 | var tmp = Qt.createQmlObject( 28 | 'import Mut 0.1; Page {id: page2}', 29 | testCase, ''); 30 | 31 | stackView.push(page); 32 | page.push(tmp); 33 | compare(stackView.currentItem, tmp); 34 | 35 | tmp.pop(); 36 | compare(stackView.currentItem, page); 37 | 38 | stackView.clear(); 39 | page.destroy(); 40 | tmp.destroy(); 41 | } 42 | 43 | /* tests that the default navigation action pops the 44 | current page from the stack. 45 | */ 46 | function test_defaultNavAction() { 47 | var page = Qt.createQmlObject( 48 | 'import Mut 0.1; Page {id: page1}', 49 | testCase, ''); 50 | var tmp = Qt.createQmlObject( 51 | 'import Mut 0.1; Page {id: page1}', 52 | testCase, ''); 53 | 54 | stackView.push(page); 55 | page.push(tmp); 56 | tmp.navAction.trigger(); 57 | compare(stackView.currentItem, page); 58 | 59 | stackView.clear(); 60 | page.destroy(); 61 | tmp.destroy(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/tst_paper.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | 6 | 7 | TestCase { 8 | id: testCase 9 | name: "Tests_Paper" 10 | 11 | function initTestCase() { 12 | Units.pixelDensity = 5.0; 13 | } 14 | 15 | /* tests that the default value of the properties are correct */ 16 | function test_defaults() { 17 | var tmp = Qt.createQmlObject( 18 | 'import Mut 0.1; Paper {id: paper1}', 19 | testCase, ''); 20 | compare(tmp.elevation, 0); 21 | compare(tmp.fillWidth, false); 22 | compare(tmp.fillHeight, false); 23 | tmp.destroy() 24 | } 25 | 26 | /* tests that effect's properties change accordingly to elevation 27 | * attribute */ 28 | function test_elevation() { 29 | var tmp = Qt.createQmlObject( 30 | 'import Mut 0.1; Paper {id: paper1}', 31 | testCase, ''); 32 | tmp.elevation = 0.0; 33 | compare(tmp.children[1].glowRadius, 0); // topEffect 34 | compare(tmp.children[2].glowRadius, 0); // bottomEffect 35 | 36 | tmp.elevation = 1.0; 37 | compare(tmp.children[1].glowRadius, 2.0); // topEffect 38 | compare(tmp.children[2].glowRadius, 1.0); // bottomEffect 39 | 40 | tmp.destroy(); 41 | } 42 | 43 | /* tests that there is no shadow when elevation is 0 */ 44 | function test_no_shadow_when_elevation_is_zero() { 45 | var tmp = Qt.createQmlObject( 46 | 'import Mut 0.1; ' + 47 | 'Paper {id: paper1; elevation: 0}', 48 | testCase, ''); 49 | compare(tmp.__hasShadow, false); 50 | compare(tmp.children[0].visible, false); 51 | compare(tmp.children[1].visible, false); 52 | } 53 | 54 | /* tests that there is no shadow when paper background is 55 | * transparent */ 56 | function test_no_shadow_when_transparent() { 57 | var tmp = Qt.createQmlObject( 58 | 'import Mut 0.1; ' + 59 | 'Paper {id: paper1; backgroundColor: "transparent"}', 60 | testCase, ''); 61 | compare(tmp.__hasShadow, false); 62 | compare(tmp.children[0].visible, false); 63 | compare(tmp.children[1].visible, false); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/tst_scrim.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtTest 1.0 4 | 5 | import Mut 0.1 6 | 7 | Item { 8 | id: container 9 | width: 300; height: 300 10 | 11 | SignalSpy { 12 | id: button_spy 13 | target: button 14 | signalName: "clicked" 15 | } 16 | 17 | Button { 18 | id: button 19 | } 20 | 21 | Scrim { 22 | id: scrim 23 | anchors.fill: parent 24 | } 25 | 26 | TestCase { 27 | id: testCase 28 | name: "Tests_Scrim" 29 | when: windowShown 30 | width: 300; height: 300 31 | 32 | function initTestCase() { 33 | Units.pixelDensity = 5; 34 | } 35 | 36 | /* should capture all mouse events */ 37 | function test_overlay_events_capture() { 38 | mouseClick(button); 39 | compare(button_spy.count, 0); 40 | button_spy.clear(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/tst_single_line_item.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtTest 1.0 4 | 5 | import Mut 0.1 6 | import Mut.Themes 0.1 7 | import Mut.ListItems 0.1 8 | 9 | Item { 10 | id: container 11 | width: 300; height: 300 12 | 13 | Action { 14 | id: primary_action 15 | onTriggered: { 16 | console.log("primary") 17 | } 18 | } 19 | 20 | Action { 21 | id: primary_action_with_icon 22 | iconSource: Qt.resolvedUrl("test.svg") 23 | onTriggered: { 24 | console.log("primary with icon") 25 | } 26 | } 27 | 28 | Action { 29 | id: secondary_action 30 | iconSource: Qt.resolvedUrl("test.svg") 31 | onTriggered: { 32 | console.log("secondary") 33 | } 34 | } 35 | 36 | SingleLineItem { 37 | id: single_line 38 | text: "Single line item" 39 | 40 | SignalSpy { 41 | id: primary_spy 42 | target: primary_action 43 | signalName: "triggered" 44 | } 45 | 46 | SignalSpy { 47 | id: secondary_spy 48 | target: secondary_action 49 | signalName: "triggered" 50 | } 51 | 52 | primaryAction: primary_action 53 | secondaryAction: secondary_action 54 | } 55 | 56 | TestCase { 57 | id: testCase 58 | name: "Tests_SingleLineItem" 59 | when: windowShown 60 | width: 300; height: 300 61 | 62 | function initTestCase() { 63 | Units.pixelDensity = 5; 64 | } 65 | 66 | /* it should trigger the surface action */ 67 | function test_click_on_surface() { 68 | mouseClick(single_line); 69 | compare(primary_spy.count, 1); 70 | compare(secondary_spy.count, 0); 71 | primary_spy.clear(); 72 | secondary_spy.clear(); 73 | } 74 | 75 | /* it should trigger the supplemental action */ 76 | function test_click_on_supplemental_action() { 77 | mouseClick(single_line, single_line.width - Units.dp(16)); 78 | compare(primary_spy.count, 0); 79 | compare(secondary_spy.count, 1); 80 | primary_spy.clear(); 81 | secondary_spy.clear(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/tst_theme.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | import Mut.Themes 0.1 6 | 7 | 8 | TestCase { 9 | name: "Tests_Theme" 10 | 11 | /* tests image function returns the correct path 12 | based on the imageAssetsPath value 13 | */ 14 | function test_image() { 15 | var result = Theme.image('foo.svg'); 16 | compare(result, ""); 17 | 18 | Theme.imageAssetsPath = "/assets/"; 19 | result = Theme.image('foo.svg'); 20 | compare(result, "file:///assets/foo.svg"); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /tests/tst_tile.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.3 3 | import QtTest 1.0 4 | 5 | import Mut 0.1 6 | import Mut.Themes 0.1 7 | import Mut.ListItems 0.1 8 | 9 | Item { 10 | id: container 11 | width: 300; height: 300 12 | 13 | Component { 14 | id: primary 15 | 16 | Text { 17 | text: "Hello world!" 18 | } 19 | } 20 | 21 | Component { 22 | id: secondary 23 | 24 | Button { 25 | action: Action { 26 | iconSource: Qt.resolvedUrl("test.svg") 27 | } 28 | } 29 | } 30 | 31 | Tile { 32 | id: tile 33 | primaryComponent: primary 34 | } 35 | 36 | TestCase { 37 | id: testCase 38 | name: "Tests_Tile" 39 | when: windowShown 40 | width: 300; height: 300 41 | 42 | function initTestCase() { 43 | Units.pixelDensity = 5; 44 | } 45 | 46 | /* default background color should be white */ 47 | function test_background_default() { 48 | compare(tile.backgroundColor, Theme.p.background); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/tst_toolbar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | import Mut.Themes 0.1 6 | 7 | TestCase { 8 | id: testCase 9 | name: "Tests_ToolBar" 10 | 11 | /* tests that the default value of the properties are correct */ 12 | function test_defaults() { 13 | var tmp = Qt.createQmlObject( 14 | 'import Mut 0.1; ToolBar {id: toobar1}', 15 | testCase, ''); 16 | 17 | Theme.p.primary = "blue"; 18 | compare(tmp.backgroundColor, "#0000ff"); 19 | Theme.p.primary = "red"; 20 | compare(tmp.backgroundColor, "#ff0000"); 21 | 22 | compare(tmp.elevation, 0); 23 | compare(tmp.fillWidth, false); 24 | tmp.destroy() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/tst_units.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtTest 1.0 3 | 4 | import Mut 0.1 5 | 6 | 7 | TestCase { 8 | name: "Tests_Units" 9 | 10 | /* tests dp computes correct component size */ 11 | function test_dp() { 12 | Units.pixelDensity = 5; 13 | compare(Units.dp(10), 8); 14 | 15 | Units.pixelDensity = 10; 16 | compare(Units.dp(10), 16); 17 | } 18 | 19 | /* tests sp computes correct font size */ 20 | function test_sp() { 21 | Units.pixelDensity = 5; 22 | compare(Units.sp(10), 8); 23 | 24 | Units.fontScale = 4; 25 | compare(Units.sp(10), 32) 26 | } 27 | } 28 | --------------------------------------------------------------------------------