GODT-1179 GODT-658: Components and login flows

This commit is contained in:
Alexander Bilyak
2021-06-06 23:57:59 +02:00
committed by Jakub
parent f5624c9932
commit 5cb893fc1b
93 changed files with 5085 additions and 1051 deletions

View File

@ -0,0 +1,117 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.13
import QtQuick.Layouts 1.12
import QtQuick.Controls.impl 2.12
Rectangle {
id: root
width: layout.width
height: layout.height
radius: 10
signal accepted()
property alias text: description.text
property var actionText: ""
property var colorText: Style.currentStyle.text_invert
property var colorMain: "#000"
property var colorHover: "#000"
property var colorActive: "#000"
property var iconSource: "../icons/ic-exclamation-circle-filled.svg"
color: root.colorMain
border.color: root.colorActive
border.width: 1
property var maxWidth: 600
property var minWidth: 400
property var usedWidth: button.width + icon.width
RowLayout {
id: layout
IconLabel {
id:icon
Layout.alignment: Qt.AlignCenter
Layout.leftMargin: 17.5
Layout.topMargin: 15.5
Layout.bottomMargin: 15.5
color: root.colorText
icon.source: root.iconSource
icon.color: root.colorText
icon.height: Style.title_line_height
}
ProtonLabel {
id: description
Layout.alignment: Qt.AlignCenter
Layout.leftMargin: 9.5
Layout.minimumWidth: root.minWidth - root.usedWidth
Layout.maximumWidth: root.maxWidth - root.usedWidth
color: root.colorText
state: "body"
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
}
Button {
id:button
Layout.fillHeight: true
hoverEnabled: true
text: root.actionText.toUpperCase()
onClicked: root.accepted()
background: RoundedRectangle {
width:parent.width
height:parent.height
strokeColor: root.colorActive
strokeWidth: root.border.width
radiusTopRight : root.radius
radiusBottomRight : root.radius
radiusTopLeft : 0
radiusBottomLeft : 0
fillColor: button.down ? root.colorActive : (
button.hovered ? root.colorHover :
root.colorMain
)
}
}
}
state: "info"
states: [
State{ name : "danger" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_danger ; colorHover : Style.currentStyle.signal_danger_hover ; colorActive : Style.currentStyle.signal_danger_active ; iconSource: "../icons/ic-exclamation-circle-filled.svg"}} ,
State{ name : "warning" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_warning ; colorHover : Style.currentStyle.signal_warning_hover ; colorActive : Style.currentStyle.signal_warning_active ; iconSource: "../icons/ic-exclamation-circle-filled.svg"}} ,
State{ name : "success" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_success ; colorHover : Style.currentStyle.signal_success_hover ; colorActive : Style.currentStyle.signal_success_active ; iconSource: "../icons/ic-info-circle-filled.svg"}} ,
State{ name : "info" ; PropertyChanges{ target : root ; colorMain : Style.currentStyle.signal_info ; colorHover : Style.currentStyle.signal_info_hover ; colorActive : Style.currentStyle.signal_info_active ; iconSource: "../icons/ic-info-circle-filled.svg"}}
]
}

View File

@ -28,49 +28,151 @@ T.Button {
readonly property bool primary: !secondary
readonly property bool isIcon: control.text === ""
property bool loading: false
// TODO: store previous enabled state and restore it?
// For now assuming that only enabled buttons could have loading state
onLoadingChanged: {
if (loading) {
enabled = false
} else {
enabled = true
}
}
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
implicitWidth: Math.max(
implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding
)
implicitHeight: Math.max(
implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding
)
padding: 8
horizontalPadding: 16
spacing: 10
icon.width: 12
icon.height: 12
icon.color: control.checked || control.highlighted ? control.palette.brightText :
control.flat && !control.down ? (control.visualFocus ? control.palette.highlight : control.palette.windowText) : control.palette.buttonText
font.family: Style.font_family
font.pixelSize: Style.body_font_size
font.letterSpacing: Style.body_letter_spacing
contentItem: IconLabel {
spacing: control.spacing
mirrored: control.mirrored
display: control.display
icon.width: 16
icon.height: 16
icon.color: {
if (primary && !isIcon) {
return "#FFFFFF"
} else {
return colorScheme.text_norm
}
}
icon: control.icon
text: control.text
font: control.font
color: {
if (!secondary) {
// Primary colors
return "#FFFFFF"
} else {
// Secondary colors
return colorScheme.text_norm
contentItem: Item {
id: _contentItem
// Since contentItem is allways resized to maximum available size - we need to "incapsulate" label
// and icon within one single item with calculated fixed implicit size
implicitHeight: labelIcon.implicitHeight
implicitWidth: labelIcon.implicitWidth
Item {
id: labelIcon
anchors.horizontalCenter: _contentItem.horizontalCenter
anchors.verticalCenter: _contentItem.verticalCenter
width: Math.min(implicitWidth, control.availableWidth)
height: Math.min(implicitHeight, control.availableHeight)
implicitWidth: {
var textImplicitWidth = control.text !== "" ? label.implicitWidth : 0
var iconImplicitWidth = iconImage.source ? iconImage.implicitWidth : 0
var spacing = (control.text !== "" && iconImage.source && control.display === AbstractButton.TextBesideIcon) ? control.spacing : 0
return control.display === AbstractButton.TextBesideIcon ? textImplicitWidth + iconImplicitWidth + spacing : Math.max(textImplicitWidth, iconImplicitWidth)
}
implicitHeight: {
var textImplicitHeight = control.text !== "" ? label.implicitHeight : 0
var iconImplicitHeight = iconImage.source ? iconImage.implicitHeight : 0
var spacing = (control.text !== "" && iconImage.source && control.display === AbstractButton.TextUnderIcon) ? control.spacing : 0
return control.display === AbstractButton.TextUnderIcon ? textImplicitHeight + iconImplicitHeight + spacing : Math.max(textImplicitHeight, iconImplicitHeight)
}
Label {
id: label
anchors.left: labelIcon.left
anchors.top: labelIcon.top
anchors.bottom: labelIcon.bottom
anchors.right: control.loading ? iconImage.left : labelIcon.right
anchors.rightMargin: control.loading ? control.spacing : 0
elide: Text.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
text: control.text
font: control.font
color: {
if (primary && !isIcon) {
return "#FFFFFF"
} else {
return colorScheme.text_norm
}
}
opacity: control.enabled || control.loading ? 1.0 : 0.5
}
ColorImage {
id: iconImage
anchors.verticalCenter: labelIcon.verticalCenter
anchors.right: labelIcon.right
width: {
// special case for loading since we want icon to be square for rotation animation
if (control.loading) {
return Math.min(control.icon.width, availableWidth, control.icon.height, availableHeight)
}
return Math.min(control.icon.width, availableWidth)
}
height: {
if (control.loading) {
return width
}
Math.min(control.icon.height, availableHeight)
}
color: control.icon.color
source: control.loading ? "../icons/Loader_16.svg" : control.icon.source
visible: control.loading || control.icon.source
RotationAnimation {
target: iconImage
loops: Animation.Infinite
duration: 1000
from: 0
to: 360
direction: RotationAnimation.Clockwise
running: control.loading
}
}
}
}
background: Rectangle {
implicitWidth: 72
implicitWidth: 36
implicitHeight: 36
radius: 4
visible: !control.flat || control.down || control.checked || control.highlighted
visible: true
color: {
if (!isIcon) {
if (!secondary) {
if (primary) {
// Primary colors
if (control.down) {
@ -81,6 +183,10 @@ T.Button {
return colorScheme.interaction_norm_hover
}
if (control.loading) {
return colorScheme.interaction_norm_hover
}
return colorScheme.interaction_norm
} else {
// Secondary colors
@ -93,10 +199,14 @@ T.Button {
return colorScheme.interaction_default_hover
}
if (control.loading) {
return colorScheme.interaction_default_hover
}
return colorScheme.interaction_default
}
} else {
if (!secondary) {
if (primary) {
// Primary icon colors
if (control.down) {
@ -107,6 +217,10 @@ T.Button {
return colorScheme.interaction_default_hover
}
if (control.loading) {
return colorScheme.interaction_default_hover
}
return colorScheme.interaction_default
} else {
// Secondary icon colors
@ -119,10 +233,18 @@ T.Button {
return colorScheme.interaction_default_hover
}
if (control.loading) {
return colorScheme.interaction_default_hover
}
return colorScheme.interaction_default
}
}
}
opacity: control.enabled ? 1.0 : 0.5
border.color: colorScheme.border_norm
border.width: secondary ? 1 : 0
opacity: control.enabled || control.loading ? 1.0 : 0.5
}
}

View File

@ -0,0 +1,132 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
T.CheckBox {
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle
property bool error: false
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 0
spacing: 8
indicator: Rectangle {
implicitWidth: 20
implicitHeight: 20
radius: 4
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2
color: {
if (!checked) {
return colorScheme.background_norm
}
if (!control.enabled) {
return colorScheme.field_disabled
}
if (control.error) {
return colorScheme.signal_danger
}
if (control.hovered) {
return colorScheme.interaction_norm_hover
}
return colorScheme.interaction_norm
}
border.width: control.checked ? 0 : 1
border.color: {
if (!control.enabled) {
return colorScheme.field_disabled
}
if (control.error) {
return colorScheme.signal_danger
}
if (control.hovered) {
return colorScheme.interaction_norm_hover
}
return colorScheme.field_norm
}
ColorImage {
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: parent.width - 4
height: parent.height - 4
color: "#FFFFFF"
source: "../icons/ic-check.svg"
visible: control.checkState === Qt.Checked
}
// TODO: do we need PartiallyChecked state?
//Rectangle {
// x: (parent.width - width) / 2
// y: (parent.height - height) / 2
// width: 16
// height: 3
// color: control.palette.text
// visible: control.checkState === Qt.PartiallyChecked
//}
}
contentItem: CheckLabel {
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
text: control.text
color: {
if (!enabled) {
return colorScheme.text_disabled
}
if (error) {
return colorScheme.signal_danger
}
return colorScheme.text_norm
}
font.family: Style.font_family
font.weight: Style.fontWidth_400
font.pixelSize: 14
lineHeight: 20
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2
}
}

View File

@ -17,10 +17,10 @@
import QtQml 2.13
// https://wiki.qt.io/Qml_Styling
// http://imaginativethinking.ca/make-qml-component-singleton/
QtObject {
// should be a pointer to ColorScheme object
property var prominent
// Primary
property color primay_norm

View File

@ -0,0 +1,43 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.13
import QtQuick.Controls 2.12
Label {
id: root
color: Style.currentStyle.text_norm
palette.link: Style.currentStyle.interaction_norm
font.family: ProtonStyle.font_family
font.weight: ProtonStyle.fontWidth_400
lineHeightMode: Text.FixedHeight
function putLink(linkURL,linkText) {
return `<a href="${linkURL}">${linkText}</a>`
}
state: "title"
states: [
State { name : "heading" ; PropertyChanges { target : root ; font.pixelSize : Style.heading_font_size ; lineHeight : Style.heading_line_height } },
State { name : "title" ; PropertyChanges { target : root ; font.pixelSize : Style.title_font_size ; lineHeight : Style.title_line_height } },
State { name : "lead" ; PropertyChanges { target : root ; font.pixelSize : Style.lead_font_size ; lineHeight : Style.lead_line_height } },
State { name : "body" ; PropertyChanges { target : root ; font.pixelSize : Style.body_font_size ; lineHeight : Style.body_line_height ; font.letterSpacing : Style.body_letter_spacing } },
State { name : "caption" ; PropertyChanges { target : root ; font.pixelSize : Style.caption_font_size ; lineHeight : Style.caption_line_height ; font.letterSpacing : Style.caption_letter_spacing } }
]
}

View File

@ -0,0 +1,115 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
T.RadioButton {
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle
property bool error: false
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 0
spacing: 8
indicator: Rectangle {
implicitWidth: 20
implicitHeight: 20
radius: width / 2
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2
color: colorScheme.background_norm
border.width: 1
border.color: {
if (!control.enabled) {
return colorScheme.field_disabled
}
if (control.error) {
return colorScheme.signal_danger
}
if (control.hovered) {
return colorScheme.interaction_norm_hover
}
return colorScheme.field_norm
}
Rectangle {
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: 8
height: 8
radius: width / 2
color: {
if (!control.enabled) {
return colorScheme.field_disabled
}
if (control.error) {
return colorScheme.signal_danger
}
if (control.hovered) {
return colorScheme.interaction_norm_hover
}
return colorScheme.interaction_norm
}
visible: control.checked
}
}
contentItem: CheckLabel {
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
text: control.text
color: {
if (!enabled) {
return colorScheme.text_disabled
}
if (error) {
return colorScheme.signal_danger
}
return colorScheme.text_norm
}
font.family: Style.font_family
font.weight: Style.fontWidth_400
font.pixelSize: 14
lineHeight: 20
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2
}
}

View File

@ -0,0 +1,116 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.8
Rectangle {
id: root
color: Style.transparent
property color fillColor : Style.currentStyle.background_norm
property color strokeColor : Style.currentStyle.background_strong
property real strokeWidth : 1
property real radiusTopLeft : 10
property real radiusBottomLeft : 10
property real radiusTopRight : 10
property real radiusBottomRight : 10
function paint() {
canvas.requestPaint()
}
onFillColorChanged : root.paint()
onStrokeColorChanged : root.paint()
onStrokeWidthChanged : root.paint()
onRadiusTopLeftChanged : root.paint()
onRadiusBottomLeftChanged : root.paint()
onRadiusTopRightChanged : root.paint()
onRadiusBottomRightChanged : root.paint()
Canvas {
id: canvas
anchors.fill: root
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = root.fillColor
ctx.strokeStyle = root.strokeColor
ctx.lineWidth = root.strokeWidth
var dimensions = {
x: ctx.lineWidth,
y: ctx.lineWidth,
w: canvas.width-2*ctx.lineWidth,
h: canvas.height-2*ctx.lineWidth,
}
var radius = {
tl: root.radiusTopLeft,
tr: root.radiusTopRight,
bl: root.radiusBottomLeft,
br: root.radiusBottomRight,
}
root.roundRect(
ctx,
dimensions,
radius, true, true
)
}
}
// adapted from: https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas/3368118#3368118
function roundRect(ctx, dim, radius, fill, stroke) {
if (typeof stroke == 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(dim.x + radius.tl, dim.y);
ctx.lineTo(dim.x + dim.w - radius.tr, dim.y);
ctx.quadraticCurveTo(dim.x + dim.w, dim.y, dim.x + dim.w, dim.y + radius.tr);
ctx.lineTo(dim.x + dim.w, dim.y + dim.h - radius.br);
ctx.quadraticCurveTo(dim.x + dim.w, dim.y + dim.h, dim.x + dim.w - radius.br, dim.y + dim.h);
ctx.lineTo(dim.x + radius.bl, dim.y + dim.h);
ctx.quadraticCurveTo(dim.x, dim.y + dim.h, dim.x, dim.y + dim.h - radius.bl);
ctx.lineTo(dim.x, dim.y + radius.tl);
ctx.quadraticCurveTo(dim.x, dim.y, dim.x + radius.tl, dim.y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
Component.onCompleted: root.paint()
}

View File

@ -17,6 +17,7 @@
pragma Singleton
import QtQml 2.13
import QtQuick 2.12
import "./"
@ -24,279 +25,271 @@ import "./"
// http://imaginativethinking.ca/make-qml-component-singleton/
QtObject {
// TODO: Once we will use Qt >=5.15 this should be refactored with inline components as follows:
// https://doc.qt.io/qt-5/qtqml-documents-definetypes.html#inline-components
//component ColorScheme: QtObject {
// property color primay_norm
// ...
//}
// component ColorScheme: QtObject {
// property color primay_norm
// ...
// }
// and instead of "var" later on "ColorScheme" should be used (also in each component)
// and instead of "var" later on "ColorScheme" should be used
property var lightStyle: ColorScheme {
id: _lightStyle
property var _lightStyle: ColorScheme {
id: lightStyle
prominent: prominentStyle
// Primary
primay_norm: "#657EE4"
// Primary
primay_norm: "#657EE4"
// Interaction-norm
interaction_norm: "#657EE4"
interaction_norm_hover: "#5064B6"
interaction_norm_active: "#3C4B88"
// Interaction-norm
interaction_norm: "#657EE4"
interaction_norm_hover: "#5064B6"
interaction_norm_active: "#3C4B88"
// Text
text_norm: "#262A33"
text_weak: "#696F7D"
text_hint: "#A4A9B5"
text_disabled: "#BABEC7"
text_invert: "#FFFFFF"
// Text
text_norm: "#262A33"
text_weak: "#696F7D"
text_hint: "#A4A9B5"
text_disabled: "#BABEC7"
text_invert: "#FFFFFF"
// Field
field_norm: "#BABEC7"
field_hover: "#A4A9B5"
field_disabled: "#D0D3DA"
// Field
field_norm: "#BABEC7"
field_hover: "#A4A9B5"
field_disabled: "#D0D3DA"
// Border
border_norm: "#D0D3DA"
border_weak: "#E7E9EC"
// Border
border_norm: "#D0D3DA"
border_weak: "#E7E9EC"
// Background
background_norm: "#FFFFFF"
background_weak: "#F3F4F6"
background_strong: "#E7E9EC"
background_avatar: "#A4A9B5"
// Background
background_norm: "#FFFFFF"
background_weak: "#F3F4F6"
background_strong: "#E7E9EC"
background_avatar: "#A4A9B5"
// Interaction-weak
interaction_weak: "#D0D3DA"
interaction_weak_hover: "#BABEC7"
interaction_weak_active: "#A4A9B5"
// Interaction-weak
interaction_weak: "#D0D3DA"
interaction_weak_hover: "#BABEC7"
interaction_weak_active: "#A4A9B5"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: "#33BABEC7"
interaction_default_active: "#4DBABEC7"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: "#33BABEC7"
interaction_default_active: "#4DBABEC7"
// Scrollbar
scrollbar_norm: "#D0D3DA"
scrollbar_hover: "#BABEC7"
// Scrollbar
scrollbar_norm: "#D0D3DA"
scrollbar_hover: "#BABEC7"
// Signal
signal_danger: "#D42F34"
signal_danger_hover: "#C7262B"
signal_danger_active: "#BA1E23"
signal_warning: "#F5830A"
signal_warning_hover: "#F5740A"
signal_warning_active: "#F5640A"
signal_success: "#1B8561"
signal_success_hover: "#147857"
signal_success_active: "#0F6B4C"
signal_info: "#1578CF"
signal_info_hover: "#0E6DC2"
signal_info_active: "#0764B5"
// Signal
signal_danger: "#D42F34"
signal_danger_hover: "#C7262B"
signal_danger_active: "#BA1E23"
signal_warning: "#F5830A"
signal_warning_hover: "#F5740A"
signal_warning_active: "#F5640A"
signal_success: "#1B8561"
signal_success_hover: "#147857"
signal_success_active: "#0F6B4C"
signal_info: "#1578CF"
signal_info_hover: "#0E6DC2"
signal_info_active: "#0764B5"
// Shadows
shadow_norm: "#FFFFFF"
shadow_lifted: "#FFFFFF"
// Shadows
shadow_norm: "#FFFFFF"
shadow_lifted: "#FFFFFF"
// Backdrop
backdrop_norm: "#7A262A33"
}
// Backdrop
backdrop_norm: "#7A262A33"
}
property var _prominentStyle: ColorScheme {
id: prominentStyle
property var prominentStyle: ColorScheme {
id: _prominentStyle
// Primary
primay_norm: "#657EE4"
prominent: this
// Interaction-norm
interaction_norm: "#657EE4"
interaction_norm_hover: "#7D92E8"
interaction_norm_active: "#98A9EE"
// Primary
primay_norm: "#657EE4"
// Text
text_norm: "#FFFFFF"
text_weak: "#949BB9"
text_hint: "#565F84"
text_disabled: "#444E72"
text_invert: "#1C223D"
// Interaction-norm
interaction_norm: "#657EE4"
interaction_norm_hover: "#7D92E8"
interaction_norm_active: "#98A9EE"
// Field
field_norm: "#565F84"
field_hover: "#949BB9"
field_disabled: "#353E60"
// Text
text_norm: "#FFFFFF"
text_weak: "#949BB9"
text_hint: "#565F84"
text_disabled: "#444E72"
text_invert: "#1C223D"
// Border
border_norm: "#353E60"
border_weak: "#2D3657"
// Field
field_norm: "#565F84"
field_hover: "#949BB9"
field_disabled: "#353E60"
// Background
background_norm: "#1C223D"
background_weak: "#272F4F"
background_strong: "#2D3657"
background_avatar: "#444E72"
// Border
border_norm: "#353E60"
border_weak: "#2D3657"
// Interaction-weak
interaction_weak: "#353E60"
interaction_weak_hover: "#444E72"
interaction_weak_active: "#565F84"
// Background
background_norm: "#1C223D"
background_weak: "#272F4F"
background_strong: "#2D3657"
background_avatar: "#444E72"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: "#4D444E72"
interaction_default_active: "#66444E72"
// Interaction-weak
interaction_weak: "#353E60"
interaction_weak_hover: "#444E72"
interaction_weak_active: "#565F84"
// Scrollbar
scrollbar_norm: "#353E60"
scrollbar_hover: "#444E72"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: "#4D444E72"
interaction_default_active: "#66444E72"
// Signal
signal_danger: "#ED4C51"
signal_danger_hover: "#F7595E"
signal_danger_active: "#FF666B"
signal_warning: "#F5930A"
signal_warning_hover: "#F5A716"
signal_warning_active: "#F5B922"
signal_success: "#349172"
signal_success_hover: "#339C79"
signal_success_active: "#31A67F"
signal_info: "#2C89DB"
signal_info_hover: "#3491E3"
signal_info_active: "#3D99EB"
// Scrollbar
scrollbar_norm: "#353E60"
scrollbar_hover: "#444E72"
// Shadows
shadow_norm: "#1C223D"
shadow_lifted: "#1C223D"
// Signal
signal_danger: "#ED4C51"
signal_danger_hover: "#F7595E"
signal_danger_active: "#FF666B"
signal_warning: "#F5930A"
signal_warning_hover: "#F5A716"
signal_warning_active: "#F5B922"
signal_success: "#349172"
signal_success_hover: "#339C79"
signal_success_active: "#31A67F"
signal_info: "#2C89DB"
signal_info_hover: "#3491E3"
signal_info_active: "#3D99EB"
// Backdrop
backdrop_norm: "#52000000"
}
// Shadows
shadow_norm: "#1C223D"
shadow_lifted: "#1C223D"
property var _darkStyle: ColorScheme {
id: darkStyle
// Backdrop
backdrop_norm: "#52000000"
}
// Primary
primay_norm: "#657EE4"
property var darkStyle: ColorScheme {
id: _darkStyle
// Interaction-norm
interaction_norm: "#657EE4"
interaction_norm_hover: "#7D92E8"
interaction_norm_active: "#98A9EE"
prominent: prominentStyle
// Text
text_norm: "#FFFFFF"
text_weak: "#A4A9B5"
text_hint: "#696F7D"
text_disabled: "#575D6B"
text_invert: "#262A33"
// Primary
primay_norm: "#657EE4"
// Field
field_norm: "#575D6B"
field_hover: "#696F7D"
field_disabled: "#464B58"
// Interaction-norm
interaction_norm: "#657EE4"
interaction_norm_hover: "#7D92E8"
interaction_norm_active: "#98A9EE"
// Border
border_norm: "#464B58"
border_weak: "#363A46"
// Text
text_norm: "#FFFFFF"
text_weak: "#A4A9B5"
text_hint: "#696F7D"
text_disabled: "#575D6B"
text_invert: "#262A33"
// Background
background_norm: "#262A33"
background_weak: "#2E323C"
background_strong: "#363A46"
background_avatar: "#575D6B"
// Field
field_norm: "#575D6B"
field_hover: "#696F7D"
field_disabled: "#464B58"
// Interaction-weak
interaction_weak: "#464B58"
interaction_weak_hover: "#575D6B"
interaction_weak_active: "#696F7D"
// Border
border_norm: "#464B58"
border_weak: "#363A46"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: "#33575D6B"
interaction_default_active: "#4D575D6B"
// Background
background_norm: "#262A33"
background_weak: "#2E323C"
background_strong: "#363A46"
background_avatar: "#575D6B"
// Scrollbar
scrollbar_norm: "#464B58"
scrollbar_hover: "#575D6B"
// Interaction-weak
interaction_weak: "#464B58"
interaction_weak_hover: "#575D6B"
interaction_weak_active: "#696F7D"
// Signal
signal_danger: "#ED4C51"
signal_danger_hover: "#F7595E"
signal_danger_active: "#FF666B"
signal_warning: "#F5930A"
signal_warning_hover: "#F5A716"
signal_warning_active: "#F5B922"
signal_success: "#349172"
signal_success_hover: "#339C79"
signal_success_active: "#31A67F"
signal_info: "#2C89DB"
signal_info_hover: "#3491E3"
signal_info_active: "#3D99EB"
// Interaction-default
interaction_default: "#00000000"
interaction_default_hover: "#33575D6B"
interaction_default_active: "#4D575D6B"
// Shadows
shadow_norm: "#262A33"
shadow_lifted: "#262A33"
// Scrollbar
scrollbar_norm: "#464B58"
scrollbar_hover: "#575D6B"
// Backdrop
backdrop_norm: "#52000000"
}
// Signal
signal_danger: "#ED4C51"
signal_danger_hover: "#F7595E"
signal_danger_active: "#FF666B"
signal_warning: "#F5930A"
signal_warning_hover: "#F5A716"
signal_warning_active: "#F5B922"
signal_success: "#349172"
signal_success_hover: "#339C79"
signal_success_active: "#31A67F"
signal_info: "#2C89DB"
signal_info_hover: "#3491E3"
signal_info_active: "#3D99EB"
// TODO: if default style should be loaded from somewhere - it should be loaded here
property var currentStyle: lightStyle
// Shadows
shadow_norm: "#262A33"
shadow_lifted: "#262A33"
property var _timer: Timer {
interval: 1000
repeat: true
running: true
onTriggered: {
switch (currentStyle) {
case lightStyle:
console.debug("Dark Style")
currentStyle = darkStyle
return
case darkStyle:
console.debug("Prominent Style")
currentStyle = prominentStyle
return
case prominentStyle:
console.debug("Light Style")
currentStyle = lightStyle
return
}
}
}
// Backdrop
backdrop_norm: "#52000000"
}
// TODO: if default style should be loaded from somewhere
// (i.e. from preferencies file) - it should be loaded here
property var currentStyle: lightStyle
property string font: {
// TODO: add OS to backend
property string font_family: {
switch (Qt.platform.os) {
case "windows":
return "Segoe UI"
case "osx":
return "SF Pro Display"
case "linux":
return "Ubuntu"
//switch (backend.OS) {
// case "Windows":
// return "Segoe UI"
// case "OSX":
// return "SF Pro Display"
// case "Linux":
// return "Ubuntu"
//}
}
property int heading_font_size: 28
property int heading_line_height: 36
property int title_font_size: 20
property int title_line_height: 24
property int lead_font_size: 18
property int lead_line_height: 26
property int body_font_size: 14
property int body_line_height: 20
property real body_letter_spacing: 0.2
property int caption_font_size: 12
property int caption_line_height: 16
property real caption_letter_spacing: 0.4
default:
console.error("Unknown platform")
}
}
property int heading_font_size: 28
property int heading_line_height: 36
property int title_font_size: 20
property int title_line_height: 24
property int lead_font_size: 18
property int lead_line_height: 26
property int body_font_size: 14
property int body_line_height: 20
property real body_letter_spacing: 0.2
property int caption_font_size: 12
property int caption_line_height: 16
property real caption_letter_spacing: 0.4
property int fontWidth_100: Font.Thin
property int fontWidth_200: Font.Light
property int fontWidth_300: Font.ExtraLight
property int fontWidth_400: Font.Normal
property int fontWidth_500: Font.Medium
property int fontWidth_600: Font.DemiBold
property int fontWidth_700: Font.Bold
property int fontWidth_800: Font.ExtraBold
property int fontWidth_900: Font.Black
property var transparent: "#00000000"
}

View File

@ -0,0 +1,149 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQuick 2.12
import QtQuick.Templates 2.12 as T
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
T.Switch {
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle
property bool loading: false
// TODO: store previous enabled state and restore it?
// For now assuming that only enabled buttons could have loading state
onLoadingChanged: {
if (loading) {
enabled = false
} else {
enabled = true
}
}
id: control
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding,
implicitIndicatorHeight + topPadding + bottomPadding)
padding: 0
spacing: 7
indicator: PaddedRectangle {
implicitWidth: 40
implicitHeight: 24
x: text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2
y: control.topPadding + (control.availableHeight - height) / 2
radius: 12
leftPadding: 0
rightPadding: 0
padding: 0
color: control.enabled || control.loading ? colorScheme.background_norm : colorScheme.background_strong
border.width: control.enabled && !loading ? 1 : 0
border.color: control.hovered ? colorScheme.field_hover : colorScheme.field_norm
Rectangle {
x: Math.max(0, Math.min(parent.width - width, control.visualPosition * parent.width - (width / 2)))
y: (parent.height - height) / 2
width: 24
height: 24
radius: 12
visible: !loading
color: {
if (!control.enabled) {
return colorScheme.field_disabled
}
if (control.checked) {
if (control.hovered) {
return colorScheme.interaction_norm_hover
}
return colorScheme.interaction_norm
}
if (control.hovered) {
return colorScheme.field_hover
}
return colorScheme.field_norm
}
ColorImage {
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: 16
height: 16
color: "#FFFFFF"
source: "../icons/ic-check.svg"
visible: control.checked
}
Behavior on x {
enabled: !control.down
SmoothedAnimation { velocity: 200 }
}
}
ColorImage {
id: loadingImage
x: parent.width - width
y: (parent.height - height) / 2
width: 18
height: 18
color: colorScheme.interaction_norm_hover
source: "../icons/Loader_16.svg"
visible: control.loading
RotationAnimation {
target: loadingImage
loops: Animation.Infinite
duration: 1000
from: 0
to: 360
direction: RotationAnimation.Clockwise
running: control.loading
}
}
}
contentItem: CheckLabel {
id: label
leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0
rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0
text: control.text
color: control.enabled || control.loading ? colorScheme.text_norm : colorScheme.text_disabled
font.family: Style.font_family
font.weight: Style.fontWidth_400
font.pixelSize: 14
lineHeight: 20
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2
}
}

View File

@ -0,0 +1,274 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
Item {
id: root
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle
property alias background: control.background
property alias bottomInset: control.bottomInset
//property alias flickable: control.flickable
property alias focusReason: control.focusReason
property alias hoverEnabled: control.hoverEnabled
property alias hovered: control.hovered
property alias implicitBackgroundHeight: control.implicitBackgroundHeight
property alias implicitBackgroundWidth: control.implicitBackgroundWidth
property alias leftInset: control.leftInset
property alias palette: control.palette
property alias placeholderText: control.placeholderText
property alias placeholderTextColor: control.placeholderTextColor
property alias rightInset: control.rightInset
property alias topInset: control.topInset
property alias activeFocusOnPress: control.activeFocusOnPress
property alias baseUrl: control.baseUrl
property alias bottomPadding: control.bottomPadding
property alias canPaste: control.canPaste
property alias canRedo: control.canRedo
property alias canUndo: control.canUndo
property alias color: control.color
property alias contentHeight: control.contentHeight
property alias contentWidth: control.contentWidth
property alias cursorDelegate: control.cursorDelegate
property alias cursorPosition: control.cursorPosition
property alias cursorRectangle: control.cursorRectangle
property alias cursorVisible: control.cursorVisible
property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment
property alias font: control.font
property alias horizontalAlignment: control.horizontalAlignment
property alias hoveredLink: control.hoveredLink
property alias inputMethodComposing: control.inputMethodComposing
property alias inputMethodHints: control.inputMethodHints
property alias leftPadding: control.leftPadding
property alias length: control.length
property alias lineCount: control.lineCount
property alias mouseSelectionMode: control.mouseSelectionMode
property alias overwriteMode: control.overwriteMode
property alias padding: control.padding
property alias persistentSelection: control.persistentSelection
property alias preeditText: control.preeditText
property alias readOnly: control.readOnly
property alias renderType: control.renderType
property alias rightPadding: control.rightPadding
property alias selectByKeyboard: control.selectByKeyboard
property alias selectByMouse: control.selectByMouse
property alias selectedText: control.selectedText
property alias selectedTextColor: control.selectedTextColor
property alias selectionColor: control.selectionColor
property alias selectionEnd: control.selectionEnd
property alias selectionStart: control.selectionStart
property alias tabStopDistance: control.tabStopDistance
property alias text: control.text
property alias textDocument: control.textDocument
property alias textFormat: control.textFormat
property alias textMargin: control.textMargin
property alias topPadding: control.topPadding
property alias verticalAlignment: control.verticalAlignment
property alias wrapMode: control.wrapMode
implicitWidth: background.width
implicitHeight: control.implicitHeight +
Math.max(label.implicitHeight + label.anchors.topMargin + label.anchors.bottomMargin, hint.implicitHeight + hint.anchors.topMargin + hint.anchors.bottomMargin) +
assistiveText.implicitHeight
property alias label: label.text
property alias hint: hint.text
property alias assistiveText: assistiveText.text
property bool error: false
// Backgroud is moved away from within control as it will be clipped with scrollview
Rectangle {
id: background
anchors.fill: controlView
radius: 4
visible: true
color: colorScheme.background_norm
border.color: {
if (!control.enabled) {
return colorScheme.field_disabled
}
if (control.activeFocus) {
return colorScheme.interaction_norm
}
if (root.error) {
return colorScheme.signal_danger
}
if (control.hovered) {
return colorScheme.field_hover
}
return colorScheme.field_norm
}
border.width: 1
}
Label {
id: label
anchors.top: root.top
anchors.left: root.left
anchors.bottomMargin: 4
color: root.enabled ? colorScheme.text_norm : colorScheme.text_disabled
font.family: Style.font_family
font.weight: Style.fontWidth_600
font.pixelSize: 14
lineHeight: 20
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.2
}
Label {
id: hint
anchors.right: root.right
anchors.bottom: controlView.top
anchors.bottomMargin: 5
color: root.enabled ? colorScheme.text_weak : colorScheme.text_disabled
font.family: Style.font_family
font.weight: Style.fontWidth_400
font.pixelSize: 12
lineHeight: 16
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.4
}
ColorImage {
id: errorIcon
visible: root.error
anchors.left: parent.left
anchors.top: assistiveText.top
anchors.bottom: assistiveText.bottom
source: "../icons/ic-exclamation-circle-filled.svg"
color: colorScheme.signal_danger
}
Label {
id: assistiveText
anchors.left: root.error ? errorIcon.right : parent.left
anchors.leftMargin: root.error ? 5 : 0
anchors.bottom: root.bottom
anchors.topMargin: 4
color: {
if (!root.enabled) {
return colorScheme.text_disabled
}
if (root.error) {
return colorScheme.signal_danger
}
return colorScheme.text_weak
}
font.family: Style.font_family
font.weight: root.error ? Style.fontWidth_600 : Style.fontWidth_400
font.pixelSize: 12
lineHeight: 16
lineHeightMode: Text.FixedHeight
font.letterSpacing: 0.4
}
ScrollView {
id: controlView
anchors.top: label.bottom
anchors.left: root.left
anchors.right: root.right
anchors.bottom: assistiveText.top
clip: true
T.TextArea {
id: control
implicitWidth: Math.max(contentWidth + leftPadding + rightPadding,
implicitBackgroundWidth + leftInset + rightInset,
placeholder.implicitWidth + leftPadding + rightPadding)
implicitHeight: Math.max(contentHeight + topPadding + bottomPadding,
implicitBackgroundHeight + topInset + bottomInset,
placeholder.implicitHeight + topPadding + bottomPadding)
padding: 8
leftPadding: 12
color: control.enabled ? colorScheme.text_norm : colorScheme.text_disabled
placeholderTextColor: control.enabled ? colorScheme.text_hint : colorScheme.text_disabled
selectionColor: control.palette.highlight
selectedTextColor: control.palette.highlightedText
cursorDelegate: Rectangle {
id: cursor
width: 1
color: colorScheme.interaction_norm
visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd
Connections {
target: control
onCursorPositionChanged: {
// keep a moving cursor visible
cursor.opacity = 1
timer.restart()
}
}
Timer {
id: timer
running: control.activeFocus && !control.readOnly
repeat: true
interval: Qt.styleHints.cursorFlashTime / 2
onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0
// force the cursor visible when gaining focus
onRunningChanged: cursor.opacity = 1
}
}
PlaceholderText {
id: placeholder
x: control.leftPadding
y: control.topPadding
width: control.width - (control.leftPadding + control.rightPadding)
height: control.height - (control.topPadding + control.bottomPadding)
text: control.placeholderText
font: control.font
color: control.placeholderTextColor
verticalAlignment: control.verticalAlignment
visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter)
elide: Text.ElideRight
renderType: control.renderType
}
}
}
}

View File

@ -0,0 +1,329 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
import QtQml 2.12
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.impl 2.12
import QtQuick.Templates 2.12 as T
import QtQuick.Layouts 1.12
Item {
id: root
property var colorScheme: parent.colorScheme ? parent.colorScheme : Style.currentStyle
property alias background: control.background
property alias bottomInset: control.bottomInset
property alias focusReason: control.focusReason
property alias hoverEnabled: control.hoverEnabled
property alias hovered: control.hovered
property alias implicitBackgroundHeight: control.implicitBackgroundHeight
property alias implicitBackgroundWidth: control.implicitBackgroundWidth
property alias leftInset: control.leftInset
property alias palette: control.palette
property alias placeholderText: control.placeholderText
property alias placeholderTextColor: control.placeholderTextColor
property alias rightInset: control.rightInset
property alias topInset: control.topInset
property alias acceptableInput: control.acceptableInput
property alias activeFocusOnPress: control.activeFocusOnPress
property alias autoScroll: control.autoScroll
property alias bottomPadding: control.bottomPadding
property alias canPaste: control.canPaste
property alias canRedo: control.canRedo
property alias canUndo: control.canUndo
property alias color: control.color
//property alias contentHeight: control.contentHeight
//property alias contentWidth: control.contentWidth
property alias cursorDelegate: control.cursorDelegate
property alias cursorPosition: control.cursorPosition
property alias cursorRectangle: control.cursorRectangle
property alias cursorVisible: control.cursorVisible
property alias displayText: control.displayText
property alias effectiveHorizontalAlignment: control.effectiveHorizontalAlignment
property alias font: control.font
property alias horizontalAlignment: control.horizontalAlignment
property alias inputMask: control.inputMask
property alias inputMethodComposing: control.inputMethodComposing
property alias inputMethodHints: control.inputMethodHints
property alias leftPadding: control.leftPadding
property alias length: control.length
property alias maximumLength: control.maximumLength
property alias mouseSelectionMode: control.mouseSelectionMode
property alias overwriteMode: control.overwriteMode
property alias padding: control.padding
property alias passwordCharacter: control.passwordCharacter
property alias passwordMaskDelay: control.passwordMaskDelay
property alias persistentSelection: control.persistentSelection
property alias preeditText: control.preeditText
property alias readOnly: control.readOnly
property alias renderType: control.renderType
property alias rightPadding: control.rightPadding
property alias selectByMouse: control.selectByMouse
property alias selectedText: control.selectedText
property alias selectedTextColor: control.selectedTextColor
property alias selectionColor: control.selectionColor
property alias selectionEnd: control.selectionEnd
property alias selectionStart: control.selectionStart
property alias text: control.text
property alias validator: control.validator
property alias verticalAlignment: control.verticalAlignment
property alias wrapMode: control.wrapMode
implicitWidth: children[0].implicitWidth
implicitHeight: children[0].implicitHeight
property alias label: label.text
property alias hint: hint.text
property alias assistiveText: assistiveText.text
property var echoMode: TextInput.Normal
property bool error: false
signal accepted()
signal editingFinished()
signal textEdited()
function clear() { control.clear() }
function copy() { control.copy() }
function cut() { control.cut() }
function deselect() { control.deselect() }
function ensureVisible(position) { control.ensureVisible(position) }
function getText(start, end) { control.getText(start, end) }
function insert(position, text) { control.insert(position, text) }
function isRightToLeft(start, end) { control.isRightToLeft(start, end) }
function moveCursorSelection(position, mode) { control.moveCursorSelection(position, mode) }
function paste() { control.paste() }
function positionAt(x, y, position) { control.positionAt(x, y, position) }
function positionToRectangle(pos) { control.positionToRectangle(pos) }
function redo() { control.redo() }
function remove(start, end) { control.remove(start, end) }
function select(start, end) { control.select(start, end) }
function selectAll() { control.selectAll() }
function selectWord() { control.selectWord() }
function undo() { control.undo() }
function forceActiveFocus() {control.forceActiveFocus()}
ColumnLayout {
anchors.fill: parent
spacing: 0
RowLayout {
Layout.fillWidth: true
spacing: 0
ProtonLabel {
id: label
Layout.fillHeight: true
Layout.fillWidth: true
color: root.enabled ? colorScheme.text_norm : colorScheme.text_disabled
font.weight: Style.fontWidth_600
state: "body"
}
ProtonLabel {
id: hint
Layout.fillHeight: true
Layout.fillWidth: true
color: root.enabled ? colorScheme.text_weak : colorScheme.text_disabled
horizontalAlignment: Text.AlignRight
state: "caption"
}
}
// Background is moved away from within control to cover eye button as well.
// In case it will remain as control background property - control's width
// will be adjusted to background's width making text field and eye button overlap
Rectangle {
id: background
Layout.fillHeight: true
Layout.fillWidth: true
radius: 4
visible: true
color: colorScheme.background_norm
border.color: {
if (!control.enabled) {
return colorScheme.field_disabled
}
if (control.activeFocus) {
return colorScheme.interaction_norm
}
if (root.error) {
return colorScheme.signal_danger
}
if (control.hovered) {
return colorScheme.field_hover
}
return colorScheme.field_norm
}
border.width: 1
implicitWidth: children[0].implicitWidth
implicitHeight: children[0].implicitHeight
RowLayout {
anchors.fill: parent
spacing: 0
T.TextField {
id: control
Layout.fillHeight: true
Layout.fillWidth: true
implicitWidth: implicitBackgroundWidth + leftInset + rightInset
|| Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding,
placeholder.implicitHeight + topPadding + bottomPadding)
padding: 8
leftPadding: 12
color: control.enabled ? colorScheme.text_norm : colorScheme.text_disabled
selectionColor: control.palette.highlight
selectedTextColor: control.palette.highlightedText
placeholderTextColor: control.enabled ? colorScheme.text_hint : colorScheme.text_disabled
verticalAlignment: TextInput.AlignVCenter
cursorDelegate: Rectangle {
id: cursor
width: 1
color: colorScheme.interaction_norm
visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd
Connections {
target: control
onCursorPositionChanged: {
// keep a moving cursor visible
cursor.opacity = 1
timer.restart()
}
}
Timer {
id: timer
running: control.activeFocus && !control.readOnly
repeat: true
interval: Qt.styleHints.cursorFlashTime / 2
onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0
// force the cursor visible when gaining focus
onRunningChanged: cursor.opacity = 1
}
}
PlaceholderText {
id: placeholder
x: control.leftPadding
y: control.topPadding
width: control.width - (control.leftPadding + control.rightPadding)
height: control.height - (control.topPadding + control.bottomPadding)
text: control.placeholderText
font: control.font
color: control.placeholderTextColor
verticalAlignment: control.verticalAlignment
visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter)
elide: Text.ElideRight
renderType: control.renderType
}
background: Item {
implicitWidth: 80
implicitHeight: 36
visible: false
}
onAccepted: {
root.accepted()
}
onEditingFinished: {
root.editingFinished()
}
onTextEdited: {
root.textEdited()
}
}
Button {
id: eyeButton
Layout.fillHeight: true
visible: root.echoMode === TextInput.Password
icon.source: control.echoMode == TextInput.Password ? "../icons/ic-eye.svg" : "../icons/ic-eye-slash.svg"
icon.color: control.color
background: Rectangle{color: "#00000000"}
onClicked: {
if (control.echoMode == TextInput.Password) {
control.echoMode = TextInput.Normal
} else {
control.echoMode = TextInput.Password
}
}
Component.onCompleted: control.echoMode = root.echoMode
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 0
// FIXME: maybe somewhere in the future there will be an Icon component capable of setting color to the icon
// but before that moment we need to use IconLabel
IconLabel {
id: errorIcon
visible: root.error && (assistiveText.text.length > 0)
icon.source: "../icons/ic-exclamation-circle-filled.svg"
icon.color: colorScheme.signal_danger
}
ProtonLabel {
id: assistiveText
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 4
color: {
if (!root.enabled) {
return colorScheme.text_disabled
}
if (root.error) {
return colorScheme.signal_danger
}
return colorScheme.text_weak
}
font.weight: root.error ? Style.fontWidth_600 : Style.fontWidth_400
state: "caption"
}
}
}
}

View File

@ -2,5 +2,12 @@ module QQtQuick.Controls.Proton
depends QtQuick.Controls 2.12
singleton ProtonStyle 4.0 Style.qml
Banner 4.0 Banner.qml
Button 4.0 Button.qml
CheckBox 4.0 CheckBox.qml
ProtonLabel 4.0 ProtonLabel.qml
RoundedRectangle 4.0 RoundedRectangle.qml
RadioButton 4.0 RadioButton.qml
Switch 4.0 Switch.qml
TextArea 4.0 TextArea.qml
TextField 4.0 TextField.qml