I have this Ruler class
Right now the scale looks like the below image. I want the ticks to have their roots towards the view instead of having them outwards. The base of the scale should be inside.
I am not able to get the roots to the scales inner border(the black line).
Here is my code for the class:
class Ruler(QWidget):
def __init__(self, orientation=Qt.Orientation.Horizontal, parent=None):
super().__init__(parent)
self._orientation = orientation
self._origin = 0.0
self._unit = 1.0
self._zoom = 1.0
self._mouse_tracking = False
self._draw_text = False
self._cursor_pos = QPoint()
self.setMouseTracking(True)
f = QFont("Courier", 9)
f.setStyleHint(QFont.StyleHint.TypeWriter)
self.setFont(f)
# Ensure room for labels inside the ruler
if orientation == Qt.Orientation.Horizontal:
self.setFixedHeight(RULER_BREADTH + 5)
else:
self.setFixedWidth(RULER_BREADTH + 5)
def minimumSizeHint(self):
return self.sizeHint() # We'll constrain via layout if needed
def sizeHint(self):
if self._orientation == Qt.Orientation.Horizontal:
return super().sizeHint().expandedTo(self.minimumSizeHint()).boundedTo(self.minimumSizeHint())
return super().sizeHint().expandedTo(self.minimumSizeHint()).boundedTo(self.minimumSizeHint())
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHints(
QPainter.RenderHint.Antialiasing |
QPainter.RenderHint.TextAntialiasing
)
# Fill background
painter.fillRect(self.rect(), QColor(236, 233, 216))
# Minor ticks: every 1 mm, length = 1/3 of breadth
self._draw_scale(painter, mm_interval=1,
tick_length=RULER_BREADTH / 3,
draw_labels=False)
# Medium ticks: every 5 mm, length = 2/3 of breadth
self._draw_scale(painter, mm_interval=5,
tick_length=RULER_BREADTH * 2 / 3,
draw_labels=False)
# Major ticks: every 10 mm, full length, with labels
self._draw_scale(painter, mm_interval=10,
tick_length=RULER_BREADTH,
draw_labels=True)
# Draw mouse position indicator
if self._mouse_tracking:
painter.setOpacity(0.4)
# assume _draw_mouse_tick exists
self._draw_mouse_tick(painter)
painter.setOpacity(1.0)
# Draw border line
painter.setPen(QPen(Qt.GlobalColor.black, 2))
if self._orientation == Qt.Orientation.Horizontal:
painter.drawLine(self.rect().bottomLeft(),
self.rect().bottomRight())
else:
painter.drawLine(self.rect().topRight(),
self.rect().bottomRight())
def _draw_scale(self, painter, mm_interval, tick_length, draw_labels=False):
is_horz = (self._orientation == Qt.Orientation.Horizontal)
length = self.width() if is_horz else self.height()
# Compute pixels-per-mm from DPI
screen = QApplication.primaryScreen()
dpi = (screen.logicalDotsPerInchX() if is_horz
else screen.logicalDotsPerInchY())
px_per_mm = dpi / 25.4
step_pix = mm_interval * px_per_mm * self._zoom
# Determine visible tick indices
first_tick = int(self._origin // step_pix) - 1
last_tick = int((self._origin + length) // step_pix) + 1
for tick in range(first_tick, last_tick + 1):
scene_pos = tick * step_pix
screen_pos = scene_pos - self._origin
if screen_pos < 0 or screen_pos > length:
continue
# Draw the tick line
painter.setPen(QPen(Qt.GlobalColor.red, 1))
if is_horz:
painter.drawLine(
QPointF(screen_pos, 0),
QPointF(screen_pos, tick_length)
)
else:
painter.drawLine(
QPointF(0, screen_pos),
QPointF(tick_length, screen_pos)
)
# Draw the label inside the ruler
if draw_labels and (tick % (10 // mm_interval) == 0):
label = str(abs(tick * mm_interval))
painter.setPen(QPen(Qt.GlobalColor.black))
if is_horz:
x = int(screen_pos + 2)
y = int(tick_length - 10)
painter.drawText(x, y, label)
else:
painter.save()
tx = tick_length - 2
ty = screen_pos + 2
painter.translate(tx, ty)
painter.rotate(-90)
painter.drawText(0, 0, label)
painter.restore()
def _draw_mouse_tick(self, painter):
if self._orientation == Qt.Orientation.Horizontal:
x = self._cursor_pos.x()
painter.drawLine(QPointF(x, 0), QPointF(x, self.height()))
else:
y = self._cursor_pos.y()
painter.drawLine(QPointF(0, y), QPointF(self.width(), y))
def mouseMoveEvent(self, event):
self._cursor_pos = event.position().toPoint()
self.update()
super().mouseMoveEvent(event)
def setOrigin(self, val):
self._origin = val
self.update()
def setUnit(self, val):
self._unit = val
self.update()
def setZoom(self, val):
self._zoom = val
self.update()
def setMouseTrack(self, track: bool):
self._mouse_tracking = track
self.update()
@staticmethod
def toMM(orientation: Qt.Orientation) -> float:
screen = QApplication.primaryScreen()
if orientation == Qt.Orientation.Horizontal:
dpi = screen.logicalDotsPerInchX()
else:
dpi = screen.logicalDotsPerInchY()
# 1 inch = 25.4 mm → mm per pixel = 25.4 mm / (dots per inch)
return 25.4 / dpi
Can anyone please help me solve the issue.
If could specify the direction of the ruler's base dynamically that would be better.
Thank you

0andtick_lengthof the line points withself.height()andself.height() - tick_length(orwidth()for the vertical orientation). Note that thesizeHint()implementation will cause a recursion (because you're callingminimumSizeHint(), which you overrode by returning again yoursizeHint()), and thatexpandedTo/boundedTocombination is pointless; besides, QWidget has invalid size hints (both returnQSize(-1, -1)) if no layout is set, so it's up to you to eventually return appropriate sizes.