QSGNode *QQuickTextField::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) { QQuickDefaultClipNode *clipNode = static_cast<QQuickDefaultClipNode *>(oldNode); if (!clipNode) clipNode = new QQuickDefaultClipNode(QRectF()); clipNode->setRect(clipRect().adjusted(leftPadding(), topPadding(), -rightPadding(), -bottomPadding())); clipNode->update(); QSGNode *textNode = QQuickTextInput::updatePaintNode(clipNode->firstChild(), data); if (!textNode->parent()) clipNode->appendChildNode(textNode); return clipNode; }
void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode, QQuickText::TextStyle style, const QColor &styleColor) { if (m_currentLine.isValid()) processCurrentLine(); QList<BinaryTreeNode *> nodes; QList<BinaryTreeNode *> imageNodes; mergeProcessedNodes(&nodes, &imageNodes); for (int i = 0; i < m_backgrounds.size(); ++i) { const QRectF &rect = m_backgrounds.at(i).first; const QColor &color = m_backgrounds.at(i).second; parentNode->addRectangleNode(rect, color); } // Add all unselected text first for (int i = 0; i < nodes.size(); ++i) { const BinaryTreeNode *node = nodes.at(i); if (node->selectionState == Unselected) parentNode->addGlyphs(node->position, node->glyphRun, node->color, style, styleColor, 0); } for (int i = 0; i < imageNodes.size(); ++i) { const BinaryTreeNode *node = imageNodes.at(i); if (node->selectionState == Unselected) parentNode->addImage(node->boundingRect, node->image); } // Then, prepend all selection rectangles to the tree for (int i = 0; i < m_selectionRects.size(); ++i) { const QRectF &rect = m_selectionRects.at(i); parentNode->addRectangleNode(rect, m_selectionColor); } // Add decorations for each node to the tree. for (int i = 0; i < m_lines.size(); ++i) { const TextDecoration &textDecoration = m_lines.at(i); QColor color = textDecoration.selectionState == Selected ? m_selectedTextColor : textDecoration.color; parentNode->addRectangleNode(textDecoration.rect, color); } // Finally add the selected text on top of everything for (int i = 0; i < nodes.size(); ++i) { const BinaryTreeNode *node = nodes.at(i); QQuickDefaultClipNode *clipNode = node->clipNode; if (clipNode != 0 && clipNode->parent() == 0) parentNode->appendChildNode(clipNode); if (node->selectionState == Selected) { QColor color = m_selectedTextColor; int previousNodeIndex = i - 1; int nextNodeIndex = i + 1; const BinaryTreeNode *previousNode = previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex); while (previousNode != 0 && qFuzzyCompare(previousNode->boundingRect.left(), node->boundingRect.left())) previousNode = --previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex); const BinaryTreeNode *nextNode = nextNodeIndex == nodes.size() ? 0 : nodes.at(nextNodeIndex); if (previousNode != 0 && previousNode->selectionState == Unselected) parentNode->addGlyphs(previousNode->position, previousNode->glyphRun, color, style, styleColor, clipNode); if (nextNode != 0 && nextNode->selectionState == Unselected) parentNode->addGlyphs(nextNode->position, nextNode->glyphRun, color, style, styleColor, clipNode); // If the previous or next node completely overlaps this one, then we have already drawn the glyphs of // this node bool drawCurrent = false; if (previousNode != 0 || nextNode != 0) { for (int i = 0; i < node->ranges.size(); ++i) { const QPair<int, int> &range = node->ranges.at(i); int rangeLength = range.second - range.first + 1; if (previousNode != 0) { for (int j = 0; j < previousNode->ranges.size(); ++j) { const QPair<int, int> &otherRange = previousNode->ranges.at(j); if (range.first <= otherRange.second && range.second >= otherRange.first) { int start = qMax(range.first, otherRange.first); int end = qMin(range.second, otherRange.second); rangeLength -= end - start + 1; if (rangeLength == 0) break; } } } if (nextNode != 0 && rangeLength > 0) { for (int j = 0; j < nextNode->ranges.size(); ++j) { const QPair<int, int> &otherRange = nextNode->ranges.at(j); if (range.first <= otherRange.second && range.second >= otherRange.first) { int start = qMax(range.first, otherRange.first); int end = qMin(range.second, otherRange.second); rangeLength -= end - start + 1; if (rangeLength == 0) break; } } } if (rangeLength > 0) { drawCurrent = true; break; } } } else { drawCurrent = true; } if (drawCurrent) parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode); } } for (int i = 0; i < imageNodes.size(); ++i) { const BinaryTreeNode *node = imageNodes.at(i); if (node->selectionState == Selected) { parentNode->addImage(node->boundingRect, node->image); if (node->selectionState == Selected) { QColor color = m_selectionColor; color.setAlpha(128); parentNode->addRectangleNode(node->boundingRect, color); } } } }
void QQuickTextNodeEngine::processCurrentLine() { // No glyphs, do nothing if (m_currentLineTree.isEmpty()) return; // 1. Go through current line and get correct decoration position for each node based on // neighbouring decorations. Add decoration to global list // 2. Create clip nodes for all selected text. Try to merge as many as possible within // the line. // 3. Add QRects to a list of selection rects. // 4. Add all nodes to a global processed list QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes); Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size()); SelectionState currentSelectionState = Unselected; QRectF currentRect; QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration; qreal underlineOffset = 0.0; qreal underlineThickness = 0.0; qreal overlineOffset = 0.0; qreal overlineThickness = 0.0; qreal strikeOutOffset = 0.0; qreal strikeOutThickness = 0.0; QRectF decorationRect = currentRect; QColor lastColor; QColor lastBackgroundColor; QVarLengthArray<TextDecoration> pendingUnderlines; QVarLengthArray<TextDecoration> pendingOverlines; QVarLengthArray<TextDecoration> pendingStrikeOuts; if (!sortedIndexes.isEmpty()) { QQuickDefaultClipNode *currentClipNode = m_hasSelection ? new QQuickDefaultClipNode(QRectF()) : 0; bool currentClipNodeUsed = false; for (int i=0; i<=sortedIndexes.size(); ++i) { BinaryTreeNode *node = 0; if (i < sortedIndexes.size()) { int sortedIndex = sortedIndexes.at(i); Q_ASSERT(sortedIndex < m_currentLineTree.size()); node = m_currentLineTree.data() + sortedIndex; } if (i == 0) currentSelectionState = node->selectionState; // Update decorations if (currentDecorations != QQuickTextNode::NoDecoration) { decorationRect.setY(m_position.y() + m_currentLine.y()); decorationRect.setHeight(m_currentLine.height()); if (node != 0) decorationRect.setRight(node->boundingRect.left()); TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor); if (currentDecorations & QQuickTextNode::Underline) pendingUnderlines.append(textDecoration); if (currentDecorations & QQuickTextNode::Overline) pendingOverlines.append(textDecoration); if (currentDecorations & QQuickTextNode::StrikeOut) pendingStrikeOuts.append(textDecoration); if (currentDecorations & QQuickTextNode::Background) m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor)); } // If we've reached an unselected node from a selected node, we add the // selection rect to the graph, and we add decoration every time the // selection state changes, because that means the text color changes if (node == 0 || node->selectionState != currentSelectionState) { currentRect.setY(m_position.y() + m_currentLine.y()); currentRect.setHeight(m_currentLine.height()); if (currentSelectionState == Selected) m_selectionRects.append(currentRect); if (currentClipNode != 0) { if (!currentClipNodeUsed) { delete currentClipNode; } else { currentClipNode->setIsRectangular(true); currentClipNode->setRect(currentRect); currentClipNode->update(); } } if (node != 0 && m_hasSelection) currentClipNode = new QQuickDefaultClipNode(QRectF()); else currentClipNode = 0; currentClipNodeUsed = false; if (node != 0) { currentSelectionState = node->selectionState; currentRect = node->boundingRect; // Make sure currentRect is valid, otherwise the unite won't work if (currentRect.isNull()) currentRect.setSize(QSizeF(1, 1)); } } else { if (currentRect.isNull()) currentRect = node->boundingRect; else currentRect = currentRect.united(node->boundingRect); } if (node != 0) { if (node->selectionState == Selected) { node->clipNode = currentClipNode; currentClipNodeUsed = true; } decorationRect = node->boundingRect; // If previous item(s) had underline and current does not, then we add the // pending lines to the lists and likewise for overlines and strikeouts if (!pendingUnderlines.isEmpty() && !(node->decorations & QQuickTextNode::Underline)) { addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness); pendingUnderlines.clear(); underlineOffset = 0.0; underlineThickness = 0.0; } // ### Add pending when overlineOffset/thickness changes to minimize number of // nodes if (!pendingOverlines.isEmpty()) { addTextDecorations(pendingOverlines, overlineOffset, overlineThickness); pendingOverlines.clear(); overlineOffset = 0.0; overlineThickness = 0.0; } // ### Add pending when overlineOffset/thickness changes to minimize number of // nodes if (!pendingStrikeOuts.isEmpty()) { addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness); pendingStrikeOuts.clear(); strikeOutOffset = 0.0; strikeOutThickness = 0.0; } // Merge current values with previous. Prefer greatest thickness QRawFont rawFont = node->glyphRun.rawFont(); if (node->decorations & QQuickTextNode::Underline) { if (rawFont.lineThickness() > underlineThickness) { underlineThickness = rawFont.lineThickness(); underlineOffset = rawFont.underlinePosition(); } } if (node->decorations & QQuickTextNode::Overline) { overlineOffset = -rawFont.ascent(); overlineThickness = rawFont.lineThickness(); } if (node->decorations & QQuickTextNode::StrikeOut) { strikeOutThickness = rawFont.lineThickness(); strikeOutOffset = rawFont.ascent() / -3.0; } currentDecorations = node->decorations; lastColor = node->color; lastBackgroundColor = node->backgroundColor; m_processedNodes.append(*node); } } if (!pendingUnderlines.isEmpty()) addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness); if (!pendingOverlines.isEmpty()) addTextDecorations(pendingOverlines, overlineOffset, overlineThickness); if (!pendingStrikeOuts.isEmpty()) addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness); } m_currentLineTree.clear(); m_currentLine = QTextLine(); m_hasSelection = false; }