23 setAttribute(Qt::WA_OpaquePaintEvent);
24 setAttribute(Qt::WA_NoSystemBackground,
false);
25 setFocusPolicy(Qt::NoFocus);
28 setMinimumSize(160, 120);
30 setMouseTracking(
true);
31 setToolTip(tr(
"Mini-map: click or drag to navigate"));
32 setAccessibleName(tr(
"Circuit minimap"));
33 setWhatsThis(tr(
"A miniature overview of the whole circuit. Click or drag inside it to "
34 "jump the main canvas to that location."));
48 if (m_view->horizontalScrollBar())
50 if (m_view->verticalScrollBar())
52 if (m_view->viewport())
53 m_view->viewport()->installEventFilter(
this);
56 m_throttle.setInterval(200);
57 m_throttle.setSingleShot(
true);
63 return QSize(200, 150);
68 if (!m_scene || !m_view) {
71 QPainter painter(
this);
72 painter.fillRect(rect(), palette().window().color());
73 painter.setRenderHint(QPainter::Antialiasing,
false);
76 double scale = 1.0, dx = 0.0, dy = 0.0, usedW = 0.0, usedH = 0.0;
77 if (!computeTransform(srcUsed, scale, dx, dy, usedW, usedH))
80 const qreal dpr = this->devicePixelRatioF();
81 const QSize pixSize = QSize(qRound(usedW * dpr), qRound(usedH * dpr));
85 if (m_cacheDirty || m_cache.size() != pixSize || m_cacheSrc != srcUsed) {
86 m_cache = QPixmap(pixSize);
87 m_cache.setDevicePixelRatio(dpr);
88 m_cache.fill(palette().window().color());
89 QPainter pixmapPainter(&m_cache);
90 pixmapPainter.setRenderHint(QPainter::Antialiasing,
false);
91 m_scene->render(&pixmapPainter, QRectF(0, 0, usedW, usedH), srcUsed, Qt::KeepAspectRatio);
98 painter.drawPixmap(QRectF(dx, dy, usedW, usedH), m_cache, QRectF(0, 0, usedW, usedH));
101 const QRectF visible = m_view->mapToScene(m_view->viewport()->rect()).boundingRect();
104 dx + (visible.left() - srcUsed.left()) * scale,
105 dy + (visible.top() - srcUsed.top()) * scale,
106 visible.width() * scale,
107 visible.height() * scale);
109 QPen pen(palette().highlight().color());
112 painter.setBrush(Qt::NoBrush);
113 painter.drawRect(vr);
115 painter.setRenderHint(QPainter::Antialiasing,
true);
116 drawMoveHandle(painter);
117 drawResizeGrips(painter);
122 if (!m_scene || !m_view) {
126 if (event->button() == Qt::LeftButton) {
127 const ResizeMode mode = resizeModeAt(event->pos());
128 if (mode != ResizeMode::None) {
131 m_lastGlobalPos =
event->globalPosition().toPoint();
137 if (isMoveHandle(event->pos())) {
139 m_lastGlobalPos =
event->globalPosition().toPoint();
146 m_view->centerOn(widgetToScene(event->pos()));
179 if (watched == (m_view ? m_view->viewport() :
nullptr) && event->type() == QEvent::Resize) {
181 return QWidget::eventFilter(watched, event);
183 return QWidget::eventFilter(watched, event);
186bool MinimapWidget::computeTransform(QRectF &srcOut,
double &scaleOut,
double &dxOut,
double &dyOut,
double &usedWOut,
double &usedHOut)
const
203 if (m_view && m_view->viewport())
204 src = src.united(m_view->mapToScene(m_view->viewport()->rect()).boundingRect());
205 if (!src.isValid() || src.isEmpty())
208 const double w =
static_cast<double>(width());
209 const double h =
static_cast<double>(height());
216 const double widgetAspect = w / h;
217 const double srcAspect = src.width() / src.height();
218 if (srcAspect < widgetAspect) {
219 const double grow = (src.height() * widgetAspect - src.width()) / 2.0;
220 src.adjust(-grow, 0.0, grow, 0.0);
221 }
else if (srcAspect > widgetAspect) {
222 const double grow = (src.width() / widgetAspect - src.height()) / 2.0;
223 src.adjust(0.0, -grow, 0.0, grow);
226 const double sx = src.width() > 0 ? w / src.width() : 1.0;
227 const double sy = src.height() > 0 ? h / src.height() : 1.0;
228 const double scale = qMin(sx, sy);
229 const double usedW = src.width() * scale;
230 const double usedH = src.height() * scale;
231 const double dx = (w - usedW) * 0.5;
232 const double dy = (h - usedH) * 0.5;
243QPointF MinimapWidget::widgetToScene(
const QPointF &p)
const
246 double scale = 1.0, dx = 0.0, dy = 0.0, usedW = 0.0, usedH = 0.0;
247 if (!computeTransform(src, scale, dx, dy, usedW, usedH))
250 double x = (p.x() - dx) / scale + src.left();
251 double y = (p.y() - dy) / scale + src.top();
254 x = qBound(src.left(), x, src.right());
255 y = qBound(src.top(), y, src.bottom());
256 return QPointF(x, y);
259MinimapWidget::ResizeMode MinimapWidget::resizeModeAt(
const QPoint &pos)
const
261 const int handleSize = 8;
262 const QRect r = rect();
263 const bool nearLeft = pos.x() <= handleSize;
264 const bool nearRight = pos.x() >= r.width() - handleSize;
265 const bool nearTop = pos.y() <= handleSize;
266 const bool nearBottom = pos.y() >= r.height() - handleSize;
268 if (nearTop && nearLeft) {
269 return ResizeMode::TopLeft;
271 if (nearTop && nearRight) {
272 return ResizeMode::TopRight;
274 if (nearBottom && nearLeft) {
275 return ResizeMode::BottomLeft;
277 if (nearBottom && nearRight) {
278 return ResizeMode::BottomRight;
281 return ResizeMode::Left;
284 return ResizeMode::Right;
287 return ResizeMode::Top;
290 return ResizeMode::Bottom;
292 return ResizeMode::None;
295Qt::CursorShape MinimapWidget::cursorForResizeMode(ResizeMode mode)
const
298 case ResizeMode::Top:
299 case ResizeMode::Bottom:
300 return Qt::SizeVerCursor;
301 case ResizeMode::Left:
302 case ResizeMode::Right:
303 return Qt::SizeHorCursor;
304 case ResizeMode::TopLeft:
305 case ResizeMode::BottomRight:
306 return Qt::SizeFDiagCursor;
307 case ResizeMode::TopRight:
308 case ResizeMode::BottomLeft:
309 return Qt::SizeBDiagCursor;
311 return Qt::ArrowCursor;
315void MinimapWidget::applyResize(
const QPoint &globalPos)
317 const QPoint delta = globalPos - m_lastGlobalPos;
318 const QRect oldGeom = geometry();
319 QRect geom = oldGeom;
324 const auto sceneAspectRatio = [
this]() ->
double {
325 QRectF src = m_scene ? m_scene->cachedItemsBoundingRect() : QRectF();
326 if (!src.isValid() || src.isEmpty())
327 src = m_scene ? m_scene->sceneRect() : QRectF();
328 if (!src.isValid() || src.width() <= 0.0 || src.height() <= 0.0)
330 return src.width() / src.height();
333 const double aspect = sceneAspectRatio();
334 int newWidth = geom.width();
335 int newHeight = geom.height();
337 const bool top = m_resizeMode == ResizeMode::Top || m_resizeMode == ResizeMode::TopLeft || m_resizeMode == ResizeMode::TopRight;
338 const bool bottom = m_resizeMode == ResizeMode::Bottom || m_resizeMode == ResizeMode::BottomLeft || m_resizeMode == ResizeMode::BottomRight;
339 const bool left = m_resizeMode == ResizeMode::Left || m_resizeMode == ResizeMode::TopLeft || m_resizeMode == ResizeMode::BottomLeft;
340 const bool right = m_resizeMode == ResizeMode::Right || m_resizeMode == ResizeMode::TopRight || m_resizeMode == ResizeMode::BottomRight;
342 if ((top && right) || (top && left) || (bottom && right) || (bottom && left)) {
345 const bool horizontalDominant = qAbs(delta.x()) >= qAbs(delta.y());
346 if (horizontalDominant) {
347 newWidth = left ? geom.width() - delta.x() : geom.width() + delta.x();
348 newHeight = qRound(newWidth / aspect);
350 newHeight = top ? geom.height() - delta.y() : geom.height() + delta.y();
351 newWidth = qRound(newHeight * aspect);
353 }
else if (top || bottom) {
354 newHeight = top ? geom.height() - delta.y() : geom.height() + delta.y();
355 newWidth = qRound(newHeight * aspect);
356 }
else if (left || right) {
357 newWidth = left ? geom.width() - delta.x() : geom.width() + delta.x();
358 newHeight = qRound(newWidth / aspect);
364 if (newWidth < minimumWidth()) {
365 newWidth = minimumWidth();
366 newHeight = qMax(minimumHeight(), qRound(newWidth / aspect));
368 if (newHeight < minimumHeight()) {
369 newHeight = minimumHeight();
370 newWidth = qMax(minimumWidth(), qRound(newHeight * aspect));
378 const int newX = left ? oldGeom.right() - newWidth + 1 : oldGeom.left();
379 const int newY = top ? oldGeom.bottom() - newHeight + 1 : oldGeom.top();
380 geom = QRect(newX, newY, newWidth, newHeight);
382 if (geom != oldGeom) {
385 m_lastGlobalPos = globalPos;
388QRect MinimapWidget::moveHandleRect()
const
390 return QRect(0, 0, width(), 24);
393bool MinimapWidget::isMoveHandle(
const QPoint &pos)
const
395 return moveHandleRect().contains(pos);
398void MinimapWidget::moveBy(
const QPoint &delta)
403 const QPoint newPos = pos() + delta;
404 const QRect parentRect = parentWidget()->rect();
405 const int margin = 12;
406 const int x = qBound(margin, newPos.x(), parentRect.width() - width() - margin);
407 const int y = qBound(margin, newPos.y(), parentRect.height() - height() - margin);
411void MinimapWidget::drawMoveHandle(QPainter &painter)
const
413 const QRect handle = moveHandleRect();
414 const bool highlighted = m_moving || m_hoverMoveHandle;
416 QColor bg = highlighted ? palette().highlight().color() : palette().windowText().color();
417 bg.setAlpha(highlighted ? 90 : 35);
418 painter.fillRect(handle, bg);
422 QColor dotColor = palette().windowText().color();
423 dotColor.setAlpha(highlighted ? 230 : 140);
424 painter.setPen(Qt::NoPen);
425 painter.setBrush(dotColor);
427 const QPointF center = handle.center();
428 constexpr double dotRadius = 1.2;
429 constexpr double spacingX = 6.0;
430 constexpr double spacingY = 5.0;
431 for (
const double rowOffset : {-spacingY / 2.0, spacingY / 2.0}) {
432 for (
const double colOffset : {-spacingX, 0.0, spacingX}) {
433 painter.drawEllipse(center + QPointF(colOffset, rowOffset), dotRadius, dotRadius);
438void MinimapWidget::drawResizeGrips(QPainter &painter)
const
447 constexpr int inset = 3;
448 constexpr int armLength = 9;
449 const QRect r = rect();
450 const Corner corners[] = {
451 {ResizeMode::TopLeft, QPoint(inset, inset), 1, 1},
452 {ResizeMode::TopRight, QPoint(r.width() - 1 - inset, inset), -1, 1},
453 {ResizeMode::BottomLeft, QPoint(inset, r.height() - 1 - inset), 1, -1},
454 {ResizeMode::BottomRight, QPoint(r.width() - 1 - inset, r.height() - 1 - inset), -1, -1},
457 for (
const Corner &corner : corners) {
458 const bool highlighted = m_resizing ? m_resizeMode == corner.mode : m_hoverResizeMode == corner.mode;
459 QColor color = highlighted ? palette().highlight().color() : palette().windowText().color();
460 color.setAlpha(highlighted ? 230 : 130);
462 pen.setWidthF(highlighted ? 2.0 : 1.4);
464 painter.drawLine(corner.origin, corner.origin + QPoint(corner.armX * armLength, 0));
465 painter.drawLine(corner.origin, corner.origin + QPoint(0, corner.armY * armLength));
472 applyResize(event->globalPosition().toPoint());
479 moveBy(event->globalPosition().toPoint() - m_lastGlobalPos);
480 m_lastGlobalPos =
event->globalPosition().toPoint();
486 updateHoverState(event->pos());
489 if (!m_dragging || !m_scene || !m_view) {
490 QWidget::mouseMoveEvent(event);
494 m_view->centerOn(widgetToScene(event->pos()));
503 m_resizeMode = ResizeMode::None;
521 QWidget::mouseReleaseEvent(event);
526 QWidget::enterEvent(event);
527 updateHoverState(mapFromGlobal(QCursor::pos()));
532 QWidget::leaveEvent(event);
534 if (m_hoverResizeMode != ResizeMode::None || m_hoverMoveHandle) {
535 m_hoverResizeMode = ResizeMode::None;
536 m_hoverMoveHandle =
false;
541void MinimapWidget::updateHoverState(
const QPoint &pos)
543 const ResizeMode mode = resizeModeAt(pos);
544 const bool onMoveHandle = mode == ResizeMode::None && isMoveHandle(pos);
546 if (mode != ResizeMode::None) {
547 setCursor(cursorForResizeMode(mode));
548 }
else if (onMoveHandle) {
549 setCursor(Qt::OpenHandCursor);
554 if (mode != m_hoverResizeMode || onMoveHandle != m_hoverMoveHandle) {
555 m_hoverResizeMode = mode;
556 m_hoverMoveHandle = onMoveHandle;
Extended QGraphicsView with zoom, pan, and fast-rendering modes.
Main circuit editing scene with undo/redo and user interaction.
Theme management types and singleton ThemeManager.
Extended QGraphicsView with enhanced navigation capabilities.
void zoomChanged()
Emitted whenever the zoom level changes.
Main circuit editing scene.
QRectF cachedItemsBoundingRect() const
void circuitHasChanged()
Emitted whenever the circuit changes (element added/removed/moved).
static ThemeManager & instance()
Returns the singleton ThemeManager instance.
void themeChanged()
Emitted whenever the active theme changes.