commit 32a567a95017e09687939e31788aa71b65e24e3b Author: Stefano Moretti Date: Sat Dec 18 19:11:48 2021 +0100 First commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..be11acb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.10) + +project(baseui VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +option(BASEUI_EMBED_QML "BaseUI embed qml" OFF) +option(BASEUI_EMBED_ICONS "BaseUI embed icons" OFF) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Qml Quick Gui QuickControls2 REQUIRED) + +add_library(${PROJECT_NAME} STATIC + include/BaseUI/core.h + src/core.cpp + src/iconprovider.h + src/icons.h + src/icons.cpp +) + +if(BASEUI_EMBED_QML) + target_sources(${PROJECT_NAME} PRIVATE "qml/BaseUI/baseui_qml.qrc") + target_compile_definitions(${PROJECT_NAME} PRIVATE BASEUI_EMBED_QML) +else() + file(GLOB QML_FILES "qml/BaseUI/*.qml" "qml/BaseUI/qmldir") + + add_custom_target(copy_qml_to_binary_dir ALL + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_BINARY_DIR}/BaseUI" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different ${QML_FILES} "${CMAKE_BINARY_DIR}/BaseUI" + COMMENT "Copying QML files to binary directory" + VERBATIM + ) +endif() + +if(BASEUI_EMBED_ICONS) + target_sources(${PROJECT_NAME} PRIVATE "icons/baseui_icons.qrc") + target_compile_definitions(${PROJECT_NAME} PRIVATE BASEUI_EMBED_ICONS) +else() + add_custom_target(copy_icons_to_binary_dir ALL + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_BINARY_DIR}/BaseUI/icons" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${PROJECT_SOURCE_DIR}/icons/codepoints.json" + "${PROJECT_SOURCE_DIR}/icons/MaterialIcons-Regular.ttf" + "${CMAKE_BINARY_DIR}/BaseUI/icons" + COMMENT "Copying icons files to binary directory" + VERBATIM + ) +endif() + +target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/include") + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) + +target_compile_definitions(${PROJECT_NAME} + PRIVATE + $<$,$>: + QT_QML_DEBUG + # enable deprecated warnings for qt < 5.13 + QT_DEPRECATED_WARNINGS + > + + $<$: + # disable deprecated warnings for qt >= 5.13 + QT_NO_DEPRECATED_WARNINGS + > +) + +target_compile_options(${PROJECT_NAME} + PRIVATE + $<$,$>: + -Wall + -Wextra + -Wpedantic + > + + $<$: + /W4 + > +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Qt${QT_VERSION_MAJOR}::Qml + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::QuickControls2 +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..4e4dc8b --- /dev/null +++ b/README.rst @@ -0,0 +1,2 @@ +BaseUI +====== diff --git a/baseui.pri b/baseui.pri new file mode 100644 index 0000000..6af2aac --- /dev/null +++ b/baseui.pri @@ -0,0 +1,54 @@ +QT += qml quick quickcontrols2 + +BASEUI_DIR = BaseUI + +INCLUDEPATH += \ + $$PWD/include + +HEADERS += \ + $$PWD/include/BaseUI/core.h \ + $$PWD/src/iconprovider.h \ + $$PWD/src/icons.h + +SOURCES += \ + $$PWD/src/core.cpp \ + $$PWD/src/icons.cpp + +QML_FILES = \ + $$PWD/qml/BaseUI/qmldir \ + $$files($$PWD/qml/BaseUI/*.qml) + +OTHER_FILES += $$QML_FILES + +contains(CONFIG, baseui_embed_qml) { + DEFINES += BASEUI_EMBED_QML + RESOURCES += $$PWD/qml/BaseUI/baseui_qml.qrc +} else { + qml_copy.path = $$BASEUI_DIR + qml_copy.files = $$QML_FILES + + qml_install.path = $$DESTDIR/$$BASEUI_DIR + qml_install.files = $$QML_FILES + + COPIES += qml_copy + INSTALLS += qml_install +} + +contains(CONFIG, baseui_embed_icons) { + DEFINES += BASEUI_EMBED_ICONS + RESOURCES += $$PWD/icons/baseui_icons.qrc +} else { + BASEUI_ICONS_DIR = $$BASEUI_DIR/icons + ICONS_FILES = \ + $$PWD/icons/codepoints.json \ + $$PWD/icons/MaterialIcons-Regular.ttf + + icons_copy.path = $$BASEUI_ICONS_DIR + icons_copy.files = $$ICONS_FILES + + icons_install.path = $$DESTDIR/$$BASEUI_ICONS_DIR + icons_install.files = $$ICONS_FILES + + COPIES += icons_copy + INSTALLS += icons_install +} diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..d4fd682 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10) + +project(example VERSION 0.1 LANGUAGES CXX) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Qml Quick Gui QuickControls2 REQUIRED) + +set(CMAKE_AUTORCC ON) + +add_executable(${PROJECT_NAME} main.cpp qml.qrc) + +add_subdirectory(baseui) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Qt${QT_VERSION_MAJOR}::Qml + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::QuickControls2 + baseui +) diff --git a/example/example.pro b/example/example.pro new file mode 100644 index 0000000..2d08a24 --- /dev/null +++ b/example/example.pro @@ -0,0 +1,7 @@ +QT += quick quickcontrols2 + +include(baseui/baseui.pri) + +SOURCES += main.cpp + +RESOURCES += qml.qrc diff --git a/example/main.cpp b/example/main.cpp new file mode 100644 index 0000000..92130b4 --- /dev/null +++ b/example/main.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + BaseUI::init(&engine); + + QUrl url("qrc:/main.qml"); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/example/main.qml b/example/main.qml new file mode 100644 index 0000000..e8f4819 --- /dev/null +++ b/example/main.qml @@ -0,0 +1,302 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.12 + +import BaseUI 1.0 as UI + +UI.App { + id: root + + width: 360 + height: 480 + + property string primary: Material.primary + property string accent: Material.accent + property bool theme: false + + initialPage: UI.AppStackPage { + id: homePage + + title: "HomePage" + + leftButton: Action { + icon.source: UI.Icons.menu + onTriggered: navDrawer.open() + } + + rightButtons: [ + Action { + icon.source: UI.Icons.more_vert + onTriggered: optionsMenu.open() + } + ] + + Pane { + id: mainPane + + anchors.fill: parent + + ColumnLayout { + width: parent.width + + UI.LabelBody { + leftPadding: 10 + rightPadding: 10 + text: Qt.application.name + } + } + } + + Drawer { + id: navDrawer + + interactive: homePage.stack.currentItem == homePage + width: Math.min(240, Math.min(parent.width, parent.height) / 3 * 2 ) + height: parent.height + + onAboutToShow: menuColumn.enabled = true + + Flickable { + anchors.fill: parent + contentHeight: menuColumn.implicitHeight + boundsBehavior: Flickable.StopAtBounds + + ColumnLayout { + id: menuColumn + + anchors { left: parent.left; right: parent.right } + spacing: 0 + + Rectangle { + id: topItem + + height: 140 + color: UI.Style.primaryColor + Layout.fillWidth: true + + Text { + text: Qt.application.name + color: UI.Style.textOnPrimary + font.pixelSize: UI.Style.fontSizeHeadline + wrapMode: Text.WordWrap + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + margins: 25 + } + } + } + + Repeater { + id: pageList + + model: [ + { icon: UI.Icons.settings, text: "Settings", page: settingsPage }, + { icon: UI.Icons.info_outline, text: "About", page: aboutPage } + ] + + delegate: ItemDelegate { + icon.source: modelData.icon + text: modelData.text + Layout.fillWidth: true + onClicked: { + // Disable, or a double click will push the page twice. + menuColumn.enabled = false + navDrawer.close() + pageStack.push(modelData.page) + } + } + } + } + } + } + + Menu { + id: optionsMenu + + modal: true + dim: false + closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape + x: parent.width - width - 6 + y: -homePage.appToolBar.height + 6 + transformOrigin: Menu.TopRight + + onAboutToShow: enabled = true + onAboutToHide: currentIndex = -1 // reset highlighting + + MenuItem { + text: "Toast test" + onTriggered: toastPopup.start("Toast message.") + } + MenuItem { + text: "Error test" + onTriggered: errorPopup.start("Error message.") + } + MenuItem { + text: "Info test" + onTriggered: infoPopup.open() + } + } + } + + Component { + id: settingsPage + + UI.AppStackPage { + title: "Settings" + padding: 0 + + Flickable { + contentHeight: settingsPane.implicitHeight + anchors.fill: parent + + Pane { + id: settingsPane + + anchors.fill: parent + padding: 0 + + ColumnLayout { + width: parent.width + spacing: 0 + + UI.SettingsSectionTitle { + text: "Display" + } + + UI.SettingsItem { + title: "Dark Theme" + check.visible: true + check.checked: root.theme + check.onClicked: root.theme = !root.theme + onClicked: check.clicked() + } + + UI.SettingsItem { + title: "Primary Color" + subtitle: primaryColorPopup.currentColorName + onClicked: primaryColorPopup.open() + } + + UI.SettingsItem { + title: "Accent Color" + subtitle: accentColorPopup.currentColorName + onClicked: accentColorPopup.open() + } + } + } + } + } + } + + Component { + id: aboutPage + + UI.AppStackPage { + title: "About" + padding: 10 + + Flickable { + contentHeight: aboutPane.implicitHeight + anchors.fill: parent + + Pane { + id: aboutPane + + anchors.fill: parent + + ColumnLayout { + width: parent.width + + UI.LabelTitle { + text: Qt.application.name + horizontalAlignment: Qt.AlignHCenter + } + + UI.LabelBody { + property string url: "http://github.com/stemoretti/baseui" + + text: "" + url + "" + linkColor: UI.Style.isDarkTheme ? "lightblue" : "blue" + onLinkActivated: Qt.openUrlExternally(link) + horizontalAlignment: Qt.AlignHCenter + } + + UI.HorizontalDivider { } + + UI.LabelSubheading { + text: "This app is based on the following software:" + wrapMode: Text.WordWrap + } + + UI.LabelBody { + text: "Qt 6
" + + "Copyright 2008-2021 The Qt Company Ltd." + + " All rights reserved." + wrapMode: Text.WordWrap + } + + UI.LabelBody { + text: "Qt is under the LGPLv3 license." + wrapMode: Text.WordWrap + } + + UI.HorizontalDivider { } + + UI.LabelBody { + text: "Material Design" + + " icons are under Apache license version 2.0" + wrapMode: Text.WordWrap + linkColor: UI.Style.isDarkTheme ? "lightblue" : "blue" + onLinkActivated: Qt.openUrlExternally(link) + } + } + } + } + } + } + + UI.PopupToast { + id: toastPopup + } + + UI.PopupError { + id: errorPopup + } + + UI.PopupInfo { + id: infoPopup + + parent: Overlay.overlay + + text: "Information message." + } + + UI.PopupColorSelection { + id: primaryColorPopup + + parent: Overlay.overlay + + currentColor: root.primary + onColorSelected: function(c) { root.primary = c } + } + + UI.PopupColorSelection { + id: accentColorPopup + + parent: Overlay.overlay + + selectAccentColor: true + currentColor: root.accent + onColorSelected: function(c) { root.accent = c } + } + + Component.onCompleted: { + UI.Style.primaryColor = Qt.binding(function() { return root.primary }) + UI.Style.accentColor = Qt.binding(function() { return root.accent }) + UI.Style.isDarkTheme = Qt.binding(function() { return root.theme }) + } +} diff --git a/example/qml.qrc b/example/qml.qrc new file mode 100644 index 0000000..5f6483a --- /dev/null +++ b/example/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/icons/LICENSE b/icons/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/icons/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/icons/MaterialIcons-Regular.ttf b/icons/MaterialIcons-Regular.ttf new file mode 100644 index 0000000..7015564 Binary files /dev/null and b/icons/MaterialIcons-Regular.ttf differ diff --git a/icons/baseui_icons.qrc b/icons/baseui_icons.qrc new file mode 100644 index 0000000..7b7e4eb --- /dev/null +++ b/icons/baseui_icons.qrc @@ -0,0 +1,6 @@ + + + MaterialIcons-Regular.ttf + codepoints.json + + diff --git a/icons/codepoints.json b/icons/codepoints.json new file mode 100644 index 0000000..3cf3044 --- /dev/null +++ b/icons/codepoints.json @@ -0,0 +1,934 @@ +{ + "3d_rotation": "\ue84d", + "ac_unit": "\ueb3b", + "access_alarm": "\ue190", + "access_alarms": "\ue191", + "access_time": "\ue192", + "accessibility": "\ue84e", + "accessible": "\ue914", + "account_balance": "\ue84f", + "account_balance_wallet": "\ue850", + "account_box": "\ue851", + "account_circle": "\ue853", + "adb": "\ue60e", + "add": "\ue145", + "add_a_photo": "\ue439", + "add_alarm": "\ue193", + "add_alert": "\ue003", + "add_box": "\ue146", + "add_circle": "\ue147", + "add_circle_outline": "\ue148", + "add_location": "\ue567", + "add_shopping_cart": "\ue854", + "add_to_photos": "\ue39d", + "add_to_queue": "\ue05c", + "adjust": "\ue39e", + "airline_seat_flat": "\ue630", + "airline_seat_flat_angled": "\ue631", + "airline_seat_individual_suite": "\ue632", + "airline_seat_legroom_extra": "\ue633", + "airline_seat_legroom_normal": "\ue634", + "airline_seat_legroom_reduced": "\ue635", + "airline_seat_recline_extra": "\ue636", + "airline_seat_recline_normal": "\ue637", + "airplanemode_active": "\ue195", + "airplanemode_inactive": "\ue194", + "airplay": "\ue055", + "airport_shuttle": "\ueb3c", + "alarm": "\ue855", + "alarm_add": "\ue856", + "alarm_off": "\ue857", + "alarm_on": "\ue858", + "album": "\ue019", + "all_inclusive": "\ueb3d", + "all_out": "\ue90b", + "android": "\ue859", + "announcement": "\ue85a", + "apps": "\ue5c3", + "archive": "\ue149", + "arrow_back": "\ue5c4", + "arrow_downward": "\ue5db", + "arrow_drop_down": "\ue5c5", + "arrow_drop_down_circle": "\ue5c6", + "arrow_drop_up": "\ue5c7", + "arrow_forward": "\ue5c8", + "arrow_upward": "\ue5d8", + "art_track": "\ue060", + "aspect_ratio": "\ue85b", + "assessment": "\ue85c", + "assignment": "\ue85d", + "assignment_ind": "\ue85e", + "assignment_late": "\ue85f", + "assignment_return": "\ue860", + "assignment_returned": "\ue861", + "assignment_turned_in": "\ue862", + "assistant": "\ue39f", + "assistant_photo": "\ue3a0", + "attach_file": "\ue226", + "attach_money": "\ue227", + "attachment": "\ue2bc", + "audiotrack": "\ue3a1", + "autorenew": "\ue863", + "av_timer": "\ue01b", + "backspace": "\ue14a", + "backup": "\ue864", + "battery_alert": "\ue19c", + "battery_charging_full": "\ue1a3", + "battery_full": "\ue1a4", + "battery_std": "\ue1a5", + "battery_unknown": "\ue1a6", + "beach_access": "\ueb3e", + "beenhere": "\ue52d", + "block": "\ue14b", + "bluetooth": "\ue1a7", + "bluetooth_audio": "\ue60f", + "bluetooth_connected": "\ue1a8", + "bluetooth_disabled": "\ue1a9", + "bluetooth_searching": "\ue1aa", + "blur_circular": "\ue3a2", + "blur_linear": "\ue3a3", + "blur_off": "\ue3a4", + "blur_on": "\ue3a5", + "book": "\ue865", + "bookmark": "\ue866", + "bookmark_border": "\ue867", + "border_all": "\ue228", + "border_bottom": "\ue229", + "border_clear": "\ue22a", + "border_color": "\ue22b", + "border_horizontal": "\ue22c", + "border_inner": "\ue22d", + "border_left": "\ue22e", + "border_outer": "\ue22f", + "border_right": "\ue230", + "border_style": "\ue231", + "border_top": "\ue232", + "border_vertical": "\ue233", + "branding_watermark": "\ue06b", + "brightness_1": "\ue3a6", + "brightness_2": "\ue3a7", + "brightness_3": "\ue3a8", + "brightness_4": "\ue3a9", + "brightness_5": "\ue3aa", + "brightness_6": "\ue3ab", + "brightness_7": "\ue3ac", + "brightness_auto": "\ue1ab", + "brightness_high": "\ue1ac", + "brightness_low": "\ue1ad", + "brightness_medium": "\ue1ae", + "broken_image": "\ue3ad", + "brush": "\ue3ae", + "bubble_chart": "\ue6dd", + "bug_report": "\ue868", + "build": "\ue869", + "burst_mode": "\ue43c", + "business": "\ue0af", + "business_center": "\ueb3f", + "cached": "\ue86a", + "cake": "\ue7e9", + "call": "\ue0b0", + "call_end": "\ue0b1", + "call_made": "\ue0b2", + "call_merge": "\ue0b3", + "call_missed": "\ue0b4", + "call_missed_outgoing": "\ue0e4", + "call_received": "\ue0b5", + "call_split": "\ue0b6", + "call_to_action": "\ue06c", + "camera": "\ue3af", + "camera_alt": "\ue3b0", + "camera_enhance": "\ue8fc", + "camera_front": "\ue3b1", + "camera_rear": "\ue3b2", + "camera_roll": "\ue3b3", + "cancel": "\ue5c9", + "card_giftcard": "\ue8f6", + "card_membership": "\ue8f7", + "card_travel": "\ue8f8", + "casino": "\ueb40", + "cast": "\ue307", + "cast_connected": "\ue308", + "center_focus_strong": "\ue3b4", + "center_focus_weak": "\ue3b5", + "change_history": "\ue86b", + "chat": "\ue0b7", + "chat_bubble": "\ue0ca", + "chat_bubble_outline": "\ue0cb", + "check": "\ue5ca", + "check_box": "\ue834", + "check_box_outline_blank": "\ue835", + "check_circle": "\ue86c", + "chevron_left": "\ue5cb", + "chevron_right": "\ue5cc", + "child_care": "\ueb41", + "child_friendly": "\ueb42", + "chrome_reader_mode": "\ue86d", + "class": "\ue86e", + "clear": "\ue14c", + "clear_all": "\ue0b8", + "close": "\ue5cd", + "closed_caption": "\ue01c", + "cloud": "\ue2bd", + "cloud_circle": "\ue2be", + "cloud_done": "\ue2bf", + "cloud_download": "\ue2c0", + "cloud_off": "\ue2c1", + "cloud_queue": "\ue2c2", + "cloud_upload": "\ue2c3", + "code": "\ue86f", + "collections": "\ue3b6", + "collections_bookmark": "\ue431", + "color_lens": "\ue3b7", + "colorize": "\ue3b8", + "comment": "\ue0b9", + "compare": "\ue3b9", + "compare_arrows": "\ue915", + "computer": "\ue30a", + "confirmation_number": "\ue638", + "contact_mail": "\ue0d0", + "contact_phone": "\ue0cf", + "contacts": "\ue0ba", + "content_copy": "\ue14d", + "content_cut": "\ue14e", + "content_paste": "\ue14f", + "control_point": "\ue3ba", + "control_point_duplicate": "\ue3bb", + "copyright": "\ue90c", + "create": "\ue150", + "create_new_folder": "\ue2cc", + "credit_card": "\ue870", + "crop": "\ue3be", + "crop_16_9": "\ue3bc", + "crop_3_2": "\ue3bd", + "crop_5_4": "\ue3bf", + "crop_7_5": "\ue3c0", + "crop_din": "\ue3c1", + "crop_free": "\ue3c2", + "crop_landscape": "\ue3c3", + "crop_original": "\ue3c4", + "crop_portrait": "\ue3c5", + "crop_rotate": "\ue437", + "crop_square": "\ue3c6", + "dashboard": "\ue871", + "data_usage": "\ue1af", + "date_range": "\ue916", + "dehaze": "\ue3c7", + "delete": "\ue872", + "delete_forever": "\ue92b", + "delete_sweep": "\ue16c", + "description": "\ue873", + "desktop_mac": "\ue30b", + "desktop_windows": "\ue30c", + "details": "\ue3c8", + "developer_board": "\ue30d", + "developer_mode": "\ue1b0", + "device_hub": "\ue335", + "devices": "\ue1b1", + "devices_other": "\ue337", + "dialer_sip": "\ue0bb", + "dialpad": "\ue0bc", + "directions": "\ue52e", + "directions_bike": "\ue52f", + "directions_boat": "\ue532", + "directions_bus": "\ue530", + "directions_car": "\ue531", + "directions_railway": "\ue534", + "directions_run": "\ue566", + "directions_subway": "\ue533", + "directions_transit": "\ue535", + "directions_walk": "\ue536", + "disc_full": "\ue610", + "dns": "\ue875", + "do_not_disturb": "\ue612", + "do_not_disturb_alt": "\ue611", + "do_not_disturb_off": "\ue643", + "do_not_disturb_on": "\ue644", + "dock": "\ue30e", + "domain": "\ue7ee", + "done": "\ue876", + "done_all": "\ue877", + "donut_large": "\ue917", + "donut_small": "\ue918", + "drafts": "\ue151", + "drag_handle": "\ue25d", + "drive_eta": "\ue613", + "dvr": "\ue1b2", + "edit": "\ue3c9", + "edit_location": "\ue568", + "eject": "\ue8fb", + "email": "\ue0be", + "enhanced_encryption": "\ue63f", + "equalizer": "\ue01d", + "error": "\ue000", + "error_outline": "\ue001", + "euro_symbol": "\ue926", + "ev_station": "\ue56d", + "event": "\ue878", + "event_available": "\ue614", + "event_busy": "\ue615", + "event_note": "\ue616", + "event_seat": "\ue903", + "exit_to_app": "\ue879", + "expand_less": "\ue5ce", + "expand_more": "\ue5cf", + "explicit": "\ue01e", + "explore": "\ue87a", + "exposure": "\ue3ca", + "exposure_neg_1": "\ue3cb", + "exposure_neg_2": "\ue3cc", + "exposure_plus_1": "\ue3cd", + "exposure_plus_2": "\ue3ce", + "exposure_zero": "\ue3cf", + "extension": "\ue87b", + "face": "\ue87c", + "fast_forward": "\ue01f", + "fast_rewind": "\ue020", + "favorite": "\ue87d", + "favorite_border": "\ue87e", + "featured_play_list": "\ue06d", + "featured_video": "\ue06e", + "feedback": "\ue87f", + "fiber_dvr": "\ue05d", + "fiber_manual_record": "\ue061", + "fiber_new": "\ue05e", + "fiber_pin": "\ue06a", + "fiber_smart_record": "\ue062", + "file_download": "\ue2c4", + "file_upload": "\ue2c6", + "filter": "\ue3d3", + "filter_1": "\ue3d0", + "filter_2": "\ue3d1", + "filter_3": "\ue3d2", + "filter_4": "\ue3d4", + "filter_5": "\ue3d5", + "filter_6": "\ue3d6", + "filter_7": "\ue3d7", + "filter_8": "\ue3d8", + "filter_9": "\ue3d9", + "filter_9_plus": "\ue3da", + "filter_b_and_w": "\ue3db", + "filter_center_focus": "\ue3dc", + "filter_drama": "\ue3dd", + "filter_frames": "\ue3de", + "filter_hdr": "\ue3df", + "filter_list": "\ue152", + "filter_none": "\ue3e0", + "filter_tilt_shift": "\ue3e2", + "filter_vintage": "\ue3e3", + "find_in_page": "\ue880", + "find_replace": "\ue881", + "fingerprint": "\ue90d", + "first_page": "\ue5dc", + "fitness_center": "\ueb43", + "flag": "\ue153", + "flare": "\ue3e4", + "flash_auto": "\ue3e5", + "flash_off": "\ue3e6", + "flash_on": "\ue3e7", + "flight": "\ue539", + "flight_land": "\ue904", + "flight_takeoff": "\ue905", + "flip": "\ue3e8", + "flip_to_back": "\ue882", + "flip_to_front": "\ue883", + "folder": "\ue2c7", + "folder_open": "\ue2c8", + "folder_shared": "\ue2c9", + "folder_special": "\ue617", + "font_download": "\ue167", + "format_align_center": "\ue234", + "format_align_justify": "\ue235", + "format_align_left": "\ue236", + "format_align_right": "\ue237", + "format_bold": "\ue238", + "format_clear": "\ue239", + "format_color_fill": "\ue23a", + "format_color_reset": "\ue23b", + "format_color_text": "\ue23c", + "format_indent_decrease": "\ue23d", + "format_indent_increase": "\ue23e", + "format_italic": "\ue23f", + "format_line_spacing": "\ue240", + "format_list_bulleted": "\ue241", + "format_list_numbered": "\ue242", + "format_paint": "\ue243", + "format_quote": "\ue244", + "format_shapes": "\ue25e", + "format_size": "\ue245", + "format_strikethrough": "\ue246", + "format_textdirection_l_to_r": "\ue247", + "format_textdirection_r_to_l": "\ue248", + "format_underlined": "\ue249", + "forum": "\ue0bf", + "forward": "\ue154", + "forward_10": "\ue056", + "forward_30": "\ue057", + "forward_5": "\ue058", + "free_breakfast": "\ueb44", + "fullscreen": "\ue5d0", + "fullscreen_exit": "\ue5d1", + "functions": "\ue24a", + "g_translate": "\ue927", + "gamepad": "\ue30f", + "games": "\ue021", + "gavel": "\ue90e", + "gesture": "\ue155", + "get_app": "\ue884", + "gif": "\ue908", + "golf_course": "\ueb45", + "gps_fixed": "\ue1b3", + "gps_not_fixed": "\ue1b4", + "gps_off": "\ue1b5", + "grade": "\ue885", + "gradient": "\ue3e9", + "grain": "\ue3ea", + "graphic_eq": "\ue1b8", + "grid_off": "\ue3eb", + "grid_on": "\ue3ec", + "group": "\ue7ef", + "group_add": "\ue7f0", + "group_work": "\ue886", + "hd": "\ue052", + "hdr_off": "\ue3ed", + "hdr_on": "\ue3ee", + "hdr_strong": "\ue3f1", + "hdr_weak": "\ue3f2", + "headset": "\ue310", + "headset_mic": "\ue311", + "healing": "\ue3f3", + "hearing": "\ue023", + "help": "\ue887", + "help_outline": "\ue8fd", + "high_quality": "\ue024", + "highlight": "\ue25f", + "highlight_off": "\ue888", + "history": "\ue889", + "home": "\ue88a", + "hot_tub": "\ueb46", + "hotel": "\ue53a", + "hourglass_empty": "\ue88b", + "hourglass_full": "\ue88c", + "http": "\ue902", + "https": "\ue88d", + "image": "\ue3f4", + "image_aspect_ratio": "\ue3f5", + "import_contacts": "\ue0e0", + "import_export": "\ue0c3", + "important_devices": "\ue912", + "inbox": "\ue156", + "indeterminate_check_box": "\ue909", + "info": "\ue88e", + "info_outline": "\ue88f", + "input": "\ue890", + "insert_chart": "\ue24b", + "insert_comment": "\ue24c", + "insert_drive_file": "\ue24d", + "insert_emoticon": "\ue24e", + "insert_invitation": "\ue24f", + "insert_link": "\ue250", + "insert_photo": "\ue251", + "invert_colors": "\ue891", + "invert_colors_off": "\ue0c4", + "iso": "\ue3f6", + "keyboard": "\ue312", + "keyboard_arrow_down": "\ue313", + "keyboard_arrow_left": "\ue314", + "keyboard_arrow_right": "\ue315", + "keyboard_arrow_up": "\ue316", + "keyboard_backspace": "\ue317", + "keyboard_capslock": "\ue318", + "keyboard_hide": "\ue31a", + "keyboard_return": "\ue31b", + "keyboard_tab": "\ue31c", + "keyboard_voice": "\ue31d", + "kitchen": "\ueb47", + "label": "\ue892", + "label_outline": "\ue893", + "landscape": "\ue3f7", + "language": "\ue894", + "laptop": "\ue31e", + "laptop_chromebook": "\ue31f", + "laptop_mac": "\ue320", + "laptop_windows": "\ue321", + "last_page": "\ue5dd", + "launch": "\ue895", + "layers": "\ue53b", + "layers_clear": "\ue53c", + "leak_add": "\ue3f8", + "leak_remove": "\ue3f9", + "lens": "\ue3fa", + "library_add": "\ue02e", + "library_books": "\ue02f", + "library_music": "\ue030", + "lightbulb_outline": "\ue90f", + "line_style": "\ue919", + "line_weight": "\ue91a", + "linear_scale": "\ue260", + "link": "\ue157", + "linked_camera": "\ue438", + "list": "\ue896", + "live_help": "\ue0c6", + "live_tv": "\ue639", + "local_activity": "\ue53f", + "local_airport": "\ue53d", + "local_atm": "\ue53e", + "local_bar": "\ue540", + "local_cafe": "\ue541", + "local_car_wash": "\ue542", + "local_convenience_store": "\ue543", + "local_dining": "\ue556", + "local_drink": "\ue544", + "local_florist": "\ue545", + "local_gas_station": "\ue546", + "local_grocery_store": "\ue547", + "local_hospital": "\ue548", + "local_hotel": "\ue549", + "local_laundry_service": "\ue54a", + "local_library": "\ue54b", + "local_mall": "\ue54c", + "local_movies": "\ue54d", + "local_offer": "\ue54e", + "local_parking": "\ue54f", + "local_pharmacy": "\ue550", + "local_phone": "\ue551", + "local_pizza": "\ue552", + "local_play": "\ue553", + "local_post_office": "\ue554", + "local_printshop": "\ue555", + "local_see": "\ue557", + "local_shipping": "\ue558", + "local_taxi": "\ue559", + "location_city": "\ue7f1", + "location_disabled": "\ue1b6", + "location_off": "\ue0c7", + "location_on": "\ue0c8", + "location_searching": "\ue1b7", + "lock": "\ue897", + "lock_open": "\ue898", + "lock_outline": "\ue899", + "looks": "\ue3fc", + "looks_3": "\ue3fb", + "looks_4": "\ue3fd", + "looks_5": "\ue3fe", + "looks_6": "\ue3ff", + "looks_one": "\ue400", + "looks_two": "\ue401", + "loop": "\ue028", + "loupe": "\ue402", + "low_priority": "\ue16d", + "loyalty": "\ue89a", + "mail": "\ue158", + "mail_outline": "\ue0e1", + "map": "\ue55b", + "markunread": "\ue159", + "markunread_mailbox": "\ue89b", + "memory": "\ue322", + "menu": "\ue5d2", + "merge_type": "\ue252", + "message": "\ue0c9", + "mic": "\ue029", + "mic_none": "\ue02a", + "mic_off": "\ue02b", + "mms": "\ue618", + "mode_comment": "\ue253", + "mode_edit": "\ue254", + "monetization_on": "\ue263", + "money_off": "\ue25c", + "monochrome_photos": "\ue403", + "mood": "\ue7f2", + "mood_bad": "\ue7f3", + "more": "\ue619", + "more_horiz": "\ue5d3", + "more_vert": "\ue5d4", + "motorcycle": "\ue91b", + "mouse": "\ue323", + "move_to_inbox": "\ue168", + "movie": "\ue02c", + "movie_creation": "\ue404", + "movie_filter": "\ue43a", + "multiline_chart": "\ue6df", + "music_note": "\ue405", + "music_video": "\ue063", + "my_location": "\ue55c", + "nature": "\ue406", + "nature_people": "\ue407", + "navigate_before": "\ue408", + "navigate_next": "\ue409", + "navigation": "\ue55d", + "near_me": "\ue569", + "network_cell": "\ue1b9", + "network_check": "\ue640", + "network_locked": "\ue61a", + "network_wifi": "\ue1ba", + "new_releases": "\ue031", + "next_week": "\ue16a", + "nfc": "\ue1bb", + "no_encryption": "\ue641", + "no_sim": "\ue0cc", + "not_interested": "\ue033", + "note": "\ue06f", + "note_add": "\ue89c", + "notifications": "\ue7f4", + "notifications_active": "\ue7f7", + "notifications_none": "\ue7f5", + "notifications_off": "\ue7f6", + "notifications_paused": "\ue7f8", + "offline_pin": "\ue90a", + "ondemand_video": "\ue63a", + "opacity": "\ue91c", + "open_in_browser": "\ue89d", + "open_in_new": "\ue89e", + "open_with": "\ue89f", + "pages": "\ue7f9", + "pageview": "\ue8a0", + "palette": "\ue40a", + "pan_tool": "\ue925", + "panorama": "\ue40b", + "panorama_fish_eye": "\ue40c", + "panorama_horizontal": "\ue40d", + "panorama_vertical": "\ue40e", + "panorama_wide_angle": "\ue40f", + "party_mode": "\ue7fa", + "pause": "\ue034", + "pause_circle_filled": "\ue035", + "pause_circle_outline": "\ue036", + "payment": "\ue8a1", + "people": "\ue7fb", + "people_outline": "\ue7fc", + "perm_camera_mic": "\ue8a2", + "perm_contact_calendar": "\ue8a3", + "perm_data_setting": "\ue8a4", + "perm_device_information": "\ue8a5", + "perm_identity": "\ue8a6", + "perm_media": "\ue8a7", + "perm_phone_msg": "\ue8a8", + "perm_scan_wifi": "\ue8a9", + "person": "\ue7fd", + "person_add": "\ue7fe", + "person_outline": "\ue7ff", + "person_pin": "\ue55a", + "person_pin_circle": "\ue56a", + "personal_video": "\ue63b", + "pets": "\ue91d", + "phone": "\ue0cd", + "phone_android": "\ue324", + "phone_bluetooth_speaker": "\ue61b", + "phone_forwarded": "\ue61c", + "phone_in_talk": "\ue61d", + "phone_iphone": "\ue325", + "phone_locked": "\ue61e", + "phone_missed": "\ue61f", + "phone_paused": "\ue620", + "phonelink": "\ue326", + "phonelink_erase": "\ue0db", + "phonelink_lock": "\ue0dc", + "phonelink_off": "\ue327", + "phonelink_ring": "\ue0dd", + "phonelink_setup": "\ue0de", + "photo": "\ue410", + "photo_album": "\ue411", + "photo_camera": "\ue412", + "photo_filter": "\ue43b", + "photo_library": "\ue413", + "photo_size_select_actual": "\ue432", + "photo_size_select_large": "\ue433", + "photo_size_select_small": "\ue434", + "picture_as_pdf": "\ue415", + "picture_in_picture": "\ue8aa", + "picture_in_picture_alt": "\ue911", + "pie_chart": "\ue6c4", + "pie_chart_outlined": "\ue6c5", + "pin_drop": "\ue55e", + "place": "\ue55f", + "play_arrow": "\ue037", + "play_circle_filled": "\ue038", + "play_circle_outline": "\ue039", + "play_for_work": "\ue906", + "playlist_add": "\ue03b", + "playlist_add_check": "\ue065", + "playlist_play": "\ue05f", + "plus_one": "\ue800", + "poll": "\ue801", + "polymer": "\ue8ab", + "pool": "\ueb48", + "portable_wifi_off": "\ue0ce", + "portrait": "\ue416", + "power": "\ue63c", + "power_input": "\ue336", + "power_settings_new": "\ue8ac", + "pregnant_woman": "\ue91e", + "present_to_all": "\ue0df", + "print": "\ue8ad", + "priority_high": "\ue645", + "public": "\ue80b", + "publish": "\ue255", + "query_builder": "\ue8ae", + "question_answer": "\ue8af", + "queue": "\ue03c", + "queue_music": "\ue03d", + "queue_play_next": "\ue066", + "radio": "\ue03e", + "radio_button_checked": "\ue837", + "radio_button_unchecked": "\ue836", + "rate_review": "\ue560", + "receipt": "\ue8b0", + "recent_actors": "\ue03f", + "record_voice_over": "\ue91f", + "redeem": "\ue8b1", + "redo": "\ue15a", + "refresh": "\ue5d5", + "remove": "\ue15b", + "remove_circle": "\ue15c", + "remove_circle_outline": "\ue15d", + "remove_from_queue": "\ue067", + "remove_red_eye": "\ue417", + "remove_shopping_cart": "\ue928", + "reorder": "\ue8fe", + "repeat": "\ue040", + "repeat_one": "\ue041", + "replay": "\ue042", + "replay_10": "\ue059", + "replay_30": "\ue05a", + "replay_5": "\ue05b", + "reply": "\ue15e", + "reply_all": "\ue15f", + "report": "\ue160", + "report_problem": "\ue8b2", + "restaurant": "\ue56c", + "restaurant_menu": "\ue561", + "restore": "\ue8b3", + "restore_page": "\ue929", + "ring_volume": "\ue0d1", + "room": "\ue8b4", + "room_service": "\ueb49", + "rotate_90_degrees_ccw": "\ue418", + "rotate_left": "\ue419", + "rotate_right": "\ue41a", + "rounded_corner": "\ue920", + "router": "\ue328", + "rowing": "\ue921", + "rss_feed": "\ue0e5", + "rv_hookup": "\ue642", + "satellite": "\ue562", + "save": "\ue161", + "scanner": "\ue329", + "schedule": "\ue8b5", + "school": "\ue80c", + "screen_lock_landscape": "\ue1be", + "screen_lock_portrait": "\ue1bf", + "screen_lock_rotation": "\ue1c0", + "screen_rotation": "\ue1c1", + "screen_share": "\ue0e2", + "sd_card": "\ue623", + "sd_storage": "\ue1c2", + "search": "\ue8b6", + "security": "\ue32a", + "select_all": "\ue162", + "send": "\ue163", + "sentiment_dissatisfied": "\ue811", + "sentiment_neutral": "\ue812", + "sentiment_satisfied": "\ue813", + "sentiment_very_dissatisfied": "\ue814", + "sentiment_very_satisfied": "\ue815", + "settings": "\ue8b8", + "settings_applications": "\ue8b9", + "settings_backup_restore": "\ue8ba", + "settings_bluetooth": "\ue8bb", + "settings_brightness": "\ue8bd", + "settings_cell": "\ue8bc", + "settings_ethernet": "\ue8be", + "settings_input_antenna": "\ue8bf", + "settings_input_component": "\ue8c0", + "settings_input_composite": "\ue8c1", + "settings_input_hdmi": "\ue8c2", + "settings_input_svideo": "\ue8c3", + "settings_overscan": "\ue8c4", + "settings_phone": "\ue8c5", + "settings_power": "\ue8c6", + "settings_remote": "\ue8c7", + "settings_system_daydream": "\ue1c3", + "settings_voice": "\ue8c8", + "share": "\ue80d", + "shop": "\ue8c9", + "shop_two": "\ue8ca", + "shopping_basket": "\ue8cb", + "shopping_cart": "\ue8cc", + "short_text": "\ue261", + "show_chart": "\ue6e1", + "shuffle": "\ue043", + "signal_cellular_4_bar": "\ue1c8", + "signal_cellular_connected_no_internet_4_bar": "\ue1cd", + "signal_cellular_no_sim": "\ue1ce", + "signal_cellular_null": "\ue1cf", + "signal_cellular_off": "\ue1d0", + "signal_wifi_4_bar": "\ue1d8", + "signal_wifi_4_bar_lock": "\ue1d9", + "signal_wifi_off": "\ue1da", + "sim_card": "\ue32b", + "sim_card_alert": "\ue624", + "skip_next": "\ue044", + "skip_previous": "\ue045", + "slideshow": "\ue41b", + "slow_motion_video": "\ue068", + "smartphone": "\ue32c", + "smoke_free": "\ueb4a", + "smoking_rooms": "\ueb4b", + "sms": "\ue625", + "sms_failed": "\ue626", + "snooze": "\ue046", + "sort": "\ue164", + "sort_by_alpha": "\ue053", + "spa": "\ueb4c", + "space_bar": "\ue256", + "speaker": "\ue32d", + "speaker_group": "\ue32e", + "speaker_notes": "\ue8cd", + "speaker_notes_off": "\ue92a", + "speaker_phone": "\ue0d2", + "spellcheck": "\ue8ce", + "star": "\ue838", + "star_border": "\ue83a", + "star_half": "\ue839", + "stars": "\ue8d0", + "stay_current_landscape": "\ue0d3", + "stay_current_portrait": "\ue0d4", + "stay_primary_landscape": "\ue0d5", + "stay_primary_portrait": "\ue0d6", + "stop": "\ue047", + "stop_screen_share": "\ue0e3", + "storage": "\ue1db", + "store": "\ue8d1", + "store_mall_directory": "\ue563", + "straighten": "\ue41c", + "streetview": "\ue56e", + "strikethrough_s": "\ue257", + "style": "\ue41d", + "subdirectory_arrow_left": "\ue5d9", + "subdirectory_arrow_right": "\ue5da", + "subject": "\ue8d2", + "subscriptions": "\ue064", + "subtitles": "\ue048", + "subway": "\ue56f", + "supervisor_account": "\ue8d3", + "surround_sound": "\ue049", + "swap_calls": "\ue0d7", + "swap_horiz": "\ue8d4", + "swap_vert": "\ue8d5", + "swap_vertical_circle": "\ue8d6", + "switch_camera": "\ue41e", + "switch_video": "\ue41f", + "sync": "\ue627", + "sync_disabled": "\ue628", + "sync_problem": "\ue629", + "system_update": "\ue62a", + "system_update_alt": "\ue8d7", + "tab": "\ue8d8", + "tab_unselected": "\ue8d9", + "tablet": "\ue32f", + "tablet_android": "\ue330", + "tablet_mac": "\ue331", + "tag_faces": "\ue420", + "tap_and_play": "\ue62b", + "terrain": "\ue564", + "text_fields": "\ue262", + "text_format": "\ue165", + "textsms": "\ue0d8", + "texture": "\ue421", + "theaters": "\ue8da", + "thumb_down": "\ue8db", + "thumb_up": "\ue8dc", + "thumbs_up_down": "\ue8dd", + "time_to_leave": "\ue62c", + "timelapse": "\ue422", + "timeline": "\ue922", + "timer": "\ue425", + "timer_10": "\ue423", + "timer_3": "\ue424", + "timer_off": "\ue426", + "title": "\ue264", + "toc": "\ue8de", + "today": "\ue8df", + "toll": "\ue8e0", + "tonality": "\ue427", + "touch_app": "\ue913", + "toys": "\ue332", + "track_changes": "\ue8e1", + "traffic": "\ue565", + "train": "\ue570", + "tram": "\ue571", + "transfer_within_a_station": "\ue572", + "transform": "\ue428", + "translate": "\ue8e2", + "trending_down": "\ue8e3", + "trending_flat": "\ue8e4", + "trending_up": "\ue8e5", + "tune": "\ue429", + "turned_in": "\ue8e6", + "turned_in_not": "\ue8e7", + "tv": "\ue333", + "unarchive": "\ue169", + "undo": "\ue166", + "unfold_less": "\ue5d6", + "unfold_more": "\ue5d7", + "update": "\ue923", + "usb": "\ue1e0", + "verified_user": "\ue8e8", + "vertical_align_bottom": "\ue258", + "vertical_align_center": "\ue259", + "vertical_align_top": "\ue25a", + "vibration": "\ue62d", + "video_call": "\ue070", + "video_label": "\ue071", + "video_library": "\ue04a", + "videocam": "\ue04b", + "videocam_off": "\ue04c", + "videogame_asset": "\ue338", + "view_agenda": "\ue8e9", + "view_array": "\ue8ea", + "view_carousel": "\ue8eb", + "view_column": "\ue8ec", + "view_comfy": "\ue42a", + "view_compact": "\ue42b", + "view_day": "\ue8ed", + "view_headline": "\ue8ee", + "view_list": "\ue8ef", + "view_module": "\ue8f0", + "view_quilt": "\ue8f1", + "view_stream": "\ue8f2", + "view_week": "\ue8f3", + "vignette": "\ue435", + "visibility": "\ue8f4", + "visibility_off": "\ue8f5", + "voice_chat": "\ue62e", + "voicemail": "\ue0d9", + "volume_down": "\ue04d", + "volume_mute": "\ue04e", + "volume_off": "\ue04f", + "volume_up": "\ue050", + "vpn_key": "\ue0da", + "vpn_lock": "\ue62f", + "wallpaper": "\ue1bc", + "warning": "\ue002", + "watch": "\ue334", + "watch_later": "\ue924", + "wb_auto": "\ue42c", + "wb_cloudy": "\ue42d", + "wb_incandescent": "\ue42e", + "wb_iridescent": "\ue436", + "wb_sunny": "\ue430", + "wc": "\ue63d", + "web": "\ue051", + "web_asset": "\ue069", + "weekend": "\ue16b", + "whatshot": "\ue80e", + "widgets": "\ue1bd", + "wifi": "\ue63e", + "wifi_lock": "\ue1e1", + "wifi_tethering": "\ue1e2", + "work": "\ue8f9", + "wrap_text": "\ue25b", + "youtube_searched_for": "\ue8fa", + "zoom_in": "\ue8ff", + "zoom_out": "\ue900", + "zoom_out_map": "\ue56b" +} diff --git a/include/BaseUI/core.h b/include/BaseUI/core.h new file mode 100644 index 0000000..48e279f --- /dev/null +++ b/include/BaseUI/core.h @@ -0,0 +1,11 @@ +#ifndef BASEUI_CORE_H +#define BASEUI_CORE_H + +class QQmlEngine; + +namespace BaseUI +{ + void init(QQmlEngine *engine); +} + +#endif diff --git a/qml/BaseUI/App.qml b/qml/BaseUI/App.qml new file mode 100644 index 0000000..af3919c --- /dev/null +++ b/qml/BaseUI/App.qml @@ -0,0 +1,28 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 + +ApplicationWindow { + property alias pageStack: stackView + property alias initialPage: stackView.initialItem + + visible: true + locale: Qt.locale("en_US") + + header: stackView.currentItem ? stackView.currentItem.appToolBar : null + + StackView { + id: stackView + + anchors.fill: parent + + onCurrentItemChanged: { + // make sure that the phone physical back button will get key events + currentItem.forceActiveFocus() + } + } + + Material.primary: Style.primaryColor + Material.accent: Style.accentColor + Material.theme: Style.isDarkTheme ? Material.Dark : Material.Light +} diff --git a/qml/BaseUI/AppStackPage.qml b/qml/BaseUI/AppStackPage.qml new file mode 100644 index 0000000..89385b9 --- /dev/null +++ b/qml/BaseUI/AppStackPage.qml @@ -0,0 +1,45 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +import BaseUI 1.0 + +Page { + id: root + + property StackView stack: StackView.view + property alias appToolBar: appToolBar + property alias leftButton: appToolBar.leftButton + property alias rightButtons: appToolBar.rightButtons + + function pop(item, operation) { + if (stack.currentItem != root) + return false + + return stack.pop(item, operation) + } + + function back() { + pop() + } + + Keys.onBackPressed: function(event) { + if (stack.depth > 1) { + event.accepted = true + back() + } else { + Qt.quit() + } + } + + Action { + id: backAction + icon.source: Icons.arrow_back + onTriggered: root.back() + } + + AppToolBar { + id: appToolBar + title: root.title + leftButton: stack && stack.depth > 1 ? backAction : null + } +} diff --git a/qml/BaseUI/AppToolBar.qml b/qml/BaseUI/AppToolBar.qml new file mode 100644 index 0000000..61913f8 --- /dev/null +++ b/qml/BaseUI/AppToolBar.qml @@ -0,0 +1,42 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +ToolBar { + property Action leftButton + property list rightButtons + + property alias title: titleLabel.text + + RowLayout { + focus: false + spacing: 0 + anchors { fill: parent; leftMargin: 4; rightMargin: 4 } + + ToolButton { + icon.source: leftButton ? leftButton.icon.source : "" + icon.color: Style.textOnPrimary + focusPolicy: Qt.NoFocus + opacity: Style.opacityTitle + enabled: leftButton && leftButton.enabled + onClicked: leftButton.trigger() + } + LabelTitle { + id: titleLabel + elide: Label.ElideRight + color: Style.textOnPrimary + Layout.fillWidth: true + } + Repeater { + model: rightButtons.length + delegate: ToolButton { + icon.source: rightButtons[index].icon.source + icon.color: Style.textOnPrimary + focusPolicy: Qt.NoFocus + opacity: Style.opacityTitle + enabled: rightButtons[index].enabled + onClicked: rightButtons[index].trigger() + } + } + } +} diff --git a/qml/BaseUI/ButtonFlat.qml b/qml/BaseUI/ButtonFlat.qml new file mode 100644 index 0000000..e51810a --- /dev/null +++ b/qml/BaseUI/ButtonFlat.qml @@ -0,0 +1,36 @@ +// ekke (Ekkehard Gentz) @ekkescorner +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Button { + id: button + + property alias textColor: buttonText.color + + focusPolicy: Qt.NoFocus + leftPadding: 6 + rightPadding: 6 + + Layout.minimumWidth: 88 + + contentItem: Text { + id: buttonText + text: button.text + opacity: enabled ? 1.0 : 0.3 + color: Style.flatButtonTextColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + font.capitalization: Font.AllUppercase + font.weight: Font.Medium + } + + background: Rectangle { + id: buttonBackground + implicitHeight: 48 + color: button.pressed ? buttonText.color : "transparent" + radius: 2 + opacity: button.pressed ? 0.12 : 1.0 + } +} diff --git a/qml/BaseUI/ButtonRaised.qml b/qml/BaseUI/ButtonRaised.qml new file mode 100644 index 0000000..bfbefaf --- /dev/null +++ b/qml/BaseUI/ButtonRaised.qml @@ -0,0 +1,45 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Button { + id: button + + property alias textColor: buttonText.color + property alias buttonColor: buttonBackground.color + + focusPolicy: Qt.NoFocus + leftPadding: 6 + rightPadding: 6 + + Layout.minimumWidth: 80 + + contentItem: Text { + id: buttonText + text: button.text + opacity: enabled ? 1.0 : 0.3 + color: Style.textOnPrimary + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + font.capitalization: Font.AllUppercase + } + + background: Rectangle { + id: buttonBackground + implicitHeight: 48 + color: Style.primaryColor + radius: 2 + opacity: button.pressed ? 0.75 : 1.0 + /* + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 2 + horizontalOffset: 1 + color: dropShadow + samples: button.pressed ? 20 : 10 + spread: 0.5 + } + */ + } +} diff --git a/qml/BaseUI/HorizontalDivider.qml b/qml/BaseUI/HorizontalDivider.qml new file mode 100644 index 0000000..7978881 --- /dev/null +++ b/qml/BaseUI/HorizontalDivider.qml @@ -0,0 +1,19 @@ +// ekke (Ekkehard Gentz) @ekkescorner +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +Item { + height: 8 + Layout.fillWidth: true + // anchors.left: parent.left + // anchors.right: parent.right + // anchors.margins: 6 + // https://www.google.com/design/spec/components/dividers.html#dividers-types-of-dividers + Rectangle { + anchors.centerIn: parent + width: parent.width + height: 1 + opacity: Style.dividerOpacity + color: Style.dividerColor + } +} diff --git a/qml/BaseUI/LabelBody.qml b/qml/BaseUI/LabelBody.qml new file mode 100644 index 0000000..f35319a --- /dev/null +++ b/qml/BaseUI/LabelBody.qml @@ -0,0 +1,9 @@ +// ekke (Ekkehard Gentz) @ekkescorner +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Label { + Layout.fillWidth: true + opacity: Style.opacityBodyAndButton +} diff --git a/qml/BaseUI/LabelSubheading.qml b/qml/BaseUI/LabelSubheading.qml new file mode 100644 index 0000000..f7011f7 --- /dev/null +++ b/qml/BaseUI/LabelSubheading.qml @@ -0,0 +1,10 @@ +// ekke (Ekkehard Gentz) @ekkescorner +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Label { + Layout.fillWidth: true + font.pixelSize: Style.fontSizeSubheading + opacity: Style.opacitySubheading +} diff --git a/qml/BaseUI/LabelTitle.qml b/qml/BaseUI/LabelTitle.qml new file mode 100644 index 0000000..040e9b3 --- /dev/null +++ b/qml/BaseUI/LabelTitle.qml @@ -0,0 +1,10 @@ +// ekke (Ekkehard Gentz) @ekkescorner +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Label { + Layout.fillWidth: true + font.pixelSize: Style.fontSizeTitle + opacity: Style.opacityTitle +} diff --git a/qml/BaseUI/PopupColorSelection.qml b/qml/BaseUI/PopupColorSelection.qml new file mode 100644 index 0000000..cd72049 --- /dev/null +++ b/qml/BaseUI/PopupColorSelection.qml @@ -0,0 +1,84 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.12 + +PopupModalBase { + property bool selectAccentColor: false + property color currentColor: Material.primary + property string currentColorName: colorModel.get(currentIndex).title + property int currentIndex: 0 + + signal colorSelected(color c) + + implicitWidth: parent.width * 0.9 + implicitHeight: Math.min(colorsList.contentHeight, parent.height * 0.9) + + ListView { + id: colorsList + anchors.fill: parent + clip: true + delegate: ItemDelegate { + width: colorsList.width + contentItem: RowLayout { + spacing: 0 + Rectangle { + visible: selectAccentColor + color: Material.primary + Layout.margins: 0 + Layout.minimumHeight: 32 + Layout.minimumWidth: 48 + } + Rectangle { + color: Material.color(model.bg) + Layout.margins: 0 + Layout.minimumHeight: 32 + Layout.minimumWidth: 32 + } + LabelBody { + text: model.title + Layout.leftMargin: 6 + Layout.fillWidth: true + } + } + onClicked: { + colorSelected(Material.color(model.bg)) + currentIndex = index + close() + } + } + + model: ListModel { + id: colorModel + ListElement { title: "Material Red"; bg: Material.Red } + ListElement { title: "Material Pink"; bg: Material.Pink } + ListElement { title: "Material Purple"; bg: Material.Purple } + ListElement { title: "Material DeepPurple"; bg: Material.DeepPurple } + ListElement { title: "Material Indigo"; bg: Material.Indigo } + ListElement { title: "Material Blue"; bg: Material.Blue } + ListElement { title: "Material LightBlue"; bg: Material.LightBlue } + ListElement { title: "Material Cyan"; bg: Material.Cyan } + ListElement { title: "Material Teal"; bg: Material.Teal } + ListElement { title: "Material Green"; bg: Material.Green } + ListElement { title: "Material LightGreen"; bg: Material.LightGreen } + ListElement { title: "Material Lime"; bg: Material.Lime } + ListElement { title: "Material Yellow"; bg: Material.Yellow } + ListElement { title: "Material Amber"; bg: Material.Amber } + ListElement { title: "Material Orange"; bg: Material.Orange } + ListElement { title: "Material DeepOrange"; bg: Material.DeepOrange } + ListElement { title: "Material Brown"; bg: Material.Brown } + ListElement { title: "Material Grey"; bg: Material.Grey } + ListElement { title: "Material BlueGrey"; bg: Material.BlueGrey } + } + } + + Component.onCompleted: { + for (var i = 0; i < colorModel.count; ++i) { + var tmp = colorModel.get(i) + if (Material.color(tmp.bg) === currentColor) { + currentIndex = i + return + } + } + } +} diff --git a/qml/BaseUI/PopupError.qml b/qml/BaseUI/PopupError.qml new file mode 100644 index 0000000..228ea21 --- /dev/null +++ b/qml/BaseUI/PopupError.qml @@ -0,0 +1,69 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.12 + +import BaseUI 1.0 + +Popup { + id: root + + function start(errorText) { + errorLabel.text = errorText + if (!errorTimer.running) + open() + else + errorTimer.restart() + } + + closePolicy: Popup.CloseOnPressOutside + bottomMargin: Screen.primaryOrientation === Qt.LandscapeOrientation ? 24 : 80 + implicitWidth: Screen.primaryOrientation === Qt.LandscapeOrientation ? parent.width * 0.50 : parent.width * 0.80 + + x: (parent.width - implicitWidth) / 2 + y: (parent.height - height) + + background: Rectangle { + color: Material.color(Material.Red, Style.isDarkTheme ? Material.Shade500 : Material.Shade800) + radius: 24 + opacity: Style.toastOpacity + } + + onAboutToShow: errorTimer.start() + onAboutToHide: errorTimer.stop() + + Timer { + id: errorTimer + + interval: 3000 + repeat: false + + onTriggered: root.close() + } + + RowLayout { + width: parent.width + + Image { + id: alarmIcon + + smooth: true + source: Icons.error + "color=white" + sourceSize.width: 36 + sourceSize.height: 36 + } + + Label { + id: errorLabel + + Layout.fillWidth: true + Layout.preferredWidth: 1 + + rightPadding: 24 + font.pixelSize: 16 + color: "white" + wrapMode: Label.WordWrap + } + } +} diff --git a/qml/BaseUI/PopupInfo.qml b/qml/BaseUI/PopupInfo.qml new file mode 100644 index 0000000..2ddce96 --- /dev/null +++ b/qml/BaseUI/PopupInfo.qml @@ -0,0 +1,40 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +PopupModalBase { + id: root + + property alias text: popupLabel.text + + closePolicy: Popup.CloseOnEscape + + ColumnLayout { + spacing: 10 + + width: parent.width + + LabelSubheading { + id: popupLabel + + Layout.fillWidth: true + + topPadding: 20 + leftPadding: 8 + rightPadding: 8 + color: Style.popupTextColor + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + linkColor: Style.isDarkTheme ? "lightblue" : "blue" + onLinkActivated: Qt.openUrlExternally(link) + } + + ButtonFlat { + Layout.alignment: Qt.AlignHCenter + + text: "OK" + textColor: Style.accentColor + onClicked: root.close() + } + } +} diff --git a/qml/BaseUI/PopupList.qml b/qml/BaseUI/PopupList.qml new file mode 100644 index 0000000..a936fe4 --- /dev/null +++ b/qml/BaseUI/PopupList.qml @@ -0,0 +1,32 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +PopupModalBase { + id: root + + property alias model: internalList.model + property alias currentIndex: internalList.currentIndex + property var delegateFunction + + signal clicked(var data, int index) + + implicitWidth: parent.width * 0.9 + implicitHeight: Math.min(internalList.contentHeight, parent.height * 0.9) + + ListView { + id: internalList + anchors.fill: parent + clip: true + highlightMoveDuration: 0 + delegate: ItemDelegate { + id: internalDelegate + width: parent.width + implicitHeight: 40 + text: delegateFunction(modelData) + onClicked: root.clicked(modelData, index) + } + onCurrentIndexChanged: { + internalList.positionViewAtIndex(currentIndex, ListView.Center) + } + } +} diff --git a/qml/BaseUI/PopupModalBase.qml b/qml/BaseUI/PopupModalBase.qml new file mode 100644 index 0000000..356d022 --- /dev/null +++ b/qml/BaseUI/PopupModalBase.qml @@ -0,0 +1,14 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Popup { + id: root + + modal: true + dim: true + padding: 0 + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + implicitWidth: Math.min(contentWidth, parent.width * 0.9) + implicitHeight: Math.min(contentHeight, parent.height * 0.9) +} diff --git a/qml/BaseUI/PopupToast.qml b/qml/BaseUI/PopupToast.qml new file mode 100644 index 0000000..b5aefd6 --- /dev/null +++ b/qml/BaseUI/PopupToast.qml @@ -0,0 +1,51 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 + +Popup { + id: root + + function start(toastText) { + toastLabel.text = toastText + if (!toastTimer.running) + open() + else + toastTimer.restart() + } + + x: (parent.width - width) / 2 + y: (parent.height - height) + + closePolicy: Popup.CloseOnPressOutside + bottomMargin: Screen.primaryOrientation === Qt.LandscapeOrientation ? 24 : 80 + + background: Rectangle { + color: Style.toastColor + radius: 24 + opacity: Style.toastOpacity + } + + onAboutToShow: toastTimer.start() + onAboutToHide: toastTimer.stop() + + Timer { + id: toastTimer + + interval: 3000 + repeat: false + + onTriggered: root.close() + } + + Label { + id: toastLabel + + width: parent.width + leftPadding: 16 + rightPadding: 16 + font.pixelSize: 16 + color: "white" + wrapMode: Label.WordWrap + } +} diff --git a/qml/BaseUI/SettingsItem.qml b/qml/BaseUI/SettingsItem.qml new file mode 100644 index 0000000..f4af6bc --- /dev/null +++ b/qml/BaseUI/SettingsItem.qml @@ -0,0 +1,48 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +ItemDelegate { + id: root + + property alias title: titleLabel.text + property string subtitle + property string subtitlePlaceholder + property alias check: settingSwitch + + Layout.fillWidth: true + + contentItem: RowLayout { + ColumnLayout { + spacing: 2 + + LabelSubheading { + id: titleLabel + + wrapMode: Text.WordWrap + + Layout.fillWidth: true + } + LabelBody { + id: subtitleLabel + + visible: text.length > 0 || root.subtitlePlaceholder.length > 0 + opacity: 0.6 + wrapMode: Text.WordWrap + elide: Text.ElideMiddle + text: root.subtitle.length > 0 ? root.subtitle : root.subtitlePlaceholder + + Layout.fillWidth: true + } + } + + Item { + Layout.fillWidth: true + } + + Switch { + id: settingSwitch + visible: false + } + } +} diff --git a/qml/BaseUI/SettingsSectionTitle.qml b/qml/BaseUI/SettingsSectionTitle.qml new file mode 100644 index 0000000..805a2e8 --- /dev/null +++ b/qml/BaseUI/SettingsSectionTitle.qml @@ -0,0 +1,18 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +Label { + leftPadding: 16 + topPadding: 6 + bottomPadding: 6 + font.bold: true + font.pixelSize: Style.fontSizeBodyAndButton + color: Style.isDarkTheme ? "white" : "black" + + background: Rectangle { + color: Style.isDarkTheme ? Qt.darker("gray") : "lightgray" + } + + Layout.fillWidth: true +} diff --git a/qml/BaseUI/Style.qml b/qml/BaseUI/Style.qml new file mode 100644 index 0000000..aa505a4 --- /dev/null +++ b/qml/BaseUI/Style.qml @@ -0,0 +1,56 @@ +pragma Singleton + +import QtQuick 2.12 +import QtQuick.Controls.Material 2.12 + +QtObject { + property bool isDarkTheme: false + + property color primaryColor: Material.color(Material.BlueGrey) + property color accentColor: Material.color(Material.Orange) + property color primaryDarkColor: Qt.darker(primaryColor) + + // ui constants + property color textOnPrimary: isDarkColor(primaryColor) ? "#FFFFFF" : "#000000" + property color textOnAccent: isDarkColor(accentColor) ? "#FFFFFF" : "#000000" + property color textOnPrimaryDark: textOnPrimary + + property color dividerColor: isDarkTheme ? "#FFFFFF" : "#000000" + property real primaryTextOpacity: isDarkTheme ? 1.0 : 0.87 + property real secondaryTextOpacity: isDarkTheme ? 0.7 : 0.54 + property real dividerOpacity: isDarkTheme ? 0.12 : 0.12 + property color flatButtonTextColor: isDarkTheme ? "#FFFFFF" : "#424242" + property color popupTextColor: isDarkTheme ? "#FFFFFF" : "#424242" + property color toastColor: isDarkTheme ? "Darkgrey" : "#323232" + property real toastOpacity: isDarkTheme ? 0.9 : 0.75 + + // font sizes - defaults from Google Material Design Guide + property int fontSizeDisplay4: 112 + property int fontSizeDisplay3: 56 + property int fontSizeDisplay2: 45 + property int fontSizeDisplay1: 34 + property int fontSizeHeadline: 24 + property int fontSizeTitle: 20 + property int fontSizeSubheading: 16 + property int fontSizeBodyAndButton: 14 // is Default + property int fontSizeCaption: 12 + // fonts are grouped into primary and secondary with different Opacity + // to make it easier to get the right property, + // here's the opacity per size: + property real opacityDisplay4: secondaryTextOpacity + property real opacityDisplay3: secondaryTextOpacity + property real opacityDisplay2: secondaryTextOpacity + property real opacityDisplay1: secondaryTextOpacity + property real opacityHeadline: primaryTextOpacity + property real opacityTitle: primaryTextOpacity + property real opacitySubheading: primaryTextOpacity + // body can be both: primary or secondary text + property real opacityBodyAndButton: primaryTextOpacity + property real opacityBodySecondary: secondaryTextOpacity + property real opacityCaption: secondaryTextOpacity + + function isDarkColor(color) { + var a = 1.0 - (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) + return color.a > 0.0 && a >= 0.3 + } +} diff --git a/qml/BaseUI/baseui_qml.qrc b/qml/BaseUI/baseui_qml.qrc new file mode 100644 index 0000000..40d865f --- /dev/null +++ b/qml/BaseUI/baseui_qml.qrc @@ -0,0 +1,23 @@ + + + qmldir + App.qml + AppStackPage.qml + AppToolBar.qml + ButtonFlat.qml + ButtonRaised.qml + HorizontalDivider.qml + LabelBody.qml + LabelSubheading.qml + LabelTitle.qml + PopupColorSelection.qml + PopupError.qml + PopupInfo.qml + PopupList.qml + PopupModalBase.qml + PopupToast.qml + SettingsItem.qml + SettingsSectionTitle.qml + Style.qml + + diff --git a/qml/BaseUI/qmldir b/qml/BaseUI/qmldir new file mode 100644 index 0000000..fd0703c --- /dev/null +++ b/qml/BaseUI/qmldir @@ -0,0 +1,21 @@ +module BaseUI + +App 1.0 App.qml +AppStackPage 1.0 AppStackPage.qml +AppToolBar 1.0 AppToolBar.qml +ButtonFlat 1.0 ButtonFlat.qml +ButtonRaised 1.0 ButtonRaised.qml +HorizontalDivider 1.0 HorizontalDivider.qml +LabelBody 1.0 LabelBody.qml +LabelSubheading 1.0 LabelSubheading.qml +LabelTitle 1.0 LabelTitle.qml +PopupColorSelection 1.0 PopupColorSelection.qml +PopupError 1.0 PopupError.qml +PopupInfo 1.0 PopupInfo.qml +PopupList 1.0 PopupList.qml +PopupModalBase 1.0 PopupModalBase.qml +PopupToast 1.0 PopupToast.qml +SettingsItem 1.0 SettingsItem.qml +SettingsSectionTitle 1.0 SettingsSectionTitle.qml + +singleton Style 1.0 Style.qml diff --git a/src/core.cpp b/src/core.cpp new file mode 100644 index 0000000..07c1e06 --- /dev/null +++ b/src/core.cpp @@ -0,0 +1,41 @@ +#include + +#include +#include +#include +#include + +#include "icons.h" + +static void initialize(QQmlEngine *engine) +{ +#ifdef BASEUI_EMBED_QML + Q_INIT_RESOURCE(baseui_qml); + engine->addImportPath(":/imports"); +#endif + + QString path = "/BaseUI/icons/"; + +#ifdef BASEUI_EMBED_ICONS + Q_INIT_RESOURCE(baseui_icons); + path = ":/imports" + path; +#else + path = QCoreApplication::applicationDirPath() + path; +#endif + + Icons::registerIcons(engine, path); +} + +namespace BaseUI +{ + +void init(QQmlEngine *engine) +{ + QQuickStyle::setStyle("Material"); + + initialize(engine); + + qmlRegisterSingletonType("BaseUI", 1, 0, "Icons", Icons::singletonProvider); +} + +} diff --git a/src/iconprovider.h b/src/iconprovider.h new file mode 100644 index 0000000..a0102e0 --- /dev/null +++ b/src/iconprovider.h @@ -0,0 +1,111 @@ +#ifndef ICONPROVIDER_H +#define ICONPROVIDER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class IconProvider : public QQuickImageProvider +{ +public: + explicit IconProvider(const QString &family, const QString &codesPath) + : QQuickImageProvider(QQuickImageProvider::Image) + , font(family) + { + QFile file(codesPath); + if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) { + auto jd = QJsonDocument::fromJson(file.readAll()); + if (!jd.isNull()) + codepoints = jd.object(); + else + qWarning() << "Invalid codepoints JSON file" << codesPath; + } else { + qWarning() << "Cannot open icon codes file" << codesPath; + qWarning() << file.errorString(); + } + } + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override + { + int width = 48; + int height = 48; + + if (requestedSize.width() > 0) + width = requestedSize.width(); + + if (requestedSize.height() > 0) + height = requestedSize.height(); + + if (size) + *size = QSize(width, height); + +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + QStringList args = id.split(",", QString::SkipEmptyParts); +#else + QStringList args = id.split(",", Qt::SkipEmptyParts); +#endif + + QString iconChar("?"); + if (!args.isEmpty()) { + QString name = args.takeFirst(); + if (codepoints.value(name).isUndefined()) + qWarning() << "Icon name" << name << "not found in" << font.family(); + else + iconChar = codepoints[name].toString(); + } else { + qWarning() << "Icon name empty"; + } + + font.setPixelSize(width < height ? width : height); + + QFontMetrics fm(font); + double widthRatio = double(width) / fm.boundingRect(iconChar).width(); + if (widthRatio < 1.0) + font.setPixelSize(font.pixelSize() * widthRatio); + + QImage image(width, height, QImage::Format_RGBA8888); + image.fill(Qt::transparent); + + QPainter painter(&image); + + for (const QString &arg : args) { +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + QStringList attr = arg.split("=", QString::SkipEmptyParts); +#else + QStringList attr = arg.split("=", Qt::SkipEmptyParts); +#endif + if (attr.isEmpty() || attr.size() > 2) { + qWarning() << "Argument" << arg << "not valid."; + } else if (attr[0] == "color") { + if (attr.size() == 2) + painter.setPen(attr[1]); + else + qWarning() << "Attribute color needs a value"; + } else if (attr[0] == "hflip") { + painter.setTransform(QTransform(-1, 0, 0, 0, 1, 0, width, 0, 1)); + } else if (attr[0] == "vflip") { + painter.setTransform(QTransform(1, 0, 0, 0, -1, 0, 0, height, 1)); + } else { + qWarning() << "Unknown attribute" << attr; + } + } + + painter.setFont(font); + painter.drawText(QRect(0, 0, width, height), Qt::AlignCenter, iconChar); + + return image; + } + + QStringList keys() { return codepoints.keys(); } + +private: + QJsonObject codepoints; + QFont font; +}; + +#endif // ICONPROVIDER_H diff --git a/src/icons.cpp b/src/icons.cpp new file mode 100644 index 0000000..2fdd248 --- /dev/null +++ b/src/icons.cpp @@ -0,0 +1,55 @@ +#include "icons.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iconprovider.h" + +Icons::Icons(QObject *parent) + : QQmlPropertyMap(this, parent) +{ + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); +} + +Icons *Icons::instance() +{ + static Icons instance_; + + return &instance_; +} + +QObject *Icons::singletonProvider(QQmlEngine *qmlEngine, QJSEngine *jsEngine) +{ + Q_UNUSED(qmlEngine) + Q_UNUSED(jsEngine) + + return instance(); +} + +void Icons::registerIcons(QQmlEngine *engine, const QString &path) +{ + QString iconProviderName = "baseui_icons"; + + if (QFontDatabase::addApplicationFont(path + "MaterialIcons-Regular.ttf") == -1) + qWarning() << "Failed to load font Material"; + + auto iconProvider = new IconProvider("Material Icons", path + "codepoints.json"); + + engine->addImageProvider(iconProviderName, iconProvider); + +#if (QT_VERSION < QT_VERSION_CHECK(6, 1, 0)) + for (const QString &key : iconProvider->keys()) + instance()->insert(key, QVariant("image://" + iconProviderName + "/" + key + ",")); +#else + QVariantHash hash; + for (const QString &key : iconProvider->keys()) + hash.insert(key, QVariant("image://" + iconProviderName + "/" + key + ",")); + instance()->insert(hash); +#endif +} diff --git a/src/icons.h b/src/icons.h new file mode 100644 index 0000000..28b16fc --- /dev/null +++ b/src/icons.h @@ -0,0 +1,28 @@ +#ifndef ICONS_H +#define ICONS_H + +#include + +class QQmlEngine; +class QJSEngine; + +class Icons : public QQmlPropertyMap +{ + Q_OBJECT + +public: + Icons(QObject *parent = nullptr); + + static Icons *instance(); + static QObject *singletonProvider(QQmlEngine *qmlEngine, QJSEngine *jsEngine); + + static void registerIcons(QQmlEngine *engine, const QString &path); + +protected: + template + explicit Icons(DerivedType *derived, QObject *parent = nullptr) + : QQmlPropertyMap(derived, parent) + {} +}; + +#endif