Skip to content

QGraphicsProxyWidget-based node widgets exhibit size drift after zoom-out / zoom-in cycles #504

@JasonLeon01

Description

@JasonLeon01

Environment

  • NodeGraphQt version: 0.6.44
  • Qt binding: PyQt5
  • OS: Cross-platform (reproduced on macOS and Windows)

What's happening

If a node has embedded widgets (QLineEdit, QSpinBox, QComboBox, etc.), zooming in and out a few times causes the widgets to drift out of alignment — they either spill outside the node border or leave a big empty gap inside. The longer you zoom, the worse it gets.

How to reproduce

  1. Create a node with a few embedded widgets — e.g. a spinbox, a text input, and a custom QPlainTextEdit.
  2. Zoom out with the scroll wheel or a trackpad pinch until the node is roughly 20–30% of its original size.
  3. Zoom back in to roughly the original level.
  4. Repeat steps 2–3 about three to five times.
  5. Have a look at the node — the widgets no longer sit properly inside the node body. They might be too wide, too narrow, or sticking out past the border.

Why it happens

A few things interact to cause this:

1. Node size is calculated once and never updated

calc_size() runs inside draw_node(), reads boundingRect() for each widget, and stores the result in _width / _height. After that, boundingRect() just hands back those fixed values forever:

def boundingRect(self):
    return QtCore.QRectF(0.0, 0.0, self._width, self._height)

Nothing ever triggers a recalculation after the initial draw. So when a widget's sizeHint() changes — due to a content update, a style change, or a hide/show cycle forcing a layout pass — the node body stays at the old size and things stop lining up.

2. NodeBaseWidget has no idea the view is being scaled

NodeBaseWidget inherits straight from QGraphicsProxyWidget with no overrides for paint(), sizeHint(), boundingRect(), or itemChange():

class NodeBaseWidget(QtWidgets.QGraphicsProxyWidget):
    def __init__(self, parent=None, name=None, label=''):
        super(NodeBaseWidget, self).__init__(parent)
        self.setZValue(Z_VAL_NODE_WIDGET)

No ItemIgnoresTransformations, no resetTransform(), no compensation for the view scale whatsoever. It just relies on Qt's default QGraphicsProxyWidget behaviour — which has known geometry drift issues when used with fitInView() (see QTBUG-55112, QTBUG-49075).

3. Widgets get hidden and shown mid-zoom, and the timing is off

When the node's on-screen width drops below 70 px, auto_switch_mode() calls set_proxy_mode(True), which hides the internal QWidget:

for w in self._widgets.values():
    w.widget().setVisible(visible)

When you zoom back in, setVisible(True) fires and Qt recalculates the widget's geometry. If that recalculation happens while the view is still mid-zoom, sizeHint() gets computed at the wrong scale. Since _width / _height are never updated, that mismatch sticks around permanently.

4. calc_size measures widgets via boundingRect(), which isn't stable for all widget types

The size calculation works like this:

widget_width = max(w.boundingRect().width() for w in self._widgets.values())
widget_height = sum(w.boundingRect().height() for w in self._widgets.values())

For QGraphicsProxyWidget, boundingRect() is derived from the embedded widget's sizeHint(). Widgets like QPlainTextEdit with QSizePolicy::Expanding have a sizeHint() that depends on content and available layout space — but inside a proxy there's no parent layout to constrain them, so the value can jump around unpredictably.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions