diff --git a/source/qml_v2/AdaptiveCardQmlEngine/AdaptiveCardQmlTypes.h b/source/qml_v2/AdaptiveCardQmlEngine/AdaptiveCardQmlTypes.h index 168a931b58..766a526cf1 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/AdaptiveCardQmlTypes.h +++ b/source/qml_v2/AdaptiveCardQmlEngine/AdaptiveCardQmlTypes.h @@ -23,5 +23,9 @@ namespace AdaptiveCardQmlEngine qmlRegisterType(QUrl("qrc:qml/CardConstants.qml"), "AdaptiveCardQmlEngine", 1, 0, "CardConstants"); qmlRegisterType(QUrl("qrc:qml/TextBlockRender.qml"), "AdaptiveCardQmlEngine", 1, 0, "TextBlockRender"); + qmlRegisterType(QUrl("qrc:qml/NumberInputRender.qml"), "AdaptiveCardQmlEngine", 1, 0, "NumberInputRender"); + qmlRegisterType(QUrl("qrc:qml/InputFieldClearIcon.qml"), "AdaptiveCardQmlEngine", 1, 0, "InputFieldClearIcon"); + qmlRegisterType(QUrl("qrc:qml/InputLabel.qml"), "AdaptiveCardQmlEngine", 1, 0, "InputLabel"); + qmlRegisterType(QUrl("qrc:qml/InputErrorMessage.qml"), "AdaptiveCardQmlEngine", 1, 0, "InputErrorMessage"); } } // namespace RendererQml diff --git a/source/qml_v2/AdaptiveCardQmlEngine/CMakeLists.txt b/source/qml_v2/AdaptiveCardQmlEngine/CMakeLists.txt index 82fe7bada8..addc056587 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/CMakeLists.txt +++ b/source/qml_v2/AdaptiveCardQmlEngine/CMakeLists.txt @@ -53,6 +53,7 @@ file(GLOB_RECURSE SOURCES "AdaptiveCardController.cpp" "AdaptiveCardModel.cpp" "CollectionItemModel.cpp" + "NumberInputModel.cpp" "TextBlockModel.cpp" "ImageModel.cpp" @@ -74,10 +75,10 @@ file(GLOB_RECURSE SOURCES "AdaptiveCardModel.h" "CollectionItemModel.h" - "TextBlockModel.h" - "ImageModel.h" "TextBlockModel.h" "RichTextBlockModel.h" + "NumberInputModel.h" + "ImageModel.h" ) # Setup Library diff --git a/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.cpp b/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.cpp index f570d92bc0..5bfdab2578 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.cpp +++ b/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.cpp @@ -2,6 +2,7 @@ #include "TextBlockModel.h" #include "ImageModel.h" #include "RichTextBlockModel.h" +#include "NumberInputModel.h" #include "AdaptiveCardEnums.h" CollectionItemModel::CollectionItemModel(std::vector> elements, QObject* parent) @@ -43,6 +44,7 @@ QHash CollectionItemModel::roleNames() const cardListModel[TextBlockRole] = "textBlockRole"; cardListModel[ImageRole] = "imageRole"; cardListModel[RichTextBlockRole] = "richTextBlockRole"; + cardListModel[NumberInputRole] = "numberInputRole"; cardListModel[FillHeightRole] = "fillHeightRole"; return cardListModel; @@ -64,6 +66,10 @@ void CollectionItemModel::populateRowData(std::shared_ptr(element), rowContent); break; + case AdaptiveCards::CardElementType::NumberInput: + populateNumberInputModel(std::dynamic_pointer_cast(element), rowContent); + break; + default: break; } @@ -86,3 +92,9 @@ void CollectionItemModel::populateRichTextBlockModel(std::shared_ptr numberInput, RowContent& rowContent) +{ + rowContent[CollectionModelRole::DelegateType] = QVariant::fromValue(AdaptiveCardEnums::CardElementType::NumberInput); + rowContent[CollectionModelRole::NumberInputRole] = QVariant::fromValue(new NumberInputModel(numberInput, nullptr)); +} diff --git a/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.h b/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.h index eb71dfaa2c..b310d6785c 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.h +++ b/source/qml_v2/AdaptiveCardQmlEngine/models/CollectionItemModel.h @@ -10,11 +10,13 @@ class TextBlockModel; class ImageModel; #include "RichTextBlock.h" +#include "NumberInput.h" #include "Enums.h" class TextBlockModel; class RichTextBlockModel; +class NumberInputModel; class CollectionItemModel : public QAbstractListModel { @@ -26,6 +28,7 @@ class CollectionItemModel : public QAbstractListModel TextBlockRole, ImageRole, RichTextBlockRole, + NumberInputRole, FillHeightRole }; @@ -48,4 +51,5 @@ class CollectionItemModel : public QAbstractListModel void populateTextBlockModel(std::shared_ptr textBlock, RowContent& rowContent); void populateImageModel(std::shared_ptr image, RowContent& rowContent); void populateRichTextBlockModel(std::shared_ptr rightTextBlock, RowContent& rowContent); + void populateNumberInputModel(std::shared_ptr numberInput, RowContent& rowContent); }; diff --git a/source/qml_v2/AdaptiveCardQmlEngine/models/NumberInputModel.cpp b/source/qml_v2/AdaptiveCardQmlEngine/models/NumberInputModel.cpp new file mode 100644 index 0000000000..e9d6fabf81 --- /dev/null +++ b/source/qml_v2/AdaptiveCardQmlEngine/models/NumberInputModel.cpp @@ -0,0 +1,13 @@ +#include "NumberInputModel.h" +#include "SharedAdaptiveCard.h" +#include +#include "Utils.h" +#include "MarkDownParser.h" + +NumberInputModel::NumberInputModel(std::shared_ptr numberInput, QObject* parent) : + QObject(parent) +{ +} +NumberInputModel::~NumberInputModel() +{ +} diff --git a/source/qml_v2/AdaptiveCardQmlEngine/models/NumberInputModel.h b/source/qml_v2/AdaptiveCardQmlEngine/models/NumberInputModel.h new file mode 100644 index 0000000000..329e2d4214 --- /dev/null +++ b/source/qml_v2/AdaptiveCardQmlEngine/models/NumberInputModel.h @@ -0,0 +1,21 @@ +#pragma once +#include "AdaptiveCardContext.h" +#include "NumberInput.h" +#include "OpenUrlAction.h" +#include "ToggleVisibilityAction.h" +#include "SubmitAction.h" + +#include +#include +#include +#include + +class NumberInputModel : public QObject +{ + Q_OBJECT + +public: + explicit NumberInputModel(std::shared_ptr numberInput, QObject* parent = nullptr); + ~NumberInputModel(); + +}; diff --git a/source/qml_v2/AdaptiveCardQmlEngine/qml/CardConstants.qml b/source/qml_v2/AdaptiveCardQmlEngine/qml/CardConstants.qml index 720a6bd9e4..661ac845c7 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/qml/CardConstants.qml +++ b/source/qml_v2/AdaptiveCardQmlEngine/qml/CardConstants.qml @@ -1,6 +1,6 @@ -import "AdaptiveCardUtils.js" as AdaptiveCardUtils +import "JSUtils/AdaptiveCardUtils.js" as AdaptiveCardUtils import QtQuick 2.15 -pragma Singleton +//pragma Singleton QtObject { property bool isDarkTheme: false diff --git a/source/qml_v2/AdaptiveCardQmlEngine/qml/CollectionItemDelegate.qml b/source/qml_v2/AdaptiveCardQmlEngine/qml/CollectionItemDelegate.qml index 99c2165994..5ad8ee71b6 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/qml/CollectionItemDelegate.qml +++ b/source/qml_v2/AdaptiveCardQmlEngine/qml/CollectionItemDelegate.qml @@ -10,9 +10,11 @@ Loader { Component.onCompleted :{ if (model.delegateType == AdaptiveCardEnums.CardElementType.TextBlock) source = "TextBlockRender.qml" - else if (model.delegateType == AdaptiveCardEnums.CardElementType.Image) - source = "ImageRender.qml" else if (model.delegateType == AdaptiveCardEnums.CardElementType.RichTextBlock) source = "RichTextBlockRender.qml"; - } + else if (model.delegateType == AdaptiveCardEnums.CardElementType.Image) + source = "ImageRender.qml" + else if (model.delegateType == AdaptiveCardEnums.CardElementType.NumberInput) + source = "NumberInputRender.qml"; + } } diff --git a/source/qml_v2/AdaptiveCardQmlEngine/qml/InputErrorMessage.qml b/source/qml_v2/AdaptiveCardQmlEngine/qml/InputErrorMessage.qml new file mode 100644 index 0000000000..9dd4b9a45f --- /dev/null +++ b/source/qml_v2/AdaptiveCardQmlEngine/qml/InputErrorMessage.qml @@ -0,0 +1,52 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.15 +import QtQuick.Controls 2.15 +import AdaptiveCardQmlEngine 1.0 +import "JSUtils/AdaptiveCardUtils.js" as AdaptiveCardUtils + +Rectangle { + id: errorMessage + + property string isErrorMessage + + width: parent.width + height: errorMessageLabel.implicitHeight + color: 'transparent' + + Button { + id: errorIcon + + width: cardConst.inputFieldConstants.errorIconWidth + anchors.left: parent.left + anchors.leftMargin: cardConst.inputFieldConstants.errorIconLeftMargin + anchors.topMargin: cardConst.inputFieldConstants.errorIconTopMargin + horizontalPadding: 0 + verticalPadding: 0 + icon.width: cardConst.inputFieldConstants.errorIconWidth + icon.height: cardConst.inputFieldConstants.errorIconHeight + icon.color: cardConst.toggleButtonConstants.errorMessageColor + anchors.top: parent.top + icon.source: cardConst.errorIcon + enabled: false + + background: Rectangle { + color: 'transparent' + } + + } + + Label { + id: errorMessageLabel + + wrapMode: Text.Wrap + font.pixelSize: cardConst.inputFieldConstants.labelPixelSize + Accessible.ignored: true + color: cardConst.toggleButtonConstants.errorMessageColor + anchors.left: errorIcon.right + anchors.leftMargin: cardConst.inputFieldConstants.errorIconLeftMargin + anchors.right: parent.right + text: isErrorMessage + } + +} diff --git a/source/qml_v2/AdaptiveCardQmlEngine/qml/InputFieldClearIcon.qml b/source/qml_v2/AdaptiveCardQmlEngine/qml/InputFieldClearIcon.qml new file mode 100644 index 0000000000..0f1f0732b7 --- /dev/null +++ b/source/qml_v2/AdaptiveCardQmlEngine/qml/InputFieldClearIcon.qml @@ -0,0 +1,31 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.15 +import QtQuick.Controls 2.15 +import AdaptiveCardQmlEngine 1.0 +import "JSUtils/AdaptiveCardUtils.js" as AdaptiveCardUtils + +Button { + id: inputFieldClearIcon + + width: cardConst.inputFieldConstants.clearIconSize + horizontalPadding: 0 + verticalPadding: 0 + icon.width: cardConst.inputFieldConstants.clearIconSize + icon.height: cardConst.inputFieldConstants.clearIconSize + icon.color: cardConst.inputFieldConstants.clearIconColorNormal + icon.source: cardConst.clearIconImage + Accessible.name: qsTr("Clear Input") + Accessible.role: Accessible.Button + + background: Rectangle { + color: 'transparent' + radius: cardConst.inputFieldConstants.borderRadius + + WCustomFocusItem { + isRectangle: true + visible: inputFieldClearIcon.activeFocus + designatedParent: parent + } + } +} diff --git a/source/qml_v2/AdaptiveCardQmlEngine/qml/InputLabel.qml b/source/qml_v2/AdaptiveCardQmlEngine/qml/InputLabel.qml new file mode 100644 index 0000000000..57eb41dba3 --- /dev/null +++ b/source/qml_v2/AdaptiveCardQmlEngine/qml/InputLabel.qml @@ -0,0 +1,19 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.15 +import QtQuick.Controls 2.15 +import AdaptiveCardQmlEngine 1.0 +import "JSUtils/AdaptiveCardUtils.js" as AdaptiveCardUtils +Label { + id: inputLabel + + property bool required + property string label + + wrapMode: Text.Wrap + width: parent.width + color: cardConst.toggleButtonConstants.textColor + font.pixelSize: cardConst.inputFieldConstants.labelPixelSize + Accessible.ignored: true + text: required ? AdaptiveCardUtils.escapeHtml(label) + " " + "*" : AdaptiveCardUtils.escapeHtml(label) +} diff --git a/source/qml_v2/AdaptiveCardQmlEngine/qml/NumberInputRender.qml b/source/qml_v2/AdaptiveCardQmlEngine/qml/NumberInputRender.qml new file mode 100644 index 0000000000..72c2184b8a --- /dev/null +++ b/source/qml_v2/AdaptiveCardQmlEngine/qml/NumberInputRender.qml @@ -0,0 +1,201 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.15 +import QtQuick.Controls 2.15 +import AdaptiveCardQmlEngine 1.0 +import "JSUtils/AdaptiveCardUtils.js" as AdaptiveCardUtils +Column { + id: numberInput + + CardConstants{ + + id:cardConst + } + + width: parent.width + spacing: cardConst.inputFieldConstants.columnSpacing + + InputLabel { + id: numberInputLabel + + label: "This is the label " + required: true + visible: label + } + + Row { + id: numberInputRow + + width: parent.width + height: cardConst.inputFieldConstants.height + + Rectangle { + id: numberInputRectangle + + border.width: cardConst.inputFieldConstants.borderWidth + border.color: true ? cardConst.inputFieldConstants.borderColorOnError : cardConst.inputFieldConstants.borderColorNormal + radius: cardConst.inputFieldConstants.borderRadius + height: parent.height + color: numberInputSpinBox.Opressed ? cardConst.inputFieldConstants.backgroundColorOnPressed : numberInputSpinBox.hovered ? cardConst.inputFieldConstants.backgroundColorOnHovered : cardConst.inputFieldConstants.backgroundColorNormal + width: parent.width - numberInputArrowRectangle.width + WCustomFocusItem { + isRectangle: true + visible: numberInputTextField.activeFocus + } + + SpinBox { + id: numberInputSpinBox + + width: parent.width - numberInputClearIcon.width - cardConst.inputFieldConstants.clearIconHorizontalPadding + padding: 0 + editable: true + stepSize: 1 + Accessible.ignored: true + + contentItem: TextField { + id: numberInputTextField + + font.pixelSize: cardConst.inputFieldConstants.pixelSize + anchors.left: parent.left + anchors.right: parent.right + selectByMouse: true + selectedTextColor: 'white' + readOnly: !numberInputSpinBox.editable + validator: numberInputSpinBox.validator + inputMethodHints: Qt.ImhFormattedNumbersOnly + onPressed: { + numberInputRectangle.colorChange(true); + event.accepted = true; + } + onReleased: { + numberInputRectangle.colorChange(false); + forceActiveFocus(); + event.accepted = true; + } + onHoveredChanged: numberInputRectangle.colorChange(false) + onActiveFocusChanged: { + numberInputRectangle.colorChange(false); + Accessible.name = getAccessibleName(); + } + + leftPadding: cardConst.inputFieldConstants.textHorizontalPadding + rightPadding: cardConst.inputFieldConstants.textHorizontalPadding + topPadding: cardConst.inputFieldConstants.textVerticalPadding + bottomPadding: cardConst.inputFieldConstants.textVerticalPadding + placeholderText: "Enter a number" + Accessible.role: Accessible.EditableText + color: cardConst.inputFieldConstants.textColor + placeholderTextColor: cardConst.inputFieldConstants.placeHolderColor + + background: Rectangle { + id: numberInputTextFieldBg + + color: 'transparent' + } + + } + + background: Rectangle { + id: numberSpinBoxBg + + color: 'transparent' + } + + up.indicator: Rectangle { + color: 'transparent' + z: -1 + } + + down.indicator: Rectangle { + color: 'transparent' + z: -1 + } + + validator: DoubleValidator { + } + } + + InputFieldClearIcon { + id: numberInputClearIcon + + Keys.onReturnPressed: onClicked() + visible: numberInputTextField.length !== 0 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: cardConst.inputFieldConstants.clearIconHorizontalPadding + onClicked: { + nextItemInFocusChain().forceActiveFocus(); + numberInputSpinBox.value = numberInputSpinBox.from; + numberInputTextField.clear(); + } + } + } + + Rectangle { + id: numberInputArrowRectangle + + property string accessibilityPrefix: '' + + width: cardConst.inputNumberConstants.upDownButtonWidth + radius: cardConst.inputFieldConstants.borderRadius + height: parent.height + border.color: cardConst.inputFieldConstants.borderColorNormal + activeFocusOnTab: true + color:(numberInputArrowIcon.pressed || activeFocus) ? cardConst.inputFieldConstants.backgroundColorOnPressed : numberInputArrowIcon.hovered ? cardConst.inputFieldConstants.backgroundColorOnHovered : cardConst.inputFieldConstants.backgroundColorNormal + + onActiveFocusChanged: { + if (activeFocus) + accessibilityPrefix = qsTr("Use up arrow to increase the value and down arrow to decrease the value") + (numberInputTextField.text ? ", Current number is " : ""); + + } + Accessible.name: accessibilityPrefix + numberInputTextField.displayText + Accessible.role: Accessible.NoRole + + Button { + id: numberInputArrowIcon + + width: parent.width + anchors.right: parent.right + horizontalPadding: cardConst.inputFieldConstants.iconPadding + verticalPadding: cardConst.inputFieldConstants.iconPadding + icon.width: cardConst.numberInputConstants.upDownIconSize + icon.height: cardConst.numberInputConstants.upDownIconSize + focusPolicy: Qt.NoFocus + icon.color: cardConst.inputNumberConstants.upDownIconColor + height: parent.height + icon.source: cardConst.numberInputUpDownArrowImage + + background: Rectangle { + color: 'transparent' + } + } + + MouseArea { + id: numberInputSpinBoxUpIndicatorArea + + width: parent.width + height: parent.height / 2 + anchors.top: parent.top + } + + MouseArea { + id: numberInputSpinBoxDownIndicatorArea + + width: parent.width + height: parent.height / 2 + anchors.top: numberInputSpinBoxUpIndicatorArea.bottom + } + + WCustomFocusItem { + isRectangle: true + } + } + } + + InputErrorMessage { + id: numberInputErrorMessage + + isErrorMessage:"errorMessage" + visible: true + } +} \ No newline at end of file diff --git a/source/qml_v2/AdaptiveCardQmlEngine/qml/WCustomFocusItem.qml b/source/qml_v2/AdaptiveCardQmlEngine/qml/WCustomFocusItem.qml index b353063c98..890382c896 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/qml/WCustomFocusItem.qml +++ b/source/qml_v2/AdaptiveCardQmlEngine/qml/WCustomFocusItem.qml @@ -93,12 +93,12 @@ Item { Canvas { id: canvas - readonly property var focusOutlineMiddleColor: CardConstants.cardConstants.focusOutlineMiddleColor - readonly property var focusOutlineOuterColor: CardConstants.cardConstants.focusOutlineOuterColor - readonly property var focusOutlineInnerColor: CardConstants.cardConstants.focusOutlineInnerColor + readonly property var focusOutlineMiddleColor: cardConst.cardConst.focusOutlineMiddleColor + readonly property var focusOutlineOuterColor: cardConst.cardConst.focusOutlineOuterColor + readonly property var focusOutlineInnerColor: cardConst.cardConst.focusOutlineInnerColor readonly property int lineThickness: 2 readonly property int outerLineThickness: 1 - readonly property int smallRadius: 4 + readonly property int smallRadius: 5 readonly property int extraSmallRadius: 2 readonly property bool isRTLEnabled: Qt.application.layoutDirection === Qt.RightToLeft diff --git a/source/qml_v2/AdaptiveCardQmlEngine/resourceEngine.qrc b/source/qml_v2/AdaptiveCardQmlEngine/resourceEngine.qrc index c23bc8b2b0..4cfeb92e46 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/resourceEngine.qrc +++ b/source/qml_v2/AdaptiveCardQmlEngine/resourceEngine.qrc @@ -16,5 +16,9 @@ images/3.jpg images/4.jpg qml/RichTextBlockRender.qml + qml/NumberInputRender.qml + qml/InputLabel.qml + qml/InputFieldClearIcon.qml + qml/InputErrorMessage.qml diff --git a/source/qml_v2/AdaptiveCardQmlEngine/utils/AdaptiveCardEnums.h b/source/qml_v2/AdaptiveCardQmlEngine/utils/AdaptiveCardEnums.h index 89e4a43806..688c102a88 100644 --- a/source/qml_v2/AdaptiveCardQmlEngine/utils/AdaptiveCardEnums.h +++ b/source/qml_v2/AdaptiveCardQmlEngine/utils/AdaptiveCardEnums.h @@ -10,6 +10,7 @@ namespace AdaptiveCardEnums TextBlock, Image, RichTextBlock, + NumberInput, Container, Column, ColumnSet, diff --git a/source/qml_v2/AdaptiveCardVisualizer/JSONSamples/numberInput.json b/source/qml_v2/AdaptiveCardVisualizer/JSONSamples/numberInput.json new file mode 100644 index 0000000000..0286aca842 --- /dev/null +++ b/source/qml_v2/AdaptiveCardVisualizer/JSONSamples/numberInput.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "TextBlock", + "text": "Default text input" + }, + { + "type": "Input.Text", + "id": "defaultInputId", + "placeholder": "enter comment", + "maxLength": 500 + }, + { + "type": "TextBlock", + "text": "Multiline text input" + }, + { + "type": "Input.Text", + "id": "multilineInputId", + "placeholder": "enter comment", + "maxLength": 50, + "isMultiline": true + }, + { + "type": "TextBlock", + "text": "Input Number" + }, + { + "type": "Input.Number", + "id": "number", + "placeholder": "Enter a number", + "min": 1, + "max": 10, + "value": 3 + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] +} diff --git a/source/qml_v2/AdaptiveCardVisualizer/SampleCardListModel.cpp b/source/qml_v2/AdaptiveCardVisualizer/SampleCardListModel.cpp index 392880625b..d736b16ce5 100644 --- a/source/qml_v2/AdaptiveCardVisualizer/SampleCardListModel.cpp +++ b/source/qml_v2/AdaptiveCardVisualizer/SampleCardListModel.cpp @@ -56,8 +56,8 @@ void SampleCardListModel::populateCardDisplayNames() mCardDisplayNames.insert("textBlock.json", "TextBlock"); mCardDisplayNames.insert("richTextBlock.json", "Rich text"); mCardDisplayNames.insert("inputText.json", "Input text"); + mCardDisplayNames.insert("numberInput.json", "Input Number"); mCardDisplayNames.insert("actionInline.json", "Action Inline"); - mCardDisplayNames.insert("inputNumber.json", "Input number"); mCardDisplayNames.insert("dateInput.json", "Input date"); mCardDisplayNames.insert("checkBoxInput.json", "Input Toggle"); mCardDisplayNames.insert("choiceSetInput.json", "Input ChoiceSet");