Skip to content

Implement text bubbles #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add TextBubble component
  • Loading branch information
adazem009 committed Feb 4, 2024
commit e8a25f92c56f027f33952702ec16a381d6b481a0
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ qt_add_qml_module(scratchcpp-render
internal/ValueMonitor.qml
internal/MonitorSlider.qml
internal/ListMonitor.qml
internal/TextBubble.qml
shaders/sprite.vert
shaders/sprite.frag
SOURCES
Expand Down
106 changes: 106 additions & 0 deletions src/internal/TextBubble.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: LGPL-3.0-or-later

import QtQuick
import ScratchCPP.Render

TextBubbleShape {
id: root
property string text: ""
property RenderedTarget target: null
property double stageWidth: 0
property double stageHeight: 0

QtObject {
// https://github.com/scratchfoundation/scratch-render/blob/ac935423afe3ba79235750eecb1e443474c6eb09/src/TextBubbleSkin.js#L7-L26
id: priv
readonly property int maxLineWidth: 170
readonly property int minWidth: 50
readonly property int padding: 10
readonly property int tailHeight: 12

readonly property string fontFamily: "Helvetica"
readonly property int fontPixelSize: 14
readonly property int lineHeight: 16

readonly property color textFill: '#575E75'

function translateX(x) {
// Translates Scratch X-coordinate to the scene coordinate system
return root.stageScale * (root.stageWidth / 2 + x)
}

function translateY(y) {
// Translates Scratch Y-coordinate to the scene coordinate system
return root.stageScale * (root.stageHeight / 2 - y)
}
}

nativeWidth: Math.max(bubbleText.contentWidth, priv.minWidth) + 2 * priv.padding
nativeHeight: bubbleText.height + 2 * priv.padding + priv.tailHeight

function positionBubble() {
// https://github.com/scratchfoundation/scratch-vm/blob/7313ce5199f8a3da7850085d0f7f6a3ca2c89bf6/src/blocks/scratch3_looks.js#L158
if(!target.visible)
return;

const targetBounds = target.getBoundsForBubble();
const stageBounds = Qt.rect(-root.stageWidth / 2, root.stageHeight / 2, root.stageWidth, root.stageHeight);

if (onSpriteRight && nativeWidth + targetBounds.right > stageBounds.right &&
(targetBounds.left - nativeWidth > stageBounds.left)) { // Only flip if it would fit
onSpriteRight = false;
} else if (!onSpriteRight && targetBounds.left - nativeWidth < stageBounds.left &&
(nativeWidth + targetBounds.right < stageBounds.right)) { // Only flip if it would fit
onSpriteRight = true;
}

const pos = [
onSpriteRight ? (
Math.max(
stageBounds.left, // Bubble should not extend past left edge of stage
Math.min(stageBounds.right - nativeWidth, targetBounds.right)
)
) : (
Math.min(
stageBounds.right - nativeWidth, // Bubble should not extend past right edge of stage
Math.max(stageBounds.left, targetBounds.left - nativeWidth)
)
),
// Bubble should not extend past the top of the stage
Math.min(stageBounds.top, targetBounds.bottom + nativeHeight)
];

x = priv.translateX(pos[0]);
y = priv.translateY(pos[1]);
}

Connections {
target: root.target

function onXChanged() { positionBubble() }
function onYChanged() { positionBubble() }
function onRotationChanged() { positionBubble() }
function onWidthChanged() { positionBubble() }
function onHeightChanged() { positionBubble() }
function onScaleChanged() { positionBubble() }
}

Text {
id: bubbleText
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: priv.padding * root.stageScale
width: priv.maxLineWidth
scale: root.stageScale
transformOrigin: Item.TopLeft
text: root.text
wrapMode: Text.Wrap
color: priv.textFill
lineHeight: priv.lineHeight
lineHeightMode: Text.FixedHeight
font.family: priv.fontFamily
font.pixelSize: priv.fontPixelSize
}

Component.onCompleted: positionBubble()
}