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);
361 newWidth = qMax(minimumWidth(), newWidth);
362 newHeight = qMax(minimumHeight(), newHeight);
363 if (newWidth < minimumWidth()) {
364 newWidth = minimumWidth();
365 newHeight = qMax(minimumHeight(), qRound(newWidth / aspect));
367 if (newHeight < minimumHeight()) {
368 newHeight = minimumHeight();
369 newWidth = qMax(minimumWidth(), qRound(newHeight * aspect));
377 const int newX = left ? oldGeom.right() - newWidth + 1 : oldGeom.left();
378 const int newY = top ? oldGeom.bottom() - newHeight + 1 : oldGeom.top();
379 geom = QRect(newX, newY, newWidth, newHeight);
381 if (geom != oldGeom) {
384 m_lastGlobalPos = globalPos;
387QRect MinimapWidget::moveHandleRect()
const
389 return QRect(0, 0, width(), 24);
392bool MinimapWidget::isMoveHandle(
const QPoint &pos)
const
394 return moveHandleRect().contains(pos);
397void MinimapWidget::moveBy(
const QPoint &delta)
402 const QPoint newPos = pos() + delta;
403 const QRect parentRect = parentWidget()->rect();
404 const int margin = 12;
405 const int x = qBound(margin, newPos.x(), parentRect.width() - width() - margin);
406 const int y = qBound(margin, newPos.y(), parentRect.height() - height() - margin);
410void MinimapWidget::drawMoveHandle(QPainter &painter)
const
412 const QRect handle = moveHandleRect();
413 const bool highlighted = m_moving || m_hoverMoveHandle;
415 QColor bg = highlighted ? palette().highlight().color() : palette().windowText().color();
416 bg.setAlpha(highlighted ? 90 : 35);
417 painter.fillRect(handle, bg);
421 QColor dotColor = palette().windowText().color();
422 dotColor.setAlpha(highlighted ? 230 : 140);
423 painter.setPen(Qt::NoPen);
424 painter.setBrush(dotColor);
426 const QPointF center = handle.center();
427 constexpr double dotRadius = 1.2;
428 constexpr double spacingX = 6.0;
429 constexpr double spacingY = 5.0;
430 for (
const double rowOffset : {-spacingY / 2.0, spacingY / 2.0}) {
431 for (
const double colOffset : {-spacingX, 0.0, spacingX}) {
432 painter.drawEllipse(center + QPointF(colOffset, rowOffset), dotRadius, dotRadius);
437void MinimapWidget::drawResizeGrips(QPainter &painter)
const
446 constexpr int inset = 3;
447 constexpr int armLength = 9;
448 const QRect r = rect();
449 const Corner corners[] = {
450 {ResizeMode::TopLeft, QPoint(inset, inset), 1, 1},
451 {ResizeMode::TopRight, QPoint(r.width() - 1 - inset, inset), -1, 1},
452 {ResizeMode::BottomLeft, QPoint(inset, r.height() - 1 - inset), 1, -1},
453 {ResizeMode::BottomRight, QPoint(r.width() - 1 - inset, r.height() - 1 - inset), -1, -1},
456 for (
const Corner &corner : corners) {
457 const bool highlighted = m_resizing ? m_resizeMode == corner.mode : m_hoverResizeMode == corner.mode;
458 QColor color = highlighted ? palette().highlight().color() : palette().windowText().color();
459 color.setAlpha(highlighted ? 230 : 130);
461 pen.setWidthF(highlighted ? 2.0 : 1.4);
463 painter.drawLine(corner.origin, corner.origin + QPoint(corner.armX * armLength, 0));
464 painter.drawLine(corner.origin, corner.origin + QPoint(0, corner.armY * armLength));
471 applyResize(event->globalPosition().toPoint());
478 moveBy(event->globalPosition().toPoint() - m_lastGlobalPos);
479 m_lastGlobalPos =
event->globalPosition().toPoint();
485 updateHoverState(event->pos());
488 if (!m_dragging || !m_scene || !m_view) {
489 QWidget::mouseMoveEvent(event);
493 m_view->centerOn(widgetToScene(event->pos()));
502 m_resizeMode = ResizeMode::None;
520 QWidget::mouseReleaseEvent(event);
525 QWidget::enterEvent(event);
526 updateHoverState(mapFromGlobal(QCursor::pos()));
531 QWidget::leaveEvent(event);
533 if (m_hoverResizeMode != ResizeMode::None || m_hoverMoveHandle) {
534 m_hoverResizeMode = ResizeMode::None;
535 m_hoverMoveHandle =
false;
540void MinimapWidget::updateHoverState(
const QPoint &pos)
542 const ResizeMode mode = resizeModeAt(pos);
543 const bool onMoveHandle = mode == ResizeMode::None && isMoveHandle(pos);
545 if (mode != ResizeMode::None) {
546 setCursor(cursorForResizeMode(mode));
547 }
else if (onMoveHandle) {
548 setCursor(Qt::OpenHandCursor);
553 if (mode != m_hoverResizeMode || onMoveHandle != m_hoverMoveHandle) {
554 m_hoverResizeMode = mode;
555 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.