mirror of
https://github.com/stemoretti/BaseUI.git
synced 2025-06-04 01:28:33 -04:00
Heavy changes
This commit is contained in:
parent
32a567a950
commit
11606b8f39
144
CMakeLists.txt
144
CMakeLists.txt
@ -1,78 +1,95 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
project(baseui VERSION 0.1 LANGUAGES CXX)
|
||||
project(BaseUI VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
option(BASEUI_EMBED_QML "BaseUI embed qml" OFF)
|
||||
option(BASEUI_EMBED_ICONS "BaseUI embed icons" OFF)
|
||||
option(BASEUI_INCLUDE_ICONS "Include Material icons" ON)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Qml Quick Gui QuickControls2 REQUIRED)
|
||||
find_package(Qt6 COMPONENTS Core Gui Qml Quick QuickControls2 ShaderTools REQUIRED)
|
||||
|
||||
add_library(${PROJECT_NAME} STATIC
|
||||
include/BaseUI/core.h
|
||||
src/core.cpp
|
||||
src/iconprovider.h
|
||||
src/icons.h
|
||||
src/icons.cpp
|
||||
set_source_files_properties(qml/Style.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
|
||||
|
||||
qt_add_qml_module(baseui
|
||||
URI BaseUI
|
||||
VERSION 1.0
|
||||
SOURCES
|
||||
include/BaseUI/core.h
|
||||
src/core.cpp
|
||||
src/iconprovider.h
|
||||
src/icons.cpp
|
||||
src/icons.h
|
||||
src/plugin.h
|
||||
QML_FILES
|
||||
qml/App.qml
|
||||
qml/AppStackPage.qml
|
||||
qml/AppToolBar.qml
|
||||
qml/ButtonContained.qml
|
||||
qml/ButtonFlat.qml
|
||||
qml/DatePicker.qml
|
||||
qml/EdgeEffect.qml
|
||||
qml/HorizontalDivider.qml
|
||||
qml/HorizontalListDivider.qml
|
||||
qml/Icon.qml
|
||||
qml/LabelBody.qml
|
||||
qml/LabelBodySecondary.qml
|
||||
qml/LabelSubheading.qml
|
||||
qml/LabelTitle.qml
|
||||
qml/ListViewEdgeEffect.qml
|
||||
qml/OptionsDialog.qml
|
||||
qml/PopupError.qml
|
||||
qml/PopupInfo.qml
|
||||
qml/PopupToast.qml
|
||||
qml/SettingsCheckItem.qml
|
||||
qml/SettingsItem.qml
|
||||
qml/SettingsSectionTitle.qml
|
||||
qml/Style.qml
|
||||
qml/TimeCircle.qml
|
||||
qml/TimePickerCircular.qml
|
||||
qml/TimePickerTumbler.qml
|
||||
RESOURCE_PREFIX
|
||||
"/baseui/imports"
|
||||
)
|
||||
|
||||
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")
|
||||
qt_add_shaders(baseui "baseui_shaders"
|
||||
BATCHABLE
|
||||
PRECOMPILE
|
||||
PREFIX
|
||||
"/baseui/imports/BaseUI"
|
||||
FILES
|
||||
qml/shaders/clock.frag
|
||||
qml/shaders/icon.frag
|
||||
)
|
||||
|
||||
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
|
||||
if(BASEUI_INCLUDE_ICONS)
|
||||
target_compile_definitions(baseui PRIVATE BASEUI_INCLUDE_ICONS)
|
||||
qt_add_resources(baseui "baseui_icons"
|
||||
PREFIX
|
||||
"/baseui/imports/BaseUI"
|
||||
FILES
|
||||
icons/codepoints.json
|
||||
icons/MaterialIcons-Regular.ttf
|
||||
)
|
||||
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(baseui
|
||||
PUBLIC "${PROJECT_SOURCE_DIR}/include"
|
||||
PRIVATE "${PROJECT_SOURCE_DIR}/src"
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC "${PROJECT_SOURCE_DIR}/include")
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
CXX_STANDARD 11
|
||||
set_target_properties(baseui PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED YES
|
||||
CXX_EXTENSIONS NO
|
||||
)
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:
|
||||
QT_QML_DEBUG
|
||||
# enable deprecated warnings for qt < 5.13
|
||||
QT_DEPRECATED_WARNINGS
|
||||
>
|
||||
target_compile_definitions(baseui
|
||||
PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
|
||||
|
||||
$<$<CONFIG:Release>:
|
||||
# disable deprecated warnings for qt >= 5.13
|
||||
QT_NO_DEPRECATED_WARNINGS
|
||||
>
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
target_compile_options(baseui
|
||||
PRIVATE
|
||||
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:GNU>>:
|
||||
-Wall
|
||||
@ -85,10 +102,17 @@ target_compile_options(${PROJECT_NAME}
|
||||
>
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
target_link_libraries(baseui
|
||||
PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Qml
|
||||
Qt${QT_VERSION_MAJOR}::Quick
|
||||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::QuickControls2
|
||||
Qt::Core
|
||||
Qt::Gui
|
||||
Qt::Qml
|
||||
Qt::Quick
|
||||
Qt::QuickControls2
|
||||
)
|
||||
|
||||
install(TARGETS baseui
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
BUNDLE DESTINATION "."
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
)
|
||||
|
54
baseui.pri
54
baseui.pri
@ -1,54 +0,0 @@
|
||||
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
|
||||
}
|
@ -2,20 +2,28 @@ 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)
|
||||
find_package(Qt6 COMPONENTS Core Gui Qml Quick QuickControls2 REQUIRED)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
add_executable(${PROJECT_NAME} main.cpp qml.qrc)
|
||||
add_subdirectory(BaseUI)
|
||||
|
||||
add_subdirectory(baseui)
|
||||
qt_add_executable(example WIN32 MACOSX_BUNDLE main.cpp)
|
||||
|
||||
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
|
||||
qt_add_qml_module(example
|
||||
URI Example
|
||||
VERSION 1.0
|
||||
QML_FILES main.qml
|
||||
NO_RESOURCE_TARGET_PATH
|
||||
)
|
||||
|
||||
target_link_libraries(example
|
||||
PUBLIC
|
||||
Qt::Core
|
||||
Qt::Gui
|
||||
Qt::Qml
|
||||
Qt::Quick
|
||||
Qt::QuickControls2
|
||||
baseui
|
||||
)
|
||||
|
@ -1,7 +0,0 @@
|
||||
QT += quick quickcontrols2
|
||||
|
||||
include(baseui/baseui.pri)
|
||||
|
||||
SOURCES += main.cpp
|
||||
|
||||
RESOURCES += qml.qrc
|
234
example/main.qml
234
example/main.qml
@ -1,14 +1,14 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Layouts
|
||||
|
||||
import BaseUI 1.0 as UI
|
||||
import BaseUI as UI
|
||||
|
||||
UI.App {
|
||||
id: root
|
||||
|
||||
width: 360
|
||||
width: 640
|
||||
height: 480
|
||||
|
||||
property string primary: Material.primary
|
||||
@ -45,6 +45,24 @@ UI.App {
|
||||
rightPadding: 10
|
||||
text: Qt.application.name
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
UI.ButtonContained {
|
||||
text: "Time Picker"
|
||||
buttonColor: UI.Style.primaryColor
|
||||
onClicked: timePicker.open()
|
||||
}
|
||||
|
||||
UI.ButtonContained {
|
||||
text: timePicker.time24h ? "24hr" : "AM/PM"
|
||||
buttonColor: UI.Style.primaryColor
|
||||
onClicked: timePicker.time24h = !timePicker.time24h
|
||||
}
|
||||
|
||||
UI.LabelBody {
|
||||
text: timePicker.timeString
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,33 +86,31 @@ UI.App {
|
||||
anchors { left: parent.left; right: parent.right }
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
id: topItem
|
||||
|
||||
height: 140
|
||||
color: UI.Style.primaryColor
|
||||
Label {
|
||||
text: Qt.application.displayName
|
||||
color: Material.foreground
|
||||
font.pixelSize: UI.Style.fontSizeHeadline
|
||||
padding: (homePage.appToolBar.implicitHeight - contentHeight) / 2
|
||||
leftPadding: 20
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UI.HorizontalListDivider {}
|
||||
|
||||
Repeater {
|
||||
id: pageList
|
||||
|
||||
model: [
|
||||
{ icon: UI.Icons.settings, text: "Settings", page: settingsPage },
|
||||
{ icon: UI.Icons.info_outline, text: "About", page: aboutPage }
|
||||
{
|
||||
icon: UI.Icons.settings,
|
||||
text: "Settings",
|
||||
page: settingsPageComponent
|
||||
},
|
||||
{
|
||||
icon: UI.Icons.info_outline,
|
||||
text: "Info",
|
||||
page: infoPageComponent
|
||||
}
|
||||
]
|
||||
|
||||
delegate: ItemDelegate {
|
||||
@ -105,7 +121,7 @@ UI.App {
|
||||
// Disable, or a double click will push the page twice.
|
||||
menuColumn.enabled = false
|
||||
navDrawer.close()
|
||||
pageStack.push(modelData.page)
|
||||
homePage.stack.push(modelData.page)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,15 +155,26 @@ UI.App {
|
||||
onTriggered: infoPopup.open()
|
||||
}
|
||||
}
|
||||
|
||||
UI.TimePickerCircular {
|
||||
id: timePicker
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: settingsPage
|
||||
id: settingsPageComponent
|
||||
|
||||
UI.AppStackPage {
|
||||
id: settingsPage
|
||||
|
||||
title: "Settings"
|
||||
padding: 0
|
||||
|
||||
leftButton: Action {
|
||||
icon.source: UI.Icons.arrow_back
|
||||
onTriggered: settingsPage.back()
|
||||
}
|
||||
|
||||
Flickable {
|
||||
contentHeight: settingsPane.implicitHeight
|
||||
anchors.fill: parent
|
||||
@ -166,24 +193,29 @@ UI.App {
|
||||
text: "Display"
|
||||
}
|
||||
|
||||
UI.SettingsItem {
|
||||
UI.SettingsCheckItem {
|
||||
title: "Dark Theme"
|
||||
check.visible: true
|
||||
check.checked: root.theme
|
||||
check.onClicked: root.theme = !root.theme
|
||||
onClicked: check.clicked()
|
||||
checkState: root.theme ? Qt.Checked : Qt.Unchecked
|
||||
onClicked: root.theme = !root.theme
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
UI.SettingsItem {
|
||||
title: "Primary Color"
|
||||
subtitle: primaryColorPopup.currentColorName
|
||||
onClicked: primaryColorPopup.open()
|
||||
subtitle: colorDialog.getColorName(root.primary)
|
||||
onClicked: {
|
||||
colorDialog.selectAccentColor = false
|
||||
colorDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
UI.SettingsItem {
|
||||
title: "Accent Color"
|
||||
subtitle: accentColorPopup.currentColorName
|
||||
onClicked: accentColorPopup.open()
|
||||
subtitle: colorDialog.getColorName(root.accent)
|
||||
onClicked: {
|
||||
colorDialog.selectAccentColor = true
|
||||
colorDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -192,18 +224,24 @@ UI.App {
|
||||
}
|
||||
|
||||
Component {
|
||||
id: aboutPage
|
||||
id: infoPageComponent
|
||||
|
||||
UI.AppStackPage {
|
||||
title: "About"
|
||||
id: infoPage
|
||||
title: "Info"
|
||||
padding: 10
|
||||
|
||||
leftButton: Action {
|
||||
icon.source: UI.Icons.arrow_back
|
||||
onTriggered: infoPage.back()
|
||||
}
|
||||
|
||||
Flickable {
|
||||
contentHeight: aboutPane.implicitHeight
|
||||
contentHeight: infoPane.implicitHeight
|
||||
anchors.fill: parent
|
||||
|
||||
Pane {
|
||||
id: aboutPane
|
||||
id: infoPane
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
@ -216,43 +254,11 @@ UI.App {
|
||||
}
|
||||
|
||||
UI.LabelBody {
|
||||
property string url: "http://github.com/stemoretti/baseui"
|
||||
|
||||
text: "<a href='" + url + "'>" + url + "</a>"
|
||||
text: "<a href='%1'>%1</a>".arg("http://github.com/stemoretti/BaseUI")
|
||||
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<br>"
|
||||
+ "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: "<a href='https://material.io/tools/icons/'"
|
||||
+ "title='Material Design'>Material Design</a>"
|
||||
+ " icons are under Apache license version 2.0"
|
||||
wrapMode: Text.WordWrap
|
||||
linkColor: UI.Style.isDarkTheme ? "lightblue" : "blue"
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,23 +281,79 @@ UI.App {
|
||||
text: "Information message."
|
||||
}
|
||||
|
||||
UI.PopupColorSelection {
|
||||
id: primaryColorPopup
|
||||
UI.OptionsDialog {
|
||||
id: colorDialog
|
||||
|
||||
parent: Overlay.overlay
|
||||
property bool selectAccentColor: false
|
||||
|
||||
currentColor: root.primary
|
||||
onColorSelected: function(c) { root.primary = c }
|
||||
}
|
||||
function getColorName(color) {
|
||||
var filtered = colorDialog.model.filter((c) => {
|
||||
return Material.color(c.bg) === color
|
||||
})
|
||||
return filtered.length ? filtered[0].name : ""
|
||||
}
|
||||
|
||||
UI.PopupColorSelection {
|
||||
id: accentColorPopup
|
||||
title: selectAccentColor ? qsTr("Choose accent color") : qsTr("Choose primary color")
|
||||
model: [
|
||||
{ name: "Material Red", bg: Material.Red },
|
||||
{ name: "Material Pink", bg: Material.Pink },
|
||||
{ name: "Material Purple", bg: Material.Purple },
|
||||
{ name: "Material DeepPurple", bg: Material.DeepPurple },
|
||||
{ name: "Material Indigo", bg: Material.Indigo },
|
||||
{ name: "Material Blue", bg: Material.Blue },
|
||||
{ name: "Material LightBlue", bg: Material.LightBlue },
|
||||
{ name: "Material Cyan", bg: Material.Cyan },
|
||||
{ name: "Material Teal", bg: Material.Teal },
|
||||
{ name: "Material Green", bg: Material.Green },
|
||||
{ name: "Material LightGreen", bg: Material.LightGreen },
|
||||
{ name: "Material Lime", bg: Material.Lime },
|
||||
{ name: "Material Yellow", bg: Material.Yellow },
|
||||
{ name: "Material Amber", bg: Material.Amber },
|
||||
{ name: "Material Orange", bg: Material.Orange },
|
||||
{ name: "Material DeepOrange", bg: Material.DeepOrange },
|
||||
{ name: "Material DeepOrange", bg: Material.DeepOrange },
|
||||
{ name: "Material Brown", bg: Material.Brown },
|
||||
{ name: "Material Grey", bg: Material.Grey },
|
||||
{ name: "Material BlueGrey", bg: Material.BlueGrey }
|
||||
]
|
||||
delegate: RowLayout {
|
||||
spacing: 0
|
||||
|
||||
parent: Overlay.overlay
|
||||
Rectangle {
|
||||
visible: colorDialog.selectAccentColor
|
||||
color: UI.Style.primaryColor
|
||||
Layout.margins: 0
|
||||
Layout.leftMargin: 10
|
||||
Layout.minimumWidth: 48
|
||||
Layout.minimumHeight: 32
|
||||
}
|
||||
|
||||
selectAccentColor: true
|
||||
currentColor: root.accent
|
||||
onColorSelected: function(c) { root.accent = c }
|
||||
Rectangle {
|
||||
color: Material.color(modelData.bg)
|
||||
Layout.margins: 0
|
||||
Layout.leftMargin: colorDialog.selectAccentColor ? 0 : 10
|
||||
Layout.minimumWidth: 32
|
||||
Layout.minimumHeight: 32
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
checked: {
|
||||
if (colorDialog.selectAccentColor)
|
||||
Material.color(modelData.bg) === root.accent
|
||||
else
|
||||
Material.color(modelData.bg) === root.primary
|
||||
}
|
||||
text: modelData.name
|
||||
Layout.leftMargin: 4
|
||||
onClicked: {
|
||||
colorDialog.close()
|
||||
if (colorDialog.selectAccentColor)
|
||||
root.accent = Material.color(modelData.bg)
|
||||
else
|
||||
root.primary = Material.color(modelData.bg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -1,6 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="imports/BaseUI/icons">
|
||||
<file>MaterialIcons-Regular.ttf</file>
|
||||
<file>codepoints.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
27
qml/App.qml
Normal file
27
qml/App.qml
Normal file
@ -0,0 +1,27 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
|
||||
property alias initialPage: stackView.initialItem
|
||||
|
||||
visible: true
|
||||
locale: Qt.locale("en_US")
|
||||
|
||||
header: stackView.currentItem?.appToolBar
|
||||
|
||||
StackView {
|
||||
id: stackView
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
// make sure that the phone physical back button will get key events
|
||||
onCurrentItemChanged: stackView.currentItem.forceActiveFocus()
|
||||
}
|
||||
|
||||
Material.primary: Style.primaryColor
|
||||
Material.accent: Style.accentColor
|
||||
Material.theme: Style.isDarkTheme ? Material.Dark : Material.Light
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import BaseUI 1.0
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Page {
|
||||
id: root
|
||||
|
||||
property StackView stack: StackView.view
|
||||
readonly 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)
|
||||
if (root.stack.currentItem != root)
|
||||
return false
|
||||
|
||||
return stack.pop(item, operation)
|
||||
return root.stack.pop(item, operation)
|
||||
}
|
||||
|
||||
function back() {
|
||||
@ -23,7 +20,7 @@ Page {
|
||||
}
|
||||
|
||||
Keys.onBackPressed: function(event) {
|
||||
if (stack.depth > 1) {
|
||||
if (root.stack.depth > 1) {
|
||||
event.accepted = true
|
||||
back()
|
||||
} else {
|
||||
@ -31,15 +28,8 @@ Page {
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
id: backAction
|
||||
icon.source: Icons.arrow_back
|
||||
onTriggered: root.back()
|
||||
}
|
||||
|
||||
AppToolBar {
|
||||
id: appToolBar
|
||||
title: root.title
|
||||
leftButton: stack && stack.depth > 1 ? backAction : null
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
ToolBar {
|
||||
id: root
|
||||
|
||||
property Action leftButton
|
||||
property list<Action> rightButtons
|
||||
|
||||
@ -14,12 +16,12 @@ ToolBar {
|
||||
anchors { fill: parent; leftMargin: 4; rightMargin: 4 }
|
||||
|
||||
ToolButton {
|
||||
icon.source: leftButton ? leftButton.icon.source : ""
|
||||
icon.source: root.leftButton?.icon.source ?? ""
|
||||
icon.color: Style.textOnPrimary
|
||||
focusPolicy: Qt.NoFocus
|
||||
opacity: Style.opacityTitle
|
||||
enabled: leftButton && leftButton.enabled
|
||||
onClicked: leftButton.trigger()
|
||||
enabled: root.leftButton && root.leftButton.enabled
|
||||
onClicked: root.leftButton.trigger()
|
||||
}
|
||||
LabelTitle {
|
||||
id: titleLabel
|
||||
@ -28,14 +30,14 @@ ToolBar {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
Repeater {
|
||||
model: rightButtons.length
|
||||
model: root.rightButtons.length
|
||||
delegate: ToolButton {
|
||||
icon.source: rightButtons[index].icon.source
|
||||
icon.source: root.rightButtons[index].icon.source
|
||||
icon.color: Style.textOnPrimary
|
||||
focusPolicy: Qt.NoFocus
|
||||
opacity: Style.opacityTitle
|
||||
enabled: rightButtons[index].enabled
|
||||
onClicked: rightButtons[index].trigger()
|
||||
enabled: root.rightButtons[index].enabled
|
||||
onClicked: root.rightButtons[index].trigger()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
// 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
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/imports/BaseUI">
|
||||
<file>qmldir</file>
|
||||
<file>App.qml</file>
|
||||
<file>AppStackPage.qml</file>
|
||||
<file>AppToolBar.qml</file>
|
||||
<file>ButtonFlat.qml</file>
|
||||
<file>ButtonRaised.qml</file>
|
||||
<file>HorizontalDivider.qml</file>
|
||||
<file>LabelBody.qml</file>
|
||||
<file>LabelSubheading.qml</file>
|
||||
<file>LabelTitle.qml</file>
|
||||
<file>PopupColorSelection.qml</file>
|
||||
<file>PopupError.qml</file>
|
||||
<file>PopupInfo.qml</file>
|
||||
<file>PopupList.qml</file>
|
||||
<file>PopupModalBase.qml</file>
|
||||
<file>PopupToast.qml</file>
|
||||
<file>SettingsItem.qml</file>
|
||||
<file>SettingsSectionTitle.qml</file>
|
||||
<file>Style.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
@ -1,21 +0,0 @@
|
||||
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
|
@ -1,9 +1,9 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Button {
|
||||
id: button
|
||||
id: root
|
||||
|
||||
property alias textColor: buttonText.color
|
||||
property alias buttonColor: buttonBackground.color
|
||||
@ -12,17 +12,16 @@ Button {
|
||||
leftPadding: 6
|
||||
rightPadding: 6
|
||||
|
||||
Layout.minimumWidth: 80
|
||||
|
||||
contentItem: Text {
|
||||
id: buttonText
|
||||
text: button.text
|
||||
text: root.text
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
color: Style.textOnPrimary
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
font.capitalization: Font.AllUppercase
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
@ -30,16 +29,6 @@ Button {
|
||||
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
|
||||
}
|
||||
*/
|
||||
opacity: root.pressed ? 0.75 : 1.0
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
// ekke (Ekkehard Gentz) @ekkescorner
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Button {
|
||||
id: button
|
||||
id: root
|
||||
|
||||
property alias textColor: buttonText.color
|
||||
|
||||
@ -12,11 +11,9 @@ Button {
|
||||
leftPadding: 6
|
||||
rightPadding: 6
|
||||
|
||||
Layout.minimumWidth: 88
|
||||
|
||||
contentItem: Text {
|
||||
id: buttonText
|
||||
text: button.text
|
||||
text: root.text
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
color: Style.flatButtonTextColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@ -29,8 +26,8 @@ Button {
|
||||
background: Rectangle {
|
||||
id: buttonBackground
|
||||
implicitHeight: 48
|
||||
color: button.pressed ? buttonText.color : "transparent"
|
||||
color: root.pressed ? buttonText.color : "transparent"
|
||||
radius: 2
|
||||
opacity: button.pressed ? 0.12 : 1.0
|
||||
opacity: root.pressed ? 0.12 : 1.0
|
||||
}
|
||||
}
|
152
qml/DatePicker.qml
Normal file
152
qml/DatePicker.qml
Normal file
@ -0,0 +1,152 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
import BaseUI as UI
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string locale: "en_US"
|
||||
property date selectedDate: new Date()
|
||||
|
||||
readonly property int day: selectedDate.getDate()
|
||||
readonly property int month: selectedDate.getMonth()
|
||||
readonly property int year: selectedDate.getFullYear()
|
||||
readonly property string dateString: year + "-" + _zeroPad(month + 1) + "-" + _zeroPad(day)
|
||||
|
||||
function _zeroPad(n) { return n > 9 ? n : '0' + n }
|
||||
|
||||
implicitHeight: column.implicitHeight
|
||||
implicitWidth: column.implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
implicitHeight: monthRow.implicitHeight
|
||||
color: Material.primary
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
id: monthRow
|
||||
|
||||
spacing: 6
|
||||
width: parent.width
|
||||
|
||||
ToolButton {
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
icon.source: UI.Icons.keyboard_arrow_left
|
||||
icon.color: Style.textOnPrimary
|
||||
onClicked: {
|
||||
if (monthGrid.month > 0) {
|
||||
monthGrid.month--
|
||||
} else {
|
||||
monthGrid.month = 11
|
||||
monthGrid.year--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LabelTitle {
|
||||
text: Qt.locale(root.locale).monthName(monthGrid.month) + " " + monthGrid.year
|
||||
elide: Text.ElideRight
|
||||
color: Style.textOnPrimary
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
leftPadding: 12
|
||||
rightPadding: 12
|
||||
icon.source: UI.Icons.keyboard_arrow_right
|
||||
icon.color: Style.textOnPrimary
|
||||
onClicked: {
|
||||
if (monthGrid.month < 11) {
|
||||
monthGrid.month++
|
||||
} else {
|
||||
monthGrid.month = 0
|
||||
monthGrid.year++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DayOfWeekRow {
|
||||
id: dayOfWeekRow
|
||||
|
||||
leftPadding: 24
|
||||
rightPadding: 24
|
||||
Layout.fillWidth: true
|
||||
font.bold: false
|
||||
|
||||
locale: Qt.locale(root.locale)
|
||||
|
||||
delegate: LabelBodySecondary {
|
||||
text: model.shortName
|
||||
font: dayOfWeekRow.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
MonthGrid {
|
||||
id: monthGrid
|
||||
|
||||
rightPadding: 24
|
||||
leftPadding: 24
|
||||
Layout.fillWidth: true
|
||||
|
||||
month: root.month
|
||||
year: root.year
|
||||
|
||||
locale: Qt.locale(root.locale)
|
||||
|
||||
onClicked: function(d) {
|
||||
// Important: check the month to avoid clicking on days outside where opacity 0
|
||||
if (d.getMonth() === monthGrid.month) {
|
||||
root.selectedDate = d
|
||||
console.log("tapped on a date ")
|
||||
} else {
|
||||
console.log("outside valid month " + d.getMonth())
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Label {
|
||||
id: dayLabel
|
||||
|
||||
readonly property bool selected:
|
||||
model.day === root.day
|
||||
&& model.month === root.month
|
||||
&& model.year === root.year
|
||||
|
||||
text: model.day
|
||||
font.bold: model.today ? true : false
|
||||
font.pixelSize: Style.fontSizeTitle
|
||||
opacity: model.month === monthGrid.month ? 1 : 0.3
|
||||
color: selected
|
||||
? Style.textOnPrimary
|
||||
: (model.today ? Style.accentColor : Material.foreground)
|
||||
minimumPointSize: 8
|
||||
fontSizeMode: Text.Fit
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
background: Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.height + 2, parent.width + 2)
|
||||
height: Math.min(parent.height + 2, parent.width + 2)
|
||||
radius: width / 2
|
||||
color: parent.selected ? Style.primaryColor : "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
qml/EdgeEffect.qml
Normal file
46
qml/EdgeEffect.qml
Normal file
@ -0,0 +1,46 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
enum Side {
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
required property int overshoot
|
||||
required property int maxOvershoot
|
||||
|
||||
property int side: EdgeEffect.Side.Top
|
||||
property color color: "gray"
|
||||
|
||||
implicitHeight: 30
|
||||
|
||||
onColorChanged: canvas.requestPaint()
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
|
||||
anchors.fill: parent
|
||||
opacity: root.overshoot / root.maxOvershoot
|
||||
|
||||
onPaint: {
|
||||
if (root.side === EdgeEffect.Side.Top) {
|
||||
var y1 = 0
|
||||
var y2 = height
|
||||
} else {
|
||||
var y1 = height
|
||||
var y2 = 0
|
||||
}
|
||||
var ctx = getContext("2d")
|
||||
ctx.save()
|
||||
ctx.reset()
|
||||
ctx.fillStyle = root.color
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, y1)
|
||||
ctx.bezierCurveTo(width / 4, y2, 3 * width / 4, y2, width, y1)
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,10 @@
|
||||
// ekke (Ekkehard Gentz) @ekkescorner
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
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
|
15
qml/HorizontalListDivider.qml
Normal file
15
qml/HorizontalListDivider.qml
Normal file
@ -0,0 +1,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
// special divider for list elements
|
||||
// using height 1 ensures that it looks good if highlighted
|
||||
Item {
|
||||
height: 1
|
||||
Layout.fillWidth: true
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
opacity: Style.dividerOpacity
|
||||
color: Style.dividerColor
|
||||
}
|
||||
}
|
20
qml/Icon.qml
Normal file
20
qml/Icon.qml
Normal file
@ -0,0 +1,20 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property alias icon: internal.source
|
||||
property color color: undefined
|
||||
|
||||
Image {
|
||||
id: internal
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: root.color != undefined
|
||||
layer.samplerName: "maskSource"
|
||||
layer.effect: ShaderEffect {
|
||||
property color color: root.color
|
||||
fragmentShader: "shaders/icon.frag.qsb"
|
||||
}
|
||||
}
|
||||
}
|
8
qml/LabelBody.qml
Normal file
8
qml/LabelBody.qml
Normal file
@ -0,0 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
opacity: Style.opacityBodyAndButton
|
||||
}
|
8
qml/LabelBodySecondary.qml
Normal file
8
qml/LabelBodySecondary.qml
Normal file
@ -0,0 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
opacity: Style.opacityBodySecondary
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
// ekke (Ekkehard Gentz) @ekkescorner
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
9
qml/LabelTitle.qml
Normal file
9
qml/LabelTitle.qml
Normal file
@ -0,0 +1,9 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Style.fontSizeTitle
|
||||
opacity: Style.opacityTitle
|
||||
}
|
42
qml/ListViewEdgeEffect.qml
Normal file
42
qml/ListViewEdgeEffect.qml
Normal file
@ -0,0 +1,42 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
ListView {
|
||||
id: root
|
||||
|
||||
boundsMovement: Flickable.StopAtBounds
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
|
||||
// XXX: disable optimizations
|
||||
cacheBuffer: height * 1000
|
||||
|
||||
add: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 200 }
|
||||
}
|
||||
moveDisplaced: Transition {
|
||||
NumberAnimation { property: "y"; duration: 200 }
|
||||
}
|
||||
removeDisplaced: Transition {
|
||||
NumberAnimation { property: "y"; duration: 200 }
|
||||
}
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator { }
|
||||
|
||||
EdgeEffect {
|
||||
width: root.width
|
||||
anchors.top: root.top
|
||||
side: EdgeEffect.Side.Top
|
||||
overshoot: root.verticalOvershoot < 0 ? -root.verticalOvershoot : 0
|
||||
maxOvershoot: root.height
|
||||
color: Style.isDarkTheme ? "gray" : "darkgray"
|
||||
}
|
||||
|
||||
EdgeEffect {
|
||||
width: root.width
|
||||
anchors.bottom: root.bottom
|
||||
side: EdgeEffect.Side.Bottom
|
||||
overshoot: root.verticalOvershoot > 0 ? root.verticalOvershoot : 0
|
||||
maxOvershoot: root.height
|
||||
color: Style.isDarkTheme ? "gray" : "darkgray"
|
||||
}
|
||||
}
|
59
qml/OptionsDialog.qml
Normal file
59
qml/OptionsDialog.qml
Normal file
@ -0,0 +1,59 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
property alias model: listView.model
|
||||
property alias delegate: listView.delegate
|
||||
|
||||
parent: Overlay.overlay
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
width: Math.min(Math.max(header.implicitWidth, listView.contentItem.childrenRect.width), parent.width * 0.9)
|
||||
height: Math.min(implicitHeight + listView.contentHeight, parent.height * 0.9)
|
||||
|
||||
padding: 0
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
|
||||
onClosed: listView.positionViewAtBeginning()
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
HorizontalListDivider {
|
||||
opacity: listView.contentY - listView.originY > 0 ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation {} }
|
||||
}
|
||||
|
||||
ListViewEdgeEffect {
|
||||
id: listView
|
||||
|
||||
clip: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
HorizontalListDivider {
|
||||
opacity: listView.contentHeight - listView.contentY - listView.height > 0 ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation {} }
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
alignment: Qt.AlignRight
|
||||
|
||||
ButtonFlat {
|
||||
text: qsTr("Cancel")
|
||||
textColor: Style.accentColor
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
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 QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Window
|
||||
|
||||
import BaseUI 1.0
|
||||
import BaseUI as UI
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
@ -45,13 +45,11 @@ Popup {
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
|
||||
Image {
|
||||
id: alarmIcon
|
||||
|
||||
smooth: true
|
||||
source: Icons.error + "color=white"
|
||||
sourceSize.width: 36
|
||||
sourceSize.height: 36
|
||||
Icon {
|
||||
width: 36
|
||||
height: 36
|
||||
icon: UI.Icons.error
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Label {
|
45
qml/PopupInfo.qml
Normal file
45
qml/PopupInfo.qml
Normal file
@ -0,0 +1,45 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
property alias text: popupLabel.text
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
|
||||
contentItem: 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)
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
alignment: Qt.AlignHCenter
|
||||
standardButtons: DialogButtonBox.Ok
|
||||
|
||||
onAccepted: root.close()
|
||||
|
||||
ButtonFlat {
|
||||
text: "OK"
|
||||
textColor: Style.accentColor
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
import QtQuick.Window
|
||||
|
||||
Popup {
|
||||
id: root
|
31
qml/SettingsCheckItem.qml
Normal file
31
qml/SettingsCheckItem.qml
Normal file
@ -0,0 +1,31 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
CheckDelegate {
|
||||
id: root
|
||||
|
||||
property alias title: root.text
|
||||
property string subtitle
|
||||
property string subtitlePlaceholder
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 2
|
||||
|
||||
LabelSubheading {
|
||||
rightPadding: root.indicator.width + root.spacing
|
||||
text: root.text
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
LabelBody {
|
||||
rightPadding: root.indicator.width + root.spacing
|
||||
text: root.subtitle.length > 0 ? root.subtitle : root.subtitlePlaceholder
|
||||
visible: text.length > 0 || root.subtitlePlaceholder.length > 0
|
||||
opacity: 0.6
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
36
qml/SettingsItem.qml
Normal file
36
qml/SettingsItem.qml
Normal file
@ -0,0 +1,36 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
ItemDelegate {
|
||||
id: root
|
||||
|
||||
property alias title: titleLabel.text
|
||||
property string subtitle
|
||||
property string subtitlePlaceholder
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
contentItem: 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
|
||||
}
|
||||
}
|
||||
}
|
18
qml/SettingsSectionTitle.qml
Normal file
18
qml/SettingsSectionTitle.qml
Normal file
@ -0,0 +1,18 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
leftPadding: 16
|
||||
topPadding: 6
|
||||
bottomPadding: 6
|
||||
font.bold: true
|
||||
font.pixelSize: Style.fontSizeBodyAndButton
|
||||
color: Style.isDarkTheme ? "white" : Qt.lighter("gray", 1.1)
|
||||
|
||||
background: Rectangle {
|
||||
color: Style.isDarkTheme ? Qt.darker("gray") : Qt.lighter("lightgray", 1.1)
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
QtObject {
|
||||
property bool isDarkTheme: false
|
256
qml/TimeCircle.qml
Normal file
256
qml/TimeCircle.qml
Normal file
@ -0,0 +1,256 @@
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Item screen
|
||||
|
||||
property int hours: 0
|
||||
property int minutes: 0
|
||||
readonly property string timeString: _zeroPad(hours) + ":" + _zeroPad(minutes)
|
||||
readonly property bool isPM: hours >= 12
|
||||
|
||||
property bool pickMinutes: false
|
||||
property bool time24h: false
|
||||
|
||||
property color clockColor: "gray"
|
||||
property color clockHandColor: "blue"
|
||||
property color labelsColor: "white"
|
||||
property color labelsSelectedColor: labelsColor
|
||||
property color labelDotColor: labelsColor
|
||||
|
||||
property real innerRadius: clock.radius * 0.5
|
||||
property real outerRadius: clock.radius * 0.8
|
||||
|
||||
property int labelsSize: 20
|
||||
property int clockHandCircleSize: 2 * labelsSize
|
||||
|
||||
function update() {
|
||||
circle.pos = circle.mapToItem(root.screen, 0, circle.height)
|
||||
circle.pos.y = Window.height - circle.pos.y
|
||||
}
|
||||
|
||||
function _zeroPad(n) { return n > 9 ? n : '0' + n }
|
||||
|
||||
function _getSelectedAngle(fullAngle) {
|
||||
if (root.pickMinutes)
|
||||
return fullAngle / 60 * root.minutes
|
||||
else if (root.hours >= 12)
|
||||
return fullAngle / 12 * (root.hours - 12)
|
||||
else
|
||||
return fullAngle / 12 * root.hours
|
||||
}
|
||||
|
||||
implicitWidth: labelsSize * 12
|
||||
implicitHeight: implicitWidth
|
||||
|
||||
onPickMinutesChanged: {
|
||||
handAnimation.enabled = circleAnimation.enabled = true
|
||||
disableAnimationTimer.start()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: disableAnimationTimer
|
||||
interval: 400
|
||||
repeat: false
|
||||
onTriggered: handAnimation.enabled = circleAnimation.enabled = false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clock
|
||||
|
||||
width: Math.min(root.width, root.height)
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: root.clockColor
|
||||
|
||||
MouseArea {
|
||||
property bool isHold: false
|
||||
|
||||
function getSectorFromAngle(rad, sectors) {
|
||||
let index = Math.round(rad / (2 * Math.PI) * sectors)
|
||||
return index < 0 ? index + sectors : index
|
||||
}
|
||||
|
||||
function selectTime(mouse, tap) {
|
||||
let x = mouse.x - width / 2
|
||||
let y = -(mouse.y - height / 2)
|
||||
let angle = Math.atan2(x, y)
|
||||
if (root.pickMinutes) {
|
||||
if (tap)
|
||||
root.minutes = getSectorFromAngle(angle, 12) * 5
|
||||
else
|
||||
root.minutes = getSectorFromAngle(angle, 60)
|
||||
} else {
|
||||
let hour = getSectorFromAngle(angle, 12)
|
||||
if (root.time24h) {
|
||||
let radius = (root.outerRadius + root.innerRadius) / 2
|
||||
if (Qt.vector2d(x, y).length() > radius) {
|
||||
if (hour == 0)
|
||||
hour = 12
|
||||
} else if (hour != 0) {
|
||||
hour += 12
|
||||
}
|
||||
} else if (root.isPM) {
|
||||
hour += 12
|
||||
}
|
||||
root.hours = hour
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
pressAndHoldInterval: 100
|
||||
|
||||
onClicked: (mouse) => { selectTime(mouse, true); root.pickMinutes = true }
|
||||
onPositionChanged: (mouse) => { if (isHold) selectTime(mouse) }
|
||||
onPressAndHold: (mouse) => { isHold = true; selectTime(mouse) }
|
||||
onReleased: { if (isHold) { isHold = false; root.pickMinutes = true } }
|
||||
}
|
||||
|
||||
// clock hand
|
||||
Rectangle {
|
||||
id: hand
|
||||
|
||||
x: clock.width / 2 - width / 2
|
||||
y: clock.height / 2 - height
|
||||
width: 2
|
||||
height: root.pickMinutes
|
||||
|| !root.time24h
|
||||
|| (root.hours != 0 && root.hours <= 12)
|
||||
? root.outerRadius
|
||||
: root.innerRadius
|
||||
|
||||
transformOrigin: Item.Bottom
|
||||
rotation: _getSelectedAngle(360)
|
||||
color: root.clockHandColor
|
||||
antialiasing: true
|
||||
Behavior on rotation {
|
||||
id: handAnimation
|
||||
enabled: false
|
||||
NumberAnimation { duration: 400 }
|
||||
}
|
||||
}
|
||||
|
||||
// label background
|
||||
Rectangle {
|
||||
id: circle
|
||||
|
||||
property point pos: Qt.point(x, y)
|
||||
property real angle: _getSelectedAngle(2 * Math.PI)
|
||||
|
||||
x: clock.width / 2 + hand.height * Math.sin(angle) - width / 2
|
||||
y: clock.height / 2 - hand.height * Math.cos(angle) - height / 2
|
||||
width: root.clockHandCircleSize
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: root.clockHandColor
|
||||
|
||||
onXChanged: pos.x = mapToItem(root.screen, 0, 0).x
|
||||
// OpenGL origin is bottom left
|
||||
onYChanged: pos.y = Window.height - mapToItem(root.screen, 0, height).y
|
||||
|
||||
Rectangle {
|
||||
width: 4
|
||||
height: width
|
||||
radius: width / 2
|
||||
anchors.centerIn: parent
|
||||
visible: root.pickMinutes && root.minutes % 5
|
||||
color: root.labelDotColor
|
||||
}
|
||||
|
||||
Behavior on angle {
|
||||
id: circleAnimation
|
||||
enabled: false
|
||||
NumberAnimation { duration: 400 }
|
||||
}
|
||||
}
|
||||
|
||||
// centerpoint
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 10
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: root.clockHandColor
|
||||
}
|
||||
|
||||
Repeater {
|
||||
anchors.centerIn: parent
|
||||
model: [ 0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ]
|
||||
delegate: Text {
|
||||
required property int modelData
|
||||
required property int index
|
||||
property real angle: 2 * Math.PI * index / 12
|
||||
x: clock.width / 2 + root.innerRadius * Math.sin(angle) - width / 2
|
||||
y: clock.height / 2 - root.innerRadius * Math.cos(angle) - height / 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: root.labelsSize
|
||||
visible: root.time24h
|
||||
opacity: root.pickMinutes ? 0 : 1
|
||||
color: root.labelsColor
|
||||
text: modelData
|
||||
layer.enabled: true
|
||||
layer.samplerName: "maskSource"
|
||||
layer.effect: shaderEffect
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
anchors.centerIn: parent
|
||||
model: [ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
|
||||
delegate: Text {
|
||||
required property int modelData
|
||||
required property int index
|
||||
property real angle: 2 * Math.PI * index / 12
|
||||
x: clock.width / 2 + root.outerRadius * Math.sin(angle) - width / 2
|
||||
y: clock.height / 2 - root.outerRadius * Math.cos(angle) - height / 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: root.labelsSize
|
||||
opacity: root.pickMinutes ? 0 : 1
|
||||
color: root.labelsColor
|
||||
text: modelData
|
||||
layer.enabled: true
|
||||
layer.samplerName: "maskSource"
|
||||
layer.effect: shaderEffect
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
anchors.centerIn: parent
|
||||
model: 60
|
||||
delegate: Text {
|
||||
required property int modelData
|
||||
required property int index
|
||||
property real angle: 2 * Math.PI * index / 60
|
||||
x: clock.width / 2 + root.outerRadius * Math.sin(angle) - width / 2
|
||||
y: clock.height / 2 - root.outerRadius * Math.cos(angle) - height / 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: root.labelsSize
|
||||
visible: modelData % 5 == 0
|
||||
opacity: root.pickMinutes ? 1 : 0
|
||||
color: root.labelsColor
|
||||
text: _zeroPad(modelData)
|
||||
layer.enabled: true
|
||||
layer.samplerName: "maskSource"
|
||||
layer.effect: shaderEffect
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: shaderEffect
|
||||
ShaderEffect {
|
||||
property point pos: circle.pos
|
||||
property real radius: root.labelsSize
|
||||
property color color: root.labelsSelectedColor
|
||||
property real dpi: Screen.devicePixelRatio
|
||||
fragmentShader: "shaders/clock.frag.qsb"
|
||||
}
|
||||
}
|
||||
}
|
206
qml/TimePickerCircular.qml
Normal file
206
qml/TimePickerCircular.qml
Normal file
@ -0,0 +1,206 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Controls.Material
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
property alias time24h: timePicker.time24h
|
||||
|
||||
readonly property string timeString: timePicker.timeString + ":00"
|
||||
|
||||
function setTime(hour, minute) {
|
||||
timePicker.hours = hour
|
||||
timePicker.minutes = minute
|
||||
}
|
||||
|
||||
readonly property bool _isLandscape: parent.width > parent.height
|
||||
|
||||
function _zeroPad(n) { return n > 9 ? n : '0' + n }
|
||||
|
||||
parent: Overlay.overlay
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
padding: 0
|
||||
topPadding: 0
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
focus: true
|
||||
|
||||
onOpened: timePicker.update()
|
||||
onClosed: timePicker.pickMinutes = false
|
||||
|
||||
on_IsLandscapeChanged: updateTimer.restart()
|
||||
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: 1
|
||||
onTriggered: timePicker.update()
|
||||
}
|
||||
|
||||
header: Pane {
|
||||
bottomPadding: 0
|
||||
|
||||
LabelSubheading {
|
||||
text: qsTr("Select time")
|
||||
font.bold: true
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Pane {
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
GridLayout {
|
||||
flow: root._isLandscape ? GridLayout.LeftToRight : GridLayout.TopToBottom
|
||||
|
||||
GridLayout {
|
||||
flow: root._isLandscape ? GridLayout.TopToBottom : GridLayout.LeftToRight
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
Label {
|
||||
color: timePicker.pickMinutes ? Material.foreground : Style.primaryColor
|
||||
font.pixelSize: Style.fontSizeDisplay3
|
||||
text: {
|
||||
let hours = timePicker.hours
|
||||
if (!timePicker.time24h) {
|
||||
if (timePicker.isPM) {
|
||||
if (timePicker.hours != 12)
|
||||
hours = timePicker.hours - 12
|
||||
} else {
|
||||
if (timePicker.hours == 0)
|
||||
hours = 12
|
||||
}
|
||||
}
|
||||
_zeroPad(hours)
|
||||
}
|
||||
background: Rectangle {
|
||||
color: timePicker.pickMinutes ? Qt.darker(Material.background, 1.1) : Qt.lighter(Style.primaryColor)
|
||||
radius: 4
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: timePicker.pickMinutes = false
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
color: Material.foreground
|
||||
font.pixelSize: Style.fontSizeDisplay3
|
||||
text: ":"
|
||||
}
|
||||
|
||||
Label {
|
||||
color: timePicker.pickMinutes ? Style.primaryColor : Material.foreground
|
||||
font.pixelSize: Style.fontSizeDisplay3
|
||||
text: _zeroPad(timePicker.minutes)
|
||||
background: Rectangle {
|
||||
color: timePicker.pickMinutes ? Qt.lighter(Style.primaryColor) : Qt.darker(Material.background, 1.1)
|
||||
radius: 4
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: timePicker.pickMinutes = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: !timePicker.time24h
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Rectangle {
|
||||
width: amLabel.width + 4
|
||||
height: amLabel.height + 4
|
||||
radius: 4
|
||||
color: timePicker.isPM ? Material.background : Style.primaryColor
|
||||
|
||||
Label {
|
||||
id: amLabel
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: Style.fontSizeTitle
|
||||
color: timePicker.isPM ? Material.foreground : Style.textOnPrimary
|
||||
text: "AM"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (timePicker.isPM)
|
||||
timePicker.hours -= 12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: pmLabel.width + 4
|
||||
height: pmLabel.height + 4
|
||||
radius: 4
|
||||
color: timePicker.isPM ? Style.primaryColor : Material.background
|
||||
|
||||
Label {
|
||||
id: pmLabel
|
||||
anchors.centerIn: parent
|
||||
font.pixelSize: Style.fontSizeTitle
|
||||
color: timePicker.isPM ? Style.textOnPrimary : Material.foreground
|
||||
text: "PM"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (!timePicker.isPM)
|
||||
timePicker.hours += 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeCircle {
|
||||
id: timePicker
|
||||
|
||||
screen: Overlay.overlay
|
||||
clockColor: Qt.darker(Material.background, 1.1)
|
||||
clockHandColor: Style.primaryColor
|
||||
labelsColor: Style.isDarkTheme ? "#FFFFFF" : "#000000"
|
||||
labelsSelectedColor: Style.textOnPrimary
|
||||
labelDotColor: Style.textOnPrimary
|
||||
labelsSize: Style.fontSizeTitle
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.leftMargin: root._isLandscape ? 30 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
alignment: Qt.AlignRight
|
||||
background: Pane {}
|
||||
|
||||
ButtonFlat {
|
||||
text: qsTr("Cancel")
|
||||
textColor: Style.primaryColor
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
}
|
||||
|
||||
ButtonFlat {
|
||||
text: qsTr("OK")
|
||||
textColor: Style.primaryColor
|
||||
implicitWidth: 80
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||
}
|
||||
}
|
||||
}
|
142
qml/TimePickerTumbler.qml
Normal file
142
qml/TimePickerTumbler.qml
Normal file
@ -0,0 +1,142 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
readonly property int hours: {
|
||||
if (root.timeAMPM) {
|
||||
if (amPmTumbler.currentIndex === 0) {
|
||||
if (hoursTumbler.currentIndex === 0)
|
||||
12
|
||||
else
|
||||
hoursTumbler.currentIndex + 12
|
||||
} else {
|
||||
if (hoursTumbler.currentIndex === 12)
|
||||
0
|
||||
else
|
||||
hoursTumbler.currentIndex
|
||||
}
|
||||
} else {
|
||||
hoursTumbler.currentIndex
|
||||
}
|
||||
}
|
||||
readonly property int minutes: minutesTumbler.currentIndex
|
||||
readonly property string timeString: _zeroPad(hours) + ":" + _zeroPad(minutes) + ":00"
|
||||
|
||||
property bool timeAMPM: false
|
||||
|
||||
function setTime(hour, minute) {
|
||||
if (root.timeAMPM) {
|
||||
if (hour >= 12) {
|
||||
hour -= 12
|
||||
// XXX: doesn't work. why?
|
||||
// amPmTumbler.positionViewAtIndex(0, Tumbler.Center)
|
||||
amPmTumbler.currentIndex = 0
|
||||
} else {
|
||||
// amPmTumbler.positionViewAtIndex(1, Tumbler.Center)
|
||||
amPmTumbler.currentIndex = 1
|
||||
}
|
||||
}
|
||||
hoursTumbler.positionViewAtIndex(hour, Tumbler.Center)
|
||||
minutesTumbler.positionViewAtIndex(minute, Tumbler.Center)
|
||||
}
|
||||
|
||||
function _zeroPad(n) { return n > 9 ? n : '0' + n }
|
||||
|
||||
parent: Overlay.overlay
|
||||
|
||||
x: (parent.width - width) / 2
|
||||
y: (parent.height - height) / 2
|
||||
|
||||
padding: 0
|
||||
topPadding: 0
|
||||
|
||||
modal: true
|
||||
dim: true
|
||||
focus: true
|
||||
|
||||
header: Pane {
|
||||
LabelSubheading {
|
||||
text: qsTr("Select time")
|
||||
font.bold: true
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Pane {
|
||||
RowLayout {
|
||||
id: tumblerRow
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
spacing: 10
|
||||
|
||||
Tumbler {
|
||||
id: hoursTumbler
|
||||
|
||||
model: root.timeAMPM ? [ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ] : 24
|
||||
delegate: delegateComponent
|
||||
}
|
||||
|
||||
Label {
|
||||
text: ":"
|
||||
font.pixelSize: 40
|
||||
}
|
||||
|
||||
Tumbler {
|
||||
id: minutesTumbler
|
||||
|
||||
model: 60
|
||||
delegate: delegateComponent
|
||||
}
|
||||
|
||||
Tumbler {
|
||||
id: amPmTumbler
|
||||
|
||||
visible: root.timeAMPM
|
||||
model: ["PM", "AM"]
|
||||
delegate: Label {
|
||||
text: modelData
|
||||
opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 40
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: DialogButtonBox {
|
||||
alignment: Qt.AlignRight
|
||||
background: Pane {}
|
||||
|
||||
ButtonFlat {
|
||||
text: qsTr("Cancel")
|
||||
textColor: Style.primaryColor
|
||||
// implicitWidth: 80
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
|
||||
}
|
||||
|
||||
ButtonFlat {
|
||||
text: qsTr("OK")
|
||||
textColor: Style.primaryColor
|
||||
implicitWidth: 80
|
||||
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: delegateComponent
|
||||
|
||||
Label {
|
||||
text: _zeroPad(modelData)
|
||||
opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 40
|
||||
}
|
||||
}
|
||||
}
|
23
qml/shaders/clock.frag
Normal file
23
qml/shaders/clock.frag
Normal file
@ -0,0 +1,23 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
vec2 pos;
|
||||
float radius;
|
||||
vec4 color;
|
||||
float dpi;
|
||||
} ubuf;
|
||||
|
||||
layout(binding = 2) uniform sampler2D maskSource;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (distance(gl_FragCoord.xy / ubuf.dpi, ubuf.pos + ubuf.radius) < ubuf.radius)
|
||||
fragColor = ubuf.color * texture(maskSource, qt_TexCoord0).a * ubuf.qt_Opacity;
|
||||
else
|
||||
fragColor = texture(maskSource, qt_TexCoord0).rgba * ubuf.qt_Opacity;
|
||||
}
|
17
qml/shaders/icon.frag
Normal file
17
qml/shaders/icon.frag
Normal file
@ -0,0 +1,17 @@
|
||||
#version 440
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf {
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
vec4 color;
|
||||
} ubuf;
|
||||
|
||||
layout(binding = 2) uniform sampler2D maskSource;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragColor = ubuf.color * texture(maskSource, qt_TexCoord0).a * ubuf.qt_Opacity;
|
||||
}
|
32
src/core.cpp
32
src/core.cpp
@ -1,41 +1,23 @@
|
||||
#include <BaseUI/core.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QQmlEngine>
|
||||
#include <QQuickStyle>
|
||||
#include <QDebug>
|
||||
|
||||
#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<Icons>("BaseUI", 1, 0, "Icons", Icons::singletonProvider);
|
||||
Icons::instance = std::make_unique<Icons>();
|
||||
#ifdef BASEUI_INCLUDE_ICONS
|
||||
QString path = ":/baseui/imports/BaseUI/icons/";
|
||||
BaseUI::Icons::registerIcons(engine, path + "MaterialIcons-Regular.ttf",
|
||||
"Material Icons", path + "codepoints.json");
|
||||
#endif
|
||||
engine->addImportPath(":/baseui/imports");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,21 +9,29 @@
|
||||
#include <QJsonObject>
|
||||
#include <QPainter>
|
||||
#include <QFontMetrics>
|
||||
#include <QVariantMap>
|
||||
|
||||
class IconProvider : public QQuickImageProvider
|
||||
{
|
||||
public:
|
||||
explicit IconProvider(const QString &family, const QString &codesPath)
|
||||
IconProvider(const QString &family, const QVariantMap &codes)
|
||||
: QQuickImageProvider(QQuickImageProvider::Image)
|
||||
, codepoints(codes)
|
||||
, font(family)
|
||||
{
|
||||
}
|
||||
|
||||
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
|
||||
QJsonDocument jd = QJsonDocument::fromJson(file.readAll());
|
||||
if (jd.isNull())
|
||||
qWarning() << "Invalid codepoints JSON file" << codesPath;
|
||||
else
|
||||
codepoints = jd.object().toVariantMap();
|
||||
} else {
|
||||
qWarning() << "Cannot open icon codes file" << codesPath;
|
||||
qWarning() << file.errorString();
|
||||
@ -44,22 +52,13 @@ public:
|
||||
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 {
|
||||
if (id.isEmpty())
|
||||
qWarning() << "Icon name empty";
|
||||
}
|
||||
else if (codepoints.value(id).isNull())
|
||||
qWarning() << "Icon name" << id << "not found in" << font.family();
|
||||
else
|
||||
iconChar = codepoints[id].toString();
|
||||
|
||||
font.setPixelSize(width < height ? width : height);
|
||||
|
||||
@ -72,29 +71,6 @@ public:
|
||||
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);
|
||||
|
||||
@ -104,7 +80,7 @@ public:
|
||||
QStringList keys() { return codepoints.keys(); }
|
||||
|
||||
private:
|
||||
QJsonObject codepoints;
|
||||
QVariantMap codepoints;
|
||||
QFont font;
|
||||
};
|
||||
|
||||
|
@ -1,55 +1,52 @@
|
||||
#include "icons.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVariant>
|
||||
#include <QQmlEngine>
|
||||
#include <QFontDatabase>
|
||||
#include <QColor>
|
||||
#include <QVariantHash>
|
||||
|
||||
#include "iconprovider.h"
|
||||
|
||||
namespace BaseUI
|
||||
{
|
||||
|
||||
Icons::Icons(QObject *parent)
|
||||
: QQmlPropertyMap(this, parent)
|
||||
{
|
||||
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
QJSEngine::setObjectOwnership(this, QJSEngine::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)
|
||||
void Icons::registerIcons(QQmlEngine *engine, const QString &fontPath,
|
||||
const QString &fontName, const QVariantMap &codes)
|
||||
{
|
||||
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");
|
||||
if (QFontDatabase::addApplicationFont(fontPath) == -1)
|
||||
qWarning() << "Failed to load font:" << fontPath;
|
||||
|
||||
auto iconProvider = new IconProvider(fontName, codes);
|
||||
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
|
||||
hash.insert(key, QVariant("image://" + iconProviderName + "/" + key));
|
||||
instance->insert(hash);
|
||||
}
|
||||
|
||||
void Icons::registerIcons(QQmlEngine *engine, const QString &fontPath,
|
||||
const QString &fontName, const QString &codesPath)
|
||||
{
|
||||
QString iconProviderName = "baseui_icons";
|
||||
|
||||
if (QFontDatabase::addApplicationFont(fontPath) == -1)
|
||||
qWarning() << "Failed to load font:" << fontPath;
|
||||
|
||||
auto iconProvider = new IconProvider(fontName, codesPath);
|
||||
engine->addImageProvider(iconProviderName, iconProvider);
|
||||
|
||||
QVariantHash hash;
|
||||
for (const QString &key : iconProvider->keys())
|
||||
hash.insert(key, QVariant("image://" + iconProviderName + "/" + key));
|
||||
instance->insert(hash);
|
||||
}
|
||||
|
||||
}
|
||||
|
50
src/icons.h
50
src/icons.h
@ -1,10 +1,13 @@
|
||||
#ifndef ICONS_H
|
||||
#define ICONS_H
|
||||
#ifndef BASEUI_ICONS_H
|
||||
#define BASEUI_ICONS_H
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QQmlPropertyMap>
|
||||
|
||||
class QQmlEngine;
|
||||
class QJSEngine;
|
||||
#include <memory>
|
||||
|
||||
namespace BaseUI
|
||||
{
|
||||
|
||||
class Icons : public QQmlPropertyMap
|
||||
{
|
||||
@ -13,11 +16,13 @@ class Icons : public QQmlPropertyMap
|
||||
public:
|
||||
Icons(QObject *parent = nullptr);
|
||||
|
||||
static Icons *instance();
|
||||
static QObject *singletonProvider(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
||||
inline static std::unique_ptr<Icons> instance;
|
||||
|
||||
static void registerIcons(QQmlEngine *engine, const QString &path);
|
||||
static void registerIcons(QQmlEngine *engine, const QString &fontPath,
|
||||
const QString &fontName, const QVariantMap &codes);
|
||||
|
||||
static void registerIcons(QQmlEngine *engine, const QString &fontPath,
|
||||
const QString &fontName, const QString &codesPath);
|
||||
protected:
|
||||
template <typename DerivedType>
|
||||
explicit Icons(DerivedType *derived, QObject *parent = nullptr)
|
||||
@ -25,4 +30,35 @@ protected:
|
||||
{}
|
||||
};
|
||||
|
||||
struct IconsForeign
|
||||
{
|
||||
Q_GADGET
|
||||
QML_FOREIGN(Icons)
|
||||
QML_SINGLETON
|
||||
QML_NAMED_ELEMENT(Icons)
|
||||
|
||||
public:
|
||||
static Icons *create(QQmlEngine *, QJSEngine *engine)
|
||||
{
|
||||
// The instance has to exist before it is used. We cannot replace it.
|
||||
Q_ASSERT(Icons::instance);
|
||||
|
||||
// The engine has to have the same thread affinity as the singleton.
|
||||
Q_ASSERT(engine->thread() == Icons::instance->thread());
|
||||
|
||||
// There can only be one engine accessing the singleton.
|
||||
if (s_engine)
|
||||
Q_ASSERT(engine == s_engine);
|
||||
else
|
||||
s_engine = engine;
|
||||
|
||||
return Icons::instance.get();
|
||||
}
|
||||
|
||||
private:
|
||||
inline static QJSEngine *s_engine = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
12
src/plugin.h
Normal file
12
src/plugin.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef BASEUI_PLUGIN_H
|
||||
#define BASEUI_PLUGIN_H
|
||||
|
||||
#include <QtQml/QQmlEngineExtensionPlugin>
|
||||
|
||||
class BaseUIPlugin : public QQmlEngineExtensionPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
|
||||
};
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user