Skip to content

Feature/brush tool#1728

Open
kancheng wants to merge 21 commits intowkentaro:mainfrom
kancheng:feature/brush-tool
Open

Feature/brush tool#1728
kancheng wants to merge 21 commits intowkentaro:mainfrom
kancheng:feature/brush-tool

Conversation

@kancheng
Copy link
Contributor

@kancheng kancheng commented Jan 5, 2026

This PR focuses only on the brush drawing tool:

  • Add brush size control
  • Add a cancel/delete action for brush drawing
  • Fix a Qt6 crash caused by float arguments passed to QPainter.drawEllipse
  • Update brush button positions

No localization or unrelated changes are included.

@kancheng
Copy link
Contributor Author

kancheng commented Jan 5, 2026

demo details
螢幕快照 2026-01-06 07-05-15

@kancheng
Copy link
Contributor Author

kancheng commented Jan 6, 2026

如需詳細演示,請參閱:#1726

@kancheng kancheng force-pushed the feature/brush-tool branch from 2f3cb48 to ccd96b0 Compare January 7, 2026 21:57
@kancheng
Copy link
Contributor Author

kancheng commented Jan 7, 2026

@wkentaro

I wanted to briefly explain the current CI status. At the moment, the only failing check is:

ci / check (pull_request)

In this PR, the only functional changes I made are in canvas.py (related to the drawing/brush functionality). The other CI jobs (builds on macOS, Ubuntu, and Windows) are all passing successfully, so there are no runtime or cross-platform issues.

I’ve just updated my branch to be fully in sync with the latest upstream state to avoid any drift. Based on my local testing and the successful build checks, the feature itself should be stable and mature at this point.

@kirillmeisser
Copy link

Hi, @kancheng thanks for your work! Me and my company would also be very interested in this feature. What is holding it back from being merged? Or do you know why the ci / check job is failing?

Copy link
Contributor

@minewilliam minewilliam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not had time to test/review the functionality yet. This is more of my impression of your PR at a glance.

  1. I think a mistake was made somewhere in a merge with main, 2 months ago. Please review your PR and make sure it does not incur a regression. Ex: type hints were added to the parameters of event handlers.
  2. You said you did not add translation in this PR, but you have 2 translation files commited?

PS: I apologize for the number of identical comments in my review.

Comment on lines +19 to +23
def keyPressEvent(self, e):
if e.key() in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Down]:
self.list_widget.keyPressEvent(e)
else:
super().keyPressEvent(a0)
super().keyPressEvent(e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +10 to +12
def keyPressEvent(self, event):
super().keyPressEvent(event)
if event.key() == Qt.Key_Escape:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if stripped_s := s.strip():
logger.debug(stripped_s)
return len(s)
def write(self, message: str) -> int:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description: str
group_id: int | None
mask: NDArray[np.bool] | None
mask: NDArray[np.bool_] | None
Copy link
Contributor

@minewilliam minewilliam Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be reverted.
np.bool_ is an alias for np.bool: The latter being preferable.
dict was replaced by ShapeDict in this commit: 0e15b8c#diff-9e7397e710163a6540ac179927130394d44dfd90e0fcb81af6c8efa9a1b88e18R110

Comment on lines +892 to +894
self._brushSizeSlider.valueChanged.connect(
lambda value: self._on_brush_size_changed(value)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self._brushSizeSlider.valueChanged.connect(
lambda value: self._on_brush_size_changed(value)
)
self._brushSizeSlider.valueChanged.connect(self._on_brush_size_changed)

return len(self.current) >= 3

def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None:
def mouseDoubleClickEvent(self, ev):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change.

Comment on lines +713 to +721
def mouseReleaseEvent(self, ev):
if ev.button() == Qt.RightButton:
menu = self.menus[len(self.selectedShapesCopy) > 0]
self.restoreCursor()
if not menu.exec_(self.mapToGlobal(a0.pos())) and self.selectedShapesCopy: # type: ignore
if not menu.exec_(self.mapToGlobal(ev.pos())) and self.selectedShapesCopy:
# Cancel the move by deleting the shadow copy.
self.selectedShapesCopy = []
self.repaint()
elif a0.button() == Qt.LeftButton:
elif ev.button() == Qt.LeftButton:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change.

Comment on lines +580 to +585
def mousePressEvent(self, ev):
pos: QPointF = self.transformPos(ev.localPos())

is_shift_pressed = a0.modifiers() & Qt.ShiftModifier
is_shift_pressed = ev.modifiers() & Qt.ShiftModifier

if a0.button() == Qt.LeftButton:
if ev.button() == Qt.LeftButton:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change.

Comment on lines +378 to +389
def mouseMoveEvent(self, ev):
"""Update line with last point and current coordinates."""
try:
pos = self.transformPos(a0.localPos())
pos = self.transformPos(ev.localPos())
except AttributeError:
return

self.mouseMoved.emit(pos)

self.prevMovePoint = pos

is_shift_pressed = a0.modifiers() & Qt.ShiftModifier
is_shift_pressed = ev.modifiers() & Qt.ShiftModifier
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change.

Comment on lines +268 to 279
def enterEvent(self, ev):
self.overrideCursor(self._cursor)
self._update_status()

def leaveEvent(self, a0: QtCore.QEvent) -> None:
def leaveEvent(self, ev):
self.unHighlight()
self.restoreCursor()
self._update_status()

def focusOutEvent(self, a0: QtGui.QFocusEvent) -> None:
def focusOutEvent(self, ev):
self.restoreCursor()
self._update_status()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

4 participants