Skip to content

Commit e8a25f9

Browse files
committed
Add TextBubble component
1 parent 54fbd20 commit e8a25f9

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ qt_add_qml_module(scratchcpp-render
1212
internal/ValueMonitor.qml
1313
internal/MonitorSlider.qml
1414
internal/ListMonitor.qml
15+
internal/TextBubble.qml
1516
shaders/sprite.vert
1617
shaders/sprite.frag
1718
SOURCES

src/internal/TextBubble.qml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
import QtQuick
4+
import ScratchCPP.Render
5+
6+
TextBubbleShape {
7+
id: root
8+
property string text: ""
9+
property RenderedTarget target: null
10+
property double stageWidth: 0
11+
property double stageHeight: 0
12+
13+
QtObject {
14+
// https://github.com/scratchfoundation/scratch-render/blob/ac935423afe3ba79235750eecb1e443474c6eb09/src/TextBubbleSkin.js#L7-L26
15+
id: priv
16+
readonly property int maxLineWidth: 170
17+
readonly property int minWidth: 50
18+
readonly property int padding: 10
19+
readonly property int tailHeight: 12
20+
21+
readonly property string fontFamily: "Helvetica"
22+
readonly property int fontPixelSize: 14
23+
readonly property int lineHeight: 16
24+
25+
readonly property color textFill: '#575E75'
26+
27+
function translateX(x) {
28+
// Translates Scratch X-coordinate to the scene coordinate system
29+
return root.stageScale * (root.stageWidth / 2 + x)
30+
}
31+
32+
function translateY(y) {
33+
// Translates Scratch Y-coordinate to the scene coordinate system
34+
return root.stageScale * (root.stageHeight / 2 - y)
35+
}
36+
}
37+
38+
nativeWidth: Math.max(bubbleText.contentWidth, priv.minWidth) + 2 * priv.padding
39+
nativeHeight: bubbleText.height + 2 * priv.padding + priv.tailHeight
40+
41+
function positionBubble() {
42+
// https://github.com/scratchfoundation/scratch-vm/blob/7313ce5199f8a3da7850085d0f7f6a3ca2c89bf6/src/blocks/scratch3_looks.js#L158
43+
if(!target.visible)
44+
return;
45+
46+
const targetBounds = target.getBoundsForBubble();
47+
const stageBounds = Qt.rect(-root.stageWidth / 2, root.stageHeight / 2, root.stageWidth, root.stageHeight);
48+
49+
if (onSpriteRight && nativeWidth + targetBounds.right > stageBounds.right &&
50+
(targetBounds.left - nativeWidth > stageBounds.left)) { // Only flip if it would fit
51+
onSpriteRight = false;
52+
} else if (!onSpriteRight && targetBounds.left - nativeWidth < stageBounds.left &&
53+
(nativeWidth + targetBounds.right < stageBounds.right)) { // Only flip if it would fit
54+
onSpriteRight = true;
55+
}
56+
57+
const pos = [
58+
onSpriteRight ? (
59+
Math.max(
60+
stageBounds.left, // Bubble should not extend past left edge of stage
61+
Math.min(stageBounds.right - nativeWidth, targetBounds.right)
62+
)
63+
) : (
64+
Math.min(
65+
stageBounds.right - nativeWidth, // Bubble should not extend past right edge of stage
66+
Math.max(stageBounds.left, targetBounds.left - nativeWidth)
67+
)
68+
),
69+
// Bubble should not extend past the top of the stage
70+
Math.min(stageBounds.top, targetBounds.bottom + nativeHeight)
71+
];
72+
73+
x = priv.translateX(pos[0]);
74+
y = priv.translateY(pos[1]);
75+
}
76+
77+
Connections {
78+
target: root.target
79+
80+
function onXChanged() { positionBubble() }
81+
function onYChanged() { positionBubble() }
82+
function onRotationChanged() { positionBubble() }
83+
function onWidthChanged() { positionBubble() }
84+
function onHeightChanged() { positionBubble() }
85+
function onScaleChanged() { positionBubble() }
86+
}
87+
88+
Text {
89+
id: bubbleText
90+
anchors.left: parent.left
91+
anchors.top: parent.top
92+
anchors.margins: priv.padding * root.stageScale
93+
width: priv.maxLineWidth
94+
scale: root.stageScale
95+
transformOrigin: Item.TopLeft
96+
text: root.text
97+
wrapMode: Text.Wrap
98+
color: priv.textFill
99+
lineHeight: priv.lineHeight
100+
lineHeightMode: Text.FixedHeight
101+
font.family: priv.fontFamily
102+
font.pixelSize: priv.fontPixelSize
103+
}
104+
105+
Component.onCompleted: positionBubble()
106+
}

0 commit comments

Comments
 (0)