Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e541370c96 | ||
|
|
6dc6ebb2fb | ||
|
|
2d031244ca | ||
|
|
9498be05ef | ||
|
|
1b25ddd2b5 | ||
|
|
9741f98b70 | ||
|
|
02923f7947 | ||
|
|
6c5957a492 | ||
|
|
eb96e5e08d | ||
|
|
457e5fe4af | ||
|
|
1c0d4b0133 | ||
|
|
ba79f62fda | ||
|
|
a9c79b0359 | ||
|
|
a668afef35 | ||
|
|
deb8b38b1f | ||
|
|
b4ebc520e1 | ||
|
|
5def2102c7 | ||
|
|
0f511ebc60 | ||
|
|
b09210cf1f | ||
|
|
3bdfc3e528 | ||
|
|
baf5948c90 | ||
|
|
4b1516c990 | ||
|
|
1a5990f24b | ||
|
|
82e1845056 | ||
|
|
75a76afcf1 | ||
|
|
0922cf507a | ||
|
|
32c5f7f8df | ||
|
|
148505ba6b | ||
|
|
d960c6dcec | ||
|
|
bb9f08155f | ||
|
|
934db52126 | ||
|
|
dcef08f3b3 | ||
|
|
20e7b345e1 | ||
|
|
5f8453659d | ||
|
|
c7ff011649 | ||
|
|
2b71ccf50f | ||
|
|
310be728a3 | ||
|
|
e2728285e1 | ||
|
|
1d9abf8e95 | ||
|
|
31e90c2d47 | ||
|
|
ae1e72fb23 | ||
|
|
7710470559 | ||
|
|
2069399979 | ||
|
|
29dd4da2e4 | ||
|
|
394040a039 | ||
|
|
a8e1ed863c | ||
|
|
d7b1ae9fbc | ||
|
|
f0c7d950ea | ||
|
|
1e5d389b53 | ||
|
|
1cda85fc0e | ||
|
|
4999368dfe | ||
|
|
7e4d69be05 | ||
|
|
3abbd3d4cd | ||
|
|
8fc5276090 | ||
|
|
cf4b092112 | ||
|
|
ddf4a8d4f1 | ||
|
|
9c1edc80db | ||
|
|
20bf5b07a6 | ||
|
|
ade6bd2d0d | ||
|
|
125760b934 | ||
|
|
e231c0a6cb | ||
|
|
a16bc6bb6e | ||
|
|
51989c0f5d | ||
|
|
88b7f6eab8 | ||
|
|
2df3c0a03e | ||
|
|
1e9cc07e2d | ||
|
|
6221c0a540 | ||
|
|
ddbffa7f28 | ||
|
|
555ad8bea8 | ||
|
|
a561a3fff8 | ||
|
|
70ec009edd | ||
|
|
31a1c60eb1 | ||
|
|
0a2cf6ab6e | ||
|
|
07fa2a1b0c | ||
|
|
43b8e43303 | ||
|
|
2cec51ff8d | ||
|
|
684f45cc08 | ||
|
|
eae4e0f405 | ||
|
|
b2ac2794bb | ||
|
|
b565a32c7e | ||
|
|
a1835164d9 | ||
|
|
06e931d749 |
@@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
project(StatusQ)
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -22,6 +22,7 @@
|
||||
\li \l{StatusDescriptionListItem}
|
||||
\li \l{StatusLetterIdenticon}
|
||||
\li \l{StatusListItem}
|
||||
\li \l{StatusListPicker}
|
||||
\li \l{StatusListSectionHeadline}
|
||||
\li \l{StatusLoadingIndicator}
|
||||
\li \l{StatusMemberListItem}
|
||||
@@ -37,5 +38,6 @@
|
||||
\li \l{StatusMessageDetails}
|
||||
\li \l{StatusTagSelector}
|
||||
\li \l{StatusToastMessage}
|
||||
\li \l{StatusWizardStepper}
|
||||
\endlist
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
\li \l{StatusBaseInput}
|
||||
\li \l{StatusIdenticonRing}
|
||||
\li \l{StatusInput}
|
||||
\li \l{StatusItemPicker}
|
||||
\li \l{StatusPickerButton}
|
||||
\li \l{StatusPinInput}
|
||||
\li \l{StatusProgressBar}
|
||||
@@ -43,5 +44,6 @@
|
||||
\li \l{StatusWalletColorSelect}
|
||||
\li \l{StatusRegularExpressionValidator}
|
||||
\li \l{StatusIntValidator}
|
||||
\li \l{StatusSeedPhraseInput}
|
||||
\endlist
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,8 @@ Item {
|
||||
id: themeSwitchWrapper
|
||||
signal checkedChanged()
|
||||
|
||||
property alias lightThemeEnabled: switchControl.checked
|
||||
|
||||
width: themeSwitch.width
|
||||
height: themeSwitch.height
|
||||
|
||||
@@ -26,6 +28,7 @@ Item {
|
||||
}
|
||||
|
||||
StatusSwitch {
|
||||
id: switchControl
|
||||
onCheckedChanged: themeSwitchWrapper.checkedChanged()
|
||||
|
||||
StatusToolTip {
|
||||
|
||||
@@ -26,7 +26,7 @@ GridLayout {
|
||||
badge.value: 1
|
||||
}
|
||||
StatusNavigationListItem {
|
||||
title: "Menu Item (selected)"
|
||||
title: "Menu Item (selected) with very long text"
|
||||
selected: true
|
||||
icon.name: "info"
|
||||
badge.value: 1
|
||||
@@ -420,10 +420,16 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.Untrustworthy
|
||||
}
|
||||
|
||||
StatusMemberListItem {
|
||||
nickName: "very-long-annoying-nickname.eth"
|
||||
isOnline: false
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.Untrustworthy
|
||||
}
|
||||
|
||||
StatusMemberListItem {
|
||||
nickName: "This girl I know from work"
|
||||
userName: "annabelle"
|
||||
isOnline: true
|
||||
isOnline: true
|
||||
}
|
||||
|
||||
StatusMemberListItem {
|
||||
|
||||
@@ -4,38 +4,58 @@ import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
GridLayout {
|
||||
columns: 6
|
||||
columnSpacing: 5
|
||||
rowSpacing: 5
|
||||
ColumnLayout {
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
StatusLoadingIndicator {
|
||||
color: Theme.palette.directColor4
|
||||
columns: 6
|
||||
columnSpacing: 5
|
||||
rowSpacing: 5
|
||||
|
||||
StatusLoadingIndicator {
|
||||
color: Theme.palette.directColor4
|
||||
}
|
||||
|
||||
StatusLetterIdenticon {
|
||||
name: "#status"
|
||||
}
|
||||
|
||||
StatusRoundedImage {
|
||||
image.source: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
|
||||
}
|
||||
|
||||
StatusBadge {}
|
||||
|
||||
StatusBadge {
|
||||
value: 1
|
||||
}
|
||||
|
||||
StatusBadge {
|
||||
value: 10
|
||||
}
|
||||
|
||||
StatusBadge {
|
||||
value: 100
|
||||
}
|
||||
|
||||
StatusRoundIcon {
|
||||
icon.name: "info"
|
||||
}
|
||||
}
|
||||
|
||||
StatusLetterIdenticon {
|
||||
name: "#status"
|
||||
}
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
StatusRoundedImage {
|
||||
image.source: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
|
||||
}
|
||||
|
||||
StatusBadge {}
|
||||
|
||||
StatusBadge {
|
||||
value: 1
|
||||
}
|
||||
|
||||
StatusBadge {
|
||||
value: 10
|
||||
}
|
||||
|
||||
StatusBadge {
|
||||
value: 100
|
||||
}
|
||||
|
||||
StatusRoundIcon {
|
||||
icon.name: "info"
|
||||
Repeater {
|
||||
model: 12
|
||||
StatusLetterIdenticon {
|
||||
name: "A"
|
||||
color: Theme.palette.userCustomizationColors[index]
|
||||
letterSize: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,10 @@ Page {
|
||||
onTextChanged: {
|
||||
sortModel(root.contactsModel);
|
||||
}
|
||||
Component.onCompleted: { sortModel(root.contactsModel); }
|
||||
Component.onCompleted: {
|
||||
textEdit.forceActiveFocus();
|
||||
sortModel(root.contactsModel);
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
|
||||
@@ -41,7 +41,7 @@ StatusAppThreePanelLayout {
|
||||
Layout.leftMargin: 17
|
||||
implicitHeight: 36
|
||||
topPadding: 8
|
||||
bottomPadding: 0
|
||||
bottomPadding: 8
|
||||
placeholderText: "Search"
|
||||
icon.name: "search"
|
||||
}
|
||||
@@ -74,6 +74,7 @@ StatusAppThreePanelLayout {
|
||||
StatusChatList {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: Models.demoChatListItems
|
||||
highlightItem: !root.createChat
|
||||
onChatItemUnmuted: {
|
||||
for (var i = 0; i < Models.demoChatListItems.count; i++) {
|
||||
let item = Models.demoChatListItems.get(i);
|
||||
@@ -128,19 +129,18 @@ StatusAppThreePanelLayout {
|
||||
centerPanel: Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.createChat ? createChatView : chatChannelView
|
||||
}
|
||||
|
||||
Component {
|
||||
id: createChatView
|
||||
CreateChatView {
|
||||
contactsModel: Models.dummyContactsModel
|
||||
Component {
|
||||
id: createChatView
|
||||
CreateChatView {
|
||||
contactsModel: Models.dummyContactsModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: chatChannelView
|
||||
ChatChannelView {
|
||||
model: Models.chatMessagesModel
|
||||
Component {
|
||||
id: chatChannelView
|
||||
ChatChannelView {
|
||||
model: Models.chatMessagesModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ QtObject {
|
||||
itemId: "x012340000"
|
||||
name: "#status"
|
||||
icon: ""
|
||||
emoji: ""
|
||||
isIdenticon: false
|
||||
colorHash: []
|
||||
color: "blue"
|
||||
description: ""
|
||||
type: StatusChatListItem.Type.PublicChat
|
||||
@@ -78,7 +80,9 @@ QtObject {
|
||||
itemId: "x012340001"
|
||||
name: "status-desktop"
|
||||
icon: ""
|
||||
emoji: ""
|
||||
isIdenticon: false
|
||||
colorHash: []
|
||||
color: "red"
|
||||
description: ""
|
||||
type: StatusChatListItem.Type.PublicChat
|
||||
@@ -95,7 +99,9 @@ QtObject {
|
||||
name: "Amazing Funny Squirrel"
|
||||
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0Bh
|
||||
CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
|
||||
emoji: ""
|
||||
isIdenticon: true
|
||||
colorHash: []
|
||||
color: "green"
|
||||
description: ""
|
||||
type: StatusChatListItem.Type.OneToOneChat
|
||||
@@ -111,7 +117,9 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
itemId: "x012340003"
|
||||
name: "Black Ops"
|
||||
icon: ""
|
||||
emoji: ""
|
||||
isIdenticon: false
|
||||
colorHash: []
|
||||
color: "purple"
|
||||
description: ""
|
||||
type: StatusChatListItem.Type.OneToOneChat
|
||||
@@ -127,7 +135,9 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
itemId: "x012340004"
|
||||
name: "Spectacular Growing Otter"
|
||||
icon: ""
|
||||
emoji: ""
|
||||
isIdenticon: false
|
||||
colorHash: []
|
||||
color: "orange"
|
||||
description: ""
|
||||
type: StatusChatListItem.Type.OneToOneChat
|
||||
@@ -143,7 +153,9 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
itemId: "x012340005"
|
||||
name: "channel-with-a-super-duper-long-name"
|
||||
icon: ""
|
||||
emoji: ""
|
||||
isIdenticon: false
|
||||
colorHash: []
|
||||
color: "green"
|
||||
description: ""
|
||||
type: StatusChatListItem.Type.PublicChat
|
||||
@@ -397,7 +409,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: ""
|
||||
iconName: "chat"
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
subItems: [
|
||||
ListElement {
|
||||
value: "sub_item_1_1"
|
||||
@@ -405,7 +416,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
|
||||
iconName: ""
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
},
|
||||
ListElement {
|
||||
value: "sub_item_1_2"
|
||||
@@ -413,7 +423,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAlklEQVR4nOzW0QmDQBAG4SSkl7SUQlJGCrElq9F3QdjjVhh/5nv3cFhY9vUIYQiNITSG0BhCExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2ImYgiNITTlTdG1nUZ5a92VITQxITFiJmIIjSE0htAYQrMHAAD//+wwFVpz+yqXAAAAAElFTkSuQmCC"
|
||||
iconName: ""
|
||||
iconColor: ""
|
||||
isIdenticon: true
|
||||
}]}
|
||||
ListElement {
|
||||
value: "item_2"
|
||||
@@ -421,7 +430,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
|
||||
iconName: ""
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
subItems: [
|
||||
ListElement {
|
||||
value: "sub_item_2_1"
|
||||
@@ -429,7 +437,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: ""
|
||||
iconName: "channel"
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
},
|
||||
ListElement {
|
||||
value: "sub_item_2_2"
|
||||
@@ -437,7 +444,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: ""
|
||||
iconName: "channel"
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
},
|
||||
ListElement {
|
||||
value: "sub_item_2_3"
|
||||
@@ -445,7 +451,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: ""
|
||||
iconName: "channel"
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
}]}
|
||||
ListElement {
|
||||
value: "item_3"
|
||||
@@ -453,7 +458,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: "";
|
||||
iconName: "info"
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
subItems: [
|
||||
ListElement {
|
||||
value: "sub_item_3_1"
|
||||
@@ -461,7 +465,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: ""
|
||||
iconName: "channel"
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
}]}
|
||||
ListElement {
|
||||
value: "item_4"
|
||||
@@ -469,7 +472,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: "";
|
||||
iconName: ""
|
||||
iconColor: "red"
|
||||
isIdenticon: false
|
||||
subItems: [
|
||||
ListElement {
|
||||
value: "sub_item_4_1"
|
||||
@@ -477,7 +479,6 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
imageSource: ""
|
||||
iconName: "channel"
|
||||
iconColor: ""
|
||||
isIdenticon: false
|
||||
}]}
|
||||
}
|
||||
|
||||
@@ -1052,4 +1053,309 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
|
||||
notificationsCount: 0
|
||||
}
|
||||
}
|
||||
|
||||
property ListModel currencyPickerModel: ListModel {
|
||||
ListElement {
|
||||
key: 0
|
||||
name: "United States Dollar"
|
||||
shortName: "USD"
|
||||
symbol: "$"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 1
|
||||
name: "British Pound"
|
||||
shortName: "GBP"
|
||||
symbol: "£"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b5.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 2
|
||||
name: "Euro"
|
||||
shortName: "EUR"
|
||||
symbol: "€"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b6.png"
|
||||
category: ""
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 3
|
||||
name: "Shout Korean Won"
|
||||
shortName: "KRW"
|
||||
symbol: "₩"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 4
|
||||
name: "Ethereum"
|
||||
shortName: "ETH"
|
||||
symbol: "Ξ"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
|
||||
category: "Tokens"
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 5
|
||||
name: "Bitcoin"
|
||||
shortName: "BTC"
|
||||
symbol: "฿"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: "Tokens"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 6
|
||||
name: "Status Network Token"
|
||||
shortName: "SNT"
|
||||
symbol: ""
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
|
||||
category: "Tokens"
|
||||
selected: false
|
||||
}
|
||||
|
||||
ListElement {
|
||||
key: 7
|
||||
name: "Emirati Dirham"
|
||||
shortName: "AED"
|
||||
symbol: "د.إ"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: "Other Fiat"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 8
|
||||
name: "Afghani"
|
||||
shortName: "AFN"
|
||||
symbol: "؋"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
|
||||
category: "Other Fiat"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 9
|
||||
name: "Argentine Peso"
|
||||
shortName: "AFN"
|
||||
symbol: "$"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: "Other Fiat"
|
||||
selected: false
|
||||
}
|
||||
}
|
||||
|
||||
property ListModel currencyPickerModel2: ListModel {
|
||||
ListElement {
|
||||
key: 0
|
||||
name: "United States Dollar"
|
||||
shortName: "USD"
|
||||
symbol: "$"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 1
|
||||
name: "British Pound"
|
||||
shortName: "GBP"
|
||||
symbol: "£"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b5.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 2
|
||||
name: "Euro"
|
||||
shortName: "EUR"
|
||||
symbol: "€"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b6.png"
|
||||
category: ""
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 3
|
||||
name: "Shout Korean Won"
|
||||
shortName: "KRW"
|
||||
symbol: "₩"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 4
|
||||
name: "Ethereum"
|
||||
shortName: "ETH"
|
||||
symbol: "Ξ"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
|
||||
category: "Tokens"
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 5
|
||||
name: "Bitcoin"
|
||||
shortName: "BTC"
|
||||
symbol: "฿"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: "Tokens"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 6
|
||||
name: "Status Network Token"
|
||||
shortName: "SNT"
|
||||
symbol: ""
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b8.png"
|
||||
category: "Tokens"
|
||||
selected: false
|
||||
}
|
||||
|
||||
ListElement {
|
||||
key: 7
|
||||
name: "Emirati Dirham"
|
||||
shortName: "AED"
|
||||
symbol: "د.إ"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: "Other Fiat"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 8
|
||||
name: "Afghani"
|
||||
shortName: "AFN"
|
||||
symbol: "؋"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b7.png"
|
||||
category: "Other Fiat"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 9
|
||||
name: "Argentine Peso"
|
||||
shortName: "AFN"
|
||||
symbol: "$"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: "Other Fiat"
|
||||
selected: false
|
||||
}
|
||||
}
|
||||
|
||||
property ListModel languagePickerModel: ListModel {
|
||||
ListElement {
|
||||
key: 0
|
||||
name: "English"
|
||||
shortName: "English"
|
||||
imageSource: "../../assets/twemoji/26x26/1f1ec-1f1e7.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 1
|
||||
name: "Korean"
|
||||
shortName: "한국어"
|
||||
imageSource: "../../assets/twemoji/26x26/1f1f0-1f1f7.png"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 2
|
||||
name: "Portuguese (Brazilian)"
|
||||
shortName: "Português"
|
||||
imageSource: "../../assets/twemoji/26x26/1f1e7-1f1f7.png"
|
||||
category: ""
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 3
|
||||
name: "Dutch"
|
||||
shortName: "Nederlands"
|
||||
imageSource: "../../assets/twemoji/26x26/1f1f3-1f1f1.png"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 4
|
||||
name: "Indonesian"
|
||||
shortName: "Bahasa Indonesia"
|
||||
imageSource: "../../assets/twemoji/26x26/1f1ee-1f1e9.png"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 5
|
||||
name: "Spanish"
|
||||
shortName: "Español"
|
||||
imageSource: "../../assets/twemoji/26x26/1f1ea-1f1e6.png"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
}
|
||||
|
||||
property ListModel languageNoImagePickerModel: ListModel {
|
||||
ListElement {
|
||||
key: 0
|
||||
name: "Chinese (Mainland China)"
|
||||
shortName: "普通话"
|
||||
category: ""
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 1
|
||||
name: "Russian"
|
||||
shortName: "Русский Язык"
|
||||
category: ""
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 2
|
||||
name: "Arabic"
|
||||
shortName: "اَلْعَرَبِيَّةُ"
|
||||
category: "Beta Languages"
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 3
|
||||
name: "Chinese (Taiwan)"
|
||||
shortName: "臺灣華語"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 4
|
||||
name: "Filipino"
|
||||
shortName: "Wikang Filipino"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 5
|
||||
name: "French"
|
||||
shortName: "Français"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 6
|
||||
name: "Italian"
|
||||
shortName: "Italiano"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 7
|
||||
name: "Turkish"
|
||||
shortName: "Türkçe"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
ListElement {
|
||||
key: 8
|
||||
name: "Urdu"
|
||||
shortName: "اُردُو"
|
||||
category: "Beta Languages"
|
||||
selected: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 190 KiB |
@@ -11,6 +11,11 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
SandboxApp app(argc, argv);
|
||||
|
||||
app.setOrganizationName("Status");
|
||||
app.setOrganizationDomain("status.im");
|
||||
app.setApplicationName("Sandbox");
|
||||
|
||||
app.startEngine();
|
||||
|
||||
return app.exec();
|
||||
|
||||
@@ -2,6 +2,8 @@ import QtQuick 2.14
|
||||
import QtQuick.Window 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtGraphicalEffects 1.13
|
||||
import QtQuick.Layouts 1.14
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import Sandbox 0.1
|
||||
|
||||
@@ -31,10 +33,7 @@ StatusWindow {
|
||||
|
||||
property real factor: 1.0
|
||||
|
||||
Component.onCompleted: {
|
||||
Theme.palette = lightTheme
|
||||
rootWindow.updatePosition();
|
||||
}
|
||||
Component.onCompleted: rootWindow.updatePosition()
|
||||
|
||||
QtObject {
|
||||
id: appSectionType
|
||||
@@ -110,12 +109,10 @@ StatusWindow {
|
||||
anchors.topMargin: 32
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 32
|
||||
onCheckedChanged: {
|
||||
if (Theme.palette === rootWindow.lightTheme) {
|
||||
Theme.palette = rootWindow.darkTheme
|
||||
} else {
|
||||
Theme.palette = rootWindow.lightTheme
|
||||
}
|
||||
lightThemeEnabled: storeSettings.lightTheme
|
||||
onLightThemeEnabledChanged: {
|
||||
Theme.palette = lightThemeEnabled ? rootWindow.darkTheme : rootWindow.lightTheme
|
||||
storeSettings.lightTheme = lightThemeEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,11 +123,13 @@ StatusWindow {
|
||||
StatusAppTwoPanelLayout {
|
||||
id: mainPageView
|
||||
|
||||
function page(name) {
|
||||
function page(name, fillPage) {
|
||||
storeSettings.fillPage = fillPage ? true : false
|
||||
viewLoader.source = Qt.resolvedUrl("./pages/" + name + "Page.qml");
|
||||
}
|
||||
function control(name) {
|
||||
viewLoader.source = Qt.resolvedUrl("./controls/" + name + ".qml");
|
||||
storeSettings.fillPage = false
|
||||
}
|
||||
|
||||
leftPanel: Item {
|
||||
@@ -275,6 +274,11 @@ StatusWindow {
|
||||
selected: viewLoader.source.toString().includes(title)
|
||||
onClicked: mainPageView.page(title);
|
||||
}
|
||||
StatusNavigationListItem {
|
||||
title: "StatusListPicker"
|
||||
selected: viewLoader.source.toString().includes(title)
|
||||
onClicked: mainPageView.page(title);
|
||||
}
|
||||
StatusListSectionHeadline { text: "StatusQ.Popup" }
|
||||
StatusNavigationListItem {
|
||||
title: "StatusPopupMenu"
|
||||
@@ -298,6 +302,11 @@ StatusWindow {
|
||||
selected: viewLoader.source.toString().includes(title)
|
||||
onClicked: mainPageView.page(title);
|
||||
}
|
||||
StatusNavigationListItem {
|
||||
title: "StatusImageCropPanel"
|
||||
selected: viewLoader.source.toString().includes(title)
|
||||
onClicked: mainPageView.page(title, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,8 +314,11 @@ StatusWindow {
|
||||
rightPanel: Item {
|
||||
id: rightPanel
|
||||
anchors.fill: parent
|
||||
|
||||
ScrollView {
|
||||
visible: !storeSettings.fillPage
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 64
|
||||
contentHeight: (pageWrapper.height + pageWrapper.anchors.topMargin) * rootWindow.factor
|
||||
contentWidth: (pageWrapper.width * rootWindow.factor)
|
||||
clip: true
|
||||
@@ -315,14 +327,16 @@ StatusWindow {
|
||||
id: pageWrapper
|
||||
width: rightPanel.width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 64
|
||||
height: Math.max(rootWindow.height, viewLoader.height + 128)
|
||||
scale: rootWindow.factor
|
||||
|
||||
Loader {
|
||||
id: viewLoader
|
||||
active: !storeSettings.fillPage
|
||||
anchors.centerIn: parent
|
||||
source: mainPageView.control("Icons")
|
||||
source: storeSettings.selected.length === 0 ? mainPageView.control("Icons") : storeSettings.selected
|
||||
onSourceChanged: {
|
||||
storeSettings.selected = viewLoader.source
|
||||
if (source.toString().includes("Icons")) {
|
||||
item.iconColor = Theme.palette.primaryColor1;
|
||||
}
|
||||
@@ -330,6 +344,15 @@ StatusWindow {
|
||||
}
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
active: storeSettings.fillPage
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 64
|
||||
visible: storeSettings.fillPage
|
||||
clip: true
|
||||
|
||||
source: viewLoader.source
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,4 +470,11 @@ StatusWindow {
|
||||
rootWindow.toggleFullScreen()
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: storeSettings
|
||||
property string selected: ""
|
||||
property bool lightTheme: true
|
||||
property bool fillPage: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import QtGraphicalEffects 1.14
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: mainLayout.implicitWidth
|
||||
implicitHeight: mainLayout.implicitHeight
|
||||
|
||||
property var testControls: [ctrl1, ctrl2, ctrl3]
|
||||
|
||||
property bool globalStylePreferRound: true
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
Text {
|
||||
text: `AR: ${ctrl1.aspectRatio.toFixed(2)}`
|
||||
}
|
||||
|
||||
StatusImageCropPanel {
|
||||
id: ctrl1
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
source: "qrc:/demoapp/data/logo-test-image.png"
|
||||
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rounded : StatusImageCrop.WindowStyle.Rectangular
|
||||
}
|
||||
Text {
|
||||
text: `AR: ${ctrl2.aspectRatio.toFixed(2)}`
|
||||
}
|
||||
StatusImageCropPanel {
|
||||
id: ctrl2
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
source: "qrc:/demoapp/data/logo-test-image.png"
|
||||
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rectangular : StatusImageCrop.WindowStyle.Rounded
|
||||
aspectRatio: 16/9
|
||||
enableCheckers: true
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
Text {
|
||||
text: `AR: ${ctrl3.aspectRatio.toFixed(2)}`
|
||||
}
|
||||
StatusImageCropPanel {
|
||||
id: ctrl3
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
source: "qrc:/demoapp/data/logo-test-image.png"
|
||||
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rounded : StatusImageCrop.WindowStyle.Rectangular
|
||||
aspectRatio: 0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.ZoomIn
|
||||
onActivated: {
|
||||
for(let i in testControls) {
|
||||
const c = testControls[i]
|
||||
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, -0.05))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.ZoomOut
|
||||
onActivated: {
|
||||
for(let i in testControls) {
|
||||
const c = testControls[i]
|
||||
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, 0.05))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Ctrl+W"]
|
||||
onActivated: globalStylePreferRound = !globalStylePreferRound
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,13 @@ Column {
|
||||
|
||||
Item {
|
||||
implicitWidth: 480
|
||||
implicitHeight: 102
|
||||
implicitHeight: 82
|
||||
z: 100000
|
||||
StatusSeedPhraseInput {
|
||||
id: statusSeedInput
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
textEdit.input.anchors.leftMargin: 16
|
||||
textEdit.input.anchors.rightMargin: 16
|
||||
textEdit.input.anchors.topMargin: 11
|
||||
@@ -88,7 +89,6 @@ Column {
|
||||
label: "Label"
|
||||
charLimit: 30
|
||||
errorMessage: "Error message"
|
||||
|
||||
input.clearable: true
|
||||
input.valid: false
|
||||
input.placeholderText: "Placeholder"
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
import "../demoapp/data" 1.0
|
||||
|
||||
GridLayout {
|
||||
id: root
|
||||
columns: 1
|
||||
rowSpacing: 150
|
||||
|
||||
GridLayout {
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
rows: 4
|
||||
columns: 2
|
||||
rowSpacing: 170
|
||||
columnSpacing: 150
|
||||
z: 100
|
||||
StatusListPicker {
|
||||
id: languagePicker
|
||||
z: 100
|
||||
inputList: Models.languagePickerModel
|
||||
placeholderSearchText: qsTr("Search Languages")
|
||||
menuAlignment: StatusListPicker.MenuAlignment.Left
|
||||
}
|
||||
|
||||
StatusListPicker {
|
||||
id: languagePicker2
|
||||
z: 100
|
||||
inputList: Models.languageNoImagePickerModel
|
||||
placeholderSearchText: qsTr("Search Languages")
|
||||
menuAlignment: StatusListPicker.MenuAlignment.Center
|
||||
}
|
||||
|
||||
StatusListPicker {
|
||||
id: currencyPicker
|
||||
inputList: Models.currencyPickerModel
|
||||
placeholderSearchText: qsTr("Search Currencies")
|
||||
multiSelection: true
|
||||
}
|
||||
|
||||
StatusListPicker {
|
||||
id: currencyPicker2
|
||||
inputList: Models.currencyPickerModel2
|
||||
placeholderSearchText: qsTr("Search Currencies")
|
||||
multiSelection: true
|
||||
printSymbol: true
|
||||
enableSelectableItem: false
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: pageDesc
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
height: 100
|
||||
width: 500
|
||||
text: "4 different configurations for the `StatusListPicker` component:\n
|
||||
* Single selection. \n
|
||||
* Single selection but dynamically changed to multiple selection (model provides multiple selected items).\n
|
||||
* Multiple selection.\n
|
||||
* Multiple selection and displayed name is the symbol + shortName\n"
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
// Outsite area
|
||||
MouseArea {
|
||||
height: root.height
|
||||
width: root.width
|
||||
onClicked: {
|
||||
languagePicker.close()
|
||||
languagePicker2.close()
|
||||
currencyPicker.close()
|
||||
currencyPicker2.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,11 @@ Item {
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
model: [
|
||||
{"title":"anna.eth wants to verify your identity", "subTitle":"Provide the code in the letter I sent to you on February 1st.", "icon":"contact", "loading":false, "type":0,"url":""},
|
||||
{"title":"Verification Request Sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":""},
|
||||
{"title":"Collectible is being minted...", "subTitle":"View on Etherscan", "icon":"", "loading":true, "type":0,"url":"http://google.com"},
|
||||
{"title":"Contact request sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":""}
|
||||
{"title":"anna.eth wants to verify your identity", "subTitle":"Provide the code in the letter I sent to you on February 1st.", "icon":"contact", "loading":false, "type":0,"url":"", "duration":0},
|
||||
{"title":"Verification Request Sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":"", "duration":4000},
|
||||
{"title":"Collectible is being minted...", "subTitle":"View on Etherscan", "icon":"", "loading":true, "type":0,"url":"http://google.com", "duration":0},
|
||||
{"title":"Contact request sent", "subTitle":"", "icon":"checkmark-circle", "loading":false, "type":1,"url":"", "duration":4000},
|
||||
{"title":"Test User", "subTitle":"Hello message...", "icon":"", "loading":false, "type":0,"url":"", "duration":4000}
|
||||
]
|
||||
delegate: StatusToastMessage {
|
||||
primaryText: modelData.title
|
||||
@@ -27,12 +28,12 @@ Item {
|
||||
loading: modelData.loading
|
||||
type: modelData.type
|
||||
linkUrl: modelData.url
|
||||
duration: modelData.duration
|
||||
onLinkActivated: {
|
||||
Qt.openUrlExternally(link);
|
||||
}
|
||||
//simulate open
|
||||
Component.onCompleted: {
|
||||
open = true;
|
||||
onClose: {
|
||||
console.warn("toast closed: ", modelData.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ Column {
|
||||
spacing: 8
|
||||
|
||||
StatusWalletColorSelect {
|
||||
model: Theme.palette.accountColors
|
||||
model: Theme.palette.userCustomizationColors
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<file>pages/StatusTabSwitchPage.qml</file>
|
||||
<file>pages/StatusWalletColorButtonPage.qml</file>
|
||||
<file>pages/StatusWalletColorSelectPage.qml</file>
|
||||
<file>pages/StatusPasswordStrengthIndicatorPage.qml</file>
|
||||
<file>pages/StatusPasswordStrengthIndicatorPage.qml</file>
|
||||
<file>controls/Buttons.qml</file>
|
||||
<file>controls/Controls.qml</file>
|
||||
<file>controls/Icons.qml</file>
|
||||
@@ -24,6 +24,8 @@
|
||||
<file>controls/ListItems.qml</file>
|
||||
<file>controls/Others.qml</file>
|
||||
<file>controls/Popups.qml</file>
|
||||
<file>demoapp/ChatChannelView.qml</file>
|
||||
<file>demoapp/CreateChatView.qml</file>
|
||||
<file>demoapp/DemoCommunityDetailModal.qml</file>
|
||||
<file>demoapp/DemoContactRequestsModal.qml</file>
|
||||
<file>demoapp/StatusAppChatView.qml</file>
|
||||
@@ -31,5 +33,14 @@
|
||||
<file>demoapp/StatusAppProfileSettingsView.qml</file>
|
||||
<file>demoapp/data/Models.qml</file>
|
||||
<file>demoapp/data/qmldir</file>
|
||||
<file>demoapp/data/logo-test-image.png</file>
|
||||
<file>DemoApp.qml</file>
|
||||
<file>ThemeSwitch.qml</file>
|
||||
<file>pages/StatusColorSelectorGridPage.qml</file>
|
||||
<file>pages/StatusListPickerPage.qml</file>
|
||||
<file>pages/StatusPinInputPage.qml</file>
|
||||
<file>pages/StatusTagSelectorPage.qml</file>
|
||||
<file>pages/StatusToastMessagePage.qml</file>
|
||||
<file>pages/StatusWizardStepperPage.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
</RCC>
|
||||
|
||||
@@ -32,7 +32,7 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (statusBadge.value > 99) {
|
||||
return "99+";
|
||||
return qsTr("99+");
|
||||
}
|
||||
return statusBadge.value;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ Column {
|
||||
property string categoryId: ""
|
||||
property var model: null
|
||||
property bool draggableItems: false
|
||||
property bool highlightItem: true
|
||||
|
||||
property alias statusChatListItems: statusChatListItems
|
||||
|
||||
@@ -90,23 +91,25 @@ Column {
|
||||
}
|
||||
|
||||
StatusChatListItem {
|
||||
|
||||
id: statusChatListItem
|
||||
|
||||
opacity: dragSensor.active ? 0.0 : 1.0
|
||||
originalOrder: model.position
|
||||
chatId: model.itemId
|
||||
categoryId: model.parentItemId? model.parentItemId : ""
|
||||
name: model.name
|
||||
icon.emoji: model.emoji
|
||||
type: !!model.type ? model.type : StatusChatListItem.Type.CommunityChat
|
||||
muted: model.muted
|
||||
hasUnreadMessages: model.hasUnreadMessages
|
||||
notificationsCount: model.notificationsCount
|
||||
highlightWhenCreated: !!model.highlight
|
||||
selected: model.active
|
||||
icon.color: model.color
|
||||
image.isIdenticon: model.isIdenticon
|
||||
selected: (model.active && statusChatList.highlightItem)
|
||||
|
||||
icon.emoji: model.emoji
|
||||
icon.color: !!model.color ? model.color : Theme.palette.userCustomizationColors[model.colorId]
|
||||
image.isIdenticon: false
|
||||
image.source: model.icon
|
||||
ringSettings.ringSpecModel: model.colorHash
|
||||
|
||||
sensor.cursorShape: dragSensor.cursorShape
|
||||
onClicked: {
|
||||
|
||||
@@ -14,10 +14,10 @@ Item {
|
||||
implicitWidth: chatListsAndCategories.width
|
||||
|
||||
property StatusTooltipSettings categoryAddButtonToolTip: StatusTooltipSettings {
|
||||
text: "Add channel inside category"
|
||||
text: qsTr("Add channel inside category")
|
||||
}
|
||||
property StatusTooltipSettings categoryMenuButtonToolTip: StatusTooltipSettings {
|
||||
text: "More"
|
||||
text: qsTr("More")
|
||||
}
|
||||
|
||||
property var model: []
|
||||
|
||||
@@ -39,21 +39,21 @@ StatusListItem {
|
||||
id: addButton
|
||||
icon.name: "add"
|
||||
icon.width: 20
|
||||
visible: statusChatListCategoryItem.showAddButton &&
|
||||
visible: statusChatListCategoryItem.showAddButton &&
|
||||
(statusChatListCategoryItem.highlighted ||
|
||||
statusChatListCategoryItem.sensor.containsMouse)
|
||||
onClicked: statusChatListCategoryItem.addButtonClicked(mouse)
|
||||
tooltip.text: "Add channel inside category"
|
||||
tooltip.text: qsTr("Add channel inside category")
|
||||
},
|
||||
StatusChatListCategoryItemButton {
|
||||
id: menuButton
|
||||
icon.name: "more"
|
||||
icon.width: 21
|
||||
visible: statusChatListCategoryItem.showMenuButton &&
|
||||
visible: statusChatListCategoryItem.showMenuButton &&
|
||||
(statusChatListCategoryItem.highlighted ||
|
||||
statusChatListCategoryItem.sensor.containsMouse)
|
||||
onClicked: statusChatListCategoryItem.menuButtonClicked(mouse)
|
||||
tooltip.text: "More"
|
||||
tooltip.text: qsTr("More")
|
||||
},
|
||||
StatusChatListCategoryItemButton {
|
||||
id: toggleButton
|
||||
|
||||
@@ -10,7 +10,7 @@ import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
Rectangle {
|
||||
id: statusChatListItem
|
||||
id: root
|
||||
|
||||
objectName: "chatItem"
|
||||
property int originalOrder: -1
|
||||
@@ -29,11 +29,10 @@ Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
color: Theme.palette.miscColor5
|
||||
letterSize: emoji ? 11 : 15
|
||||
emoji: ""
|
||||
emojiSize: Emoji.size.verySmall
|
||||
charactersLen: 1
|
||||
charactersLen: root.type === StatusChatListItem.Type.OneToOneChat ? 2 : 1
|
||||
}
|
||||
property alias ringSettings: identicon.ringSettings
|
||||
property int type: StatusChatListItem.Type.PublicChat
|
||||
property bool highlighted: false
|
||||
property bool highlightWhenCreated: false
|
||||
@@ -79,16 +78,16 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: statusChatListItem.clicked(mouse)
|
||||
onClicked: root.clicked(mouse)
|
||||
|
||||
StatusSmartIdenticon {
|
||||
id: identicon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
image: statusChatListItem.image
|
||||
icon: statusChatListItem.icon
|
||||
name: statusChatListItem.name
|
||||
image: root.image
|
||||
icon: root.icon
|
||||
name: root.name
|
||||
}
|
||||
|
||||
StatusIcon {
|
||||
@@ -98,21 +97,21 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
width: 14
|
||||
visible: statusChatListItem.type !== StatusChatListItem.Type.OneToOneChat
|
||||
visible: root.type !== StatusChatListItem.Type.OneToOneChat
|
||||
opacity: {
|
||||
if (statusChatListItem.muted && !hoverHander.hovered && !statusChatListItem.highlighted) {
|
||||
if (root.muted && !hoverHander.hovered && !root.highlighted) {
|
||||
return 0.4
|
||||
}
|
||||
return statusChatListItem.hasUnreadMessages ||
|
||||
statusChatListItem.notificationsCount > 0 ||
|
||||
statusChatListItem.selected ||
|
||||
statusChatListItem.highlighted ||
|
||||
return root.hasUnreadMessages ||
|
||||
root.notificationsCount > 0 ||
|
||||
root.selected ||
|
||||
root.highlighted ||
|
||||
statusBadge.visible ||
|
||||
hoverHander.hovered ? 1.0 : 0.7
|
||||
}
|
||||
|
||||
icon: {
|
||||
switch (statusChatListItem.type) {
|
||||
switch (root.type) {
|
||||
case StatusChatListItem.Type.PublicCat:
|
||||
return Theme.palette.name == "light" ? "tiny/public-chat" : "tiny/public-chat-white"
|
||||
break;
|
||||
@@ -137,27 +136,27 @@ Rectangle {
|
||||
anchors.rightMargin: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: (statusChatListItem.type === StatusChatListItem.Type.PublicChat &&
|
||||
!statusChatListItem.name.startsWith("#") ?
|
||||
"#" + statusChatListItem.name :
|
||||
statusChatListItem.name)
|
||||
text: (root.type === StatusChatListItem.Type.PublicChat &&
|
||||
!root.name.startsWith("#") ?
|
||||
"#" + root.name :
|
||||
root.name)
|
||||
elide: Text.ElideRight
|
||||
color: {
|
||||
if (statusChatListItem.muted && !hoverHander.hovered && !statusChatListItem.highlighted) {
|
||||
if (root.muted && !hoverHander.hovered && !root.highlighted) {
|
||||
return Theme.palette.directColor5
|
||||
}
|
||||
return statusChatListItem.hasUnreadMessages ||
|
||||
statusChatListItem.notificationsCount > 0 ||
|
||||
statusChatListItem.selected ||
|
||||
statusChatListItem.highlighted ||
|
||||
statusChatListItem.highlightWhenCreated ||
|
||||
return root.hasUnreadMessages ||
|
||||
root.notificationsCount > 0 ||
|
||||
root.selected ||
|
||||
root.highlighted ||
|
||||
root.highlightWhenCreated ||
|
||||
hoverHander.hovered ||
|
||||
statusBadge.visible ? Theme.palette.directColor1 : Theme.palette.directColor4
|
||||
}
|
||||
font.weight: !statusChatListItem.muted &&
|
||||
(statusChatListItem.hasUnreadMessages ||
|
||||
statusChatListItem.notificationsCount > 0 ||
|
||||
statusChatListItem.highlightWhenCreated ||
|
||||
font.weight: !root.muted &&
|
||||
(root.hasUnreadMessages ||
|
||||
root.notificationsCount > 0 ||
|
||||
root.highlightWhenCreated ||
|
||||
statusBadge.visible) ? Font.Bold : Font.Medium
|
||||
font.pixelSize: 15
|
||||
}
|
||||
@@ -170,18 +169,18 @@ Rectangle {
|
||||
width: 14
|
||||
opacity: mutedIconSensor.containsMouse ? 1.0 : 0.2
|
||||
icon: Theme.palette.name === "light" ? "tiny/muted" : "tiny/muted-white"
|
||||
visible: statusChatListItem.muted
|
||||
visible: root.muted
|
||||
|
||||
MouseArea {
|
||||
id: mutedIconSensor
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: statusChatListItem.unmute()
|
||||
onClicked: root.unmute()
|
||||
}
|
||||
|
||||
StatusToolTip {
|
||||
text: "Unmute"
|
||||
text: qsTr("Unmute")
|
||||
visible: mutedIconSensor.containsMouse
|
||||
}
|
||||
}
|
||||
@@ -193,11 +192,11 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
|
||||
color: statusChatListItem.muted ? Theme.palette.primaryColor2 : Theme.palette.primaryColor1
|
||||
color: root.muted ? Theme.palette.primaryColor2 : Theme.palette.primaryColor1
|
||||
border.width: 4
|
||||
border.color: color
|
||||
value: statusChatListItem.notificationsCount
|
||||
visible: statusChatListItem.notificationsCount > 0
|
||||
value: root.notificationsCount
|
||||
visible: root.notificationsCount > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ Rectangle {
|
||||
onClicked: statusChatToolBar.searchButtonClicked()
|
||||
|
||||
// initializing the tooltip
|
||||
tooltip.text: "Search"
|
||||
tooltip.text: qsTr("Search")
|
||||
tooltip.orientation: StatusToolTip.Orientation.Bottom
|
||||
tooltip.y: parent.height + 12
|
||||
}
|
||||
@@ -72,7 +72,7 @@ Rectangle {
|
||||
onClicked: statusChatToolBar.membersButtonClicked()
|
||||
|
||||
// initializing the tooltip
|
||||
tooltip.text: "Members"
|
||||
tooltip.text: qsTr("Members")
|
||||
tooltip.orientation: StatusToolTip.Orientation.Bottom
|
||||
tooltip.y: parent.height + 12
|
||||
}
|
||||
@@ -87,7 +87,7 @@ Rectangle {
|
||||
|
||||
// initializing the tooltip
|
||||
tooltip.visible: !!tooltip.text && menuButton.hovered && !popupMenuSlot.item.opened
|
||||
tooltip.text: "More"
|
||||
tooltip.text: qsTr("More")
|
||||
tooltip.orientation: StatusToolTip.Orientation.Bottom
|
||||
tooltip.y: parent.height + 12
|
||||
|
||||
@@ -141,7 +141,7 @@ Rectangle {
|
||||
type: StatusFlatRoundButton.Type.Secondary
|
||||
|
||||
// initializing the tooltip
|
||||
tooltip.text: "Activity"
|
||||
tooltip.text: qsTr("Activity")
|
||||
tooltip.orientation: StatusToolTip.Orientation.Bottom
|
||||
tooltip.y: parent.height + 12
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusImageCropPanel
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Components
|
||||
\since StatusQ.Components 0.1
|
||||
\brief Draw a crop-window onto an image and allows manipulating the position of the crop-window. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
Adds mouse pan and zoom functionality on top of StatusImageCrop functionality. Also adds small
|
||||
practical features and optimizes drawing by minimizing StatusImageCrop area
|
||||
|
||||
\sa StatusImageCrop for more details
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
StatusImageCropPanel {
|
||||
width: 400
|
||||
height: 200
|
||||
source: "qrc:/demoapp/data/logo-test-image.png"
|
||||
windowStyle: StatusImageCrop.WindowStyle.Rectangular
|
||||
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
|
||||
}
|
||||
\endqml
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: mainLayout.implicitWidth
|
||||
implicitHeight: mainLayout.implicitHeight
|
||||
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::aspectRatio
|
||||
Initial aspect-ratio of the crop-window
|
||||
*/
|
||||
property real aspectRatio: 1
|
||||
/*!
|
||||
\qmlproperty bool StatusImageCrop::interactive
|
||||
If true allows user to interact with the image. Set to false for previewing existing crop-data
|
||||
*/
|
||||
property bool interactive: true
|
||||
/*!
|
||||
\qmlproperty bool StatusImageCrop::margins
|
||||
Space to keep around the control borders and crop area
|
||||
*/
|
||||
property int margins: 10
|
||||
|
||||
/*!
|
||||
\qmlproperty url StatusImageCropPanel::source
|
||||
\sa StatusImageCrop::source
|
||||
*/
|
||||
/*required*/ property alias source: cropEditor.source
|
||||
|
||||
/*!
|
||||
\qmlproperty WindowStyle StatusImageCropPanel::windowStyle
|
||||
\sa StatusImageCrop::windowStyle
|
||||
*/
|
||||
property alias windowStyle: cropEditor.windowStyle
|
||||
/*!
|
||||
\qmlproperty int StatusImageCropPanel::radius
|
||||
\sa StatusImageCrop::radius
|
||||
*/
|
||||
property alias radius: cropEditor.radius
|
||||
/*!
|
||||
\qmlproperty color StatusImageCropPanel::wallColor
|
||||
\sa StatusImageCrop::wallColor
|
||||
*/
|
||||
property alias wallColor: cropEditor.wallColor
|
||||
/*!
|
||||
\qmlproperty real StatusImageCropPanel::wallTransparency
|
||||
\sa StatusImageCrop::wallTransparency
|
||||
*/
|
||||
property alias wallTransparency: cropEditor.wallTransparency
|
||||
/*!
|
||||
\qmlproperty rect StatusImageCropPanel::cropRect
|
||||
\sa StatusImageCrop::cropRect
|
||||
*/
|
||||
property alias cropRect: cropEditor.cropRect
|
||||
/*!
|
||||
\qmlproperty rect StatusImageCropPanel::cropRect
|
||||
\sa StatusImageCrop::cropRect
|
||||
*/
|
||||
readonly property alias cropWindow: cropEditor.cropWindow
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::scrollZoomFactor
|
||||
How fast is image scaled (zoomed) when using mouse scroll
|
||||
*/
|
||||
property real scrollZoomFactor: 0.5
|
||||
/*!
|
||||
\qmlproperty bool StatusImageCropPanel::enableCheckers
|
||||
Shows helper guiding checkers pattern where image is not covering
|
||||
*/
|
||||
property bool enableCheckers: root.interactive
|
||||
/*!
|
||||
\qmlproperty size StatusImageCropPanel::sourceSize
|
||||
\sa StatusImageCrop::sourceSize
|
||||
*/
|
||||
property alias sourceSize: cropEditor.sourceSize
|
||||
|
||||
/*
|
||||
\qmlmethod StatusImageCropPanel::setCropRect(rect)
|
||||
\sa StatusImageCrop::cropRect
|
||||
*/
|
||||
function setCropRect(newRect) {
|
||||
cropEditor.setCropRect(newRect)
|
||||
aspectRatio = cropEditor.aspectRatio
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
function updateAspectRatio(newAR) {
|
||||
// Keep width and adjust height
|
||||
const eW = cropEditor.cropRect.width
|
||||
const w = (eW <= 0) ? cropEditor.sourceSize.width : eW
|
||||
const h = w/newAR
|
||||
const c = (eW <= 0)
|
||||
? Qt.point(cropEditor.sourceSize.width/2, cropEditor.sourceSize.height/2)
|
||||
: Qt.point(cropEditor.cropRect.x + w/2, cropEditor.cropRect.y + cropEditor.cropRect.height/2)
|
||||
const nR = Qt.rect(c.x - w/2, c.y-h/2, w, h)
|
||||
cropEditor.setCropRect(nR)
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: d.updateAspectRatio(root.aspectRatio)
|
||||
onAspectRatioChanged: d.updateAspectRatio(root.aspectRatio)
|
||||
onSourceSizeChanged: d.updateAspectRatio(root.aspectRatio)
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
id: cropSpaceItem
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
id: leftOverlay
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: cropEditor.left
|
||||
anchors.bottom: parent.bottom
|
||||
color: wallColor
|
||||
opacity: wallTransparency
|
||||
z: cropEditor.z + 1
|
||||
}
|
||||
Rectangle {
|
||||
id: topOverlay
|
||||
anchors.left: leftOverlay.right
|
||||
anchors.top: parent.top
|
||||
anchors.right: rightOverlay.left
|
||||
anchors.bottom: cropEditor.top
|
||||
color: wallColor
|
||||
opacity: wallTransparency
|
||||
z: cropEditor.z + 1
|
||||
}
|
||||
|
||||
StatusImageCrop {
|
||||
id: cropEditor
|
||||
anchors.centerIn: parent
|
||||
width: aspectRatio < cropSpaceItem.width/cropSpaceItem.height ? cropSpaceItem.height * aspectRatio : cropSpaceItem.width - root.margins * 2
|
||||
height: aspectRatio < cropSpaceItem.width/cropSpaceItem.height ? cropSpaceItem.height - root.margins * 2 : cropSpaceItem.width / aspectRatio
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rightOverlay
|
||||
anchors.left: cropEditor.right
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
color: wallColor
|
||||
opacity: wallTransparency
|
||||
z: cropEditor.z + 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bottomOverlay
|
||||
anchors.left: leftOverlay.right
|
||||
anchors.top: cropEditor.bottom
|
||||
anchors.right: rightOverlay.left
|
||||
anchors.bottom: parent.bottom
|
||||
color: wallColor
|
||||
opacity: wallTransparency
|
||||
z: cropEditor.z + 1
|
||||
}
|
||||
|
||||
// Checkers
|
||||
Canvas {
|
||||
visible: root.enableCheckers
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
for(let xI = 0; xI < Math.ceil(width/10); xI++) {
|
||||
for(let yI = 0; yI < Math.ceil(height/10); yI++) {
|
||||
ctx.fillStyle = (xI % 2) === (yI % 2) ? "#FFFFFE" : "#DBDBDB"
|
||||
ctx.fillRect(xI * 10, yI * 10, 10, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
z: cropEditor.z - 1
|
||||
}
|
||||
|
||||
// Drag and zoom
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
enabled: root.interactive
|
||||
|
||||
property var lastDragPoint: null
|
||||
onReleased: lastDragPoint = null
|
||||
|
||||
onMouseXChanged: updateDrag(Qt.point(mouse.x, mouse.y))
|
||||
onMouseYChanged: updateDrag(Qt.point(mouse.x, mouse.y))
|
||||
|
||||
onWheel: {
|
||||
const delta = wheel.angleDelta.y / 120
|
||||
cropEditor.setCropRect(cropEditor.getZoomRect(cropEditor.zoomScale + delta * root.scrollZoomFactor))
|
||||
}
|
||||
|
||||
function moveRect(r /*rect*/, delta /*real*/) {
|
||||
return Qt.rect(r.x + delta.x, r.y + delta.y, r.width, r.height)
|
||||
}
|
||||
|
||||
function scaleSize(sz /*size*/, s /*size*/) {
|
||||
return Qt.point(sz.width * s, sz.height * s)
|
||||
}
|
||||
|
||||
function updateDrag(p) {
|
||||
let delta = (lastDragPoint ? Qt.size(lastDragPoint.x - p.x, lastDragPoint.y - p.y) : Qt.size(0, 0))
|
||||
delta = scaleSize(delta, 1/cropEditor.scrToImgScale)
|
||||
cropEditor.setCropRect(moveRect(cropEditor.cropRect, delta))
|
||||
lastDragPoint = p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: root.interactive
|
||||
|
||||
StatusIcon {
|
||||
icon: "remove-circle"
|
||||
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 20
|
||||
}
|
||||
StatusSlider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 20
|
||||
Layout.bottomMargin: 25
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
enabled: root.interactive
|
||||
|
||||
from: cropEditor.minZoomScale
|
||||
to: cropEditor.maxZoomScale
|
||||
value: cropEditor.zoomScale
|
||||
live: false
|
||||
onMoved: cropEditor.setCropRect(cropEditor.getZoomRect(valueAt(visualPosition)))
|
||||
}
|
||||
StatusIcon {
|
||||
icon: "add-circle"
|
||||
|
||||
Layout.preferredWidth: 20
|
||||
Layout.preferredHeight: 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ Rectangle {
|
||||
property alias identiconText: identiconText
|
||||
property string name
|
||||
property string emoji
|
||||
property string emojiSize
|
||||
property string emojiSize: Emoji.size.small
|
||||
property int letterSize: 21
|
||||
property int charactersLen: 1
|
||||
|
||||
@@ -29,14 +29,14 @@ Rectangle {
|
||||
|
||||
font.weight: Font.Bold
|
||||
font.pixelSize: root.letterSize
|
||||
color: Qt.rgba(255, 255, 255, 0.7)
|
||||
color: d.luminance(root.color) > 0.5 ? Qt.rgba(0, 0, 0, 0.5) : Qt.rgba(255, 255, 255, 0.7)
|
||||
|
||||
text: {
|
||||
if (emoji) {
|
||||
if(Utils.isHtml(emoji))
|
||||
return emoji
|
||||
else
|
||||
return Emoji.parse(emoji, emojiSize || undefined)
|
||||
return Emoji.parse(root.emoji, emojiSize)
|
||||
}
|
||||
|
||||
const shift = (root.name.charAt(0) === "#") ||
|
||||
@@ -45,5 +45,15 @@ Rectangle {
|
||||
return root.name.substring(shift, shift + charactersLen).toUpperCase()
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
function luminance(color) {
|
||||
let r = Math.pow(color.r, 2.2) * 0.2126
|
||||
let g = Math.pow(color.g, 2.2) * 0.7151
|
||||
let b = Math.pow(color.b, 2.2) * 0.0721
|
||||
return r + g + b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ Rectangle {
|
||||
property string title: ""
|
||||
property string titleAsideText: ""
|
||||
property string subTitle: ""
|
||||
property string tertiaryTitle: ""
|
||||
property string tertiaryTitle: ""
|
||||
property string label: ""
|
||||
property real leftPadding: 16
|
||||
property real rightPadding: 16
|
||||
property bool enabled: true
|
||||
property bool highlighted: false
|
||||
property bool propagateTitleClicks: true
|
||||
property bool propagateTitleClicks: true
|
||||
property int type: StatusListItem.Type.Primary
|
||||
property list<Item> components
|
||||
property var bottomModel: []
|
||||
@@ -139,7 +139,8 @@ Rectangle {
|
||||
name: statusListItem.title
|
||||
active: statusListItem.icon.isLetterIdenticon ||
|
||||
!!statusListItem.icon.name ||
|
||||
!!statusListItem.image.source.toString()
|
||||
!!statusListItem.image.source.toString() ||
|
||||
!!statusListItem.icon.emoji
|
||||
badge.border.color: statusListItem.color
|
||||
ringSettings: statusListItem.ringSettings
|
||||
}
|
||||
@@ -168,26 +169,34 @@ Rectangle {
|
||||
text: statusListItem.title
|
||||
font.pixelSize: 15
|
||||
height: visible ? contentHeight : 0
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
elide: Text.ElideRight
|
||||
anchors.left: parent.left
|
||||
anchors.right: !statusListItem.titleAsideText && !titleIconsRow.sourceComponent ? parent.right : undefined
|
||||
anchors.top: bottomModel.length === 0 ? undefined: parent.top
|
||||
anchors.topMargin: bottomModel.length === 0 ? undefined : 20
|
||||
width: Math.min(implicitWidth, parent.width)
|
||||
color: {
|
||||
if (!statusListItem.enabled) {
|
||||
return Theme.palette.baseColor1
|
||||
}
|
||||
switch (statusListItem.type) {
|
||||
case StatusListItem.Type.Primary:
|
||||
return Theme.palette.directColor1
|
||||
case StatusListItem.Type.Secondary:
|
||||
return Theme.palette.primaryColor1
|
||||
case StatusListItem.Type.Danger:
|
||||
return Theme.palette.dangerColor1
|
||||
}
|
||||
if (!statusListItem.enabled) {
|
||||
return Theme.palette.baseColor1
|
||||
}
|
||||
switch (statusListItem.type) {
|
||||
case StatusListItem.Type.Primary:
|
||||
return Theme.palette.directColor1
|
||||
case StatusListItem.Type.Secondary:
|
||||
return Theme.palette.primaryColor1
|
||||
case StatusListItem.Type.Danger:
|
||||
return Theme.palette.dangerColor1
|
||||
}
|
||||
}
|
||||
|
||||
StatusToolTip {
|
||||
id: statusListItemTitleTooltip
|
||||
text: statusListItemTitle.text
|
||||
delay: 0
|
||||
visible: statusListItemTitle.truncated && statusListItemTitleMouseArea.containsMouse
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: statusListItemTitleMouseArea
|
||||
anchors.fill: parent
|
||||
enabled: statusListItem.enabled
|
||||
cursorShape: sensor.enabled && containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusListPicker
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Components
|
||||
\since StatusQ.Components 0.1
|
||||
\brief It is a combination of StatusPickerButton and a drop-down list component that provides a way of presenting a list of selectable options to the user. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
The \c StatusListPicker is populated with a data model. The data model is commonly a JavaScript array or a ListModel object.
|
||||
|
||||
StatusListPicker can be made as a single or multiple options picker by setting its StatusListPicker::multiSelection property properly or will be auto-set if the model contains / provides more than one selected items.
|
||||
The StatusPickerButton text holds the text of the current item selected in the list picker. If there is more than one item selected, a string composed will be displayed.
|
||||
The drop-down list incorporates a searcher by the following model roles: `name` or / and `shortName`.
|
||||
|
||||
NOTE: Make sure to set the appropriate z-index when instantiating the component in order to place it on top of your view in order to display the drop-down data properly.
|
||||
Make sure to position the component inside its parent also when it is spanned in order to achieve a better user experience.
|
||||
|
||||
Example of how the component looks like:
|
||||
\image status_list_picker.png
|
||||
Example of how to use it:
|
||||
\qml
|
||||
StatusListPicker {
|
||||
id: currencyPicker
|
||||
inputList: Models.currencyPickerModel
|
||||
searchText: qsTr("Search Language")
|
||||
multiSelection: true
|
||||
printSymbol: true
|
||||
}
|
||||
\endqml
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::inputList
|
||||
This property holds the data that will be populated in the list picker.
|
||||
|
||||
NOTE: This model property should not change so it is an in / out property where the selected role will be dynamically changed according to user actions.
|
||||
|
||||
Here an example of the model roles expected:
|
||||
\qml
|
||||
inputList: ListModel {
|
||||
ListElement {
|
||||
key: 0
|
||||
name: "United States Dollar"
|
||||
shortName: "USD"
|
||||
symbol: "$"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b4.png"
|
||||
category: "fiat"
|
||||
selected: true
|
||||
}
|
||||
ListElement {
|
||||
key: 1
|
||||
name: "British Pound"
|
||||
shortName: "GBP"
|
||||
symbol: "£"
|
||||
imageSource: "../../assets/twemoji/26x26/1f4b5.png"
|
||||
category: "fiat"
|
||||
selected: false
|
||||
}
|
||||
}
|
||||
\endqml
|
||||
*/
|
||||
property ListModel inputList: ListModel { }
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::searchText
|
||||
This property holds the search text the searcher input displays by default.
|
||||
*/
|
||||
property string searchText: ""
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::placeholderSearchText
|
||||
This property holds the placeholder text the searcher input displays by default.
|
||||
*/
|
||||
property string placeholderSearchText: qsTr("Search")
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::multiSelection
|
||||
This property holds whether the list picker is a single or a multiple picker option by displaying a StatusRadioButton or StatusCheckBox instead.
|
||||
*/
|
||||
property bool multiSelection: false
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::printSymbol
|
||||
This property holds whether the selected items will display a composition of the role `symbol` in the StatusPickerButton text or just only the role `shortName`.
|
||||
*/
|
||||
property bool printSymbol: false
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::maxPickerHeight
|
||||
This property holds the maximum drop-down list height allowed. The drop-down list height will be set as the minimum value between the list content height and the maxPickerHeight property.
|
||||
*/
|
||||
property int maxPickerHeight: 718
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::enableSelectableItem
|
||||
This property holds if the item in the list will be highlighted when hovering and clickable in its complete area or just not highlighted and able to be selected by clicking only the checbox or radiobutton area.
|
||||
*/
|
||||
property bool enableSelectableItem: true
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusListPicker::menuAlignment
|
||||
This property holds the allignment of the menu in terms of the button
|
||||
*/
|
||||
property int menuAlignment: StatusListPicker.MenuAlignment.Right
|
||||
|
||||
/*!
|
||||
\qmlproperty enum StatusListPicker::MenuAlignment
|
||||
This property holds the allignment of the menu in terms of the button
|
||||
values can be Left, Right or Center
|
||||
*/
|
||||
enum MenuAlignment {
|
||||
Left,
|
||||
Right,
|
||||
Center
|
||||
}
|
||||
|
||||
/*
|
||||
\qmlmethod StatusListPicker::close()
|
||||
It can be used to force to close the drop-down picker list whenever the consumer needs it. For example by adding an outside MouseArea to close the picker when user clicks outsite the component:
|
||||
\qml
|
||||
// Outsite area
|
||||
MouseArea {
|
||||
height: root.height
|
||||
width: root.width
|
||||
onClicked: { currencyPicker.close() }
|
||||
}
|
||||
\endqml
|
||||
*/
|
||||
function close() {
|
||||
picker.visible = false
|
||||
|
||||
// Reset searcher:
|
||||
root.searchText = ""
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlsignal StatusListPicker::itemPickerChanged(string key, bool selected)
|
||||
This signal is emitted when an item changes its selected value.
|
||||
*/
|
||||
signal itemPickerChanged(string key, bool selected)
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
property var filteredModel: ListModel { }
|
||||
|
||||
// Used to set up component and the needed private properties
|
||||
function initialize() {
|
||||
if(filteredModel.count === 0) {
|
||||
// It is necessary to load the model, not loaded yet!
|
||||
var selected = 0
|
||||
var selectionText = ""
|
||||
for(var i = 0; i < root.inputList.count; i++) {
|
||||
var item = root.inputList.get(i)
|
||||
d.filteredModel.append(item)
|
||||
if(item.selected) selected++;
|
||||
}
|
||||
|
||||
// If the given model has more than one selected elements, the behaviour of the list picker will be as a multiple selector with checkboxes although it is
|
||||
// set in the radiobutton mode. Radiobutton mode is only for mutial-exclusion.
|
||||
if(selected > 1)
|
||||
multiSelection = true
|
||||
|
||||
// Update selected items text:
|
||||
d.getSelectedItemsText()
|
||||
}
|
||||
}
|
||||
|
||||
// Used to update model elements given a specific text by filtering them for its `name` and / or `shortName`.
|
||||
function applyFilter(text) {
|
||||
const input = text.toLowerCase()
|
||||
filteredModel.clear()
|
||||
for(var i = 0; i < root.inputList.count; i++) {
|
||||
let item = root.inputList.get(i)
|
||||
if(item.name.toLowerCase().includes(input) || item.shortName.toLowerCase().includes(input))
|
||||
filteredModel.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
function formatSymbolShortNameText(symbol, shortName) {
|
||||
var formattedText = ""
|
||||
if(root.printSymbol && symbol)
|
||||
formattedText = symbol + shortName
|
||||
else
|
||||
formattedText = shortName
|
||||
return formattedText
|
||||
}
|
||||
|
||||
function getSelectedItemsText() {
|
||||
var res = ""
|
||||
for(var i = 0; i < root.inputList.count; i++) {
|
||||
var item = root.inputList.get(i)
|
||||
if(item.selected) {
|
||||
if(res != "")
|
||||
res += ", "
|
||||
res += d.formatSymbolShortNameText(item.symbol, item.shortName)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Used to update the base input list model with the new selected property
|
||||
function updateInputListModel(key, selected, isSingleSelection) {
|
||||
// If it is a single selection we must ensure that when a new key is selected others are cleared.
|
||||
// It must be done manually because as there is the option of filterning, the model visualized changes
|
||||
// and it could be possible to have a filtered list without an element selected (but in the base model
|
||||
// it is) so, the binding to not selected will not be executed, as in the current filtered model
|
||||
// there are no changes to update, just only the selected one.
|
||||
for(var i = 0; i < root.inputList.count; i++) {
|
||||
if(root.inputList.get(i).key === key) {
|
||||
root.inputList.get(i).selected = selected
|
||||
}
|
||||
else if(isSingleSelection && selected) {
|
||||
// Clear all the list
|
||||
root.inputList.get(i).selected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
width: 110
|
||||
height: 38
|
||||
|
||||
Component.onCompleted: d.initialize()
|
||||
|
||||
StatusPickerButton {
|
||||
id: btn
|
||||
anchors.fill: parent
|
||||
bgColor: Theme.palette.primaryColor3
|
||||
contentColor: Theme.palette.primaryColor1
|
||||
text: picker.selectedItemsText
|
||||
textPixelSize: 13
|
||||
type: StatusPickerButton.Type.Down
|
||||
|
||||
onClicked: {
|
||||
picker.visible = !picker.visible
|
||||
|
||||
// Restart list position:
|
||||
content.positionViewAtBeginning()
|
||||
content.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: picker
|
||||
|
||||
property string selectedItemsText: ""
|
||||
|
||||
width: content.itemWidth
|
||||
height: Math.min(content.contentHeight + content.anchors.topMargin + content.anchors.bottomMargin, root.maxPickerHeight)
|
||||
anchors.left: root.menuAlignment === StatusListPicker.MenuAlignment.Left ? btn.left : undefined
|
||||
anchors.right: root.menuAlignment === StatusListPicker.MenuAlignment.Right ? btn.right : undefined
|
||||
anchors.horizontalCenter: root.menuAlignment === StatusListPicker.MenuAlignment.Center ? btn.horizontalCenter : undefined
|
||||
anchors.top: btn.bottom
|
||||
anchors.topMargin: 4
|
||||
visible: false
|
||||
color: Theme.palette.statusPopupMenu.backgroundColor
|
||||
radius: 8
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
source: picker
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 4
|
||||
radius: 12
|
||||
samples: 25
|
||||
spread: 0.2
|
||||
color: Theme.palette.dropShadow
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: content
|
||||
|
||||
property int itemHeight: 40
|
||||
property int itemWidth: 360
|
||||
|
||||
model: d.filteredModel
|
||||
width: itemWidth
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
currentIndex: -1
|
||||
clip: true
|
||||
headerPositioning: ListView.OverlayHeader
|
||||
header: Rectangle {
|
||||
id: header
|
||||
width: content.itemWidth
|
||||
height: searchInput.height + 24
|
||||
color: Theme.palette.statusPopupMenu.backgroundColor
|
||||
z: 3 // Above delegate (z=1) and above section.delegate (z = 2)
|
||||
|
||||
StatusBaseInput {
|
||||
id: searchInput
|
||||
implicitHeight: 36
|
||||
width: content.itemWidth - 2 * 18
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
placeholderText: root.placeholderSearchText
|
||||
text: root.searchText
|
||||
icon.name: "search"
|
||||
|
||||
onTextChanged: {
|
||||
d.applyFilter(text)
|
||||
root.searchText = text
|
||||
}
|
||||
}
|
||||
}// End of search input item
|
||||
delegate: StatusItemPicker {
|
||||
width: content.itemWidth
|
||||
height: content.itemHeight
|
||||
color: mouseArea.containsMouse ? Theme.palette.baseColor4 : "transparent"
|
||||
image: StatusImageSettings {
|
||||
source: model.imageSource ? model.imageSource : ""
|
||||
width: 15
|
||||
height: 15
|
||||
isIdenticon: false
|
||||
}
|
||||
name: model.name
|
||||
shortName: model.shortName
|
||||
selectorType: root.multiSelection ? StatusItemPicker.SelectorType.CheckBox : StatusItemPicker.SelectorType.RadioButton
|
||||
selected: model.selected
|
||||
radioGroup: radioBtnGroup
|
||||
|
||||
onCheckedChanged: {
|
||||
d.updateInputListModel(model.key, checked, selectorType === StatusItemPicker.SelectorType.RadioButton)
|
||||
if(selectorType === StatusItemPicker.SelectorType.RadioButton && checked) {
|
||||
// Update selected item text
|
||||
picker.selectedItemsText = d.formatSymbolShortNameText(model.symbol, model.shortName)
|
||||
}
|
||||
else {
|
||||
// Update selected items text (multiple selection, text chain).
|
||||
picker.selectedItemsText = d.getSelectedItemsText()
|
||||
}
|
||||
|
||||
// Used to notify selected property changes in the specific item picker.
|
||||
itemPickerChanged(model.key, checked)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
enabled: root.enableSelectableItem
|
||||
anchors.fill: parent
|
||||
cursorShape: root.enableSelectableItem ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
if(selectorType === StatusItemPicker.SelectorType.RadioButton) {
|
||||
// Just only allow to select, not unselect in case of single selection (radiobutton)
|
||||
if(!selected) selected = !selected
|
||||
}
|
||||
else
|
||||
// In case of multiple selections you can both select and unselect.
|
||||
selected = !selected
|
||||
}
|
||||
}
|
||||
}
|
||||
section.property: "category"
|
||||
section.criteria: ViewSection.FullString
|
||||
section.delegate: Item {
|
||||
width: content.itemWidth
|
||||
height: content.itemHeight
|
||||
|
||||
StatusBaseText {
|
||||
anchors.leftMargin: 18
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: section
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 15
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}// End of Category item
|
||||
|
||||
onActiveFocusChanged: { if(!activeFocus) root.close() }
|
||||
|
||||
// Not visual element to control mutual-exclusion of radiobuttons that are not sharing the same parent (inside list view)
|
||||
ButtonGroup {
|
||||
id: radioBtnGroup
|
||||
}
|
||||
}// End of Content
|
||||
}// End of Rectangle picker
|
||||
}
|
||||
@@ -2,36 +2,100 @@ import QtQuick 2.0
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusMemberListItem
|
||||
\inherits StatusListItem
|
||||
\inqmlmodule StatusQ.Components
|
||||
\since StatusQ.Components 0.1
|
||||
\brief It is a list item with a specific format to display members of a community or chat. Inherits from \c StatusListItem.
|
||||
|
||||
The \c StatusMemberListItem is a clickable / hovering list item with a specific format to display members of a community or chat.
|
||||
|
||||
It displays the member avatar, trust and mutual contact indicators, nick name, user name, chat key and information about the member connectivity.
|
||||
|
||||
Example of how the control looks like:
|
||||
\image status_member_list_item.png
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
StatusMemberListItem {
|
||||
nickName: "carmen.eth"
|
||||
isOnline: false
|
||||
trustIndicator: StatusContactVerificationIcons.TrustedType.Untrustworthy
|
||||
}
|
||||
|
||||
StatusMemberListItem {
|
||||
nickName: "This girl I know from work"
|
||||
userName: "annabelle"
|
||||
isOnline: true
|
||||
}
|
||||
\endqml
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
StatusListItem {
|
||||
id: root
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusMemberListItem::nickName
|
||||
This property holds the nick name of the member represented.
|
||||
*/
|
||||
property string nickName: ""
|
||||
/*!
|
||||
\qmlproperty string StatusMemberListItem::userName
|
||||
This property holds the user name of the member represented.
|
||||
*/
|
||||
property string userName: ""
|
||||
/*!
|
||||
\qmlproperty string StatusMemberListItem::chatKey
|
||||
This property holds the chat key of the member represented.
|
||||
*/
|
||||
property string chatKey: ""
|
||||
/*!
|
||||
\qmlproperty string StatusMemberListItem::isMutualContact
|
||||
This property holds if the member represented is a mutual contact.
|
||||
*/
|
||||
property bool isMutualContact: false
|
||||
/*!
|
||||
\qmlproperty string StatusMemberListItem::trustIndicator
|
||||
This property holds the trust indicator of the member represented. Possible values are:
|
||||
\list
|
||||
\li StatusContactVerificationIcons.TrustedType.None
|
||||
\li StatusContactVerificationIcons.TrustedType.Verified
|
||||
\li StatusContactVerificationIcons.TrustedType.Untrustworthy
|
||||
\endlist
|
||||
*/
|
||||
property var trustIndicator: StatusContactVerificationIcons.TrustedType.None
|
||||
/*!
|
||||
\qmlproperty string StatusMemberListItem::isOnline
|
||||
This property holds the connectivity status of the member represented.
|
||||
*/
|
||||
property bool isOnline: false
|
||||
|
||||
// Subtitle composition:
|
||||
function composeSubtitile() {
|
||||
var compose = ""
|
||||
if(root.userName !== "")
|
||||
compose = "(" + root.userName + ")"
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
if(compose !== "" && root.chatKey !== "")
|
||||
// Composition
|
||||
compose += " • " + composeShortKeyChat(root.chatKey)
|
||||
// Subtitle composition:
|
||||
function composeSubtitile() {
|
||||
var compose = ""
|
||||
if(root.userName !== "")
|
||||
compose = "(" + root.userName + ")"
|
||||
|
||||
else if(root.chatKey !== "")
|
||||
compose = composeShortKeyChat(root.chatKey)
|
||||
if(compose !== "" && root.chatKey !== "")
|
||||
// Composition
|
||||
compose += " • " + composeShortKeyChat(root.chatKey)
|
||||
|
||||
return compose
|
||||
}
|
||||
else if(root.chatKey !== "")
|
||||
compose = composeShortKeyChat(root.chatKey)
|
||||
|
||||
// Short keychat composition:
|
||||
function composeShortKeyChat(chatKey) {
|
||||
return chatKey.substring(0, 5) + "..." + chatKey.substring(chatKey.length - 3)
|
||||
return compose
|
||||
}
|
||||
|
||||
// Short keychat composition:
|
||||
function composeShortKeyChat(chatKey) {
|
||||
return chatKey.substring(0, 5) + "..." + chatKey.substring(chatKey.length - 3)
|
||||
}
|
||||
}
|
||||
|
||||
// root object settings:
|
||||
@@ -40,7 +104,7 @@ StatusListItem {
|
||||
isMutualContact: root.isMutualContact
|
||||
trustIndicator: root.trustIndicator
|
||||
}
|
||||
subTitle: composeSubtitile()
|
||||
subTitle: d.composeSubtitile()
|
||||
statusListItemSubTitle.font.pixelSize: 10
|
||||
icon.isLetterIdenticon: !root.image.source.toString()
|
||||
statusListItemIcon.badge.visible: true
|
||||
|
||||
@@ -32,7 +32,7 @@ StatusListItem {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: statusNavigationListItem.clicked(mouse)
|
||||
onClicked: statusNavigationListItem.clicked(itemId, mouse)
|
||||
}
|
||||
|
||||
components: [
|
||||
|
||||
@@ -15,7 +15,6 @@ Loader {
|
||||
property StatusIconSettings icon: StatusIconSettings {
|
||||
width: 40
|
||||
height: 40
|
||||
charactersLen: 1
|
||||
}
|
||||
|
||||
property StatusImageSettings image: StatusImageSettings {
|
||||
@@ -25,7 +24,7 @@ Loader {
|
||||
|
||||
property StatusIdenticonRingSettings ringSettings: StatusIdenticonRingSettings {
|
||||
initalAngleRad: 0
|
||||
ringPxSize: 1.5
|
||||
ringPxSize: Math.max(1.5, statusSmartIdenticon.image.width / 24.0)
|
||||
distinctiveColors: Theme.palette.identiconRingColors
|
||||
}
|
||||
|
||||
@@ -87,7 +86,6 @@ Loader {
|
||||
emoji: statusSmartIdenticon.icon.emoji
|
||||
emojiSize: statusSmartIdenticon.icon.emojiSize
|
||||
letterSize: statusSmartIdenticon.icon.letterSize
|
||||
identiconText.color: statusSmartIdenticon.icon.textColor
|
||||
charactersLen: statusSmartIdenticon.icon.charactersLen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,29 +6,122 @@ import QtGraphicalEffects 1.0
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusTagSelector
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Components
|
||||
\since StatusQ.Components 0.1
|
||||
\brief Displays a tag selector component together with a list of where to select and add the tags from.
|
||||
Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
The \c StatusTagSelector displays a list of asorted elements together with a text input where tags are added. As the user
|
||||
types some text, the list elements are filtered and if the user selects any of those a new tag is created.
|
||||
For example:
|
||||
|
||||
\qml
|
||||
StatusTagSelector {
|
||||
width: 650
|
||||
height: 44
|
||||
anchors.centerIn: parent
|
||||
namesModel: ListModel {
|
||||
ListElement {
|
||||
publicId: "0x0"
|
||||
name: "Maria"
|
||||
icon: ""
|
||||
isIdenticon: false
|
||||
onlineStatus: 3
|
||||
}
|
||||
ListElement {
|
||||
publicId: "0x1"
|
||||
name: "James"
|
||||
icon: "https://pbs.twimg.com/profile_images/1369221718338895873/T_5fny6o_400x400.jpg"
|
||||
isIdenticon: false
|
||||
onlineStatus: 1
|
||||
}
|
||||
ListElement {
|
||||
publicId: "0x2"
|
||||
name: "Paul"
|
||||
icon: ""
|
||||
isIdenticon: false
|
||||
onlineStatus: 2
|
||||
}
|
||||
}
|
||||
toLabelText: qsTr("To: ")
|
||||
warningText: qsTr("USER LIMIT REACHED")
|
||||
}
|
||||
\endqml
|
||||
|
||||
\image status_tag_selector.png
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: 448
|
||||
implicitHeight: 44 + ((userListView.count > 0) ? 44 + ((((userListView.count * 64) > root.maxHeight)
|
||||
? root.maxHeight : (userListView.count * 64))) :0)
|
||||
implicitHeight: (104 + contactsLabel.height + contactsLabel.anchors.topMargin + (userListView.model.count * 64)) > root.maxHeight ? root.maxHeight :
|
||||
(104 + contactsLabel.height + contactsLabel.anchors.topMargin + (userListView.model.count * 64))
|
||||
|
||||
property real maxHeight
|
||||
/*!
|
||||
\qmlproperty real StatusTagSelector::maxHeight
|
||||
This property holds the maximum height of the component.
|
||||
*/
|
||||
property real maxHeight: (488 + contactsLabel.height + contactsLabel.anchors.topMargin) //default min
|
||||
/*!
|
||||
\qmlproperty alias StatusTagSelector::textEdit
|
||||
This property holds a reference to the TextEdit component.
|
||||
*/
|
||||
property alias textEdit: edit
|
||||
/*!
|
||||
\qmlproperty alias StatusTagSelector::text
|
||||
This property holds a reference to the TextEdit's text property.
|
||||
*/
|
||||
property alias text: edit.text
|
||||
/*!
|
||||
\qmlproperty string StatusTagSelector::warningText
|
||||
This property sets the warning text.
|
||||
*/
|
||||
property string warningText: ""
|
||||
/*!
|
||||
\qmlproperty string StatusTagSelector::toLabelText
|
||||
This property sets the 'to' label text.
|
||||
*/
|
||||
property string toLabelText: ""
|
||||
/*!
|
||||
\qmlproperty string StatusTagSelector::listLabel
|
||||
This property sets the elements list label text.
|
||||
*/
|
||||
property string listLabel: ""
|
||||
/*!
|
||||
\qmlproperty int StatusTagSelector::nameCountLimit
|
||||
This property sets the tags count limit.
|
||||
*/
|
||||
property int nameCountLimit: 5
|
||||
|
||||
/*!
|
||||
\qmlproperty ListModel StatusTagSelector::sortedList
|
||||
This property holds the sorted list model.
|
||||
*/
|
||||
property ListModel sortedList: ListModel { }
|
||||
/*!
|
||||
\qmlproperty ListModel StatusTagSelector::namesModel
|
||||
This property holds the asorted names model.
|
||||
*/
|
||||
property ListModel namesModel: ListModel { }
|
||||
|
||||
/*!
|
||||
\qmlmethod
|
||||
This function is used to find an entry in a model.
|
||||
*/
|
||||
function find(model, criteria) {
|
||||
for (var i = 0; i < model.count; ++i) if (criteria(model.get(i))) return model.get(i);
|
||||
return null;
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod
|
||||
This function is used to insert a new tag.
|
||||
*/
|
||||
function insertTag(name, id) {
|
||||
if (!find(namesModel, function(item) { return item.publicId === id }) && namesModel.count < root.nameCountLimit) {
|
||||
namesModel.insert(namesModel.count, {"name": name, "publicId": id});
|
||||
@@ -37,15 +130,20 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod
|
||||
This function is used to sort the source model.
|
||||
*/
|
||||
function sortModel(inputModel) {
|
||||
sortedList.clear();
|
||||
if (text !== "") {
|
||||
for (var i = 0; i < inputModel.count; i++ ) {
|
||||
var entry = inputModel.get(i);
|
||||
if (entry.name.toLowerCase().includes(text.toLowerCase())) {
|
||||
sortedList.insert(sortedList.count, {"publicId": entry.publicId, "name": entry.name,
|
||||
"icon": entry.icon, "isIdenticon": entry.isIdenticon,
|
||||
"onlineStatus": entry.onlineStatus});
|
||||
if (entry.name.toLowerCase().includes(text.toLowerCase()) &&
|
||||
!find(namesModel, function(item) { return item.name === entry.name })) {
|
||||
sortedList.append({"publicId": entry.publicId, "name": entry.name,
|
||||
"icon": entry.icon, "isIdenticon": entry.isIdenticon,
|
||||
"onlineStatus": entry.onlineStatus});
|
||||
userListView.model = sortedList;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +152,16 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when a new tag is created.
|
||||
*/
|
||||
signal addMember(string memberId)
|
||||
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when a tag is removed.
|
||||
*/
|
||||
signal removeMember(string memberId)
|
||||
|
||||
Rectangle {
|
||||
@@ -63,7 +170,6 @@ Item {
|
||||
height: 44
|
||||
radius: 8
|
||||
color: Theme.palette.baseColor2
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 16
|
||||
@@ -77,8 +183,8 @@ Item {
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
Layout.preferredWidth: (namesList.contentWidth > (parent.width - 142)) ?
|
||||
(parent.width - 142) : namesList.contentWidth
|
||||
Layout.preferredWidth: (namesList.contentWidth > (parent.width - 177)) ?
|
||||
(parent.width - 177) : namesList.contentWidth
|
||||
implicitHeight: 30
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
visible: (namesList.count > 0)
|
||||
@@ -92,9 +198,13 @@ Item {
|
||||
model: namesModel
|
||||
orientation: ListView.Horizontal
|
||||
spacing: 8
|
||||
onCountChanged: {
|
||||
contentX = contentWidth;
|
||||
function scrollToEnd() {
|
||||
if (contentWidth > width) {
|
||||
contentX = contentWidth;
|
||||
}
|
||||
}
|
||||
onWidthChanged: { scrollToEnd(); }
|
||||
onCountChanged: { scrollToEnd(); }
|
||||
delegate: Rectangle {
|
||||
id: nameDelegate
|
||||
width: (nameText.contentWidth + 34)
|
||||
@@ -129,30 +239,36 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
TextInput {
|
||||
id: edit
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
focus: true
|
||||
color: Theme.palette.directColor1
|
||||
clip: true
|
||||
font.pixelSize: 15
|
||||
wrapMode: TextEdit.NoWrap
|
||||
font.family: Theme.palette.baseFont.name
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 44
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
visible: (namesModel.count < 5)
|
||||
enabled: visible
|
||||
focus: true
|
||||
font.pixelSize: 15
|
||||
font.family: Theme.palette.baseFont.name
|
||||
color: Theme.palette.directColor1
|
||||
Keys.onPressed: {
|
||||
if ((event.key === Qt.Key_Backspace || event.key === Qt.Key_Escape)
|
||||
&& getText(cursorPosition, (cursorPosition-1)) === "") {
|
||||
&& getText(cursorPosition, (cursorPosition-1)) === ""
|
||||
&& (namesList.count-1) >= 0) {
|
||||
removeMember(namesModel.get(namesList.count-1).publicId);
|
||||
namesModel.remove((namesList.count-1), 1);
|
||||
}
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (sortedList.count > 0)) {
|
||||
root.insertTag(sortedList.get(userListView.currentIndex).name, sortedList.get(userListView.currentIndex).publicId);
|
||||
}
|
||||
}
|
||||
Keys.onUpPressed: { userListView.decrementCurrentIndex(); }
|
||||
Keys.onDownPressed: { userListView.incrementCurrentIndex(); }
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: warningTextLabel
|
||||
visible: (namesModel.count === root.nameCountLimit)
|
||||
Layout.preferredWidth: 120
|
||||
Layout.preferredWidth: visible ? 120 : 0
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
font.pixelSize: 10
|
||||
color: Theme.palette.dangerColor1
|
||||
@@ -167,8 +283,9 @@ Item {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
anchors.top: tagSelectorRect.bottom
|
||||
anchors.topMargin: 32
|
||||
visible: (namesModel.count === 0)
|
||||
anchors.topMargin: visible ? 32 : 0
|
||||
height: visible ? contentHeight : 0
|
||||
visible: (root.sortedList.count === 0)
|
||||
color: Theme.palette.baseColor1
|
||||
text: root.listLabel
|
||||
}
|
||||
@@ -178,12 +295,13 @@ Item {
|
||||
width: 360
|
||||
anchors {
|
||||
top: (root.sortedList.count > 0) ? tagSelectorRect.bottom : contactsLabel.bottom
|
||||
topMargin: 8//Style.current.padding
|
||||
topMargin: 8//Style.current.halfPadding
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 20//Style.current.bigPadding
|
||||
bottomMargin: 16//Style.current.padding
|
||||
}
|
||||
visible: ((root.namesModel.count === 0) || (root.sortedList.count > 0))
|
||||
x: ((root.namesModel.count > 0) && ((edit.x + 8) <= (root.width - suggestionsContainer.width)))
|
||||
clip: true
|
||||
visible: ((edit.text === "") || (root.sortedList.count > 0))
|
||||
x: ((root.namesModel.count > 0) && (root.sortedList.count > 0) && ((edit.x + 8) <= (root.width - suggestionsContainer.width)))
|
||||
? (edit.x + 8) : 0
|
||||
background: Rectangle {
|
||||
id: bgRect
|
||||
@@ -215,12 +333,22 @@ Item {
|
||||
policy: ScrollBar.AsNeeded
|
||||
}
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
onCountChanged: {
|
||||
userListView.currentIndex = 0;
|
||||
}
|
||||
delegate: Item {
|
||||
id: wrapper
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
height: 64
|
||||
property bool hovered: false
|
||||
height: visible ? 64 : 0
|
||||
visible: {
|
||||
for (let i = 0; i < namesModel.count; i++) {
|
||||
if (namesModel.get(i).publicId === model.publicId) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Rectangle {
|
||||
id: rectangle
|
||||
anchors.fill: parent
|
||||
@@ -228,7 +356,7 @@ Item {
|
||||
anchors.leftMargin: 8
|
||||
radius: 8
|
||||
visible: (root.sortedList.count > 0)
|
||||
color: (wrapper.hovered) ? Theme.palette.baseColor2 : "transparent"
|
||||
color: (userListView.currentIndex === index) ? Theme.palette.baseColor2 : "transparent"
|
||||
}
|
||||
|
||||
StatusSmartIdenticon {
|
||||
@@ -270,10 +398,7 @@ Item {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
wrapper.hovered = true;
|
||||
}
|
||||
onExited: {
|
||||
wrapper.hovered = false;
|
||||
userListView.currentIndex = index;
|
||||
}
|
||||
onClicked: {
|
||||
root.insertTag(model.name, model.publicId);
|
||||
|
||||
@@ -7,30 +7,114 @@ import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusToastMessage
|
||||
\inherits Control
|
||||
\inqmlmodule StatusQ.Components
|
||||
\since StatusQ.Components 0.1
|
||||
\brief Displays a toast message in the UI. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Control}.
|
||||
|
||||
The \c StatusToastMessage displays a toast message in the UI either standalone or as part
|
||||
of a stack of toast messages.
|
||||
For example:
|
||||
|
||||
\qml
|
||||
StatusToastMessage {
|
||||
primaryText: qsTr("Collectible is being minted...")
|
||||
secondaryText: qsTr("View on Etherscan")
|
||||
loading: true
|
||||
type: 0
|
||||
linkUrl: "http://google.com"
|
||||
duration: 5000
|
||||
}
|
||||
\endqml
|
||||
|
||||
\image status_toast_message.png
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
|
||||
|
||||
Control {
|
||||
id: root
|
||||
width: 343
|
||||
height: !!secondaryText ? 68 : 48
|
||||
anchors.right: parent.right
|
||||
|
||||
property bool open: false
|
||||
/*!
|
||||
\qmlproperty bool StatusToastMessage::open
|
||||
This property represents all steps and their descriptions as provided by the user.
|
||||
*/
|
||||
property bool open: true
|
||||
/*!
|
||||
\qmlproperty string StatusToastMessage::primaryText
|
||||
This property represents the title text of the ToastMessage.
|
||||
*/
|
||||
property string primaryText: ""
|
||||
/*!
|
||||
\qmlproperty string StatusToastMessage::secondaryText
|
||||
This property represents the subtitle text of the ToastMessage.
|
||||
*/
|
||||
property string secondaryText: ""
|
||||
/*!
|
||||
\qmlproperty bool StatusToastMessage::loading
|
||||
This property represents activates/deactivates the loading indicator of the ToastMessage.
|
||||
*/
|
||||
property bool loading: false
|
||||
property string iconName: ""
|
||||
/*!
|
||||
\qmlproperty string StatusToastMessage::linkUrl
|
||||
This property represents all steps and their descriptions as provided by the user.
|
||||
*/
|
||||
property string linkUrl: ""
|
||||
|
||||
/*!
|
||||
\qmlproperty int StatusToastMessage::duration
|
||||
This property represents duration in milliseconds, for how long a toast will be visible. If 0 is set for
|
||||
duration, that means a toast won't dissapear without user's interaction (click on close).
|
||||
*/
|
||||
property int duration: 0
|
||||
|
||||
/*!
|
||||
\qmlproperty StatusIconSettings StatusToastMessage::icon
|
||||
This property holds a set of settings for the icon of the ToastMessage.
|
||||
*/
|
||||
property StatusIconSettings icon: StatusIconSettings {
|
||||
width: 23
|
||||
height: 23
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty int StatusToastMessage::type
|
||||
This property holds the type of the ToastMessage. Values are:
|
||||
\list
|
||||
\li Default
|
||||
\li Success
|
||||
\endlist
|
||||
|
||||
*/
|
||||
property int type: StatusToastMessage.Type.Default
|
||||
enum Type {
|
||||
Default,
|
||||
Success
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod
|
||||
This function is used to open the ToastMessage setting all its properties.
|
||||
Examples of usage:
|
||||
\qml
|
||||
StatusToastMessage {
|
||||
id: toastMessage
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
toastMessage.open(qsTr("Verification Request Sent"), "", "checkmark-circle", 1, false,"");
|
||||
}
|
||||
}
|
||||
\endqml
|
||||
|
||||
*/
|
||||
function open(title, subTitle, iconName, type, loading, url) {
|
||||
root.primaryText = title;
|
||||
root.secondaryText = subTitle;
|
||||
@@ -41,12 +125,36 @@ Control {
|
||||
root.open = true;
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when the ToastMessage is closed (after animation).
|
||||
*/
|
||||
signal close()
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when the ToastMessage contains a url and this url
|
||||
is clicked by the user.
|
||||
*/
|
||||
signal linkActivated(var link)
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property string openedState: "opened"
|
||||
readonly property string closedState: "closed"
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: root.duration
|
||||
running: root.duration > 0
|
||||
onTriggered: {
|
||||
root.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "opened"
|
||||
name: d.openedState
|
||||
when: root.open
|
||||
PropertyChanges {
|
||||
target: root
|
||||
@@ -55,16 +163,13 @@ Control {
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "closed"
|
||||
name: d.closedState
|
||||
when: !root.open
|
||||
PropertyChanges {
|
||||
target: root
|
||||
anchors.rightMargin: -width
|
||||
opacity: 0.0
|
||||
}
|
||||
StateChangeScript {
|
||||
script: { root.close(); }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -72,6 +177,12 @@ Control {
|
||||
Transition {
|
||||
to: "*"
|
||||
NumberAnimation { properties: "anchors.rightMargin,opacity"; duration: 400 }
|
||||
|
||||
onRunningChanged: {
|
||||
if(!running && state == d.closedState) {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -114,9 +225,14 @@ Control {
|
||||
radius: (root.width/2)
|
||||
color: (root.type === StatusToastMessage.Type.Success) ?
|
||||
Theme.palette.successColor2 : Theme.palette.primaryColor3
|
||||
visible: loader.sourceComponent != undefined
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: root.loading ? loadingInd : statusIcon
|
||||
sourceComponent: root.loading ? loadingInd :
|
||||
root.icon.name != ""? statusIcon :
|
||||
undefined
|
||||
|
||||
Component {
|
||||
id: loadingInd
|
||||
StatusLoadingIndicator {
|
||||
|
||||
@@ -6,10 +6,42 @@ import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusWizardStepper
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Components
|
||||
\since StatusQ.Components 0.1
|
||||
\brief Displays a step sequence provided by the stepsModel. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
The \c StatusWizardStepper steps and their descriptions as provided by the stepsModel.
|
||||
For example:
|
||||
|
||||
\qml
|
||||
StatusWizardStepper {
|
||||
id: wizardStepper
|
||||
stepsModel: ListModel {
|
||||
ListElement {description: qsTr("Send Request"); loadingTime: 0; stepCompleted: false}
|
||||
ListElement {description: qsTr("Receive Response"); loadingTime: 0; stepCompleted: false}
|
||||
ListElement {description: qsTr("Confirm Identity"); loadingTime: 0; stepCompleted: false}
|
||||
}
|
||||
}
|
||||
\endqml
|
||||
|
||||
\image status_wizard_stepper.png
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
|
||||
Item {
|
||||
property int maxDuration: 2000
|
||||
|
||||
id: wizardWrapper
|
||||
width: parent.width
|
||||
height: 56
|
||||
/*!
|
||||
\qmlproperty ListModel StatusWizardStepper::stepsModel
|
||||
This property represents all steps and their descriptions as provided by the user.
|
||||
*/
|
||||
property ListModel stepsModel: ListModel { }
|
||||
|
||||
ListView {
|
||||
@@ -35,8 +67,7 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -12
|
||||
from: 0
|
||||
//TODO what is to: here?
|
||||
to: 2000
|
||||
to: wizardWrapper.maxDuration
|
||||
value: loadingTime
|
||||
backgroundColor: "transparent"
|
||||
backgroundBorderColor: Theme.palette.primaryColor1
|
||||
|
||||
@@ -24,6 +24,7 @@ StatusRoundedImage 0.1 StatusRoundedImage.qml
|
||||
StatusMacWindowButtons 0.1 StatusMacWindowButtons.qml
|
||||
StatusListItemBadge 0.1 StatusListItemBadge.qml
|
||||
StatusListItemTag 0.1 StatusListItemTag.qml
|
||||
StatusListPicker 0.1 StatusListPicker.qml
|
||||
StatusExpandableItem 0.1 StatusExpandableItem.qml
|
||||
StatusSmartIdenticon 0.1 StatusSmartIdenticon.qml
|
||||
StatusMessage 0.1 StatusMessage.qml
|
||||
@@ -31,3 +32,4 @@ StatusMessageDetails 0.1 StatusMessageDetails.qml
|
||||
StatusTagSelector 0.1 StatusTagSelector.qml
|
||||
StatusToastMessage 0.1 StatusToastMessage.qml
|
||||
StatusWizardStepper 0.1 StatusWizardStepper.qml
|
||||
StatusImageCropPanel 0.1 StatusImageCropPanel.qml
|
||||
|
||||
@@ -55,7 +55,7 @@ Item {
|
||||
return
|
||||
}
|
||||
if (selectedAccount.color) {
|
||||
selectedIconImg.color = Utils.getThemeAccountColor(selectedAccount.color, Theme.palette.accountColors) || Theme.palette.accountColors[0]
|
||||
selectedIconImg.color = Utils.getThemeAccountColor(selectedAccount.color, Theme.palette.userCustomizationColors) || Theme.palette.userCustomizationColors[0]
|
||||
}
|
||||
if (selectedAccount.name) {
|
||||
selectedTextField.text = selectedAccount.name
|
||||
@@ -208,7 +208,7 @@ Item {
|
||||
width: 20
|
||||
height: 20
|
||||
icon: "filled-account"
|
||||
color: Utils.getThemeAccountColor(model.color, Theme.palette.accountColors) || Theme.palette.accountColors[0]
|
||||
color: Utils.getThemeAccountColor(model.color, Theme.palette.userCustomizationColors) || Theme.palette.userCustomizationColors[0]
|
||||
}
|
||||
|
||||
Column {
|
||||
|
||||
@@ -3,13 +3,61 @@ import QtQuick 2.14
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusBanner
|
||||
\inherits Column
|
||||
\inqmlmodule StatusQ.Controls
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief It displays a banner with a custom text, size and type. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-column.html}{Column}.
|
||||
|
||||
The \c StatusBanner displays a banner with a custom text, size and type (Info, Danger, Success or Warning).
|
||||
|
||||
Example of how the control looks like:
|
||||
\image status_banner.png
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
StatusBanner {
|
||||
width: parent.width
|
||||
visible: popup.userIsBlocked
|
||||
type: StatusBanner.Type.Danger
|
||||
statusText: qsTr("Blocked")
|
||||
}
|
||||
\endqml
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
Column {
|
||||
id: statusBanner
|
||||
width: parent.width
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusBanner::statusText
|
||||
This property holds the text the banner will display.
|
||||
*/
|
||||
property string statusText
|
||||
/*!
|
||||
\qmlproperty string StatusBanner::type
|
||||
This property holds type of banner. Possible values are:
|
||||
\qml
|
||||
enum Type {
|
||||
Info, // 0
|
||||
Danger, // 1
|
||||
Success, // 2
|
||||
Warning // 3
|
||||
}
|
||||
\endqml
|
||||
*/
|
||||
property int type: StatusBanner.Type.Info
|
||||
/*!
|
||||
\qmlproperty string StatusBanner::textPixels
|
||||
This property holds the pixels size of the text inside the banner.
|
||||
*/
|
||||
property int textPixels: 15
|
||||
/*!
|
||||
\qmlproperty string StatusBanner::statusBannerHeight
|
||||
This property holds the height of the banner rectangle.
|
||||
*/
|
||||
property int statusBannerHeight: 38
|
||||
|
||||
// "private" properties
|
||||
@@ -27,6 +75,8 @@ Column {
|
||||
Warning // 3
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
|
||||
// Component definition
|
||||
Rectangle {
|
||||
id: topDiv
|
||||
|
||||
@@ -40,11 +40,9 @@ Rectangle {
|
||||
return 5
|
||||
case StatusBaseButton.Size.Small:
|
||||
return 10
|
||||
break;
|
||||
case StatusBaseButton.Size.Large:
|
||||
default:
|
||||
return 11
|
||||
break;
|
||||
}
|
||||
}
|
||||
property real defaultBottomPadding: {
|
||||
@@ -53,11 +51,9 @@ Rectangle {
|
||||
return 5
|
||||
case StatusBaseButton.Size.Small:
|
||||
return 10
|
||||
break;
|
||||
case StatusBaseButton.Size.Large:
|
||||
default:
|
||||
return 11
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +105,17 @@ Rectangle {
|
||||
hoverEnabled: !loading
|
||||
enabled: !loading
|
||||
|
||||
|
||||
Loader {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
active: loading
|
||||
sourceComponent: StatusLoadingIndicator {
|
||||
color: statusBaseButton.enabled ? textColor
|
||||
: disabledTextColor
|
||||
} // Indicator
|
||||
} // Loader
|
||||
|
||||
Row {
|
||||
id: layout
|
||||
anchors.left: parent.left
|
||||
@@ -121,28 +128,20 @@ Rectangle {
|
||||
height: statusBaseButton.icon.height
|
||||
icon: statusBaseButton.icon.name
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !loading && statusBaseButton.icon.name !== ""
|
||||
opacity: !loading && statusBaseButton.icon.name !== ""
|
||||
visible: statusBaseButton.icon.name !== ""
|
||||
color: statusBaseButton.enabled ? textColor
|
||||
: disabledTextColor
|
||||
} // Icon
|
||||
StatusBaseText {
|
||||
id: label
|
||||
visible: !loading
|
||||
opacity: !loading
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pixelSize: size === StatusBaseButton.Size.Large ? 15 : 13 // by design
|
||||
|
||||
color: statusBaseButton.enabled ? textColor
|
||||
: disabledTextColor
|
||||
} // Text
|
||||
|
||||
Loader {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
active: loading
|
||||
sourceComponent: StatusLoadingIndicator {
|
||||
color: statusBaseButton.enabled ? textColor
|
||||
: disabledTextColor
|
||||
} // Indicator
|
||||
} // Loader
|
||||
} // Ro
|
||||
|
||||
|
||||
|
||||
@@ -8,53 +8,224 @@ import StatusQ.Controls 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusBaseInput
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Controls
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief Displays a customized TextInput component.
|
||||
Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
The \c StatusBaseInput displays an enhanced TextInput for users to type text.
|
||||
For example:
|
||||
|
||||
\qml
|
||||
StatusBaseInput {
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: 36
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
clearable: true
|
||||
placeholderText: qsTr("Search")
|
||||
icon.name: "search"
|
||||
}
|
||||
\endqml
|
||||
|
||||
\image status_base_input.png
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::multiline
|
||||
This property indicates whether the StatusBaseInput allows multiline text. Default value is false.
|
||||
*/
|
||||
property bool multiline: false
|
||||
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::acceptReturn
|
||||
This property indicates whether the StatusBaseInput allows pressing Enter or Return.
|
||||
This is used in case multiline is false and we still want to interact using Enter or Return. Default value is false.
|
||||
*/
|
||||
property bool acceptReturn: false
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::clearable
|
||||
This property indicates whether the StatusBaseInput allows clearing all text. Default value is false.
|
||||
*/
|
||||
property bool clearable: false
|
||||
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::inputMethodHints
|
||||
This property holds a reference to the TextEdit component's inputMethodHints property.
|
||||
*/
|
||||
property alias inputMethodHints: edit.inputMethodHints
|
||||
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::selectedText
|
||||
This property holds a reference to the TextEdit component's selectedText property.
|
||||
*/
|
||||
property alias selectedText: edit.selectedText
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::selectedTextColor
|
||||
This property holds a reference to the TextEdit component's selectedTextColor property.
|
||||
*/
|
||||
property alias selectedTextColor: edit.selectedTextColor
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::selectionStart
|
||||
This property holds a reference to the TextEdit component's selectionStart property.
|
||||
*/
|
||||
property alias selectionStart: edit.selectionStart
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::selectionEnd
|
||||
This property holds a reference to the TextEdit component's selectionEnd property.
|
||||
*/
|
||||
property alias selectionEnd: edit.selectionEnd
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::cursorPosition
|
||||
This property holds a reference to the TextEdit component's cursorPosition property.
|
||||
*/
|
||||
property alias cursorPosition: edit.cursorPosition
|
||||
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::edit
|
||||
This property holds a reference to the TextEdit component.
|
||||
*/
|
||||
property alias edit: edit
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::text
|
||||
This property holds a reference to the TextEdit component's text property.
|
||||
*/
|
||||
property alias text: edit.text
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::background
|
||||
This property holds a reference to the background component.
|
||||
*/
|
||||
property alias background: background
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::color
|
||||
This property holds a reference to the TextEdit component's color property.
|
||||
*/
|
||||
|
||||
property alias color: edit.color
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::font
|
||||
This property holds a reference to the TextEdit component's font property.
|
||||
*/
|
||||
property alias font: edit.font
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::focussed
|
||||
This property holds a reference to the TextEdit component's activeFocus property.
|
||||
*/
|
||||
property alias focussed: edit.activeFocus
|
||||
property alias verticalAlignmet: edit.verticalAlignment
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::verticalAlignment
|
||||
This property holds a reference to the TextEdit component's verticalAlignment property.
|
||||
*/
|
||||
property alias verticalAlignment: edit.verticalAlignment
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::horizontalAlignment
|
||||
This property holds a reference to the TextEdit component's horizontalAlignment property.
|
||||
*/
|
||||
property alias horizontalAlignment: edit.horizontalAlignment
|
||||
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::placeholderText
|
||||
This property holds a reference to the placeholder component's placeholderText property.
|
||||
*/
|
||||
property alias placeholderText: placeholder.text
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::placeholderTextColor
|
||||
This property holds a reference to the placeholder component's placeholderTextColor property.
|
||||
*/
|
||||
property alias placeholderTextColor: placeholder.color
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::placeholderFont
|
||||
This property holds a reference to the placeholder component's font property.
|
||||
*/
|
||||
property alias placeholderFont: placeholder.font
|
||||
|
||||
/*!
|
||||
\qmlproperty real StatusBaseInput::leftPadding
|
||||
This property sets the leftComponentLoader's left padding.
|
||||
*/
|
||||
property real leftPadding: leftComponentLoader.item ? 8 : 16
|
||||
/*!
|
||||
\qmlproperty real StatusBaseInput::rightPadding
|
||||
This property sets the right padding.
|
||||
*/
|
||||
property real rightPadding: 16
|
||||
/*!
|
||||
\qmlproperty real StatusBaseInput::topPadding
|
||||
This property sets the top padding.
|
||||
*/
|
||||
property real topPadding: 12
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::bottomPadding
|
||||
This property sets the bottom padding.
|
||||
*/
|
||||
property real bottomPadding: 12
|
||||
/*!
|
||||
\qmlproperty var StatusBaseInput::tabNavItem
|
||||
This property sets the tab key navigation item.
|
||||
*/
|
||||
property var tabNavItem: null
|
||||
|
||||
/*!
|
||||
\qmlproperty real StatusBaseInput::minimumHeight
|
||||
This property sets the minimum height.
|
||||
*/
|
||||
property real minimumHeight: 0
|
||||
/*!
|
||||
\qmlproperty alias StatusBaseInput::maximumHeight
|
||||
This property sets the maximum height.
|
||||
*/
|
||||
property real maximumHeight: 0
|
||||
/*!
|
||||
\qmlproperty int StatusBaseInput::maximumLength
|
||||
This property sets the text's maximum length.
|
||||
*/
|
||||
property int maximumLength: 0
|
||||
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::valid
|
||||
This property sets the valid state. Default value is true.
|
||||
*/
|
||||
property bool valid: true
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::pristine
|
||||
This property sets the pristine. Default value is true.
|
||||
*/
|
||||
property bool pristine: true
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::dirty
|
||||
This property sets the dirty property. Default value is false.
|
||||
*/
|
||||
property bool dirty: false
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::pending
|
||||
This property holds the maximum height of the component. Default value is false.
|
||||
*/
|
||||
property bool pending: false
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::leftIcon
|
||||
This property sets the visibility of the left icon. Default value is true.
|
||||
*/
|
||||
property bool leftIcon: true
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::isIconSelectable
|
||||
This property indicates whether icon is selectable. Default value is false.
|
||||
*/
|
||||
property bool isIconSelectable: false
|
||||
/*!
|
||||
\qmlproperty bool StatusBaseInput::showBackground
|
||||
This property sets the visibility of the background. Default value is true.
|
||||
*/
|
||||
property bool showBackground: true
|
||||
|
||||
/*!
|
||||
\qmlproperty StatusIconSettings StatusBaseInput::icon
|
||||
This property holds a set of settings for the icon of the StatusBaseInput.
|
||||
*/
|
||||
property StatusIconSettings icon: StatusIconSettings {
|
||||
width: 24
|
||||
height: 24
|
||||
@@ -69,11 +240,36 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusBaseInput::letterIconName
|
||||
This property holds the letter that should be displayed in the letter identicon component.
|
||||
*/
|
||||
property string letterIconName: ""
|
||||
/*!
|
||||
\qmlproperty Component StatusBaseInput::leftComponent
|
||||
This property sets the left component to be displayed inside the input.
|
||||
*/
|
||||
property Component leftComponent
|
||||
/*!
|
||||
\qmlproperty Component StatusBaseInput::selectedText
|
||||
This property sets the right component to be displayed inside the input.
|
||||
*/
|
||||
property Component rightComponent
|
||||
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when the icon is clicked.
|
||||
*/
|
||||
signal iconClicked
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when a hard key is pressed passing as parameter the keyboard event.
|
||||
*/
|
||||
signal keyPressed(var event)
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when the text edit is clicked.
|
||||
*/
|
||||
signal editClicked()
|
||||
|
||||
implicitWidth: 448
|
||||
@@ -112,15 +308,17 @@ Item {
|
||||
|
||||
MouseArea {
|
||||
id: sensor
|
||||
enabled: !edit.activeFocus
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.IBeamCursor
|
||||
onClicked: {
|
||||
if (edit.activeFocus)
|
||||
return;
|
||||
edit.forceActiveFocus()
|
||||
root.editClicked()
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: root.leftPadding
|
||||
@@ -134,7 +332,7 @@ Item {
|
||||
sourceComponent: {
|
||||
if (root.leftComponent) return root.leftComponent
|
||||
if (!root.leftIcon) return undefined
|
||||
if (root.icon.emoji) return identiconComponent
|
||||
if (root.icon.emoji || root.icon.isLetterIdenticon) return identiconComponent
|
||||
if (root.icon.name) return isIconSelectable ? identiconComponent : iconComponent
|
||||
return undefined
|
||||
}
|
||||
@@ -176,7 +374,7 @@ Item {
|
||||
|
||||
width: flick.width
|
||||
height: flick.height
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
selectByMouse: true
|
||||
selectionColor: Theme.palette.primaryColor2
|
||||
selectedTextColor: color
|
||||
@@ -186,8 +384,8 @@ Item {
|
||||
color: Theme.palette.directColor1
|
||||
wrapMode: root.multiline ? Text.WrapAtWordBoundaryOrAnywhere : TextEdit.NoWrap
|
||||
|
||||
Keys.onReturnPressed: event.accepted = !multiline
|
||||
Keys.onEnterPressed: event.accepted = !multiline
|
||||
Keys.onReturnPressed: event.accepted = !multiline && !acceptReturn
|
||||
Keys.onEnterPressed: event.accepted = !multiline && !acceptReturn
|
||||
Keys.forwardTo: [root]
|
||||
KeyNavigation.priority: !!root.tabNavItem ? KeyNavigation.BeforeItem : KeyNavigation.AfterItem
|
||||
KeyNavigation.tab: root.tabNavItem
|
||||
@@ -199,6 +397,10 @@ Item {
|
||||
onCursorRectangleChanged: flick.ensureVisible(cursorRectangle)
|
||||
onActiveFocusChanged: if (root.pristine) root.pristine = false
|
||||
onTextChanged: {
|
||||
if (previousText === text) {
|
||||
// Not sure why, but the textChanged event was triggered even if it didn't really
|
||||
return
|
||||
}
|
||||
root.dirty = true
|
||||
if (root.maximumLength > 0) {
|
||||
if (text.length > root.maximumLength) {
|
||||
@@ -214,13 +416,26 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
cursorDelegate: Rectangle {
|
||||
color: Theme.palette.primaryColor1
|
||||
implicitWidth: 2
|
||||
implicitHeight: 22
|
||||
radius: 1
|
||||
visible: edit.cursorVisible
|
||||
|
||||
SequentialAnimation on visible {
|
||||
loops: Animation.Infinite
|
||||
running: edit.cursorVisible
|
||||
PropertyAnimation { to: false; duration: 600; }
|
||||
PropertyAnimation { to: true; duration: 600; }
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: placeholder
|
||||
visible: (edit.text.length === 0)
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: root.rightPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.fill: parent
|
||||
verticalAlignment: parent.verticalAlignment
|
||||
font.pixelSize: 15
|
||||
elide: StatusBaseText.ElideRight
|
||||
font.family: Theme.palette.baseFont.name
|
||||
@@ -269,13 +484,15 @@ Item {
|
||||
StatusSmartIdenticon {
|
||||
id: identicon
|
||||
|
||||
icon.width: !root.icon.emoji ? 20 : 30
|
||||
icon.height: !root.icon.emoji ? 20 : 30
|
||||
icon.width: 24
|
||||
icon.height: 24
|
||||
icon.background: root.icon.background
|
||||
icon.color: root.icon.color
|
||||
icon.letterSize: root.icon.letterSize
|
||||
icon.emoji: root.icon.emoji
|
||||
icon.name: !root.icon.emoji ? root.icon.name : ""
|
||||
icon.isLetterIdenticon: root.icon.isLetterIdenticon
|
||||
name: root.letterIconName
|
||||
|
||||
MouseArea {
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
|
||||
@@ -25,8 +25,10 @@ Rectangle {
|
||||
property StatusIconSettings icon: StatusIconSettings {
|
||||
width: 36
|
||||
height: 36
|
||||
letterSize: 20
|
||||
charactersLen: 2
|
||||
}
|
||||
property alias ringSettings: identicon.ringSettings
|
||||
|
||||
property int type: StatusChatInfoButton.Type.PublicChat
|
||||
property alias tooltip: statusToolTip
|
||||
property alias sensor: sensor
|
||||
|
||||
@@ -11,6 +11,8 @@ TabButton {
|
||||
property string name: ""
|
||||
property bool highlighted: false
|
||||
|
||||
property alias identicon: identicon
|
||||
|
||||
implicitWidth: 40
|
||||
implicitHeight: 40
|
||||
|
||||
@@ -32,7 +34,6 @@ TabButton {
|
||||
icon.name: statusIconTabButton.icon.name
|
||||
icon.color: (statusIconTabButton.hovered || highlighted || statusIconTabButton.checked) ? Theme.palette.primaryColor1 : statusIconTabButton.icon.color
|
||||
icon.isLetterIdenticon: statusIconTabButton.name !== "" && !statusIconTabButton.icon.source.toString()
|
||||
icon.letterSize: 15
|
||||
name: statusIconTabButton.name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ Item {
|
||||
}
|
||||
|
||||
function getSegmentsCount() {
|
||||
if (typeof settings.ringSpecModel.rowCount !== "undefined") {
|
||||
return settings.ringSpecModel.rowCount()
|
||||
}
|
||||
if (typeof settings.ringSpecModel.count !== "undefined") {
|
||||
return settings.ringSpecModel.count
|
||||
}
|
||||
@@ -86,6 +89,9 @@ Item {
|
||||
}
|
||||
|
||||
function getSegment(i) {
|
||||
if (typeof settings.ringSpecModel.rowCount !== "undefined") {
|
||||
return abstactItemModelWrapper.itemAt(i)
|
||||
}
|
||||
if (typeof settings.ringSpecModel.count !== "undefined") {
|
||||
return settings.ringSpecModel.get(i)
|
||||
}
|
||||
@@ -101,6 +107,14 @@ Item {
|
||||
return Math.max(1, units)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: abstactItemModelWrapper
|
||||
model: typeof settings.ringSpecModel.rowCount !== "undefined" ? settings.ringSpecModel : null
|
||||
delegate: Item {
|
||||
readonly property int segmentLength: model.segmentLength
|
||||
readonly property int colorId: model.colorId
|
||||
}
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
const context = getContext("2d")
|
||||
@@ -127,6 +141,11 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.settings
|
||||
onRingSpecModelChanged: requestPaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import QtGraphicalEffects 1.1
|
||||
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusImageCrop
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Controls
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief Draw a crop-window onto an image. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
Sizing
|
||||
|
||||
User resizes the control as needed. Control adapts and centers the crop
|
||||
window inside the available space
|
||||
|
||||
Performance
|
||||
|
||||
The \c StatusImageCrop uses Canvas to draw the overlay in multiple steps.
|
||||
Drawing with canvas is inefficient therefore keep the usage to a minimum,
|
||||
as in the required space
|
||||
|
||||
API
|
||||
|
||||
The setCropRect is the only way to set the content shown in the crop window.
|
||||
|
||||
/note "Fit window" used here reffer to optimum zoom to fit all the content
|
||||
in the view window; produces window borders if AR of the image is different
|
||||
than the window. This ia opposite to "Fill window" reffers to optimum zoom
|
||||
to fill all the view; parts of the content won't be visible if AR of the
|
||||
image is different than the AR of window
|
||||
/note The math is based on pixels so no subpixel accuracy. Didn't find it necessary for the current needs
|
||||
/note windowRect aspect ratio and cropRect aspect ratio are keept in sync
|
||||
|
||||
Example of two controls; top WindowStyle.Rounded, bottom WindowStyle.Rectangular
|
||||
\image eg-StatusImageCrop.png
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
StatusImageCrop {
|
||||
anchors.fill: parent
|
||||
|
||||
width: 100
|
||||
height: 100 * aspectRatio
|
||||
source: "qrc:/demoapp/data/logo-test-image.png"
|
||||
windowStyle: StatusImageCrop.WindowStyle.Rounded
|
||||
}
|
||||
\endqml
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: 400
|
||||
implicitHeight: 400 / aspectRatio
|
||||
|
||||
/*!
|
||||
\qmlproperty url StatusImageCrop::source
|
||||
Path to the image to be cropped
|
||||
*/
|
||||
/*required*/ property alias source: mainImage.source
|
||||
|
||||
enum WindowStyle {
|
||||
Rounded,
|
||||
Rectangular
|
||||
}
|
||||
/*!
|
||||
\qmlproperty WindowStyle StatusImageCrop::windowStyle
|
||||
Crop window styles
|
||||
\value Rounded ellipses bounded by cropRect rectangle
|
||||
\value Rectangular with rounded corners specified by radius
|
||||
*/
|
||||
property int windowStyle: StatusImageCrop.WindowStyle.Rectangular
|
||||
/*!
|
||||
\qmlproperty int StatusImageCrop::radius
|
||||
Valid only when windowStyle is Rounded
|
||||
*/
|
||||
property int radius: 8
|
||||
/*!
|
||||
\qmlproperty color StatusImageCrop::wallColor
|
||||
Color used outside the crop-rect. Tells user which part won't be available after cropping
|
||||
*/
|
||||
property color wallColor: Theme.palette.black
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::wallTransparency
|
||||
Transparency outside the crop-rect 0.0 - 1.0 (inclusive). Tells user which part won't
|
||||
be available after cropping
|
||||
*/
|
||||
property real wallTransparency: 0.7
|
||||
|
||||
/*!
|
||||
\qmlproperty bool StatusImageCrop::allowZoomToFitAllContent
|
||||
|
||||
\c true to limit the content to fit at minimum zoom; that's it, the user will see the entire image
|
||||
in the crop window, in case of aspect-ratio mismatch, allowing transparent/no-content borders in the crop window.
|
||||
\c false to limit the content to fill at minimum zoom; that's it, the user will see only part of the image
|
||||
in the crop window in case of aspect-ratio mismatch. This way, we don't allow transparent/no-content space
|
||||
*/
|
||||
property bool allowZoomToFitAllContent: false
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::minZoomScale
|
||||
Minimum allowed zoom. This is 1 if \c allowZoomToFitAllContent is true else depends on the
|
||||
source aspect ratio.
|
||||
*/
|
||||
readonly property real minZoomScale: allowZoomToFitAllContent ? 1 : d.zoomToFill
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::maxZoomScale
|
||||
Don't allow to zoom in more than maxZoomScale factor
|
||||
*/
|
||||
property real maxZoomScale: 10
|
||||
|
||||
/*!
|
||||
\qmlproperty rect StatusImageCrop::cropRect
|
||||
The content shown in the crop window. Values are expected to be in image coordinates.
|
||||
The crop-window aspect-ratio is adjusted to match the cropRect value.
|
||||
The default value shows an 1.0 AR to Fit Window
|
||||
e.g. to set the crop window to show and match image's AR use Qt.rect(0, 0, sourceSize.width, sourceSize.height)
|
||||
*/
|
||||
readonly property rect cropRect: d.cropRect
|
||||
/*!
|
||||
\qmlproperty rect StatusImageCrop::cropRect
|
||||
|
||||
The position and zis of the crop rectangle in item's space; without extra spacing
|
||||
*/
|
||||
readonly property alias cropWindow: d.cropWindow
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::aspectRatio
|
||||
*/
|
||||
readonly property real aspectRatio: d.cropRect.height !== 0 ? d.cropRect.width/d.cropRect.height : 0
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::zoomScale
|
||||
*/
|
||||
readonly property real zoomScale: d.currentZoom(sourceSize, Qt.size(d.cropRect.width, d.cropRect.height))
|
||||
|
||||
/*!
|
||||
\qmlproperty size StatusImageCrop::sourceSize
|
||||
|
||||
Image size if one is set. StatusImageCrop::cropRect is relative to the rect with top-corner
|
||||
(0,0) and StatusImageCrop::sourceSize
|
||||
|
||||
\sa StatusImageCrop::source
|
||||
*/
|
||||
readonly property size sourceSize: mainImage.sourceSize
|
||||
/*!
|
||||
\qmlproperty real StatusImageCrop::scrToImgScale
|
||||
|
||||
Screen crop window to \a cropRect ratio. Can be used to translate from
|
||||
screen coordinates into image coordinates e.g. to adjust \a cropRect accordingly
|
||||
|
||||
\note windowRect aspect-ratio and d.cropRect aspect-ratio are the same
|
||||
\sa StatusImageCrop::cropRect
|
||||
*/
|
||||
readonly property real scrToImgScale: windowRect.wW/d.cropRect.width
|
||||
|
||||
/*!
|
||||
\qmlmethod StatusImageCrop::setCropRect(rect)
|
||||
|
||||
The only way to set StatusImageCrop::cropRect from outside
|
||||
|
||||
The new rect will be adjusted to account for the zoom [1, StatusImageCrop::maxZoomScale]
|
||||
|
||||
\note the source image must be set and \c sourceSize valid otherwise, an error is logged to \c console
|
||||
\note If the new rect has a diferent area the crop window will adjust to the new AR
|
||||
*/
|
||||
function setCropRect(newRect /*rect*/) {
|
||||
if(newRect.width === 0 || newRect.height === 0)
|
||||
return
|
||||
if(root.sourceSize.width === 0 || root.sourceSize.height === 0)
|
||||
console.error("Wrong source size. Ensure source is set")
|
||||
|
||||
let n = newRect
|
||||
const s = root.sourceSize
|
||||
let nZoom = d.currentZoom(s, Qt.size(n.width, n.height))
|
||||
if(nZoom > root.maxZoomScale) {
|
||||
nZoom = root.maxZoomScale
|
||||
n = root.getZoomRect(nZoom)
|
||||
}
|
||||
else if(nZoom < root.minZoomScale) {
|
||||
nZoom = root.minZoomScale
|
||||
n = root.getZoomRect(nZoom)
|
||||
}
|
||||
|
||||
// Limit panning
|
||||
if((n.width/n.height) < (s.width/s.height)) {
|
||||
// Crop window narrower than source
|
||||
if(n.x < 0)
|
||||
n.x = 0
|
||||
const upBoundY = n.height > s.height ? s.height - n.height : 0
|
||||
if(n.y < upBoundY)
|
||||
n.y = upBoundY
|
||||
if((n.x + n.width) > s.width)
|
||||
n.x = s.width - n.width
|
||||
const loBoundY = n.height > s.height ? 0 : s.height - n.height
|
||||
if(n.y > loBoundY)
|
||||
n.y = loBoundY
|
||||
}
|
||||
else {
|
||||
// Crop window wider than source
|
||||
const leftBoundX = n.width > s.width ? s.width - n.width : 0
|
||||
if(n.x < leftBoundX)
|
||||
n.x = leftBoundX
|
||||
if(n.y < 0)
|
||||
n.y = 0
|
||||
const rightBoundY = n.width > s.width ? 0 : s.width - n.width
|
||||
if(n.x > rightBoundY)
|
||||
n.x = rightBoundY
|
||||
if((n.y + n.height) > s.height)
|
||||
n.y = s.height - n.height
|
||||
}
|
||||
d.cropRect = n
|
||||
}
|
||||
|
||||
function getZoomRect(scale /*real*/) {
|
||||
const oldCenter = root.rectCenter(root.cropRect)
|
||||
const inflatedRect = root.inflateRectBy(root.minimumCropRect(), 1/scale)
|
||||
return root.recenterRect(inflatedRect, oldCenter);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod StatusImageCrop::inflateRectBy(rect target, real scale)
|
||||
Inflates the curren \a target rectangle with the \a scale while keeping the center fixed
|
||||
*/
|
||||
function inflateRectBy(target /*rect*/, scale /*real*/) {
|
||||
const inflatedWidth = target.width * scale
|
||||
const inflatedHeight = target.height * scale
|
||||
return Qt.rect(target.x - (inflatedWidth - target.width)/2, target.y - (inflatedHeight - target.height)/2, inflatedWidth, inflatedHeight)
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod StatusImageCrop::rectCenter(rect target)
|
||||
Returns the center point of the \a target rectangle as a Qt.point
|
||||
*/
|
||||
function rectCenter(target /*rect*/) /*Qt.point*/ {
|
||||
return Qt.point(target.x + target.width/2, target.y + target.height/2)
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod StatusImageCrop::recenterRect(rect target)
|
||||
Move the \a target rectangle's center to a /a newCenter
|
||||
*/
|
||||
function recenterRect(target /*rect*/, newCenter/*point*/) {
|
||||
return Qt.rect(newCenter.x - target.width/2 , newCenter.y - target.height/2, target.width, target.height)
|
||||
}
|
||||
|
||||
//> cropRect for minimum zoom: 1.0
|
||||
function minimumCropRect() {
|
||||
const sourceAR = root.sourceSize.width/root.sourceSize.height
|
||||
const widthBound = sourceAR > root.aspectRatio
|
||||
const minCropSize = widthBound ? Qt.size(root.sourceSize.width, root.sourceSize.width/root.aspectRatio)
|
||||
: Qt.size(root.sourceSize.height * root.aspectRatio, root.sourceSize.height)
|
||||
let res = Qt.rect(widthBound ? 0 : -(root.sourceSize.width - minCropSize.width)/2, // x
|
||||
widthBound ? -(root.sourceSize.height - minCropSize.height)/2 : 0, // y
|
||||
minCropSize.width, minCropSize.height)
|
||||
return res
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property rect cropRect: fillContentInSquaredWindow(root.sourceSize)
|
||||
onCropRectChanged: windowRect.requestPaint()
|
||||
|
||||
readonly property real zoomToFill: {
|
||||
const rectangle = fillContentInSquaredWindow(root.sourceSize)
|
||||
return d.currentZoom(root.sourceSize, Qt.size(rectangle.width, rectangle.height))
|
||||
}
|
||||
|
||||
property rect cropWindow
|
||||
// Probably called from render thread, run async
|
||||
signal updateCropWindow(rect newRect)
|
||||
onUpdateCropWindow: cropWindow = newRect
|
||||
|
||||
function fillContentInSquaredWindow(c /*size*/) {
|
||||
if(c.width > c.height) {
|
||||
const border = (c.width - c.height)/2
|
||||
return Qt.rect(border, 0, c.height, c.height)
|
||||
}
|
||||
else {
|
||||
const border = (c.height - c.width)/2
|
||||
return Qt.rect(0, border, c.width, c.width)
|
||||
}
|
||||
}
|
||||
|
||||
//> 1.0 is the content represented by w fully inscribed in c
|
||||
function currentZoom(c /*size*/, w /*size*/) {
|
||||
const wScale = c.width/w.width
|
||||
const hScale = c.height/w.height
|
||||
return Math.max(wScale, hScale)
|
||||
}
|
||||
}
|
||||
|
||||
onWindowStyleChanged: windowRect.requestPaint()
|
||||
onSourceSizeChanged: d.cropRect = d.fillContentInSquaredWindow(sourceSize)
|
||||
|
||||
Canvas {
|
||||
id: windowRect
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
layer.enabled: true
|
||||
|
||||
property bool widthFit: (root.width / root.aspectRatio) <= root.height
|
||||
// Window width
|
||||
property real wW: widthFit ? root.width : root.height * root.aspectRatio
|
||||
// Window height
|
||||
property real wH: widthFit ? root.width / root.aspectRatio : root.height
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.save()
|
||||
// TODO: observed drawing artefacts at the edge. Draw one pixel more for now until root cause is found
|
||||
ctx.clearRect(0, 0, width + 1, height + 1)
|
||||
// Fill all with wallColor in order to clip the window from it
|
||||
ctx.fillStyle = Qt.rgba(root.wallColor.r, root.wallColor.g, root.wallColor.b, root.wallTransparency)
|
||||
ctx.fillRect(0, 0, width + 1, height + 1)
|
||||
// Cut opaque new pixels from background
|
||||
ctx.globalCompositeOperation = "source-out"
|
||||
// Draw the window
|
||||
ctx.beginPath()
|
||||
const cW = Qt.rect((width - wW)/2, (height - wH)/2, wW, wH)
|
||||
if(root.windowStyle === StatusImageCrop.WindowStyle.Rounded)
|
||||
ctx.ellipse(cW.x, cW.y, cW.width, cW.height)
|
||||
else if(root.windowStyle === StatusImageCrop.WindowStyle.Rectangular)
|
||||
ctx.roundedRect(cW.x, cW.y, cW.width, cW.height, root.radius, root.radius)
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
d.updateCropWindow(cW)
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: mainImage
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
z: windowRect.z - 1
|
||||
|
||||
// Transform to keep the center of the image window in x and y coordinates
|
||||
transform: [
|
||||
Translate {
|
||||
readonly property real s: mainImage.scale
|
||||
x: -(d.cropRect.x + d.cropRect.width/2)
|
||||
y: -(d.cropRect.y + d.cropRect.height/2)
|
||||
},
|
||||
Scale {
|
||||
xScale: root.scrToImgScale
|
||||
yScale: root.scrToImgScale
|
||||
}
|
||||
]
|
||||
// Align window center to window rect
|
||||
x: windowRect.x + windowRect.width/2
|
||||
y: windowRect.y + windowRect.height/2
|
||||
}
|
||||
}
|
||||
@@ -13,18 +13,15 @@ import StatusQ.Controls.Validators 0.1
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief The StatusInput control provides a generic user text input
|
||||
|
||||
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
StatusInput {
|
||||
label: "Label"
|
||||
charLimit: 30
|
||||
errorMessage: "Input doesn't match validator"
|
||||
|
||||
errorMessage: qsTr("Input doesn't match validator")
|
||||
input.clearable: true
|
||||
input.placeholderText: "Placeholder text"
|
||||
input.placeholderText: qsTr("Placeholder text")
|
||||
}
|
||||
\endqml
|
||||
|
||||
@@ -49,10 +46,32 @@ Item {
|
||||
|
||||
height: implicitHeight
|
||||
width: implicitWidth
|
||||
|
||||
/*!
|
||||
\qmlproperty alias StatusInput::input
|
||||
This property holds a reference to the TextEdit component.
|
||||
*/
|
||||
property alias input: statusBaseInput
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusInput::letterIconName
|
||||
This property holds a reference to the StatusBaseInput's letterIconName property.
|
||||
*/
|
||||
property alias letterIconName: statusBaseInput.letterIconName
|
||||
|
||||
/*!
|
||||
\qmlproperty alias StatusInput::valid
|
||||
This property holds a reference to the TextEdit's valid property.
|
||||
*/
|
||||
property alias valid: statusBaseInput.valid
|
||||
/*!
|
||||
\qmlproperty alias StatusInput::pending
|
||||
This property holds a reference to the TextEdit's pending property.
|
||||
*/
|
||||
property alias pending: statusBaseInput.pending
|
||||
/*!
|
||||
\qmlproperty alias StatusInput::text
|
||||
This property holds a reference to the TextEdit's text property.
|
||||
*/
|
||||
property alias text: statusBaseInput.text
|
||||
|
||||
/*!
|
||||
@@ -71,16 +90,48 @@ Item {
|
||||
\endqml
|
||||
*/
|
||||
property alias errorMessageCmp: errorMessage
|
||||
/*!
|
||||
\qmlproperty string StatusInput::label
|
||||
This property sets the label text.
|
||||
*/
|
||||
property string label: ""
|
||||
/*!
|
||||
\qmlproperty string StatusInput::secondaryLabel
|
||||
This property sets the secondary label text.
|
||||
*/
|
||||
property string secondaryLabel: ""
|
||||
/*!
|
||||
\qmlproperty int StatusInput::charLimit
|
||||
This property sets the character limit of the text input.
|
||||
*/
|
||||
property int charLimit: 0
|
||||
/*!
|
||||
\qmlproperty string StatusInput::errorMessage
|
||||
This property sets the error message text.
|
||||
*/
|
||||
property string errorMessage: ""
|
||||
/*!
|
||||
\qmlproperty real StatusBaseInput::leftPadding
|
||||
This property sets the leftComponentLoader's left padding.
|
||||
*/
|
||||
property real leftPadding: 16
|
||||
/*!
|
||||
\qmlproperty real StatusBaseInput::rightPadding
|
||||
This property sets the right padding.
|
||||
*/
|
||||
property real rightPadding: 16
|
||||
/*!
|
||||
\qmlproperty list StatusBaseInput::validators
|
||||
This property sets the list of validators to be considered.
|
||||
*/
|
||||
property list<StatusValidator> validators
|
||||
/*!
|
||||
\qmlproperty list StatusBaseInput::validators
|
||||
This property sets the list of async validators to be considered.
|
||||
*/
|
||||
property list<StatusAsyncValidator> asyncValidators
|
||||
/*!
|
||||
\qmlproperty int (enumeration ValidationMode)
|
||||
\qmlproperty int StatusInput::validationMode
|
||||
This property describes the validation mode
|
||||
|
||||
Note that this property should be always strictly compared with one of ValidationMode's value
|
||||
@@ -102,12 +153,31 @@ Item {
|
||||
\endqml
|
||||
*/
|
||||
property int validationMode: StatusInput.ValidationMode.OnlyWhenDirty
|
||||
/*!
|
||||
\qmlproperty string StatusBaseInput::validatedValue
|
||||
This property sets the validated value text.
|
||||
*/
|
||||
property string validatedValue
|
||||
|
||||
/*!
|
||||
\qmlproperty var StatusBaseInput::pendingValidators
|
||||
This property sets the pending validators to be considered.
|
||||
*/
|
||||
property var pendingValidators: []
|
||||
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when the icon is clicked.
|
||||
*/
|
||||
signal iconClicked()
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when a hard key is pressed passing as parameter the keyboard event.
|
||||
*/
|
||||
signal keyPressed(var event)
|
||||
/*!
|
||||
\qmlsignal
|
||||
This signal is emitted when the text edit is clicked.
|
||||
*/
|
||||
signal editClicked()
|
||||
|
||||
/*!
|
||||
@@ -133,9 +203,20 @@ Item {
|
||||
IgnoreInvalidInput // ignore the new content if it doesn't match
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty var StatusBaseInput::errors
|
||||
This property holds the validation errors.
|
||||
*/
|
||||
property var errors: ({})
|
||||
/*!
|
||||
\qmlproperty var StatusBaseInput::errors
|
||||
This property holds the validation async errors.
|
||||
*/
|
||||
property var asyncErrors: ({})
|
||||
|
||||
/*!
|
||||
\qmlmethod
|
||||
This function resets the text input validation and text.
|
||||
*/
|
||||
function reset() {
|
||||
statusBaseInput.valid = true
|
||||
statusBaseInput.pristine = true
|
||||
@@ -144,6 +225,10 @@ Item {
|
||||
}
|
||||
|
||||
property string _previousText: text
|
||||
/*!
|
||||
\qmlmethod
|
||||
This function validates the text input's text.
|
||||
*/
|
||||
function validate() {
|
||||
|
||||
if (!statusBaseInput.dirty && validationMode === StatusInput.ValidationMode.OnlyWhenDirty) {
|
||||
@@ -208,7 +293,10 @@ Item {
|
||||
root.validatedValue = root.text
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod
|
||||
This function updates the text input async validation.
|
||||
*/
|
||||
function updateAsyncValidity(validatorName, value, result) {
|
||||
if (!asyncErrors) {
|
||||
asyncErrors = {}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusItemPicker
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Controls
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief It presents a selectable item to the user. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-rectangle.html}{Rectangle}.
|
||||
|
||||
The \c StatusItemPicker is populated with the given properties data.
|
||||
|
||||
StatusItemPicker can be made as a RadioButton or CheckBox selectable item.
|
||||
|
||||
Example of how the component looks like:
|
||||
\image status_item_picker.png
|
||||
Example of how to use it:
|
||||
\qml
|
||||
StatusItemPicker {
|
||||
width: 300
|
||||
height: 40
|
||||
imageSource: model.imageSource
|
||||
name: model.name
|
||||
shortName: model.shortName
|
||||
selectorType: root.multiSelection ? StatusItemPicker.SelectorType.CheckBox : StatusItemPicker.SelectorType.RadioButton
|
||||
selected: model.selected
|
||||
radioGroup: content.radioGroup
|
||||
|
||||
onCheckedChanged: { // Some updates }
|
||||
}
|
||||
\endqml
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusItemPicker::image
|
||||
This property holds the image settings information.
|
||||
*/
|
||||
property StatusImageSettings image
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusItemPicker::name
|
||||
This property holds the main text or name to be displayed.
|
||||
*/
|
||||
property string name
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusItemPicker::shortName
|
||||
This property holds the secondary text or short name to be displayed.
|
||||
*/
|
||||
property string shortName
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusItemPicker::selectorType
|
||||
This property holds the selector type. Possible options are:
|
||||
\qml
|
||||
enum SelectorType {
|
||||
RadioButton,
|
||||
CheckBox
|
||||
}
|
||||
\endqml
|
||||
By default, RadioButton.
|
||||
*/
|
||||
property int selectorType: StatusItemPicker.SelectorType.RadioButton
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusItemPicker::selected
|
||||
This property holds if the item is selected.
|
||||
*/
|
||||
property bool selected
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusItemPicker::radioGroup
|
||||
This property holds the button group object the radiobutton belongs to.
|
||||
*/
|
||||
property ButtonGroup radioGroup
|
||||
|
||||
/*!
|
||||
\qmlsignal StatusItemPicker::checkedChanged(bool checked)
|
||||
This signal is emitted when the item is selected by pressing the radiobutton or the checkbox.
|
||||
*/
|
||||
signal checkedChanged(bool checked)
|
||||
|
||||
enum SelectorType {
|
||||
RadioButton,
|
||||
CheckBox
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property int minShortNameWidth: 50
|
||||
|
||||
function availableTextWidth() {
|
||||
return root.width - imageItem.width - row.spacing - shortNameItem.anchors.rightMargin - selector.width - selector.anchors.rightMargin - 24/*Margin between both texts*/
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 18
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 8
|
||||
|
||||
StatusIcon {
|
||||
id: imageItem
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
source: root.image.source ? root.image.source : ""
|
||||
width: root.image.width
|
||||
height: root.image.height
|
||||
visible: root.image.source !== undefined
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: nameItem
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: dummyNameItem.width > d.availableTextWidth() - d.minShortNameWidth ?
|
||||
d.availableTextWidth() - d.minShortNameWidth :
|
||||
dummyNameItem.width
|
||||
text: root.name
|
||||
color: Theme.palette.directColor1
|
||||
font.pixelSize: 15
|
||||
clip: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
// Dummy object just to exactly know the width needed by `name` and dynamically set a limit for nameItem and shortNameItem components
|
||||
StatusBaseText {
|
||||
id: dummyNameItem
|
||||
visible: false
|
||||
text: root.name
|
||||
font.pixelSize: 15
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: shortNameItem
|
||||
width: d.availableTextWidth() - nameItem.width < d.minShortNameWidth ?
|
||||
d.minShortNameWidth :
|
||||
d.availableTextWidth() - nameItem.width
|
||||
anchors.right: selector.left
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.shortName
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 15
|
||||
clip: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
|
||||
// 2 different options: Or with radio buttons or with checkboxes:
|
||||
Loader {
|
||||
id: selector
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.rightMargin: 18
|
||||
sourceComponent: root.selectorType === StatusItemPicker.SelectorType.RadioButton ? radioBtn : checkbox
|
||||
}
|
||||
|
||||
Component {
|
||||
id: radioBtn
|
||||
StatusRadioButton {
|
||||
ButtonGroup.group: root.radioGroup
|
||||
checked: root.selected
|
||||
onCheckedChanged: {
|
||||
root.selected = checked
|
||||
root.checkedChanged(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: checkbox
|
||||
StatusCheckBox {
|
||||
checked: root.selected
|
||||
onCheckedChanged: {
|
||||
root.selected = checked
|
||||
root.checkedChanged(checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End of Content item
|
||||
@@ -4,15 +4,91 @@ import QtQuick.Controls 2.14
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusPasswordStrengthIndicator
|
||||
\inherits StatusProgressBar
|
||||
\inqmlmodule StatusQ.Controls
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief It allows visualizing the password strength. Inherits from StatusProgressBar.
|
||||
|
||||
The \c StatusPasswordStrengthIndicator changes its text and color depending on the selected StatusPasswordStrengthIndicator::strength.
|
||||
|
||||
Since it inherits from \c StatusProgressBar, `value` property will change the length of the bar accordingly.
|
||||
|
||||
When `value` property is 0, StatusPasswordStrengthIndicator::strength is automatically set to StatusPasswordStrengthIndicator.Strength.None and no text is displayed.
|
||||
|
||||
Example of how the control looks like:
|
||||
\image status_password_strength_indicator.png
|
||||
|
||||
Example of how to use it:
|
||||
|
||||
\qml
|
||||
StatusPasswordStrengthIndicator {
|
||||
id: weakPw
|
||||
strength: StatusPasswordStrengthIndicator.Strength.Weak
|
||||
value: 0.5
|
||||
}
|
||||
|
||||
StatusPasswordStrengthIndicator {
|
||||
id: sosoPw
|
||||
strength: StatusPasswordStrengthIndicator.Strength.SoSo
|
||||
value: 0.25
|
||||
}
|
||||
\endqml
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
StatusProgressBar {
|
||||
id: control
|
||||
|
||||
/*!
|
||||
\qmlproperty string StatusPasswordStrengthIndicator::strength
|
||||
This property holds the password strength value. Possible values are:
|
||||
\list
|
||||
\li StatusPasswordStrengthIndicator.Strength.None
|
||||
\li StatusPasswordStrengthIndicator.Strength.VeryWeak
|
||||
\li StatusPasswordStrengthIndicator.Strength.Weak
|
||||
\li StatusPasswordStrengthIndicator.Strength.SoSo
|
||||
\li StatusPasswordStrengthIndicator.Strength.Good
|
||||
\li StatusPasswordStrengthIndicator.Strength.Great
|
||||
\endlist
|
||||
*/
|
||||
property var strength
|
||||
property string labelVeryWeak: "Very weak"
|
||||
property string labelWeak: "Weak"
|
||||
property string labelSoso: "So-so"
|
||||
property string labelGood: "Good"
|
||||
property string labelGreat: "Great"
|
||||
/*!
|
||||
\qmlproperty string StatusPasswordStrengthIndicator::labelVeryWeak
|
||||
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.VeryWeak.
|
||||
|
||||
Default value: "Very weak"
|
||||
*/
|
||||
property string labelVeryWeak: qsTr("Very weak")
|
||||
/*!
|
||||
\qmlproperty string StatusPasswordStrengthIndicator::labelWeak
|
||||
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Weak.
|
||||
|
||||
Default value: "Weak"
|
||||
*/
|
||||
property string labelWeak: qsTr("Weak")
|
||||
/*!
|
||||
\qmlproperty string StatusPasswordStrengthIndicator::labelSoso
|
||||
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.SoSo.
|
||||
|
||||
Default value: "So-so"
|
||||
*/
|
||||
property string labelSoso: qsTr("So-so")
|
||||
/*!
|
||||
\qmlproperty string StatusPasswordStrengthIndicator::labelGood
|
||||
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Good.
|
||||
|
||||
Default value: "Good"
|
||||
*/
|
||||
property string labelGood: qsTr("Good")
|
||||
/*!
|
||||
\qmlproperty string StatusPasswordStrengthIndicator::labelGreat
|
||||
This property holds the text shown when the strength is StatusPasswordStrengthIndicator.Strength.Great.
|
||||
|
||||
Default value: "Great"
|
||||
*/
|
||||
property string labelGreat: qsTr("Great")
|
||||
|
||||
enum Strength {
|
||||
None, // 0
|
||||
|
||||
@@ -11,7 +11,14 @@ Button {
|
||||
|
||||
property color bgColor: Theme.palette.baseColor2
|
||||
property color contentColor: Theme.palette.baseColor1
|
||||
signal clicked()
|
||||
property var type: StatusPickerButton.Type.Next
|
||||
property int lateralMargins: 16
|
||||
property int textPixelSize: 15
|
||||
|
||||
enum Type {
|
||||
Next,
|
||||
Down
|
||||
}
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
@@ -20,31 +27,57 @@ Button {
|
||||
anchors.fill: parent
|
||||
radius: 8
|
||||
color: root.bgColor
|
||||
}
|
||||
StatusIcon {
|
||||
id: nextIcon
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
icon: "next"
|
||||
color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1)
|
||||
? root.contentColor : Theme.palette.directColor1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.fill: parent
|
||||
state: root.type === StatusPickerButton.Type.Next ? "NEXT" : "DOWN"
|
||||
|
||||
StatusBaseText {
|
||||
id: textLabel
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: 16
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - icon.width - anchors.rightMargin - anchors.leftMargin - icon.anchors.rightMargin - icon.anchors.leftMargin
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.pixelSize: 15
|
||||
font.pixelSize: root.textPixelSize
|
||||
color: root.contentColor
|
||||
text: root.text
|
||||
clip: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StatusIcon {
|
||||
id: icon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: !Qt.colorEqual(root.contentColor, Theme.palette.baseColor1) ? root.contentColor : Theme.palette.directColor1
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "NEXT"
|
||||
PropertyChanges {target: icon; icon: "next"}
|
||||
PropertyChanges {target: icon; anchors.left: undefined }
|
||||
PropertyChanges {target: icon; anchors.right: parent.right }
|
||||
PropertyChanges {target: icon; anchors.rightMargin: root.lateralMargins / 2 }
|
||||
PropertyChanges {target: icon; anchors.leftMargin: root.lateralMargins / 2 }
|
||||
PropertyChanges {target: textLabel; anchors.left: parent.left }
|
||||
PropertyChanges {target: textLabel; anchors.right: undefined }
|
||||
PropertyChanges {target: textLabel; anchors.rightMargin: undefined }
|
||||
PropertyChanges {target: textLabel; anchors.leftMargin: root.lateralMargins }
|
||||
},
|
||||
State {
|
||||
name: "DOWN"
|
||||
PropertyChanges {target: icon; icon: "chevron-down"}
|
||||
PropertyChanges {target: icon; anchors.left: parent.left }
|
||||
PropertyChanges {target: icon; anchors.right: undefined }
|
||||
PropertyChanges {target: icon; anchors.rightMargin: root.lateralMargins / 2 }
|
||||
PropertyChanges {target: icon; anchors.leftMargin: root.lateralMargins }
|
||||
PropertyChanges {target: textLabel; anchors.left: icon.right }
|
||||
PropertyChanges {target: textLabel; anchors.right: undefined }
|
||||
PropertyChanges {target: textLabel; anchors.rightMargin: root.lateralMargins / 2 }
|
||||
PropertyChanges {target: textLabel; anchors.leftMargin: undefined }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -52,8 +85,6 @@ Button {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.clicked();
|
||||
}
|
||||
onClicked: { root.clicked() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,24 @@ ProgressBar {
|
||||
radius: 5
|
||||
|
||||
StatusBaseText {
|
||||
id: textItem
|
||||
anchors.centerIn: parent
|
||||
property bool _fittedInBar: width < bar.width ? true : false
|
||||
text: control.text
|
||||
font.pixelSize: 12
|
||||
color: Theme.palette.indirectColor1
|
||||
Component.onCompleted: opacity = width < bar.width ? 1 : 0
|
||||
on_FittedInBarChanged: {
|
||||
if (_fittedInBar && opacity == 0) {
|
||||
fadeIn.start();
|
||||
}
|
||||
else if (!_fittedInBar) {
|
||||
fadeIn.stop();
|
||||
opacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
OpacityAnimator { id: fadeIn; target: textItem; from: 0; to: 1; duration: 250 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,102 @@ import StatusQ.Popups 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
/*!
|
||||
\qmltype StatusSeedPhraseInput
|
||||
\inherits Item
|
||||
\inqmlmodule StatusQ.Controls
|
||||
\since StatusQ.Controls 0.1
|
||||
\brief Displays a text input with suggestions. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-item.html}{Item}.
|
||||
|
||||
The \c StatusSeedPhraseInput item displays a text input with suggestions filtered based on the typed text.
|
||||
For example:
|
||||
|
||||
\qml
|
||||
StatusSeedPhraseInput {
|
||||
id: statusSeedInput
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
textEdit.input.anchors.leftMargin: 16
|
||||
textEdit.input.anchors.rightMargin: 16
|
||||
textEdit.input.anchors.topMargin: 11
|
||||
leftComponentText: "1"
|
||||
inputList: ListModel {
|
||||
ListElement {
|
||||
seedWord: "panda"
|
||||
}
|
||||
ListElement {
|
||||
seedWord: "posible"
|
||||
}
|
||||
ListElement {
|
||||
seedWord: "wing"
|
||||
}
|
||||
}
|
||||
}
|
||||
\endqml
|
||||
|
||||
\image status_seed_phrase_input.png
|
||||
|
||||
For a list of components available see StatusQ.
|
||||
*/
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 162
|
||||
height: 44
|
||||
/*!
|
||||
\qmlproperty alias StatusSeedPhraseInput::textEdit
|
||||
This property is an alias to the StatusInput's textEdit property.
|
||||
*/
|
||||
property alias textEdit: seedWordInput
|
||||
/*!
|
||||
\qmlproperty alias StatusSeedPhraseInput::text
|
||||
This property is an alias to the StatusInput's text property.
|
||||
*/
|
||||
property alias text: seedWordInput.text
|
||||
/*!
|
||||
\qmlproperty string StatusSeedPhraseInput::leftComponentText
|
||||
This property sets the StatusInput's left component's text.
|
||||
*/
|
||||
property string leftComponentText: ""
|
||||
/*!
|
||||
\qmlproperty ListModel StatusSeedPhraseInput::inputList
|
||||
This property holds the filtered words list based on the user's
|
||||
input text.
|
||||
*/
|
||||
property ListModel inputList: ListModel { }
|
||||
/*!
|
||||
\qmlproperty ListModel StatusSeedPhraseInput::filteredList
|
||||
This signal is emitted when the user selects a word from
|
||||
the suggestions list, either by clicking on it or by completing
|
||||
typing 4 charactersnd passes as a parameter the selected word.
|
||||
The corresponding handler is \c onDoneInsertingWord
|
||||
*/
|
||||
property ListModel filteredList: ListModel { }
|
||||
/*!
|
||||
\qmlsignal doneInsertingWord
|
||||
This signal is emitted when the user selects a word from the suggestions list
|
||||
either by mouse click or by typing 4 characters that match and passes as a parameter
|
||||
the selected word. The corresponding handler is \c onDoneInsertingWord.
|
||||
*/
|
||||
signal doneInsertingWord(string word)
|
||||
/*!
|
||||
\qmlsignal keyPressed
|
||||
This signal is emitted when the user presses a keyboard key and passes as a
|
||||
parameter the event. The corresponding handler is \c onKeyPressed.
|
||||
*/
|
||||
signal keyPressed(var event)
|
||||
/*!
|
||||
\qmlsignal editClicked
|
||||
This signal is emitted when the user clicks inside the StatusInput.
|
||||
The corresponding handler is \c onEditClicked
|
||||
*/
|
||||
signal editClicked()
|
||||
|
||||
function setWord(word) {
|
||||
seedWordInput.ignoreTextChange = true
|
||||
seedWordInput.input.edit.text = word
|
||||
}
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (root.activeFocus) {
|
||||
seedWordInput.input.edit.forceActiveFocus();
|
||||
@@ -27,39 +110,61 @@ Item {
|
||||
}
|
||||
|
||||
StatusInput {
|
||||
property bool ignoreTextChange: false
|
||||
|
||||
id: seedWordInput
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: parent.height
|
||||
input.anchors.topMargin: 0
|
||||
input.anchors.leftMargin: 0
|
||||
input.anchors.rightMargin: 0
|
||||
input.anchors.bottom: input.parent.bottom
|
||||
input.leftComponent: StatusBaseText {
|
||||
text: root.leftComponentText
|
||||
color: seedWordInput.input.edit.activeFocus ?
|
||||
Theme.palette.primaryColor1 : Theme.palette.baseColor1
|
||||
font.pixelSize: 15
|
||||
}
|
||||
input.acceptReturn: true
|
||||
onTextChanged: {
|
||||
if (ignoreTextChange) {
|
||||
ignoreTextChange = false
|
||||
return
|
||||
}
|
||||
filteredList.clear();
|
||||
if (text !== "") {
|
||||
for (var i = 0; i < inputList.count;i++) {
|
||||
if (inputList.get(i).seedWord.startsWith(text)) {
|
||||
let textToCheck = text.trim()
|
||||
if (textToCheck !== "") {
|
||||
for (var i = 0; i < inputList.count; i++) {
|
||||
if (inputList.get(i).seedWord.startsWith(textToCheck)) {
|
||||
filteredList.insert(filteredList.count, {"seedWord": inputList.get(i).seedWord});
|
||||
}
|
||||
}
|
||||
seedSuggestionsList.model = filteredList;
|
||||
if ((text.length === 3) && (filteredList.count === 1) &&
|
||||
((input.edit.keyEvent !== Qt.Key_Backspace) && (input.edit.keyEvent !== Qt.Key_Delete))) {
|
||||
seedWordInput.text = filteredList.get(0).seedWord;
|
||||
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length;
|
||||
if (filteredList.count === 1 && input.edit.keyEvent !== Qt.Key_Backspace
|
||||
&& input.edit.keyEvent !== Qt.Key_Delete
|
||||
&& filteredList.get(0).seedWord.trim() === textToCheck) {
|
||||
seedWordInput.input.edit.cursorPosition = textToCheck.length;
|
||||
seedSuggestionsList.model = 0;
|
||||
root.doneInsertingWord(seedWordInput.text);
|
||||
root.doneInsertingWord(textToCheck);
|
||||
}
|
||||
} else {
|
||||
seedSuggestionsList.model = 0;
|
||||
}
|
||||
}
|
||||
onKeyPressed: {
|
||||
if (input.edit.keyEvent === Qt.Key_Tab || input.edit.keyEvent === Qt.Key_Return || input.edit.keyEvent === Qt.Key_Enter) {
|
||||
if (!!text && seedSuggestionsList.count > 0) {
|
||||
seedSuggestionsList.completeWordFill(filteredList.get(seedSuggestionsList.currentIndex).seedWord)
|
||||
event.accepted = true
|
||||
return
|
||||
}
|
||||
}
|
||||
if (input.edit.keyEvent === Qt.Key_Down) {
|
||||
seedSuggestionsList.incrementCurrentIndex()
|
||||
}
|
||||
if (input.edit.keyEvent === Qt.Key_Up) {
|
||||
seedSuggestionsList.decrementCurrentIndex()
|
||||
}
|
||||
root.keyPressed(event);
|
||||
}
|
||||
onEditClicked: {
|
||||
@@ -101,6 +206,19 @@ Item {
|
||||
anchors.topMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
|
||||
onCountChanged: {
|
||||
seedSuggestionsList.currentIndex = 0
|
||||
}
|
||||
|
||||
function completeWordFill(seedWord) {
|
||||
let seedWordTrimmed = seedWord.trim();
|
||||
root.setWord(seedWordTrimmed);
|
||||
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length;
|
||||
seedSuggestionsList.model = 0;
|
||||
root.doneInsertingWord(seedWordTrimmed);
|
||||
}
|
||||
|
||||
clip: true
|
||||
ScrollBar.vertical: ScrollBar { }
|
||||
delegate: Item {
|
||||
@@ -110,7 +228,8 @@ Item {
|
||||
Rectangle {
|
||||
width: seedSuggestionsList.width
|
||||
height: parent.height
|
||||
color: mouseArea.containsMouse? Theme.palette.primaryColor1 : "transparent"
|
||||
color: mouseArea.containsMouse || index === seedSuggestionsList.currentIndex ?
|
||||
Theme.palette.primaryColor1 : "transparent"
|
||||
}
|
||||
StatusBaseText {
|
||||
id: suggWord
|
||||
@@ -118,7 +237,7 @@ Item {
|
||||
anchors.leftMargin: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: seedWord
|
||||
color: mouseArea.containsMouse ? Theme.palette.indirectColor1 : Theme.palette.directColor1
|
||||
color: mouseArea.containsMouse || index === seedSuggestionsList.currentIndex ? Theme.palette.indirectColor1 : Theme.palette.directColor1
|
||||
font.pixelSize: 13
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
@@ -129,10 +248,7 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
seedWordInput.text = seedWord;
|
||||
seedWordInput.input.edit.cursorPosition = seedWordInput.text.length;
|
||||
root.doneInsertingWord(seedWordInput.text);
|
||||
seedSuggestionsList.model = 0;
|
||||
seedSuggestionsList.completeWordFill(seedWord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ Item {
|
||||
property alias color: edit.color
|
||||
property alias font: edit.font
|
||||
property alias focussed: edit.activeFocus
|
||||
property alias verticalAlignmet: edit.verticalAlignment
|
||||
property alias verticalAlignment: edit.verticalAlignment
|
||||
property alias horizontalAlignment: edit.horizontalAlignment
|
||||
|
||||
signal linkActivated(string link)
|
||||
|
||||
@@ -6,37 +6,35 @@ import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
/*!
|
||||
/note beware, the slider processes the mouse events only in its control space. So it must be at least as high
|
||||
as the /c handle so user can grab it fully.
|
||||
*/
|
||||
Slider {
|
||||
id: statusSlider
|
||||
|
||||
implicitWidth: 360
|
||||
implicitHeight: 4
|
||||
implicitHeight: Math.max(handle.implicitHeight, background.implicitHeight)
|
||||
|
||||
leftPadding: 0
|
||||
|
||||
background: Rectangle {
|
||||
id: bgRect
|
||||
|
||||
x: statusSlider.leftPadding
|
||||
y: statusSlider.topPadding + statusSlider.availableHeight / 2 - height / 2
|
||||
implicitWidth: 100
|
||||
implicitHeight: 4
|
||||
width: statusSlider.availableWidth
|
||||
height: implicitHeight
|
||||
color: {
|
||||
if (statusSlider.value === statusSlider.to) {
|
||||
return Theme.palette.primaryColor1
|
||||
}
|
||||
return Theme.palette.primaryColor3
|
||||
}
|
||||
color: Theme.palette.primaryColor3
|
||||
radius: 2
|
||||
|
||||
Rectangle {
|
||||
width: statusSlider.visualPosition * parent.width
|
||||
height: parent.height
|
||||
color: Theme.palette.primaryColor1
|
||||
radius: 2
|
||||
anchors.fill: parent
|
||||
visible: statusSlider.value > statusSlider.from && statusSlider.value < statusSlider.to
|
||||
gradient: Gradient {
|
||||
GradientStop { color: Theme.palette.primaryColor1 ; position: 0 }
|
||||
GradientStop { color: Theme.palette.primaryColor3 ; position: (((statusSlider.value - statusSlider.from) * 100) / (statusSlider.to - statusSlider.from) / 100).toFixed(2) }
|
||||
GradientStop { color: Theme.palette.primaryColor1 ; position: (((statusSlider.value - statusSlider.from) * 100) / (statusSlider.to - statusSlider.from) / 100).toFixed(2) }
|
||||
GradientStop { color: Theme.palette.primaryColor3 ; position: 1 }
|
||||
orientation: Gradient.Horizontal
|
||||
}
|
||||
}
|
||||
} // background
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ TabBar {
|
||||
|
||||
background: Rectangle {
|
||||
implicitHeight: 36
|
||||
color: Theme.palette.directColor7
|
||||
color: Theme.palette.statusSwitchTab.barBackgroundColor
|
||||
radius: 8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ TabButton {
|
||||
StatusBaseText {
|
||||
id: label
|
||||
text: statusSwitchTabButton.text
|
||||
color: Theme.palette.primaryColor1
|
||||
color: statusSwitchTabButton.checked ?
|
||||
Theme.palette.statusSwitchTab.selectedTextColor :
|
||||
Theme.palette.statusSwitchTab.textColor
|
||||
font.weight: Font.Medium
|
||||
font.pixelSize: 15
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -36,7 +38,7 @@ TabButton {
|
||||
implicitHeight: 36
|
||||
implicitWidth: 148
|
||||
color: statusSwitchTabButton.checked ?
|
||||
Theme.palette.statusSwitchTab.backgroundColor :
|
||||
Theme.palette.statusSwitchTab.buttonBackgroundColor :
|
||||
"transparent"
|
||||
radius: 8
|
||||
layer.enabled: true
|
||||
|
||||
@@ -10,7 +10,7 @@ import StatusQ.Controls 0.1
|
||||
Item {
|
||||
id: control
|
||||
property string selectedColor
|
||||
property string label: "Account color"
|
||||
property string label: qsTr("Account color")
|
||||
property var model
|
||||
property bool enabled: true
|
||||
|
||||
@@ -45,7 +45,7 @@ Item {
|
||||
return true
|
||||
}
|
||||
// Check the colors in the other theme
|
||||
const currentColor = Utils.getThemeAccountColor(upperCaseColor, Theme.palette.accountColors)
|
||||
const currentColor = Utils.getThemeAccountColor(upperCaseColor, Theme.palette.userCustomizationColors)
|
||||
if (!currentColor) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import StatusQ.Core.Utils 0.1
|
||||
StatusValidator {
|
||||
name: "addressOrEns"
|
||||
|
||||
errorMessage: "Please enter a valid address or ENS name."
|
||||
errorMessage: qsTr("Please enter a valid address or ENS name.")
|
||||
|
||||
validate: function (t) {
|
||||
return Utils.isValidAddress(t) || Utils.isValidEns(t) ?
|
||||
|
||||
@@ -4,7 +4,7 @@ import StatusQ.Core.Utils 0.1
|
||||
StatusValidator {
|
||||
name: "address"
|
||||
|
||||
errorMessage: "Please enter a valid address."
|
||||
errorMessage: qsTr("Please enter a valid address.")
|
||||
|
||||
validate: function (t) {
|
||||
return Utils.isValidAddress(t) ? true : { actual: t }
|
||||
|
||||
@@ -6,7 +6,7 @@ StatusAsyncValidator {
|
||||
|
||||
name: "asyncEns"
|
||||
|
||||
errorMessage: "ENS name could not be resolved in to an address"
|
||||
errorMessage: qsTr("ENS name could not be resolved in to an address")
|
||||
|
||||
validate: function (asyncResult) {
|
||||
return Utils.isValidAddress(asyncResult)
|
||||
|
||||
@@ -5,7 +5,7 @@ Item {
|
||||
id: statusAsyncValidator
|
||||
|
||||
property string name: ""
|
||||
property string errorMessage: "invalid input"
|
||||
property string errorMessage: qsTr("invalid input")
|
||||
signal asyncComplete(var result)
|
||||
signal validationComplete(var value, bool valid)
|
||||
|
||||
|
||||
@@ -45,10 +45,10 @@ StatusValidator {
|
||||
property real top
|
||||
|
||||
name: "floatValidator"
|
||||
errorMessage: "Please enter a valid numeric value."
|
||||
errorMessage: qsTr("Please enter a valid numeric value.")
|
||||
|
||||
validate: function (t) {
|
||||
return !isNaN(t) && t > bottom && t <= top ? true : {
|
||||
return !isNaN(t) && t >= bottom && t <= top ? true : {
|
||||
bottom: bottom,
|
||||
top: top,
|
||||
actual: t
|
||||
|
||||
@@ -47,7 +47,7 @@ StatusValidator {
|
||||
property int top
|
||||
|
||||
name: "intValidator"
|
||||
errorMessage: "Please enter a valid numeric value."
|
||||
errorMessage: qsTr("Please enter a valid numeric value.")
|
||||
validatorObj: IntValidator { bottom: root.bottom; locale: root.locale; top: root.top }
|
||||
|
||||
validate: function (t) {
|
||||
|
||||
@@ -5,7 +5,7 @@ StatusValidator {
|
||||
|
||||
name: "url"
|
||||
|
||||
errorMessage: "Please enter a valid URL"
|
||||
errorMessage: qsTr("Please enter a valid URL")
|
||||
|
||||
validate: function (value) {
|
||||
return Utils.isURL(value);
|
||||
|
||||
@@ -5,7 +5,7 @@ QtObject {
|
||||
id: statusValidator
|
||||
|
||||
property string name: ""
|
||||
property string errorMessage: "invalid input"
|
||||
property string errorMessage: qsTr("invalid input")
|
||||
property var validatorObj
|
||||
|
||||
property var validate: function (value) {
|
||||
|
||||
@@ -9,6 +9,7 @@ StatusChatListCategoryItemButton 0.1 StatusChatListCategoryItemButton.qml
|
||||
StatusColorSelector 0.1 StatusColorSelector.qml
|
||||
StatusIconTabButton 0.1 StatusIconTabButton.qml
|
||||
StatusIdenticonRing 0.1 StatusIdenticonRing.qml
|
||||
StatusItemPicker 0.1 StatusItemPicker.qml
|
||||
StatusNavBarTabButton 0.1 StatusNavBarTabButton.qml
|
||||
StatusTabBarIconButton 0.1 StatusTabBarIconButton.qml
|
||||
StatusToolTip 0.1 StatusToolTip.qml
|
||||
@@ -36,3 +37,4 @@ StatusWalletColorButton 0.1 StatusWalletColorButton.qml
|
||||
StatusWalletColorSelect 0.1 StatusWalletColorSelect.qml
|
||||
StatusColorSelectorGrid 0.1 StatusColorSelectorGrid.qml
|
||||
StatusSeedPhraseInput 0.1 StatusSeedPhraseInput.qml
|
||||
StatusImageCrop 0.1 StatusImageCrop.qml
|
||||
|
||||
@@ -14,7 +14,7 @@ import StatusQ.Core.Theme 0.1
|
||||
\qml
|
||||
StatusBaseText {
|
||||
width: 240
|
||||
text: "Hello World!"
|
||||
text: qsTr("Hello World!")
|
||||
font.pixelSize: 24
|
||||
color: Theme.pallete.directColor1
|
||||
}
|
||||
|
||||
@@ -2,20 +2,24 @@ import QtQuick 2.13
|
||||
import StatusQ.Core 0.1
|
||||
|
||||
QtObject {
|
||||
id: statusIconSettings
|
||||
id: root
|
||||
|
||||
property string name
|
||||
property real width
|
||||
property real height
|
||||
property color color
|
||||
property color textColor: Qt.rgba(255, 255, 255, 0.7)
|
||||
property color disabledColor
|
||||
property url source
|
||||
property int rotation
|
||||
property bool isLetterIdenticon
|
||||
property int letterSize
|
||||
property int letterSize: emoji ? 11 : (charactersLen == 1 ? _oneLetterSize : _twoLettersSize)
|
||||
property int charactersLen: 1
|
||||
property string emoji
|
||||
property string emojiSize
|
||||
property string emojiSize: _emojiSize
|
||||
property StatusIconBackgroundSettings background: StatusIconBackgroundSettings {}
|
||||
|
||||
// can't have QtObject { id: d } inside QtObject, using '_' to indicate private
|
||||
readonly property int _oneLetterSize: Math.max(15, root.width / 2.0)
|
||||
readonly property int _twoLettersSize: Math.max(12, root.width / 2.2)
|
||||
readonly property string _emojiSize: "%1x%1".arg(Math.max(16, root.width * 0.6))
|
||||
}
|
||||
|
||||
@@ -147,15 +147,19 @@ ThemePalette {
|
||||
miscColor11: getColor('yellow2')
|
||||
miscColor12: getColor('green6')
|
||||
|
||||
accountColors: [
|
||||
getColor('blue5'),
|
||||
getColor('yellow2'),
|
||||
"#E6ABFC",
|
||||
getColor('moss2'),
|
||||
"#FB8383",
|
||||
getColor('green4'),
|
||||
"#ADA3FF",
|
||||
getColor('brown3')
|
||||
userCustomizationColors: [
|
||||
"#AAC6FF",
|
||||
"#887AF9",
|
||||
"#51D0F0",
|
||||
"#D37EF4",
|
||||
"#FA6565",
|
||||
"#FFCA0F",
|
||||
"#93DB33",
|
||||
"#10A88E",
|
||||
"#AD4343",
|
||||
"#EAD27B",
|
||||
"silver", // update me when figma is ready
|
||||
"darkgrey", // update me when figma is ready
|
||||
]
|
||||
|
||||
identiconRingColors: ["#000000", "#726F6F", "#C4C4C4", "#E7E7E7", "#FFFFFF", "#00FF00",
|
||||
@@ -226,7 +230,10 @@ ThemePalette {
|
||||
}
|
||||
|
||||
property QtObject statusSwitchTab: QtObject {
|
||||
property color backgroundColor: baseColor3
|
||||
property color buttonBackgroundColor: primaryColor1
|
||||
property color barBackgroundColor: primaryColor3
|
||||
property color selectedTextColor: white
|
||||
property color textColor: primaryColor1
|
||||
}
|
||||
|
||||
property QtObject statusSelect: QtObject {
|
||||
|
||||
@@ -145,15 +145,19 @@ ThemePalette {
|
||||
miscColor11: getColor('brown2')
|
||||
miscColor12: getColor('green5')
|
||||
|
||||
accountColors: [
|
||||
getColor('blue'),
|
||||
getColor('brown2'),
|
||||
getColor('violet'),
|
||||
"#1D806F",
|
||||
getColor('red2'),
|
||||
getColor('green2'),
|
||||
getColor('purple'),
|
||||
getColor('brown')
|
||||
userCustomizationColors: [
|
||||
"#2946C4",
|
||||
"#887AF9",
|
||||
"#51D0F0",
|
||||
"#D37EF4",
|
||||
"#FA6565",
|
||||
"#FFCA0F",
|
||||
"#7CDA00",
|
||||
"#26A69A",
|
||||
"#8B3131",
|
||||
"#9B832F",
|
||||
"silver", // update me when figma is ready
|
||||
"darkgrey", // update me when figma is ready
|
||||
]
|
||||
|
||||
identiconRingColors: ["#000000", "#726F6F", "#C4C4C4", "#E7E7E7", "#FFFFFF", "#00FF00",
|
||||
@@ -224,7 +228,10 @@ ThemePalette {
|
||||
}
|
||||
|
||||
property QtObject statusSwitchTab: QtObject {
|
||||
property color backgroundColor: white
|
||||
property color buttonBackgroundColor: primaryColor1
|
||||
property color barBackgroundColor: primaryColor3
|
||||
property color selectedTextColor: white
|
||||
property color textColor: primaryColor1
|
||||
}
|
||||
|
||||
property QtObject statusSelect: QtObject {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick 2.13
|
||||
|
||||
QtObject {
|
||||
|
||||
|
||||
id: theme
|
||||
|
||||
property string name
|
||||
@@ -90,7 +90,7 @@ QtObject {
|
||||
property color miscColor11
|
||||
property color miscColor12
|
||||
|
||||
property var accountColors: []
|
||||
property var userCustomizationColors: []
|
||||
|
||||
property var identiconRingColors: []
|
||||
|
||||
@@ -156,7 +156,10 @@ QtObject {
|
||||
}
|
||||
|
||||
property QtObject statusSwitchTab: QtObject {
|
||||
property color backgroundColor
|
||||
property color buttonBackgroundColor
|
||||
property color barBackgroundColor
|
||||
property color selectedTextColor
|
||||
property color textColor
|
||||
}
|
||||
|
||||
property QtObject statusSelect: QtObject {
|
||||
|
||||
@@ -14,17 +14,26 @@ QtObject {
|
||||
"small": "18x18",
|
||||
"verySmall": "16x16"
|
||||
}
|
||||
readonly property var format: {
|
||||
"png": "png",
|
||||
"svg": "svg"
|
||||
}
|
||||
property string base: Qt.resolvedUrl("../../../assets/twemoji/")
|
||||
function parse(text, renderSize = size.small) {
|
||||
|
||||
function parse(text, renderSize = size.small, renderFormat = format.svg) {
|
||||
const renderSizes = renderSize.split("x");
|
||||
if (!renderSize.includes("x") || renderSizes.length !== 2) {
|
||||
throw new Error("Invalid value for 'renderSize' parameter: ", renderSize);
|
||||
}
|
||||
|
||||
Twemoji.twemoji.base = base
|
||||
Twemoji.twemoji.ext = ".svg"
|
||||
Twemoji.twemoji.size = "svg"
|
||||
const path = renderFormat == format.svg ? "svg/" : "72x72/"
|
||||
Twemoji.twemoji.base = base + path
|
||||
Twemoji.twemoji.ext = `.${renderFormat}`
|
||||
|
||||
return Twemoji.twemoji.parse(text, {
|
||||
callback: (iconId, options) => {
|
||||
return options.base + iconId + options.ext;
|
||||
},
|
||||
attributes: function() {
|
||||
return {
|
||||
width: renderSizes[0],
|
||||
@@ -34,6 +43,10 @@ QtObject {
|
||||
}
|
||||
})
|
||||
}
|
||||
function iconId(text) {
|
||||
const parsed = parse(text);
|
||||
return parsed.match('src=".*\/(.+?).svg')[1]
|
||||
}
|
||||
function fromCodePoint(value) {
|
||||
return Twemoji.twemoji.convert.fromCodePoint(value)
|
||||
}
|
||||
@@ -98,7 +111,7 @@ QtObject {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getRandomEmoji() {
|
||||
function getRandomEmoji(size) {
|
||||
var randomEmoji = EmojiJSON.emoji_json[Math.floor(Math.random() * EmojiJSON.emoji_json.length)]
|
||||
|
||||
const extenstionIndex = randomEmoji.unicode.lastIndexOf('.');
|
||||
@@ -115,6 +128,7 @@ QtObject {
|
||||
})
|
||||
const encodedIcon = String.fromCodePoint(...codePointParts);
|
||||
|
||||
return Emoji.parse(encodedIcon) + ' '
|
||||
// Adding a space because otherwise, some emojis would fuse since emoji is just a string
|
||||
return Emoji.parse(encodedIcon, size || undefined) + ' '
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ Rectangle {
|
||||
id: root
|
||||
property string chatId: ""
|
||||
property string name: "channelName"
|
||||
property string message: "My latest message\n with a return"
|
||||
property string message: qsTr("My latest message\n with a return")
|
||||
property string identicon: ""
|
||||
|
||||
// TODO: what about dark theme?
|
||||
@@ -22,7 +22,7 @@ Rectangle {
|
||||
|
||||
Loader {
|
||||
id: identicon
|
||||
sourceComponent: root.identicon === "" ? statusIdenticon : userOrChannelIdenticon
|
||||
sourceComponent: root.identicon === "" ? statusIdenticon : userOrChannelIdenticon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -120,7 +120,7 @@ Rectangle {
|
||||
font.pixelSize: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "Open"
|
||||
text: qsTr("Open")
|
||||
color: "black"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ import "statusModal" as Spares
|
||||
]
|
||||
rightButtons: [
|
||||
StatusButton {
|
||||
text: "Button"
|
||||
text: qsTr("Button")
|
||||
},
|
||||
StatusButton {
|
||||
text: "Button"
|
||||
text: qsTr("Button")
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -87,7 +87,7 @@ QC.Popup {
|
||||
border.width: 1
|
||||
StatusBaseText {
|
||||
anchors.centerIn: parent
|
||||
text: "Add any header here"
|
||||
text: qsTr("Add any header here")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
@@ -115,7 +115,7 @@ QC.Popup {
|
||||
border.width: 1
|
||||
StatusBaseText {
|
||||
anchors.centerIn: parent
|
||||
text: "Add any footer here"
|
||||
text: qsTr("Add any footer here")
|
||||
font.pixelSize: 15
|
||||
color: Theme.palette.directColor1
|
||||
}
|
||||
@@ -137,7 +137,7 @@ QC.Popup {
|
||||
This property exposes the different properties of the standard header.
|
||||
\endqml
|
||||
*/
|
||||
property StatusModalHeaderSettings header: StatusModalHeaderSettings {}
|
||||
property StatusModalHeaderSettings header: StatusModalHeaderSettings {}
|
||||
/*!
|
||||
\qmlproperty rightButtons
|
||||
This property helps user assign the right buttons on the footer.
|
||||
|
||||
@@ -7,6 +7,7 @@ import StatusQ.Popups 0.1
|
||||
|
||||
StatusPopupMenu {
|
||||
id: root
|
||||
dim: false
|
||||
|
||||
property var searchPopup
|
||||
property var locationModel
|
||||
@@ -15,7 +16,7 @@ StatusPopupMenu {
|
||||
signal itemClicked(string firstLevelItemValue, string secondLevelItemValue)
|
||||
|
||||
StatusMenuItem {
|
||||
text: "Anywhere"
|
||||
text: qsTr("Anywhere")
|
||||
onTriggered: {
|
||||
searchPopup.resetSearchSelection();
|
||||
root.itemClicked("", "");
|
||||
@@ -36,7 +37,6 @@ StatusPopupMenu {
|
||||
item.parentIconName = model.iconName;
|
||||
item.parentImageSource = model.imageSource;
|
||||
item.parentIdenticonColor = !!model.iconColor ? model.iconColor : Theme.palette.primaryColor1;
|
||||
item.parentIsIdenticon = model.isIdenticon;
|
||||
root.subMenuItemIcons.push({
|
||||
source: model.imageSource,
|
||||
icon: model.iconName,
|
||||
@@ -80,6 +80,7 @@ StatusPopupMenu {
|
||||
id: subMenus
|
||||
StatusPopupMenu {
|
||||
id: menu
|
||||
dim: false
|
||||
property var subItemsModel
|
||||
property string parentValue
|
||||
property string parentIconName
|
||||
@@ -103,7 +104,6 @@ StatusPopupMenu {
|
||||
image.source: model.imageSource
|
||||
iconSettings.name: model.iconName
|
||||
iconSettings.color: model.iconColor
|
||||
image.isIdenticon: model.isIdenticon
|
||||
onTriggered: {
|
||||
searchPopup.resetSearchSelection()
|
||||
if (menuLoader.parentTitleText === "Chat") {
|
||||
|
||||
@@ -18,8 +18,8 @@ StatusModal {
|
||||
showFooter: false
|
||||
|
||||
property string searchText: contentItem.searchText
|
||||
property string noResultsLabel: "No results"
|
||||
property string defaultSearchLocationText: "Anywhere"
|
||||
property string noResultsLabel: qsTr("No results")
|
||||
property string defaultSearchLocationText: qsTr("Anywhere")
|
||||
property bool loading: false
|
||||
property Menu searchOptionsPopupMenu: Menu { }
|
||||
property var searchResults: [ ]
|
||||
@@ -121,7 +121,7 @@ StatusModal {
|
||||
root.searchSelectionButton = searchOptionsMenuButton
|
||||
}
|
||||
|
||||
property string prefixText: "In"
|
||||
property string prefixText: qsTr("In")
|
||||
property string primaryText: ""
|
||||
property string secondaryText: ""
|
||||
property StatusIconSettings iconSettings: StatusIconSettings {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 10.75C16 11.1642 16.3383 11.4934 16.7469 11.5617C18.8767 11.9175 20.5 13.7692 20.5 16C20.5 18.4853 18.4853 20.5 16 20.5H8C5.51472 20.5 3.5 18.4853 3.5 16C3.5 13.7692 5.12331 11.9175 7.25314 11.5617C7.66169 11.4934 8 11.1642 8 10.75C8 10.3358 7.66253 9.99508 7.25149 10.0462C4.29103 10.4146 2 12.9398 2 16C2 19.3137 4.68629 22 8 22H16C19.3137 22 22 19.3137 22 16C22 12.9398 19.709 10.4146 16.7485 10.0462C16.3375 9.99508 16 10.3358 16 10.75Z" fill="black"/>
|
||||
<path d="M12.75 6.01777C12.75 5.57232 13.2886 5.34923 13.6036 5.66421L15.4697 7.53033C15.7626 7.82322 16.2374 7.82322 16.5303 7.53033C16.8232 7.23744 16.8232 6.76256 16.5303 6.46967L12.5303 2.46967C12.2374 2.17678 11.7626 2.17678 11.4697 2.46967L7.46967 6.46967C7.17678 6.76256 7.17678 7.23744 7.46967 7.53033C7.76256 7.82322 8.23744 7.82322 8.53033 7.53033L10.3964 5.66421C10.7114 5.34923 11.25 5.57232 11.25 6.01777V16C11.25 16.4142 11.5858 16.75 12 16.75C12.4142 16.75 12.75 16.4142 12.75 16V6.01777Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.281 20C14.9447 20 14.7031 20.3279 14.7344 20.6628C14.7444 20.7703 14.7499 20.8827 14.7499 21C14.7499 22.3889 13.5436 23.75 11.9999 23.75C10.4563 23.75 9.24992 22.3889 9.24992 21C9.24992 20.8827 9.25542 20.7703 9.26547 20.6628C9.29677 20.3279 9.05515 20 8.71885 20H5.52103C3.78474 20 2.87319 17.9394 4.04115 16.6547L5.218 15.3601C5.72872 14.7983 6.069 14.1028 6.19909 13.3548L7.31456 6.94085C7.71072 4.66293 9.68781 3 11.9999 3C14.312 3 16.2891 4.66293 16.6853 6.94085L17.8008 13.3548C17.9308 14.1028 18.2711 14.7983 18.7818 15.3601L19.9587 16.6547C21.1266 17.9394 20.2151 20 18.4788 20H15.281ZM11.0322 20.0283L11.0313 20.0293L11.039 20.0214L11.0415 20.0188C11.0427 20.0175 11.0433 20.0168 11.0434 20.0167C11.0438 20.0163 11.0434 20.0167 11.0434 20.0167L11.0415 20.0188C11.0525 20.0073 11.0681 20 11.084 20H12.9159C12.9318 20 12.9469 20.0068 12.9579 20.0183C12.9608 20.0217 12.9679 20.0302 12.978 20.0438C13.001 20.0744 13.0389 20.13 13.0791 20.2104C13.1582 20.3687 13.2499 20.6296 13.2499 21C13.2499 21.6111 12.6654 22.25 11.9999 22.25C11.3344 22.25 10.7499 21.6111 10.7499 21C10.7499 20.6296 10.8416 20.3687 10.9207 20.2104C10.9609 20.13 10.9988 20.0744 11.0218 20.0438C11.032 20.0302 11.0386 20.0222 11.0415 20.0188L11.039 20.0214L11.0372 20.0233L11.034 20.0265L11.0322 20.0283ZM5.15106 17.6637L6.32791 16.3691C7.03015 15.5967 7.49804 14.6403 7.67691 13.6118L8.79238 7.19786C9.06358 5.63842 10.4171 4.5 11.9999 4.5C13.5828 4.5 14.9363 5.63842 15.2075 7.19786L16.3229 13.6118C16.5018 14.6403 16.9697 15.5967 17.6719 16.3691L18.8488 17.6637C19.1408 17.9849 18.9129 18.5 18.4788 18.5H5.52103C5.08696 18.5 4.85907 17.9849 5.15106 17.6637Z" fill="black"/>
|
||||
<rect x="0.585786" y="20.7988" width="28" height="4" rx="2" transform="rotate(-45 0.585786 20.7988)" fill="black" stroke="white" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18.5C14.6944 18.5 18.5 14.6944 18.5 10C18.5 5.30558 14.6944 1.5 10 1.5C5.30558 1.5 1.5 5.30558 1.5 10C1.5 14.6944 5.30558 18.5 10 18.5ZM10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20Z" fill="#4360DF"/>
|
||||
<path d="M7.56858 13.1746C7.22903 12.9373 6.75712 12.9308 6.46422 13.2237C6.17133 13.5166 6.16876 13.996 6.49708 14.2485C7.46695 14.9946 8.68155 15.4381 9.99976 15.4381C11.318 15.4381 12.5326 14.9946 13.5024 14.2485C13.8308 13.996 13.8282 13.5166 13.5353 13.2237C13.2424 12.9308 12.7705 12.9373 12.4309 13.1746C11.742 13.6558 10.9039 13.9381 9.99976 13.9381C9.09565 13.9381 8.25747 13.6558 7.56858 13.1746Z" fill="#4360DF"/>
|
||||
<path d="M15 8.5C15 9.32843 14.3284 10 13.5 10C12.6716 10 12 9.32843 12 8.5C12 7.67157 12.6716 7 13.5 7C14.3284 7 15 7.67157 15 8.5Z" fill="#4360DF"/>
|
||||
<path d="M8 8.5C8 9.32843 7.32843 10 6.5 10C5.67157 10 5 9.32843 5 8.5C5 7.67157 5.67157 7 6.5 7C7.32843 7 8 7.67157 8 8.5Z" fill="#4360DF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,76 @@
|
||||
# Code examples
|
||||
|
||||
## Parent assumptions
|
||||
|
||||
### Don't assume parent's context bad example
|
||||
|
||||
`PreanchoredItem.qml` component
|
||||
|
||||
```qml
|
||||
Item {
|
||||
// This limits the usage of the component and can lead to maintenance burden
|
||||
anchors.fill: parent
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Broken usage of `PreanchoredItem` component
|
||||
|
||||
```qml
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Using PreanchoredItem with a container (layouts, delegate in a ListView ..) will result in warning or broken UI layout.
|
||||
ColumnLayout {
|
||||
// This will generate a qml warning and Layout won't work as expected
|
||||
PreanchoredItem {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
// ....
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Give user choice
|
||||
|
||||
User should be allowed to choose how to size the control based on it's design and UX requirements. Only provide hints and recommendations.
|
||||
|
||||
`GoodItem.qml` component
|
||||
|
||||
```qml
|
||||
Item {
|
||||
// Rather provide implicit sizes if appropriate and leave user to layout or anchors as
|
||||
// he see fit given its panel/view design
|
||||
implicitWidth: 200
|
||||
implicitHeight: 100
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Usage of `GoodItem` component
|
||||
|
||||
```qml
|
||||
// Using GoodItem with a container (layouts, positioners, delegate in a ListView ..) works well
|
||||
ColumnLayout {
|
||||
// This will generate a qml warning and Layout won't work as expected
|
||||
GoodItem {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
GoodItem {
|
||||
Layout.preferredWidth: true
|
||||
Layout.preferredHeight: true
|
||||
}
|
||||
|
||||
GoodItem {
|
||||
// Nothing will still show the "optimal" size provided by the control
|
||||
}
|
||||
|
||||
// ....
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,167 @@
|
||||
# Contributing guidelines
|
||||
|
||||
## Generic Reusable Component Design
|
||||
|
||||
Define the component's purpose. Narrow down the scope as much as possible.
|
||||
|
||||
Avoid assumptions about its parent/owner
|
||||
|
||||
- See [broken behavior example](./code-examples.md#Dont-assume-parents-context-bad-example)
|
||||
- See [well-behaved behavior example](./code-examples.md#Give-user-choice)
|
||||
|
||||
Define and make clear the interface/contract that defines requirements.
|
||||
|
||||
To simplify maintenance consider the following options for implementing custom controls, generic containers or views:
|
||||
|
||||
- Use QML2's customization mechanics in place if the change is simple: [Check docs for examples](https://doc.qt.io/qt-6/qtquickcontrols2-customize.html)
|
||||
- If the control should be generic or the changes are extensive and the design maps to a QML control, use QML2's customization mechanisms in a generic QML control: [Check docs for examples](https://doc.qt.io/qt-6/qtquickcontrols2-customize.html) instead of starting from scratch.
|
||||
- Use Qt controls instead of defining our own. Defining and implementing a complex control from scratch should be the last resort and there should be an excellent reason to do that.
|
||||
- For keyboard input item, ensure focus properties are set; see [Qt docs](https://doc.qt.io/qt-6/qtquick-input-focus.html)
|
||||
|
||||
When unsure, check Qt's excellent documentation.
|
||||
|
||||
- For main components/controls, there is a good overview that is worth revisiting from time to time
|
||||
- Functions/Properties usually have a short on-point description
|
||||
- In `QtCreator` you can quickly open the doc panel using `F1` key while having a cursor on component or function/property and recheck its invariants, pre and post conditions
|
||||
|
||||
Consider that design follows user-friendlier principles.
|
||||
|
||||
- Have commonly used items at hand
|
||||
- Have transition if possible
|
||||
- Use Qt's property animation for states
|
||||
- Avoid often and radical size changes to items in views/layouts. Radical size change confuses users.
|
||||
- If the content is not available, consider having placeholders using the estimated size
|
||||
(delegates, dummy items)
|
||||
|
||||
Have the base control as the component's root. This way, control inherits all the interface and reduce the code.
|
||||
If it doesn't map to an existing one, use a base control like `Item`.
|
||||
|
||||
- Don't use layouts or positioners as base for controls, they don't have the same behavior when used in layouts/containers
|
||||
- Layouts have `fillWidth`/`fillHeight` as true by default and they will be extended. Controls don't and they will follow implicit sizes
|
||||
|
||||
## QML well-behaved components checklist
|
||||
|
||||
### Positioning and sizing
|
||||
|
||||
[Positioners and Layouts In QML](https://doc.qt.io/qt-6/qtquick-usecase-layouts.html)
|
||||
|
||||
- Define size hints appropriately. Define implicit properties (`impicitWidth` and `implicitHeight`)
|
||||
- They are used to break polishing binding loops for adaptive control
|
||||
- All the containers use the implicit size for deriving the initial size (Layouts, Popups, Delegates, GridView, ListView ...). Size hints combined with resize adaptability are the appropriate way to have reactive controls.
|
||||
- For complex controls, look if layouts are the choice as the main position driver. [Layouts](###Layouts)
|
||||
|
||||
```qml
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: mainLayout.implicitWidth
|
||||
implicitHeight: mainLayout.implicitHeight
|
||||
RowLayout { // Column, Grid
|
||||
id: mainLayout
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
- Adapt to the requested size if control can scale or it make sense to be extensible. For sizes bigger than content follow QML way for similar controls. e.g. Text leaves empty space around content
|
||||
- If the control is not adaptable and makes sense only in its full size, define default sizes and make it work by default with positioners (`Row`, `Column`, `Grid`).
|
||||
- [Item Positioners in QML](https://doc.qt.io/qt-5/qtquick-positioning-layouts.html)
|
||||
- Don't mix hints with sizes, it will create binding loops
|
||||
|
||||
### Quality
|
||||
|
||||
Pixel miss-alignment for sensitive elements like icons and images with straight and thin lines can degrade the quality perceptibly.
|
||||
|
||||
Dividing sizes and coordinates are the usual root cause of pixel miss-alignment. Correct subpixel division by rounding the result of the operation to a fixed value, e.g. `x: (width/2).toFixed()`
|
||||
|
||||
- Example of pixel miss-alignment. The right control has a position offset of `Qt.pos(0.7, 0.4)` therefore, the image interpolation generates a blurred rendering.
|
||||
- 
|
||||
|
||||
## Topics
|
||||
|
||||
### QML Coding Conventions
|
||||
|
||||
[Follow Qt's way if appropriate](https://doc.qt.io/qt-6/qml-codingconventions.html)
|
||||
|
||||
### Layouts
|
||||
|
||||
[Qt Quick Layouts Overview](https://doc.qt.io/qt-6/qtquicklayouts-overview.html)
|
||||
|
||||
Hierarchically sources hints from children. Implicit properties will have the recommended aggregated size.
|
||||
|
||||
Layouts as children of other layouts have `fillWidth` and `fillHeight` enabled; controls don't.
|
||||
|
||||
Use `Layout.preferredWidth` and `Layout.preferredHeight` attached properties to overwrite item's `implicitWidth` if they are not appropriate to the design.
|
||||
|
||||
### Scope
|
||||
|
||||
Follow [Qt's recommendations](https://doc.qt.io/qt-5/qtqml-documents-scope.html) if appropriate
|
||||
|
||||
- Avoid dynamic scoping in out-of-line components, see more: https://doc.qt.io/qt-5/qtqml-documents-scope.html#component-instance-hierarchy
|
||||
- Example
|
||||
|
||||
```qml
|
||||
Item {
|
||||
// Probably won't work as intended. If another `testComp` instance is defined in QML's document hierarchy model and has a `testProp` property, that will be sourced instead
|
||||
property bool booleanProp: testComp.testProp
|
||||
// Same behavior if the TestComponent is defined in a file TestComponent.qml
|
||||
component TestComponent: Item {
|
||||
id: testComp
|
||||
property bool testProp: false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Component `id`s are not accessible outside the component's scope. If required, the component can expose the instance through properties binding. E.g. `readonly property ComponentType exposedComponent: privateInstanceId`
|
||||
|
||||
- If in doubt, explicitly use an instance variable to access properties
|
||||
- If the scope is clear and there is no ambiguity, use the property directly for readability
|
||||
|
||||
## Testing
|
||||
|
||||
Test in isolation
|
||||
|
||||
- Use `qmlproject`s and `qml` for quick complex scenarios
|
||||
- Use [`QtQuick.TestCase`](https://doc.qt.io/qt-5/qtquicktest-index.html)
|
||||
|
||||
Integration tests
|
||||
|
||||
- Use sandbox test app
|
||||
- Use QML `qmlproject`s and `qmlscene` for quick debugging with layouts
|
||||
|
||||
Try scenarios
|
||||
|
||||
- Embed in Layouts with different properties: fixed size, min-max, not size set
|
||||
|
||||
```qml
|
||||
Window {
|
||||
width: mainLayout.implicitWidth
|
||||
heigh: mainLayout.implicitHeight
|
||||
|
||||
GridLayout {
|
||||
id: mainLayout
|
||||
rows: 3
|
||||
column: 3
|
||||
|
||||
anchor.fill: parent
|
||||
Label { text: "Fixed width" }
|
||||
TestControl {
|
||||
Layout.preferredWidth: 100
|
||||
Layout.preferredHeight: 100
|
||||
}
|
||||
Label { text: "Fill space" }
|
||||
TestControl {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
Label { text: "Width range" }
|
||||
TestControl {
|
||||
Layout.minWidth: 50
|
||||
Layout.maxWidth: 150
|
||||
}
|
||||
TestControl {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Resize window to check the behavior for each case
|
||||
- Visually validate that each control is behaving as expected
|
||||
- Add controls with different properties that affect control size behavior
|
||||
@@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(TestControls LANGUAGES CXX)
|
||||
project(TestStatusQ LANGUAGES CXX)
|
||||
|
||||
enable_testing()
|
||||
|
||||
# TODO: Workaround until we make StatusQ a CMake library
|
||||
list(APPEND QML_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../../src/")
|
||||
list(APPEND QML_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../src/")
|
||||
set(QML_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
|
||||
set(QML2_IMPORT_PATH "${QML_DIRS}" CACHE STRING "Qt Creator extra qml import paths")
|
||||
|
||||
@@ -14,18 +14,20 @@ find_package(Qt${QT_VERSION_MAJOR} COMPONENTS QuickTest Qml Quick REQUIRED)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# no need to copy around qml test files for shadow builds - just set the respective define
|
||||
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
add_executable(${PROJECT_NAME} main.cpp)
|
||||
add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})
|
||||
|
||||
add_test(NAME ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} -input "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_custom_target("Run_${PROJECT_NAME}" COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
add_dependencies("Run_${PROJECT_NAME}" ${PROJECT_NAME})
|
||||
|
||||
# TODO: move this to a test helpers library
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
@@ -33,11 +35,10 @@ target_include_directories(${PROJECT_NAME}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_subdirectory(TestHelpers)
|
||||
add_subdirectory(src)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::QuickTest
|
||||
Qt${QT_VERSION_MAJOR}::Qml
|
||||
Qt${QT_VERSION_MAJOR}::Quick
|
||||
)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import QtQuick 2.0
|
||||
import QtTest 1.0
|
||||
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import StatusQ.TestHelpers 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 400
|
||||
height: 300
|
||||
|
||||
Component {
|
||||
id: noSourceComponent
|
||||
|
||||
StatusImageCropPanel {
|
||||
anchors.fill: parent
|
||||
|
||||
windowStyle: StatusImageCrop.WindowStyle.Rounded
|
||||
}
|
||||
}
|
||||
|
||||
property url testImageUrl: `${Qt.resolvedUrl(".")}../../sandbox/demoapp/data/logo-test-image.png`
|
||||
|
||||
Component {
|
||||
id: withSourceComponent
|
||||
|
||||
StatusImageCropPanel {
|
||||
anchors.fill: parent
|
||||
|
||||
// TODO: generate test image and break the sandbox dependency
|
||||
source: root.testImageUrl
|
||||
windowStyle: StatusImageCrop.WindowStyle.Rectangular
|
||||
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: testLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: false
|
||||
}
|
||||
|
||||
TestCase {
|
||||
id: qmlWarningsTest
|
||||
|
||||
name: "StatusImageCropPanel"
|
||||
|
||||
when: windowShown
|
||||
|
||||
//
|
||||
// Test guards
|
||||
|
||||
function init() {
|
||||
qtOuput.restartCapturing()
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
testLoader.active = false
|
||||
}
|
||||
|
||||
//
|
||||
// Tests
|
||||
|
||||
function test_no_source_initialization() {
|
||||
testLoader.sourceComponent = noSourceComponent
|
||||
testLoader.active = true
|
||||
verify(waitForRendering(testLoader.item))
|
||||
testLoader.active = false
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
|
||||
function test_with_source_initialization() {
|
||||
testLoader.sourceComponent = withSourceComponent
|
||||
testLoader.active = true
|
||||
verify(waitForRendering(testLoader.item))
|
||||
testLoader.active = false
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
|
||||
function test_setCrop_error_if_no_source_regression() {
|
||||
testLoader.sourceComponent = noSourceComponent
|
||||
testLoader.active = true
|
||||
verify(waitForRendering(testLoader.item))
|
||||
testLoader.item.setCropRect(Qt.rect(10, 10, 300, 300))
|
||||
verify(qtOuput.qtOuput().length !== 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
qtOuput.restartCapturing()
|
||||
testLoader.item.source = root.testImageUrl
|
||||
verify(waitForRendering(testLoader.item))
|
||||
testLoader.active = false
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
}
|
||||
|
||||
MonitorQtOutput {
|
||||
id: qtOuput
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQml 2.14
|
||||
import QtTest 1.0
|
||||
|
||||
QtObject {
|
||||
|
||||
//> Simulate key and wait for side effects
|
||||
function pressKeyAndWait(test, item, key) {
|
||||
test.keyClick(key)
|
||||
|
||||
test.waitForRendering(item)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
///
|
||||
/// \brief Monitor output for tests and declarativelly control message handler availability
|
||||
/// \todo Check that QML doesn't keep instance between test runs
|
||||
///
|
||||
class MonitorQtOutput : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MonitorQtOutput();
|
||||
~MonitorQtOutput();
|
||||
|
||||
Q_INVOKABLE QString qtOuput();
|
||||
|
||||
signals:
|
||||
|
||||
private:
|
||||
static void qtMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
|
||||
|
||||
// Use it to keep track of qInstallMessageHandler call
|
||||
static std::weak_ptr<QString> m_qtMessageOutputForSharing;
|
||||
static std::mutex m_mutex;
|
||||
std::shared_ptr<QString> m_thisMessageOutput;
|
||||
int m_start = 0;
|
||||
};
|
||||
@@ -19,14 +19,15 @@ Item {
|
||||
|
||||
TestCase {
|
||||
id: testCase
|
||||
name: "CheckQmlWarnings"
|
||||
name: "StatusBaseInput"
|
||||
|
||||
when: windowShown
|
||||
|
||||
//
|
||||
// Test guards
|
||||
|
||||
function initTestCase() {
|
||||
function init() {
|
||||
qtOuput.restartCapturing()
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import QtQuick 2.0
|
||||
import QtTest 1.0
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
|
||||
import StatusQ.TestHelpers 0.1
|
||||
|
||||
Item {
|
||||
width: 400
|
||||
height: 300
|
||||
|
||||
Component {
|
||||
id: noSourceComponent
|
||||
|
||||
StatusImageCrop {
|
||||
anchors.fill: parent
|
||||
|
||||
windowStyle: StatusImageCrop.WindowStyle.Rounded
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: withSourceComponent
|
||||
|
||||
StatusImageCrop {
|
||||
anchors.fill: parent
|
||||
|
||||
// TODO: generate test image and break the sandbox dependency
|
||||
source: `${Qt.resolvedUrl(".")}../../sandbox/demoapp/data/logo-test-image.png`
|
||||
windowStyle: StatusImageCrop.WindowStyle.Rectangular
|
||||
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: testLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: false
|
||||
}
|
||||
|
||||
TestCase {
|
||||
id: qmlWarningsTest
|
||||
|
||||
name: "StatusImageCrop-CheckQmlWarnings"
|
||||
|
||||
when: windowShown
|
||||
|
||||
//
|
||||
// Test guards
|
||||
|
||||
function init() {
|
||||
qtOuput.restartCapturing()
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
testLoader.active = false
|
||||
}
|
||||
|
||||
//
|
||||
// Tests
|
||||
|
||||
function test_no_source_initialization() {
|
||||
testLoader.sourceComponent = noSourceComponent
|
||||
testLoader.active = true
|
||||
verify(waitForRendering(testLoader.item))
|
||||
testLoader.active = false
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
|
||||
function test_with_source_initialization() {
|
||||
testLoader.sourceComponent = withSourceComponent
|
||||
testLoader.active = true
|
||||
verify(waitForRendering(testLoader.item))
|
||||
testLoader.active = false
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
}
|
||||
|
||||
StatusImageCrop {
|
||||
id: testControl
|
||||
|
||||
// TODO: generate test image and break the sandbox dependency
|
||||
source: `${Qt.resolvedUrl(".")}../../sandbox/demoapp/data/logo-test-image.png`
|
||||
windowStyle: StatusImageCrop.WindowStyle.Rectangular
|
||||
Component.onCompleted: setCropRect(Qt.rect(10, 0, sourceSize.width - 20, sourceSize.height))
|
||||
}
|
||||
|
||||
TestCase {
|
||||
name: "StatusImageCrop-Functionality"
|
||||
|
||||
//
|
||||
// Test guards
|
||||
|
||||
function init() {
|
||||
qtOuput.restartCapturing()
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
}
|
||||
|
||||
//
|
||||
// Tests
|
||||
|
||||
function test_inflateRect_by2() {
|
||||
const refRect = Qt.rect(2,2,5,6)
|
||||
const infateResult = testControl.inflateRectBy(refRect, 2)
|
||||
const inflatedBy2Rect = Qt.rect(-0.5, -1, 10, 12)
|
||||
compare(infateResult, inflatedBy2Rect, "Inflating rectangle failed")
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
|
||||
function test_inflateRectBy_deflateByHalf() {
|
||||
const refRect = Qt.rect(3,4,2,3)
|
||||
const inflateResult = testControl.inflateRectBy(refRect, 0.5)
|
||||
const defaltedByHalfRect = Qt.rect(3.5, 4.75, 1, 1.5)
|
||||
compare(inflateResult, defaltedByHalfRect, "Inflating rectangle failed")
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
|
||||
function test_rectCenter() {
|
||||
compare(testControl.rectCenter(Qt.rect(2, 3, 2, 4)), Qt.point(3, 5), "Fail to retrieve center");
|
||||
compare(testControl.rectCenter(Qt.rect(-1, 3, 1, 3)), Qt.point(-0.5, 4.5), "Fail to retrieve center");
|
||||
}
|
||||
|
||||
function test_recenterRect() {
|
||||
compare(testControl.recenterRect(Qt.rect(1.5, 2, 3, 4), Qt.point(0, 0)), Qt.rect(-1.5, -2, 3, 4))
|
||||
compare(testControl.recenterRect(Qt.rect(0, 0, 1.5, 3), Qt.point(0.75, 1.5)), Qt.rect(0, 0, 1.5, 3))
|
||||
}
|
||||
}
|
||||
|
||||
MonitorQtOutput {
|
||||
id: qtOuput
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,8 @@ Item {
|
||||
|
||||
//
|
||||
// Test guards
|
||||
function initTestCase() {
|
||||
function init() {
|
||||
qtOuput.restartCapturing()
|
||||
mouseClick(statusInput)
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ Item {
|
||||
TestCase {
|
||||
id: qmlWarnTC
|
||||
|
||||
name: "CheckQmlWarnings"
|
||||
name: "StatusInput"
|
||||
|
||||
when: windowShown
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
Start testing: Apr 18 18:49 CEST
|
||||
----------------------------------------------------------
|
||||
End testing: Apr 18 18:49 CEST
|
||||
@@ -14,13 +14,11 @@ public slots:
|
||||
void qmlEngineAvailable(QQmlEngine *engine)
|
||||
{
|
||||
// TODO: Workaround until we make StatusQ a CMake library
|
||||
engine->addImportPath("../../src/");
|
||||
engine->addImportPath(".");
|
||||
engine->addImportPath("../src/");
|
||||
engine->addImportPath("./qml/");
|
||||
// TODO: Alternative to not yet supported QML_ELEMENT
|
||||
qmlRegisterType<MonitorQtOutput>("StatusQ.TestHelpers", 0, 1, "MonitorQtOutput");
|
||||
}
|
||||
private:
|
||||
MonitorQtOutput _monitorOutput;
|
||||
};
|
||||
|
||||
QUICK_TEST_MAIN_WITH_SETUP(TestControls, TestSetup)
|
||||