diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ shadowedtexture.cpp colorutils.cpp pagerouter.cpp + avatar.cpp scenegraph/shadowedrectanglenode.cpp scenegraph/shadowedrectanglematerial.cpp scenegraph/shadowedborderrectanglematerial.cpp diff --git a/src/avatar.h b/src/avatar.h new file mode 100644 --- /dev/null +++ b/src/avatar.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +class AvatarPrivate : public QObject { + Q_OBJECT + +public: + Q_INVOKABLE QString initialsFromString(const QString& name); + Q_INVOKABLE QColor colorsFromString(const QString& name); +}; \ No newline at end of file diff --git a/src/avatar.cpp b/src/avatar.cpp new file mode 100644 --- /dev/null +++ b/src/avatar.cpp @@ -0,0 +1,46 @@ +#include "avatar.h" + +auto AvatarPrivate::initialsFromString(const QString& string) -> QString +{ + if (string.isEmpty()) return QStringLiteral(""); + + auto normalized = string.normalized(QString::NormalizationForm_D); + if (normalized.contains(QStringLiteral(" "))) { + QStringList split = normalized.split(QStringLiteral(" ")); + auto first = split.first(); + auto last = split.last(); + if (first.isEmpty()) { + return QString(last.front()); + } + if (last.isEmpty()) { + return QString(first.front()); + } + return QString(first.front())+QString(last.front()); + } else { + return QString(normalized.front()); + } +} + +const QList c_colors = { + QColor("#e93a9a"), + QColor("#e93d58"), + QColor("#e9643a"), + QColor("#ef973c"), + QColor("#e8cb2d"), + QColor("#b6e521"), + QColor("#3dd425"), + QColor("#00d485"), + QColor("#00d3b8"), + QColor("#3daee9"), + QColor("#b875dc"), + QColor("#926ee4"), +}; + +#include + +auto AvatarPrivate::colorsFromString(const QString& string) -> QColor +{ + auto hash = qHash(string); + auto index = hash % (c_colors.length()-1); + return c_colors[index]; +} \ No newline at end of file diff --git a/src/controls/Avatar.qml b/src/controls/Avatar.qml new file mode 100644 --- /dev/null +++ b/src/controls/Avatar.qml @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2020 Carson Black + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.5 +import org.kde.kirigami 2.13 as Kirigami +import org.kde.kirigami.private 2.13 +import QtGraphicalEffects 1.0 + +Rectangle { + id: avatarRoot + + implicitWidth: 48 + implicitHeight: 48 + + radius: avatarRoot.width / 2 + + enum ImageMode { + AlwaysShowImage, + AdaptiveImageOrInitals, + AlwaysShowInitials + } + enum InitialsMode { + UseInitials, + UseIcon + } + + property string name + property alias source: avatarImage.source + property int initialsMode: Kirigami.Avatar.InitialsMode.UseInitials + property int imageMode: Kirigami.Avatar.ImageMode.AdaptiveImageOrInitals + + color: AvatarPrivate.colorsFromString(name) + + QtObject { + id: __private + property bool showImage: { + return (avatarRoot.imageMode == Kirigami.Avatar.ImageMode.AlwaysShowImage) || + ( + avatarImage.status == Image.Ready && + avatarRoot.imageMode == Kirigami.Avatar.ImageMode.AdaptiveImageOrInitals + ) + + } + } + + Kirigami.Heading { + visible: avatarRoot.initialsMode == Kirigami.Avatar.InitialsMode.UseInitials && !__private.showImage + + text: AvatarPrivate.initialsFromString(name) + color: Kirigami.ColorUtils.brightnessForColor(AvatarPrivate.colorsFromString(name)) == Kirigami.ColorUtils.Light + ? "black" + : "white" + + anchors.centerIn: parent + } + Kirigami.Icon { + visible: avatarRoot.initialsMode == Kirigami.Avatar.InitialsMode.UseIcon && !__private.showImage + + source: "user" + + anchors.fill: parent + anchors.margins: Kirigami.Units.gridUnit*2 + + Kirigami.Theme.textColor: Kirigami.ColorUtils.brightnessForColor(AvatarPrivate.colorsFromString(name)) == Kirigami.ColorUtils.Light + ? "black" + : "white" + } + Rectangle { + id: mask + visible: __private.showImage + + radius: avatarRoot.width / 2 + + anchors.fill: parent + + Rectangle { + id: outerBorderMask + + color: "transparent" + + border { + width: 1 + color: "black" + } + + anchors.fill: parent + + radius: width / 2 + } + } + Image { + id: avatarImage + visible: __private.showImage + + mipmap: true + smooth: true + + fillMode: Image.PreserveAspectFit + layer.enabled: true + layer.effect: OpacityMask { + maskSource: mask + } + + anchors.fill: parent + } + Rectangle { + color: Qt.rgba(0,0,0,0.2) + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: outerBorderMask + } + + anchors.fill: parent + } +} \ No newline at end of file diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -21,6 +21,7 @@ #include "shadowedtexture.h" #include "colorutils.h" #include "pagerouter.h" +#include "avatar.h" #include #include @@ -255,6 +256,9 @@ qmlRegisterUncreatableType(uri, 2, 12, "PageRouterAttached", QStringLiteral("PageRouterAttached cannot be created")); qmlRegisterType(componentUrl(QStringLiteral("RouterWindow.qml")), uri, 2, 12, "RouterWindow"); + qmlRegisterSingletonType("org.kde.kirigami.private", 2, 13, "AvatarPrivate", [] (QQmlEngine*, QJSEngine*) -> QObject* { return new AvatarPrivate; }); + qmlRegisterType(componentUrl(QStringLiteral("Avatar.qml")), uri, 2, 13, "Avatar"); + qmlProtectModule(uri, 2); }