import QtQuick import QtQuick.Controls Canvas { id: avatar signal clicked signal clickedOutside property bool active: false property string source: "" property string shape: Config.avatarShape property int squareRadius: Config.avatarBorderRadius === 0 ? 1 : Config.avatarBorderRadius // min: 1 property bool drawStroke: (active && Config.avatarActiveBorderSize > 0) || (!active && Config.avatarInactiveBorderSize > 0) property color strokeColor: active ? Config.avatarActiveBorderColor : Config.avatarInactiveBorderColor property int strokeSize: active ? Config.avatarActiveBorderSize : Config.avatarInactiveBorderSize property string tooltipText: "" property bool showTooltip: false onSourceChanged: delayPaintTimer.running = true onPaint: { // FIX: Canvas zero dimension protection if (width <= 0 || height <= 0) return; var ctx = getContext("2d"); ctx.reset(); // Clear previous drawing ctx.beginPath(); if (shape === "square") { // Squircle, actually var r = width * squareRadius / 100; ctx.moveTo(width - r, 0); ctx.arcTo(width, 0, width, height, r); ctx.arcTo(width, height, 0, height, r); ctx.arcTo(0, height, 0, 0, r); ctx.arcTo(0, 0, width, 0, r); ctx.closePath(); } else { // Circle ctx.ellipse(0, 0, width, height); } ctx.clip(); if (source === "") source = "../icons/user-default.png"; ctx.drawImage(source, 0, 0, width, height); // Border if (drawStroke) { ctx.strokeStyle = strokeColor; ctx.lineWidth = strokeSize; ctx.stroke(); } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.ArrowCursor function isCursorInsideAvatar() { if (!mouseArea.containsMouse) return false; if (avatar.shape === "square") return true; // Ellipse center and radius var centerX = width / 2; var centerY = height / 2; var radiusX = centerX; var radiusY = centerY; // Distance from center var dx = (mouseArea.mouseX - centerX) / radiusX; var dy = (mouseArea.mouseY - centerY) / radiusY; // Check if pointer is inside the ellipse return (dx * dx + dy * dy) <= 1.0; } onReleased: function (mouse) { var isInside = isCursorInsideAvatar(); if (isInside) { avatar.clicked(); } else { avatar.clickedOutside(); } mouse.accepted = isInside; } function updateHover() { if (isCursorInsideAvatar()) { cursorShape = Qt.PointingHandCursor; } else { cursorShape = Qt.ArrowCursor; } } onMouseXChanged: updateHover() onMouseYChanged: updateHover() ToolTip { parent: mouseArea enabled: Config.tooltipsEnable && !Config.tooltipsDisableUser property bool shouldShow: enabled && avatar.showTooltip || (enabled && mouseArea.isCursorInsideAvatar() && avatar.tooltipText !== "") visible: shouldShow delay: 300 contentItem: Text { font.family: Config.tooltipsFontFamily font.pixelSize: Config.tooltipsFontSize text: avatar.tooltipText color: Config.tooltipsContentColor } background: Rectangle { color: Config.tooltipsBackgroundColor opacity: Config.tooltipsBackgroundOpacity border.width: 0 radius: Config.tooltipsBorderRadius } } } // FIX: paint() not affect event if source is not empty in initialization Timer { id: delayPaintTimer repeat: false interval: 150 onTriggered: avatar.requestPaint() running: true } // FIX: Critical timer memory leak prevention // Overkill, but fine... Component.onDestruction: { if (delayPaintTimer) { delayPaintTimer.running = false; delayPaintTimer.stop(); } } }