add noctalia and fuzzel
This commit is contained in:
@@ -0,0 +1,701 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Widgets
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
property string text: ""
|
||||
property var rows: null // Array of arrays for grid mode: [["col1", "col2"], ["row2col1", "row2col2"]]
|
||||
property bool isGridMode: rows !== null && Array.isArray(rows) && rows.length > 0
|
||||
property int columnCount: isGridMode && rows.length > 0 ? rows[0].length : 0
|
||||
property string direction: "auto" // "auto", "left", "right", "top", "bottom"
|
||||
property int margin: Style.marginXS // distance from target
|
||||
property int padding: Style.marginM
|
||||
property int gridPaddingVertical: Style.marginXS // extra vertical padding for grid mode
|
||||
property int delay: 0
|
||||
property int hideDelay: 0
|
||||
property int maxWidth: 340
|
||||
|
||||
property int animationDuration: Style.animationFast
|
||||
property real animationScale: 0.85
|
||||
|
||||
// Font scale calculation matching NText behavior
|
||||
readonly property real gridFontScale: {
|
||||
const baseScale = (tooltipText.family === Settings.data.ui.fontDefault ? Settings.data.ui.fontDefaultScale : Settings.data.ui.fontFixedScale);
|
||||
return baseScale * Style.uiScaleRatio;
|
||||
}
|
||||
|
||||
// For measuring grid cell widths - must match actual NText font
|
||||
TextMetrics {
|
||||
id: cellMetrics
|
||||
font.family: tooltipText.family
|
||||
font.pointSize: Math.max(1, Style.fontSizeS * gridFontScale)
|
||||
}
|
||||
|
||||
// For measuring grid row height - hidden NText matches actual grid items exactly
|
||||
NText {
|
||||
id: rowHeightMeasure
|
||||
visible: false
|
||||
text: "Ag" // Characters with ascenders and descenders
|
||||
pointSize: Style.fontSizeS
|
||||
family: tooltipText.family
|
||||
}
|
||||
|
||||
// Internal properties
|
||||
property var targetItem: null
|
||||
property real anchorX: 0
|
||||
property real anchorY: 0
|
||||
property bool isPositioned: false
|
||||
property bool animatingOut: true
|
||||
property int screenWidth: 1920
|
||||
property int screenHeight: 1080
|
||||
property int screenX: 0
|
||||
property int screenY: 0
|
||||
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
anchor.item: targetItem
|
||||
anchor.rect.x: anchorX
|
||||
anchor.rect.y: anchorY
|
||||
|
||||
// Timer for showing tooltip after delay
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: root.delay
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.positionAndShow();
|
||||
}
|
||||
}
|
||||
|
||||
// Timer for hiding tooltip after delay
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: root.hideDelay
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.startHideAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
// Show animation
|
||||
ParallelAnimation {
|
||||
id: showAnimation
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "scale"
|
||||
from: root.animationScale
|
||||
to: 1.0
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
|
||||
// Hide animation
|
||||
ParallelAnimation {
|
||||
id: hideAnimation
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "opacity"
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
duration: root.animationDuration * 0.75 // Slightly faster hide
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "scale"
|
||||
from: 1.0
|
||||
to: root.animationScale
|
||||
duration: root.animationDuration * 0.75
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
|
||||
onFinished: {
|
||||
root.completeHide();
|
||||
}
|
||||
}
|
||||
|
||||
// Function to show tooltip (content can be string or array of arrays for grid mode)
|
||||
function show(target, content, customDirection, showDelay, fontFamily) {
|
||||
if (!target || !content || content === "" || (Array.isArray(content) && content.length === 0))
|
||||
return;
|
||||
|
||||
root.delay = showDelay;
|
||||
|
||||
// Stop any running timers and animations
|
||||
hideTimer.stop();
|
||||
showTimer.stop();
|
||||
hideAnimation.stop();
|
||||
animatingOut = false;
|
||||
|
||||
// If we're already showing for a different target, hide immediately
|
||||
if (visible && targetItem !== target) {
|
||||
hideImmediately();
|
||||
}
|
||||
|
||||
// Set content based on type
|
||||
if (Array.isArray(content)) {
|
||||
rows = content;
|
||||
text = "";
|
||||
} else {
|
||||
// Convert \n to <br> for RichText format
|
||||
const processedText = content.replace(/\n/g, '<br>');
|
||||
text = processedText;
|
||||
rows = null;
|
||||
}
|
||||
|
||||
targetItem = target;
|
||||
|
||||
// Find the correct screen dimensions based on target's global position
|
||||
// Respect all screens positionning
|
||||
const targetGlobal = target.mapToGlobal(target.width / 2, target.height / 2);
|
||||
let foundScreen = false;
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
const s = Quickshell.screens[i];
|
||||
if (targetGlobal.x >= s.x && targetGlobal.x < s.x + s.width && targetGlobal.y >= s.y && targetGlobal.y < s.y + s.height) {
|
||||
screenWidth = s.width;
|
||||
screenHeight = s.height;
|
||||
screenX = s.x;
|
||||
screenY = s.y;
|
||||
foundScreen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundScreen) {
|
||||
Logger.w("Tooltip", "No screen found for target position!");
|
||||
}
|
||||
|
||||
// Initialize animation state (hidden)
|
||||
tooltipContainer.opacity = 0.0;
|
||||
tooltipContainer.scale = root.animationScale;
|
||||
|
||||
// Start show timer (will position and then make visible)
|
||||
showTimer.start();
|
||||
|
||||
if (customDirection !== undefined) {
|
||||
direction = customDirection;
|
||||
} else {
|
||||
direction = "auto";
|
||||
}
|
||||
|
||||
tooltipText.family = fontFamily ? fontFamily : Settings.data.ui.fontDefault;
|
||||
}
|
||||
|
||||
// Calculate grid dimensions using measurements that match actual NText rendering
|
||||
function calculateGridSize() {
|
||||
if (!rows || rows.length === 0)
|
||||
return {
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
const numCols = rows[0].length;
|
||||
const numRows = rows.length;
|
||||
let columnWidths = [];
|
||||
|
||||
// Find max width for each column using TextMetrics
|
||||
for (let col = 0; col < numCols; col++) {
|
||||
let maxWidth = 0;
|
||||
for (let row = 0; row < numRows; row++) {
|
||||
cellMetrics.text = rows[row][col] || "";
|
||||
if (cellMetrics.width > maxWidth) {
|
||||
maxWidth = cellMetrics.width;
|
||||
}
|
||||
}
|
||||
columnWidths.push(maxWidth);
|
||||
}
|
||||
|
||||
// Calculate total width: sum of columns + spacing between columns
|
||||
let totalWidth = 0;
|
||||
for (let i = 0; i < columnWidths.length; i++) {
|
||||
totalWidth += columnWidths[i];
|
||||
}
|
||||
totalWidth += (numCols - 1) * Style.marginM; // columnSpacing
|
||||
|
||||
// Calculate total height using hidden NText for accurate row height
|
||||
const rowHeight = rowHeightMeasure.implicitHeight;
|
||||
const totalHeight = numRows * rowHeight;
|
||||
|
||||
return {
|
||||
width: totalWidth,
|
||||
height: totalHeight
|
||||
};
|
||||
}
|
||||
|
||||
// Function to position and display the tooltip
|
||||
function positionAndShow() {
|
||||
if (!targetItem || !targetItem.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate tooltip dimensions based on content mode
|
||||
let contentWidth, contentHeight;
|
||||
if (isGridMode) {
|
||||
const gridSize = calculateGridSize();
|
||||
contentWidth = gridSize.width;
|
||||
contentHeight = gridSize.height;
|
||||
} else {
|
||||
contentWidth = tooltipText.implicitWidth;
|
||||
contentHeight = tooltipText.implicitHeight;
|
||||
}
|
||||
|
||||
const extraPad = isGridMode ? gridPaddingVertical : 0;
|
||||
const tipWidth = Math.ceil(Math.min(contentWidth + ((padding + extraPad) * 2), maxWidth));
|
||||
root.implicitWidth = tipWidth;
|
||||
|
||||
const tipHeight = Math.ceil(contentHeight + ((padding + extraPad) * 2));
|
||||
root.implicitHeight = tipHeight;
|
||||
|
||||
// Get target's global position and convert to screen-relative
|
||||
var targetGlobalAbs = targetItem.mapToGlobal(0, 0);
|
||||
var targetGlobal = {
|
||||
"x": targetGlobalAbs.x - screenX,
|
||||
"y": targetGlobalAbs.y - screenY
|
||||
};
|
||||
const targetWidth = targetItem.width;
|
||||
const targetHeight = targetItem.height;
|
||||
|
||||
var newAnchorX = 0;
|
||||
var newAnchorY = 0;
|
||||
|
||||
if (direction === "auto") {
|
||||
// Calculate available space in each direction (screen-relative)
|
||||
const spaceLeft = targetGlobal.x;
|
||||
const spaceRight = screenWidth - (targetGlobal.x + targetWidth);
|
||||
const spaceTop = targetGlobal.y;
|
||||
const spaceBottom = screenHeight - (targetGlobal.y + targetHeight);
|
||||
|
||||
// Try positions in order of available space
|
||||
const positions = [
|
||||
{
|
||||
"dir": "bottom",
|
||||
"space": spaceBottom,
|
||||
"x": (targetWidth - tipWidth) / 2,
|
||||
"y": targetHeight + margin,
|
||||
"fits": spaceBottom >= tipHeight + margin
|
||||
},
|
||||
{
|
||||
"dir": "top",
|
||||
"space": spaceTop,
|
||||
"x": (targetWidth - tipWidth) / 2,
|
||||
"y": -tipHeight - margin,
|
||||
"fits": spaceTop >= tipHeight + margin
|
||||
},
|
||||
{
|
||||
"dir": "right",
|
||||
"space": spaceRight,
|
||||
"x": targetWidth + margin,
|
||||
"y": (targetHeight - tipHeight) / 2,
|
||||
"fits": spaceRight >= tipWidth + margin
|
||||
},
|
||||
{
|
||||
"dir": "left",
|
||||
"space": spaceLeft,
|
||||
"x": -tipWidth - margin,
|
||||
"y": (targetHeight - tipHeight) / 2,
|
||||
"fits": spaceLeft >= tipWidth + margin
|
||||
}
|
||||
];
|
||||
|
||||
// Find first position that fits
|
||||
var selectedPosition = null;
|
||||
for (var i = 0; i < positions.length; i++) {
|
||||
if (positions[i].fits) {
|
||||
selectedPosition = positions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If none fit perfectly
|
||||
if (!selectedPosition) {
|
||||
// Sort by available space and use position with most space
|
||||
positions.sort(function (a, b) {
|
||||
return b.space - a.space;
|
||||
});
|
||||
selectedPosition = positions[0];
|
||||
}
|
||||
|
||||
newAnchorX = selectedPosition.x;
|
||||
newAnchorY = selectedPosition.y;
|
||||
} else {
|
||||
// Manual direction positioning
|
||||
switch (direction) {
|
||||
case "left":
|
||||
newAnchorX = -tipWidth - margin;
|
||||
newAnchorY = (targetHeight - tipHeight) / 2;
|
||||
break;
|
||||
case "right":
|
||||
newAnchorX = targetWidth + margin;
|
||||
newAnchorY = (targetHeight - tipHeight) / 2;
|
||||
break;
|
||||
case "top":
|
||||
newAnchorX = (targetWidth - tipWidth) / 2;
|
||||
newAnchorY = -tipHeight - margin;
|
||||
break;
|
||||
case "bottom":
|
||||
newAnchorX = (targetWidth - tipWidth) / 2;
|
||||
newAnchorY = targetHeight + margin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust horizontal position to keep tooltip on screen.
|
||||
// For top/bottom tooltips, always adjust horizontally (they don't overlap horizontally).
|
||||
// For left/right tooltips, check for overlap before adjusting to avoid landing on the target.
|
||||
const globalX = targetGlobal.x + newAnchorX;
|
||||
const effectiveDirection = direction === "auto" ? selectedPosition.dir : direction;
|
||||
const isHorizontalTooltip = (effectiveDirection === "top" || effectiveDirection === "bottom");
|
||||
|
||||
if (globalX < 0) {
|
||||
const adjustedX = -targetGlobal.x + margin;
|
||||
if (isHorizontalTooltip) {
|
||||
newAnchorX = adjustedX;
|
||||
} else {
|
||||
const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorX = adjustedX;
|
||||
}
|
||||
} else if (globalX + tipWidth > screenWidth) {
|
||||
const adjustedX = screenWidth - targetGlobal.x - tipWidth - margin;
|
||||
if (isHorizontalTooltip) {
|
||||
newAnchorX = adjustedX;
|
||||
} else {
|
||||
const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorX = adjustedX;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust vertical position to keep tooltip on screen.
|
||||
// For left/right tooltips, always adjust vertically (they don't overlap vertically).
|
||||
// For top/bottom tooltips, check for overlap before adjusting to avoid landing on the target.
|
||||
const globalY = targetGlobal.y + newAnchorY;
|
||||
const isVerticalTooltip = (effectiveDirection === "left" || effectiveDirection === "right");
|
||||
|
||||
if (globalY < 0) {
|
||||
const adjustedY = -targetGlobal.y + margin;
|
||||
if (isVerticalTooltip) {
|
||||
newAnchorY = adjustedY;
|
||||
} else {
|
||||
const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorY = adjustedY;
|
||||
}
|
||||
} else if (globalY + tipHeight > screenHeight) {
|
||||
const adjustedY = screenHeight - targetGlobal.y - tipHeight - margin;
|
||||
if (isVerticalTooltip) {
|
||||
newAnchorY = adjustedY;
|
||||
} else {
|
||||
const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorY = adjustedY;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply position first (before making visible)
|
||||
// Use floor for negative values to push tooltip away from target
|
||||
anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX);
|
||||
anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY);
|
||||
isPositioned = true;
|
||||
|
||||
// Now make visible and start animation
|
||||
root.visible = true;
|
||||
showAnimation.start();
|
||||
}
|
||||
|
||||
// Function to hide tooltip
|
||||
function hide() {
|
||||
// Stop show timer if it's running
|
||||
showTimer.stop();
|
||||
|
||||
// Stop hide timer if it's running
|
||||
hideTimer.stop();
|
||||
|
||||
if (hideDelay > 0 && visible && !animatingOut) {
|
||||
hideTimer.start();
|
||||
} else {
|
||||
startHideAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
function startHideAnimation() {
|
||||
if (!visible || animatingOut)
|
||||
return;
|
||||
animatingOut = true;
|
||||
showAnimation.stop(); // Stop show animation if running
|
||||
hideAnimation.start();
|
||||
}
|
||||
|
||||
function completeHide() {
|
||||
visible = false;
|
||||
animatingOut = false;
|
||||
text = "";
|
||||
rows = null;
|
||||
isPositioned = false;
|
||||
tooltipContainer.opacity = 1.0;
|
||||
tooltipContainer.scale = 1.0;
|
||||
}
|
||||
|
||||
// Quick hide without delay or animation
|
||||
function hideImmediately() {
|
||||
showTimer.stop();
|
||||
hideTimer.stop();
|
||||
showAnimation.stop();
|
||||
hideAnimation.stop();
|
||||
animatingOut = false;
|
||||
completeHide();
|
||||
}
|
||||
|
||||
// Update content function (supports both text and rows)
|
||||
function updateContent(newContent) {
|
||||
if (visible && targetItem) {
|
||||
if (Array.isArray(newContent)) {
|
||||
rows = newContent;
|
||||
text = "";
|
||||
} else {
|
||||
// Convert \n to <br> for RichText format
|
||||
const processedText = newContent.replace(/\n/g, '<br>');
|
||||
text = processedText;
|
||||
rows = null;
|
||||
}
|
||||
|
||||
// Defer dimension recalculation to allow GridLayout to update
|
||||
Qt.callLater(updateContentDeferred);
|
||||
}
|
||||
}
|
||||
|
||||
function updateContentDeferred() {
|
||||
if (!visible || !targetItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recalculate dimensions based on content mode
|
||||
// Use calculateGridSize() for consistency with initial show
|
||||
let contentWidth, contentHeight;
|
||||
if (isGridMode) {
|
||||
const gridSize = calculateGridSize();
|
||||
contentWidth = gridSize.width;
|
||||
contentHeight = gridSize.height;
|
||||
} else {
|
||||
contentWidth = tooltipText.implicitWidth;
|
||||
contentHeight = tooltipText.implicitHeight;
|
||||
}
|
||||
|
||||
const extraPad = isGridMode ? gridPaddingVertical : 0;
|
||||
const tipWidth = Math.ceil(Math.min(contentWidth + ((padding + extraPad) * 2), maxWidth));
|
||||
root.implicitWidth = tipWidth;
|
||||
|
||||
const tipHeight = Math.ceil(contentHeight + ((padding + extraPad) * 2));
|
||||
root.implicitHeight = tipHeight;
|
||||
|
||||
// Reposition based on current direction (screen-relative)
|
||||
// Round all values to avoid sub-pixel positioning issues with fractional scaling
|
||||
var targetGlobalAbs = targetItem.mapToGlobal(0, 0);
|
||||
var targetGlobal = {
|
||||
"x": targetGlobalAbs.x - screenX,
|
||||
"y": targetGlobalAbs.y - screenY
|
||||
};
|
||||
const targetWidth = targetItem.width;
|
||||
const targetHeight = targetItem.height;
|
||||
|
||||
// Recalculate base anchor position (center on target for top/bottom, etc.)
|
||||
var newAnchorX = anchorX;
|
||||
var newAnchorY = anchorY;
|
||||
|
||||
// Determine which direction the tooltip is currently positioned
|
||||
// and recalculate the centering for that direction
|
||||
var isHorizontalTooltip = false;
|
||||
var isVerticalTooltip = false;
|
||||
if (anchorY > targetHeight / 2) {
|
||||
// Tooltip is below target
|
||||
newAnchorX = (targetWidth - tipWidth) / 2;
|
||||
isHorizontalTooltip = true;
|
||||
} else if (anchorY < -tipHeight / 2) {
|
||||
// Tooltip is above target
|
||||
newAnchorX = (targetWidth - tipWidth) / 2;
|
||||
isHorizontalTooltip = true;
|
||||
} else if (anchorX > targetWidth / 2) {
|
||||
// Tooltip is to the right
|
||||
newAnchorY = (targetHeight - tipHeight) / 2;
|
||||
isVerticalTooltip = true;
|
||||
} else if (anchorX < -tipWidth / 2) {
|
||||
// Tooltip is to the left
|
||||
newAnchorY = (targetHeight - tipHeight) / 2;
|
||||
isVerticalTooltip = true;
|
||||
}
|
||||
|
||||
// Adjust horizontal position to keep tooltip on screen if needed.
|
||||
const globalX = targetGlobal.x + newAnchorX;
|
||||
if (globalX < 0) {
|
||||
const adjustedX = -targetGlobal.x + margin;
|
||||
if (isHorizontalTooltip) {
|
||||
newAnchorX = adjustedX;
|
||||
} else {
|
||||
const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorX = adjustedX;
|
||||
}
|
||||
} else if (globalX + tipWidth > screenWidth) {
|
||||
const adjustedX = screenWidth - targetGlobal.x - tipWidth - margin;
|
||||
if (isHorizontalTooltip) {
|
||||
newAnchorX = adjustedX;
|
||||
} else {
|
||||
const wouldOverlap = adjustedX < targetWidth && adjustedX + tipWidth > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorX = adjustedX;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust vertical position to keep tooltip on screen if needed.
|
||||
const globalY = targetGlobal.y + newAnchorY;
|
||||
if (globalY < 0) {
|
||||
const adjustedY = -targetGlobal.y + margin;
|
||||
if (isVerticalTooltip) {
|
||||
newAnchorY = adjustedY;
|
||||
} else {
|
||||
const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorY = adjustedY;
|
||||
}
|
||||
} else if (globalY + tipHeight > screenHeight) {
|
||||
const adjustedY = screenHeight - targetGlobal.y - tipHeight - margin;
|
||||
if (isVerticalTooltip) {
|
||||
newAnchorY = adjustedY;
|
||||
} else {
|
||||
const wouldOverlap = adjustedY < targetHeight && adjustedY + tipHeight > 0;
|
||||
if (!wouldOverlap)
|
||||
newAnchorY = adjustedY;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the new anchor positions
|
||||
// Use floor for negative values to push tooltip away from target
|
||||
anchorX = newAnchorX < 0 ? Math.floor(newAnchorX) : Math.round(newAnchorX);
|
||||
anchorY = newAnchorY < 0 ? Math.floor(newAnchorY) : Math.round(newAnchorY);
|
||||
|
||||
// Force anchor update
|
||||
Qt.callLater(() => {
|
||||
if (root.anchor && root.visible) {
|
||||
root.anchor.updateAnchor();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Backward compatibility alias
|
||||
function updateText(newText) {
|
||||
updateContent(newText);
|
||||
}
|
||||
|
||||
// Reset function to clean up state
|
||||
function reset() {
|
||||
// Stop all timers and animations
|
||||
showTimer.stop();
|
||||
hideTimer.stop();
|
||||
showAnimation.stop();
|
||||
hideAnimation.stop();
|
||||
|
||||
// Clear all state
|
||||
visible = false;
|
||||
animatingOut = false;
|
||||
text = "";
|
||||
rows = null;
|
||||
isPositioned = false;
|
||||
|
||||
// Reset to defaults
|
||||
direction = "auto";
|
||||
delay = 0;
|
||||
hideDelay = 0;
|
||||
|
||||
// Reset container state
|
||||
tooltipContainer.opacity = 1.0;
|
||||
tooltipContainer.scale = 1.0;
|
||||
}
|
||||
|
||||
// Tooltip content container for animations
|
||||
Item {
|
||||
id: tooltipContainer
|
||||
anchors.fill: parent
|
||||
|
||||
// Animation properties
|
||||
opacity: 1.0
|
||||
scale: 1.0
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: border.width
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: Math.min(Style.radiusS, Math.min(width, height) / 3)
|
||||
|
||||
// Only show content when we have content
|
||||
visible: root.text !== "" || root.isGridMode
|
||||
|
||||
// Text content (default mode)
|
||||
NText {
|
||||
id: tooltipText
|
||||
visible: !root.isGridMode
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: root.padding
|
||||
text: root.text
|
||||
pointSize: Style.fontSizeS
|
||||
family: Settings.data.ui.fontFixed
|
||||
color: Color.mOnSurfaceVariant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
width: Math.min(implicitWidth, root.maxWidth - (root.padding * 2))
|
||||
richTextEnabled: true
|
||||
}
|
||||
|
||||
// Grid content (grid mode)
|
||||
GridLayout {
|
||||
id: gridContent
|
||||
visible: root.isGridMode
|
||||
anchors.centerIn: parent
|
||||
columns: root.columnCount
|
||||
rowSpacing: 0
|
||||
columnSpacing: Style.marginM
|
||||
|
||||
Repeater {
|
||||
model: root.isGridMode ? [].concat.apply([], root.rows) : []
|
||||
|
||||
NText {
|
||||
text: modelData
|
||||
pointSize: Style.fontSizeS
|
||||
family: tooltipText.family
|
||||
color: Color.mOnSurfaceVariant
|
||||
Layout.preferredHeight: rowHeightMeasure.implicitHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
reset();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user