wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
ExerciseOverlay.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <QApplication>
7#include <QFontInfo>
8#include <QHBoxLayout>
9#include <QLabel>
10#include <QPainter>
11#include <QPushButton>
12#include <QVBoxLayout>
13
16
18 : QWidget(parent)
19 , m_engine(engine)
20{
21 setFixedWidth(480);
22 setAttribute(Qt::WA_TranslucentBackground);
23
24 setupUi();
25
27 this, &ExerciseOverlay::applyTheme);
28}
29
30void ExerciseOverlay::setupUi()
31{
32 auto *outerLayout = new QVBoxLayout(this);
33 outerLayout->setContentsMargins(12, 12, 12, 12);
34 outerLayout->setSpacing(8);
35
36 // Step counter row
37 auto *topRow = new QHBoxLayout;
38 m_stepCounter = new QLabel(this);
39 topRow->addWidget(m_stepCounter);
40 topRow->addStretch();
41 outerLayout->addLayout(topRow);
42
43 // Instruction text
44 m_instructionLabel = new QLabel(this);
45 m_instructionLabel->setWordWrap(true);
46 outerLayout->addWidget(m_instructionLabel);
47
48 // Hint text (hidden by default)
49 m_hintLabel = new QLabel(this);
50 m_hintLabel->setWordWrap(true);
51 m_hintLabel->hide();
52 outerLayout->addWidget(m_hintLabel);
53
54 // Navigation row: [Exit] [Hint] ←stretch→ [← Back] [Next →]
55 auto *btnRow = new QHBoxLayout;
56 btnRow->setSpacing(6);
57
58 m_closeButton = new QPushButton(tr("Exit"), this);
59 m_closeButton->setToolTip(tr("Close exercise"));
60
61 m_hintButton = new QPushButton(tr("Hint"), this);
62
63 m_prevButton = new QPushButton(tr("← Back"), this);
64
65 m_nextButton = new QPushButton(tr("Next →"), this);
66 m_nextButton->setEnabled(false);
67
68 btnRow->addWidget(m_closeButton);
69 btnRow->addWidget(m_hintButton);
70 btnRow->addStretch();
71 btnRow->addWidget(m_prevButton);
72 btnRow->addWidget(m_nextButton);
73 outerLayout->addLayout(btnRow);
74
75 adjustSize();
76
77 connect(m_closeButton, &QPushButton::clicked, this, &ExerciseOverlay::closeRequested);
78 connect(m_hintButton, &QPushButton::clicked, this, [this] {
79 m_hintVisible = !m_hintVisible;
80 m_hintLabel->setVisible(m_hintVisible);
81 m_hintButton->setText(m_hintVisible ? tr("Hide hint") : tr("Hint"));
82 adjustSize();
84 });
85 connect(m_prevButton, &QPushButton::clicked, this, [this] {
86 m_engine->goToPreviousStep();
87 });
88 connect(m_nextButton, &QPushButton::clicked, this, [this] {
89 m_engine->advanceStep();
90 });
91
92 applyTheme();
93}
94
95int ExerciseOverlay::scaledFontPx(int basePx)
96{
97 constexpr int baselinePx = 13;
98 const int currentPx = QFontInfo(QApplication::font()).pixelSize();
99 return qRound(static_cast<double>(basePx) * currentPx / baselinePx);
100}
101
102void ExerciseOverlay::applyTheme()
103{
104 const bool dark = (ThemeManager::effectiveTheme() == Theme::Dark);
105 const int navFontPx = scaledFontPx(13);
106
107 if (dark) {
108 m_bgColor = QColor(30, 30, 30, 200);
109 m_textColor = QColor(255, 255, 255);
110 m_hintColor = QColor(255, 255, 180, 230);
111 m_counterColor = QColor(255, 255, 255, 200);
112
113 const QString navStyle = QString("QPushButton { color: white; background: rgba(255,255,255,40); border: 1px solid rgba(255,255,255,80); border-radius: 4px; padding: 3px 10px; font-size: %1px; } QPushButton:hover { background: rgba(255,255,255,60); } QPushButton:disabled { color: rgba(255,255,255,80); background: rgba(80,80,80,60); }").arg(navFontPx);
114 m_closeButton->setStyleSheet(navStyle);
115 m_hintButton->setStyleSheet(navStyle);
116 m_prevButton->setStyleSheet(navStyle);
117 m_nextButton->setStyleSheet(QString("QPushButton { color: white; background: rgba(60,150,60,180); border: 1px solid rgba(255,255,255,80); border-radius: 4px; padding: 3px 10px; font-size: %1px; } QPushButton:hover { background: rgba(60,180,60,200); } QPushButton:disabled { background: rgba(100,100,100,80); color: rgba(255,255,255,100); }").arg(navFontPx));
118 } else {
119 m_bgColor = QColor(245, 245, 245, 230);
120 m_textColor = QColor(20, 20, 20);
121 m_hintColor = QColor(110, 90, 0, 230);
122 m_counterColor = QColor(80, 80, 80, 200);
123
124 const QString navStyle = QString("QPushButton { color: rgb(20,20,20); background: rgba(0,0,0,20); border: 1px solid rgba(0,0,0,80); border-radius: 4px; padding: 3px 10px; font-size: %1px; } QPushButton:hover { background: rgba(0,0,0,40); } QPushButton:disabled { color: rgba(0,0,0,60); background: rgba(0,0,0,10); }").arg(navFontPx);
125 m_closeButton->setStyleSheet(navStyle);
126 m_hintButton->setStyleSheet(navStyle);
127 m_prevButton->setStyleSheet(navStyle);
128 m_nextButton->setStyleSheet(QString("QPushButton { color: white; background: rgba(40,130,40,220); border: 1px solid rgba(0,100,0,150); border-radius: 4px; padding: 3px 10px; font-size: %1px; } QPushButton:hover { background: rgba(40,160,40,230); } QPushButton:disabled { background: rgba(150,150,150,80); color: rgba(255,255,255,120); }").arg(navFontPx));
129 }
130
131 m_stepCounter->setStyleSheet(
132 QString("color: rgba(%1,%2,%3,%4); font-size: %5px;")
133 .arg(m_counterColor.red()).arg(m_counterColor.green())
134 .arg(m_counterColor.blue()).arg(m_counterColor.alpha()).arg(scaledFontPx(12)));
135 m_instructionLabel->setStyleSheet(
136 QString("color: rgb(%1,%2,%3); font-size: %4px; font-weight: bold;")
137 .arg(m_textColor.red()).arg(m_textColor.green()).arg(m_textColor.blue())
138 .arg(scaledFontPx(14)));
139 m_hintLabel->setStyleSheet(
140 QString("color: rgba(%1,%2,%3,%4); font-size: %5px;")
141 .arg(m_hintColor.red()).arg(m_hintColor.green())
142 .arg(m_hintColor.blue()).arg(m_hintColor.alpha()).arg(scaledFontPx(13)));
143
144 update();
145}
146
148{
149 if (!parentWidget()) {
150 return;
151 }
152 adjustSize();
153 const int x = (parentWidget()->width() - width()) / 2;
154 const int y = parentWidget()->height() - height() - 12;
155 move(x, y);
156}
157
158void ExerciseOverlay::onStepChanged(int step, int total, const ExerciseStep &stepData)
159{
160 m_stepCounter->setText(tr("Step %1 of %2").arg(step + 1).arg(total));
161 m_instructionLabel->setText(stepData.instruction);
162 m_hintLabel->setText(stepData.hint);
163
164 // Reset hint state on step change
165 m_hintVisible = false;
166 m_hintLabel->hide();
167 m_hintButton->setText(tr("Hint"));
168
169 const bool isObserveStep = stepData.requiredElements.isEmpty() && stepData.requiredConnections.isEmpty();
170 const bool isLastStep = (step == total - 1);
171
172 updateNextButton(isLastStep);
173 // Next button enabled only for observe steps (auto-advance handles others)
174 m_nextButton->setEnabled(isObserveStep);
175
176 m_prevButton->setEnabled(step > 0);
177
178 adjustSize();
180}
181
183{
184 const ExerciseStep &stepData = m_engine->currentStepData();
185 const int step = m_engine->currentStep();
186 const int total = m_engine->totalSteps();
187
188 m_stepCounter->setText(tr("Step %1 of %2").arg(step + 1).arg(total));
189 m_instructionLabel->setText(stepData.instruction);
190 m_hintLabel->setText(stepData.hint);
191 updateNextButton(step == total - 1);
192
193 m_closeButton->setText(tr("Exit"));
194 m_closeButton->setToolTip(tr("Close exercise"));
195 m_hintButton->setText(m_hintVisible ? tr("Hide hint") : tr("Hint"));
196 m_prevButton->setText(tr("← Back"));
197
198 adjustSize();
200}
201
203{
204 m_stepCounter->setText({});
205 m_instructionLabel->setText(tr("Exercise complete! Well done."));
206 m_hintLabel->hide();
207 m_hintButton->hide();
208 m_prevButton->hide();
209 m_nextButton->setText(tr("Close"));
210 m_nextButton->setEnabled(true);
211 disconnect(m_nextButton, &QPushButton::clicked, nullptr, nullptr);
212 connect(m_nextButton, &QPushButton::clicked, this, &ExerciseOverlay::closeRequested);
213 adjustSize();
215}
216
217void ExerciseOverlay::updateNextButton(bool isLastStep)
218{
219 m_nextButton->setText(isLastStep ? tr("Finish") : tr("Next →"));
220}
221
222void ExerciseOverlay::paintEvent(QPaintEvent *event)
223{
224 Q_UNUSED(event)
225 QPainter painter(this);
226 painter.setRenderHint(QPainter::Antialiasing);
227 painter.setBrush(m_bgColor);
228 painter.setPen(Qt::NoPen);
229 painter.drawRoundedRect(rect(), 8, 8);
230}
Theme management types and singleton ThemeManager.
ExerciseOverlay(ExerciseEngine *engine, QWidget *parent=nullptr)
void onStepChanged(int step, int total, const ExerciseStep &stepData)
void closeRequested()
void paintEvent(QPaintEvent *event) override
static ThemeManager & instance()
Returns the singleton ThemeManager instance.
void themeChanged()
Emitted whenever the active theme changes.
static Theme effectiveTheme()
Returns the effective theme (Light or Dark), resolving System to the OS preference.
QVector< ExerciseConnectionRequirement > requiredConnections
QVector< ExerciseElementRequirement > requiredElements
QString instruction