Bubbling.

Took 16 seconds

# Commit time for manual adjustment:
# Took 8 seconds

Took 16 seconds

# Commit time for manual adjustment:
# Took 6 seconds

Took 9 seconds

# Commit time for manual adjustment:
# Took 8 seconds


Took 14 seconds
This commit is contained in:
Lukas Brübach
2025-12-07 22:14:20 +01:00
parent d074fd5491
commit 06a162c1f3
6 changed files with 147 additions and 37 deletions

View File

@@ -168,6 +168,7 @@ set(cockatrice_SOURCES
src/interface/widgets/general/layout_containers/flow_widget.cpp
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
src/interface/widgets/general/layout_containers/overlap_widget.cpp
src/interface/widgets/general/tutorial/tutorial_bubble_widget.cpp
src/interface/widgets/general/tutorial/tutorial_controller.cpp
src/interface/widgets/general/tutorial/tutorial_overlay.cpp
src/interface/widgets/menus/deck_editor_menu.cpp

View File

@@ -0,0 +1,56 @@
#include "tutorial_bubble_widget.h"
BubbleWidget::BubbleWidget(QWidget *parent) : QFrame(parent)
{
setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
setStyleSheet("background:white; border-radius:8px;");
QGridLayout *layout = new QGridLayout(this);
layout->setContentsMargins(12, 10, 12, 10);
layout->setHorizontalSpacing(8);
layout->setVerticalSpacing(8);
counterLabel = new QLabel(this);
counterLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
closeButton = new QPushButton("", this);
closeButton->setFixedSize(20, 20);
textLabel = new QLabel(this);
textLabel->setWordWrap(true);
textLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
textLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
textLabel->setStyleSheet("color:black;"); // guard against global styles
// nav buttons
previousSequenceButton = new QPushButton("<<", this);
previousStepButton = new QPushButton("<", this);
nextStepButton = new QPushButton(">", this);
nextSequenceButton = new QPushButton(">>", this);
QHBoxLayout *navLayout = new QHBoxLayout;
navLayout->addStretch();
navLayout->addWidget(previousSequenceButton);
navLayout->addWidget(previousStepButton);
navLayout->addWidget(nextStepButton);
navLayout->addWidget(nextSequenceButton);
// Layout
layout->addWidget(counterLabel, 0, 0, Qt::AlignLeft | Qt::AlignVCenter);
layout->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 1);
layout->addWidget(closeButton, 0, 2, Qt::AlignRight);
layout->addWidget(textLabel, 1, 0, 1, 3);
layout->addLayout(navLayout, 2, 0, 1, 3);
// Make column 1 take extra space so text gets room to expand/wrap
layout->setColumnStretch(1, 1);
// sensible default maximum width for bubble so text will wrap
setMaximumWidth(420);
}
void BubbleWidget::setText(const QString &text)
{
textLabel->setText(text);
update();
}

View File

@@ -0,0 +1,24 @@
#ifndef COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
#define COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H
#include <QFrame>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
class BubbleWidget : public QFrame
{
Q_OBJECT
public:
QLabel *textLabel;
QLabel *counterLabel;
QPushButton *closeButton;
QPushButton *previousSequenceButton;
QPushButton *previousStepButton;
QPushButton *nextStepButton;
QPushButton *nextSequenceButton;
BubbleWidget(QWidget *parent);
void setText(const QString &text);
};
#endif // COCKATRICE_TUTORIAL_BUBBLE_WIDGET_H

View File

@@ -15,7 +15,11 @@ TutorialController::TutorialController(QWidget *_tutorializedWidget)
tutorialOverlay->hide();
connect(tutorialOverlay, &TutorialOverlay::nextStep, this, &TutorialController::nextStep);
connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, [this]() { tutorialOverlay->hide(); });
connect(tutorialOverlay, &TutorialOverlay::prevStep, this, &TutorialController::prevStep);
connect(tutorialOverlay, &TutorialOverlay::nextSequence, this, &TutorialController::nextSequence);
connect(tutorialOverlay, &TutorialOverlay::prevSequence, this, &TutorialController::prevSequence);
connect(tutorialOverlay, &TutorialOverlay::skipTutorial, this, &TutorialController::exitTutorial);
}
void TutorialController::addSequence(const TutorialSequence &seq)
@@ -100,12 +104,13 @@ void TutorialController::prevSequence()
{
if (currentSequence <= 0) {
// already at first sequence -> stay
currentStep = 0;
showStep();
return;
}
// go to last step of previous sequence
currentSequence--;
currentStep = sequences[currentSequence].steps.size() - 1;
currentStep = 0;
showStep();
}

View File

@@ -24,6 +24,15 @@ TutorialOverlay::TutorialOverlay(QWidget *parent) : QWidget(parent, Qt::Window)
setGeometry(r);
}
bubble = new BubbleWidget(this);
bubble->hide();
connect(bubble->nextStepButton, &QPushButton::clicked, this, &TutorialOverlay::nextStep);
connect(bubble->previousStepButton, &QPushButton::clicked, this, &TutorialOverlay::prevStep);
connect(bubble->previousSequenceButton, &QPushButton::clicked, this, &TutorialOverlay::prevSequence);
connect(bubble->nextSequenceButton, &QPushButton::clicked, this, &TutorialOverlay::nextSequence);
connect(bubble->closeButton, &QPushButton::clicked, this, &TutorialOverlay::skipTutorial);
}
void TutorialOverlay::setTargetWidget(QWidget *w)
@@ -35,6 +44,26 @@ void TutorialOverlay::setTargetWidget(QWidget *w)
void TutorialOverlay::setText(const QString &t)
{
tutorialText = t;
bubble->setText(tutorialText);
bubble->adjustSize(); // let layout recalc sizes
QSize bsize = bubble->sizeHint();
const QSize minSize(160, 60);
if (bsize.width() < minSize.width()) {
bsize.setWidth(minSize.width());
}
if (bsize.height() < minSize.height()) {
bsize.setHeight(minSize.height());
}
// Compute the bubble rect from the current target hole
QRect hole = targetRectOnOverlay().adjusted(-6, -6, 6, 6);
highlightBubbleRect = computeBubbleRect(hole, bsize);
bubble->setGeometry(highlightBubbleRect);
bubble->raise();
bubble->show();
update();
}
@@ -63,42 +92,42 @@ QRect TutorialOverlay::targetRectOnOverlay() const
return QRect(localTopLeft, targetWidget->size());
}
QRect TutorialOverlay::computeBubbleRect(const QRect &hole) const
QRect TutorialOverlay::computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const
{
const int bubbleW = 250;
const int bubbleH = 120;
const int margin = 16;
QRect r = rect(); // overlay bounds
QRect bubble;
// Try right
bubble = QRect(hole.right() + margin, hole.top(), bubbleW, bubbleH);
bubble = QRect(hole.right() + margin, hole.top(), bubbleSize.width(), bubbleSize.height());
if (r.contains(bubble)) {
return bubble;
}
// Try left
bubble = QRect(hole.left() - margin - bubbleW, hole.top(), bubbleW, bubbleH);
bubble = QRect(hole.left() - margin - bubbleSize.width(), hole.top(), bubbleSize.width(), bubbleSize.height());
if (r.contains(bubble)) {
return bubble;
}
// Try above, centered
bubble = QRect(hole.center().x() - bubbleW / 2, hole.top() - margin - bubbleH, bubbleW, bubbleH);
bubble = QRect(hole.center().x() - bubbleSize.width() / 2, hole.top() - margin - bubbleSize.height(),
bubbleSize.width(), bubbleSize.height());
if (r.contains(bubble)) {
return bubble;
}
// Try below, centered
bubble = QRect(hole.center().x() - bubbleW / 2, hole.bottom() + margin, bubbleW, bubbleH);
bubble = QRect(hole.center().x() - bubbleSize.width() / 2, hole.bottom() + margin, bubbleSize.width(),
bubbleSize.height());
if (r.contains(bubble)) {
return bubble;
}
// Last-resort: clamp inside overlay
bubble.moveLeft(std::max(r.left(), std::min(bubble.left(), r.right() - bubbleW)));
bubble.moveTop(std::max(r.top(), std::min(bubble.top(), r.bottom() - bubbleH)));
bubble.moveLeft(std::max(r.left(), std::min(bubble.left(), r.right() - bubbleSize.width())));
bubble.moveTop(std::max(r.top(), std::min(bubble.top(), r.bottom() - bubbleSize.height())));
bubble.setSize(bubbleSize);
return bubble;
}
@@ -117,11 +146,9 @@ void TutorialOverlay::paintEvent(QPaintEvent *)
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
// Darken the screen
QColor overlay(0, 0, 0, 160);
p.fillRect(rect(), overlay);
// Highlight hole
QRect hole = targetRectOnOverlay().adjusted(-6, -6, 6, 6);
if (!hole.isEmpty()) {
QPainterPath path;
@@ -135,25 +162,17 @@ void TutorialOverlay::paintEvent(QPaintEvent *)
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
}
// Draw bubble
p.setPen(Qt::NoPen);
p.setBrush(QColor(255, 255, 255));
highlightBubbleRect = computeBubbleRect(hole);
p.drawRoundedRect(highlightBubbleRect, 8, 8);
// recompute bubble size/position in case available geometry changed:
bubble->adjustSize();
QSize bsize = bubble->sizeHint();
const QSize minSize(160, 60);
if (bsize.width() < minSize.width())
bsize.setWidth(minSize.width());
if (bsize.height() < minSize.height())
bsize.setHeight(minSize.height());
// Text
p.setPen(Qt::black);
p.drawText(highlightBubbleRect.adjusted(10, 10, -10, -10), Qt::TextWordWrap, tutorialText);
}
void TutorialOverlay::mousePressEvent(QMouseEvent *ev)
{
// Clicks inside bubble → next
if (highlightBubbleRect.contains(ev->pos())) {
emit nextStep();
return;
}
// Click anywhere else means skip
emit skipTutorial();
highlightBubbleRect = computeBubbleRect(hole, bsize);
bubble->setGeometry(highlightBubbleRect);
bubble->raise();
bubble->show();
}

View File

@@ -1,6 +1,8 @@
#ifndef COCKATRICE_TUTORIAL_OVERLAY_H
#define COCKATRICE_TUTORIAL_OVERLAY_H
#include "tutorial_bubble_widget.h"
#include <QPointer>
#include <QWidget>
@@ -16,22 +18,25 @@ public:
signals:
void nextStep();
void prevStep();
void nextSequence();
void prevSequence();
void skipTutorial();
protected:
void paintEvent(QPaintEvent *) override;
void resizeEvent(QResizeEvent *) override;
void showEvent(QShowEvent *) override;
void mousePressEvent(QMouseEvent *) override;
private:
QRect targetRectOnOverlay() const;
QRect computeBubbleRect(const QRect &hole) const;
QRect computeBubbleRect(const QRect &hole, const QSize &bubbleSize) const;
QPointer<QWidget> targetWidget;
QString tutorialText;
QRect highlightBubbleRect;
BubbleWidget *bubble;
};
#endif // COCKATRICE_TUTORIAL_OVERLAY_H