Coverage for paintmaskanimator/canvas_input_events.py: 68%
460 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
1"""Pointer / tablet / keyboard event handlers for :class:`PaintCanvas`.
3Split out of ``canvas.py`` as a mixin. These are the Qt event overrides that
4turn mouse/tablet/keyboard input into painting, panning, zooming, and tool
5shortcuts. They run against a live ``PaintCanvas`` instance; because the mixin
6precedes ``QWidget`` in the MRO, these overrides win over the base class.
7"""
8import time
9from typing import Any
10from PySide6.QtCore import QPoint, QPointF, QRectF, Qt
11from PySide6.QtGui import QCursor, QPolygonF
12from .brush_raster import stamp_outline
13from .i18n import tr
14from ._canvas_members import CanvasMembers
15from .logging_setup import get_logger
16from .utils import drag_zoom_factor, hide_screen_color_loupe, show_screen_color_loupe
18log = get_logger(__name__)
21class InputEventMixin(CanvasMembers):
22 _EYEDROPPER_PREVIEW_INTERVAL_NS = 33_000_000
24 def _sample_eyedropper_event(self, event, *, force=False):
25 """Update the loupe and sampled colour without overloading mouse moves."""
26 p = self.widget_to_canvas(event.position())
27 if not self.inside(p): 27 ↛ 28line 27 didn't jump to line 28 because the condition on line 27 was never true
28 return False
29 now = time.monotonic_ns()
30 last = getattr(self, "_eyedropper_preview_ns", 0)
31 if not force and now - last < self._EYEDROPPER_PREVIEW_INTERVAL_NS: 31 ↛ 32line 31 didn't jump to line 32 because the condition on line 31 was never true
32 return False
33 self._eyedropper_preview_ns = now
34 show_screen_color_loupe(
35 self,
36 event.globalPosition().toPoint(),
37 self.opaque_paint_color(),
38 )
39 pixel = (int(p.x()), int(p.y()))
40 if force or pixel != getattr(self, "_eyedropper_sample_pixel", None): 40 ↛ 43line 40 didn't jump to line 43 because the condition on line 40 was always true
41 self._eyedropper_sample_pixel = pixel
42 self.sample_color(p, include_canvas_background=True)
43 return True
45 def _finish_eyedropper_event(self, event):
46 """Sample the release point without doing one last expensive screen grab."""
47 p = self.widget_to_canvas(event.position())
48 if not self.inside(p): 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true
49 return
50 pixel = (int(p.x()), int(p.y()))
51 if pixel != getattr(self, "_eyedropper_sample_pixel", None): 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true
52 self._eyedropper_sample_pixel = pixel
53 self.sample_color(p, include_canvas_background=True)
55 def enterEvent(self, event):
56 local = self.mapFromGlobal(QCursor.pos())
57 self._brush_cursor_widget_pos = QPointF(local)
58 self._brush_cursor_inside = self.inside(
59 self.widget_to_canvas(self._brush_cursor_widget_pos)
60 )
61 self.update_tool_cursor()
62 self.update()
63 super().enterEvent(event)
65 def leaveEvent(self, event):
66 self._brush_cursor_inside = False
67 self.update_tool_cursor()
68 self.update()
69 super().leaveEvent(event)
71 def wheelEvent(self,e):
72 delta = e.angleDelta().y()
73 if not delta:
74 # 横スクロールや、macOSトラックパッドの移動量0の開始/終了イベント。
75 e.accept()
76 return
77 # ホイール1ノッチ(120)で1.15倍。トラックパッドは細かいイベントを大量に
78 # 送るので、1イベント1段にすると一瞬で最大倍率まで飛んでしまう。
79 self.set_zoom_around_widget_point(
80 self.zoom * 1.15 ** (delta / 120), e.position()
81 )
82 self.viewChanged.emit(float(self.zoom), float(self.rotation))
83 self.update()
84 e.accept()
86 def keyPressEvent(self,e):
87 if self.skewer_fill_pending() and e.key() == Qt.Key.Key_Escape: 87 ↛ 88line 87 didn't jump to line 88 because the condition on line 87 was never true
88 if e.modifiers() & Qt.KeyboardModifier.ShiftModifier:
89 self.skip_skewer_fill_direction()
90 else:
91 self.skip_skewer_fill_frame()
92 e.accept()
93 return
94 if self.skewer_fill_has_guess() and e.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 self.accept_skewer_guess()
96 e.accept()
97 return
98 if ( 98 ↛ 102line 98 didn't jump to line 102 because the condition on line 98 was never true
99 self._onion_interaction_mode is not None
100 and e.key() == Qt.Key.Key_Escape
101 ):
102 self.cancel_onion_interaction(restore=True)
103 e.accept()
104 return
106 if self.transform_active and e.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true
107 window: Any = self.window()
108 if hasattr(window, "commit_transform_or_tween"):
109 window.tween.commit_transform_or_tween()
110 else:
111 self.commit_selection_transform()
112 e.accept()
113 return
114 if self.transform_active and e.key() == Qt.Key.Key_Escape: 114 ↛ 115line 114 didn't jump to line 115 because the condition on line 114 was never true
115 window: Any = self.window()
116 if hasattr(window, "cancel_transform_or_tween"):
117 window.tween.cancel_transform_or_tween()
118 else:
119 self.cancel_selection_transform()
120 e.accept()
121 return
122 if self.effective_tool() == "line" and self.line_curve_stage: 122 ↛ 137line 122 didn't jump to line 137 because the condition on line 122 was always true
123 if e.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter): 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true
124 self.commit_line_tool()
125 e.accept()
126 return
127 if e.key() == Qt.Key.Key_Escape: 127 ↛ 137line 127 didn't jump to line 137 because the condition on line 127 was always true
128 self.line_start = None
129 self.line_end = None
130 self.line_control = None
131 self.line_curve_stage = 0
132 self._brush_shift_line = False
133 self.drawing = False
134 self.update()
135 e.accept()
136 return
137 super().keyPressEvent(e)
139 def keyReleaseEvent(self,e):
140 super().keyReleaseEvent(e)
142 def mousePressEvent(self,e):
143 self.setFocus()
144 self.last_widget = e.position()
145 self._brush_cursor_widget_pos = QPointF(e.position())
146 self._brush_cursor_inside = self.inside(
147 self.widget_to_canvas(e.position())
148 )
149 self.update_tool_cursor()
150 resize_modifiers = Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier
151 if (e.button() == Qt.MouseButton.LeftButton
152 and e.modifiers() & resize_modifiers == resize_modifiers
153 and self.tool in ("brush", "line") and not self.drawing
154 and self._onion_interaction_mode is None and not self.transform_active):
155 self._brush_resize_anchor = (e.position().x(), float(self.pen_size))
156 e.accept()
157 return
158 if e.button() == Qt.MouseButton.MiddleButton: 158 ↛ 159line 158 didn't jump to line 159 because the condition on line 158 was never true
159 self.middle_hand = True
160 self.drawing = True
161 self.update_tool_cursor()
162 return
164 if ( 164 ↛ 171line 164 didn't jump to line 171 because the condition on line 164 was never true
165 self._onion_interaction_mode is not None
166 and e.button() in (
167 Qt.MouseButton.LeftButton,
168 Qt.MouseButton.RightButton,
169 )
170 ):
171 self._begin_onion_interaction_drag(
172 e.position(),
173 e.button(),
174 e.modifiers(),
175 )
176 e.accept()
177 return
179 if e.button() == Qt.MouseButton.RightButton:
180 if self._sample_eyedropper_event(e, force=True): 180 ↛ 183line 180 didn't jump to line 183 because the condition on line 180 was always true
181 self._right_eyedropper_drag = True
182 e.accept()
183 return
184 if e.button() != Qt.MouseButton.LeftButton: 184 ↛ 185line 184 didn't jump to line 185 because the condition on line 184 was never true
185 return
187 t = self.effective_tool()
188 p = self.widget_to_canvas(e.position())
189 if t == "hand":
190 self.drawing = True
191 self.update_tool_cursor()
192 return
193 if t in ("zoom", "rotate"):
194 self.drawing = True
195 self.last_widget = e.position()
196 # ドラッグ中は押した位置を中心に拡大縮小する。
197 self._zoom_drag_anchor = QPointF(e.position())
198 self.update_tool_cursor()
199 return
200 if self.transform_active:
201 self.drawing = self.begin_transform_drag(p)
202 if self.drawing: 202 ↛ 204line 202 didn't jump to line 204 because the condition on line 202 was always true
203 self.update()
204 return
205 if ( 205 ↛ 209line 205 didn't jump to line 209 because the condition on line 205 was never true
206 not self.inside(p)
207 and t not in ("lasso", "lasso_fill", "rect_select")
208 ):
209 return
210 if t == "eyedropper":
211 self.drawing = True
212 self._sample_eyedropper_event(e, force=True)
213 return
214 if t == "line":
215 if self.line_curve_stage == 2:
216 self.line_control = QPointF(p)
217 self.commit_line_tool()
218 return
219 self.line_start = QPointF(p)
220 self.line_end = QPointF(p)
221 self.line_control = None
222 self.line_curve_stage = 1
223 self.drawing = True
224 self.update()
225 return
226 if t == "shape": 226 ↛ 227line 226 didn't jump to line 227 because the condition on line 226 was never true
227 self.shape_start = QPointF(p)
228 self.shape_end = QPointF(p)
229 self.drawing = True
230 self.update()
231 return
232 if t == "brush":
233 if e.modifiers() & Qt.KeyboardModifier.ShiftModifier:
234 self._begin_brush_straight_line(p)
235 return
236 self._brush_started_with_content = bool(self.active_layer.has_content)
237 self.ensure_editable_key()
238 self._begin_opaque_brush_stroke()
239 self.drawing = True
240 self._reset_brush_stabilizer(p)
241 self._start_brush_follow_timer()
242 self.last_canvas = QPointF(p)
243 self.draw_line(p, p, 1.0)
244 used_color = self.opaque_paint_color()
245 if not self.is_pseudo_transparent_color(used_color): 245 ↛ exitline 245 didn't return from function 'mousePressEvent' because the condition on line 245 was always true
246 self.colorUsed.emit(used_color)
247 elif t in ("bucket", "closed_fill"):
248 # 串刺し塗りは既存のセルにだけ塗る。保持セルを新しいキーに分けない。
249 if self._bucket_all_frames_enabled():
250 self.flood_fill(QPoint(int(p.x()), int(p.y())))
251 else:
252 self.ensure_editable_key()
253 # 押した所を塗り、そのままなぞると通った囲まれた部分も塗る。
254 self.abort_skewer_fill()
255 self.begin_bucket_drag(QPointF(p))
256 self.drawing = True
257 elif t == "auto_select": 257 ↛ 258line 257 didn't jump to line 258 because the condition on line 257 was never true
258 self.auto_select_region(
259 QPoint(int(p.x()), int(p.y())),
260 e.modifiers(),
261 )
262 elif t in ("lasso", "lasso_fill"): 262 ↛ 263line 262 didn't jump to line 263 because the condition on line 262 was never true
263 self.ensure_editable_key()
264 self.drawing = True
265 self.lasso = [p]
266 elif t == "rect_select": 266 ↛ 271line 266 didn't jump to line 271 because the condition on line 266 was always true
267 self.ensure_editable_key()
268 self.drawing = True
269 self.rect_start = p
270 self.rect_end = p
271 elif t == "mesh":
272 self.ensure_editable_key()
273 if not self.mesh_points:
274 self.push_layer_undo()
275 self.init_mesh()
276 self.mesh_active = self.nearest_mesh(p)
277 self.drawing = self.mesh_active >= 0
278 self.update()
280 def mouseMoveEvent(self,e):
281 if self._brush_resize_anchor is not None:
282 anchor_x, size = self._brush_resize_anchor
283 value = max(0.5, min(400.0, round((size + (e.position().x() - anchor_x) * 0.1) * 2) / 2))
284 panel = self._active_tool_panel()
285 if panel is not None:
286 panel.brush_size_spinbox.setValue(value)
287 else:
288 self.set_pen_size(value)
289 self.update()
290 e.accept()
291 return
292 old_cursor = QPointF(self._brush_cursor_widget_pos)
293 self._brush_cursor_widget_pos = QPointF(e.position())
294 self._brush_cursor_inside = self.inside(
295 self.widget_to_canvas(e.position())
296 )
297 self.update_tool_cursor()
298 t = self.effective_tool()
299 if t == "brush":
300 if self.zoom < 1.0:
301 # A scaled QImage repainted through a moving, small clip can
302 # choose a different source-pixel phase on Windows. Repaint
303 # the scaled canvas in one coordinate space so the cursor does
304 # not leave a visibly jittered trail behind it.
305 self.update()
306 else:
307 # Raster snapping can put the outline almost a whole canvas
308 # pixel away from the pointer. Use its actual transformed
309 # bounds so high zoom and rotation cannot leave stale edges.
310 damage = QRectF()
311 for position in (old_cursor, self._brush_cursor_widget_pos):
312 bounds = stamp_outline(
313 self.widget_to_canvas(position),
314 max(0.5, float(self.pen_size)),
315 ).boundingRect()
316 mapped = QPolygonF([
317 self.canvas_to_widget(corner)
318 for corner in (
319 bounds.topLeft(), bounds.topRight(),
320 bounds.bottomRight(), bounds.bottomLeft(),
321 )
322 ]).boundingRect()
323 damage = damage.united(mapped.adjusted(-2, -2, 2, 2))
324 self.update(damage.toAlignedRect())
325 p = self.widget_to_canvas(e.position())
327 if (
328 getattr(self, "_right_eyedropper_drag", False)
329 and e.buttons() & Qt.MouseButton.RightButton
330 ):
331 self._sample_eyedropper_event(e)
332 e.accept()
333 return
335 if self._onion_interaction_mode is not None: 335 ↛ 336line 335 didn't jump to line 336 because the condition on line 335 was never true
336 if self._onion_interaction_dragging:
337 self._update_onion_interaction_drag(e.position())
338 return
340 # 曲線の2段階目はボタンを押していなくても曲率をプレビューする。
341 if t == "line" and self.line_curve_stage == 2 and not self.drawing: 341 ↛ 342line 341 didn't jump to line 342 because the condition on line 341 was never true
342 if self.inside(p):
343 self.line_control = QPointF(p)
344 self.update()
345 return
346 if not self.drawing:
347 return
348 if self.middle_hand or t == "hand": 348 ↛ 349line 348 didn't jump to line 349 because the condition on line 348 was never true
349 delta = e.position() - self.last_widget
350 self.pan += delta
351 self.last_widget = e.position()
352 self.update()
353 return
354 if t == "zoom":
355 delta = e.position().x() - self.last_widget.x()
356 self.set_zoom_around_widget_point(
357 self.zoom * drag_zoom_factor(delta),
358 getattr(self, "_zoom_drag_anchor", None) or e.position(),
359 )
360 self.last_widget = e.position()
361 self.viewChanged.emit(float(self.zoom), float(self.rotation))
362 self.update()
363 return
364 if t == "rotate": 364 ↛ 365line 364 didn't jump to line 365 because the condition on line 364 was never true
365 delta = e.position().x() - self.last_widget.x()
366 self.rotation = ((self.rotation + delta * 0.35 + 180.0) % 360.0) - 180.0
367 self.last_widget = e.position()
368 self.viewChanged.emit(float(self.zoom), float(self.rotation))
369 self.update()
370 return
371 if self.transform_active and self.drawing:
372 self.update_transform_drag(p)
373 return
374 if t == "eyedropper" and self.inside(p): 374 ↛ 375line 374 didn't jump to line 375 because the condition on line 374 was never true
375 self._sample_eyedropper_event(e)
376 return
377 if t == "brush":
378 # キャンバス外の点も捨てずに渡す。捨てると外を通った区間が抜け、
379 # 戻ってきた点と外へ出る前の点が直線で結ばれてしまう。
380 # はみ出した部分は draw_line が画像範囲へクリップする。
381 self._draw_stabilized_brush_to(p, 1.0)
382 elif t == "line" and self.line_curve_stage == 1 and self.inside(p):
383 self.line_end = QPointF(p)
384 self.update()
385 elif t == "shape" and self.inside(p): 385 ↛ 386line 385 didn't jump to line 386 because the condition on line 385 was never true
386 self.shape_end = QPointF(p)
387 self.update()
388 elif t in ("lasso", "lasso_fill"): 388 ↛ 390line 388 didn't jump to line 390 because the condition on line 388 was never true
389 # キャンバス外から開始/通過しても軌跡を保持する。
390 self.lasso.append(QPointF(p))
391 self.update()
392 elif t == "rect_select":
393 # キャンバス外の座標も保持し、確定時に画像領域へクリップする。
394 self.rect_end = QPointF(p)
395 self.update()
396 elif t == "mesh" and self.mesh_active >= 0: 396 ↛ 397line 396 didn't jump to line 397 because the condition on line 396 was never true
397 self.mesh_points[self.mesh_active] = p
398 self.update()
399 elif t in ("bucket", "closed_fill") and self._bucket_drag is not None: 399 ↛ exitline 399 didn't return from function 'mouseMoveEvent' because the condition on line 399 was always true
400 self.continue_bucket_drag(QPointF(p))
402 def mouseDoubleClickEvent(self, e):
403 if e.button() == Qt.MouseButton.LeftButton and self.effective_tool() == "rotate":
404 self.rotation = 0.0
405 self.viewChanged.emit(float(self.zoom), 0.0)
406 self.update()
407 e.accept()
408 return
409 super().mouseDoubleClickEvent(e)
411 def mouseReleaseEvent(self,e):
412 if self._brush_resize_anchor is not None and e.button() == Qt.MouseButton.LeftButton:
413 self.mouseMoveEvent(e)
414 self._brush_resize_anchor = None
415 e.accept()
416 return
417 if e.button() == Qt.MouseButton.MiddleButton: 417 ↛ 418line 417 didn't jump to line 418 because the condition on line 417 was never true
418 self.middle_hand = False
419 self.drawing = False
420 self.update_tool_cursor()
421 return
423 if ( 423 ↛ 430line 423 didn't jump to line 430 because the condition on line 423 was never true
424 self._onion_interaction_mode is not None
425 and e.button() in (
426 Qt.MouseButton.LeftButton,
427 Qt.MouseButton.RightButton,
428 )
429 ):
430 if self._onion_interaction_dragging:
431 self._update_onion_interaction_drag(e.position())
432 self.cancel_onion_interaction(restore=False)
433 e.accept()
434 return
436 if e.button() == Qt.MouseButton.RightButton:
437 if getattr(self, "_right_eyedropper_drag", False): 437 ↛ 439line 437 didn't jump to line 439 because the condition on line 437 was always true
438 self._finish_eyedropper_event(e)
439 self._right_eyedropper_drag = False
440 self._eyedropper_sample_pixel = None
441 hide_screen_color_loupe(self)
442 e.accept()
443 return
444 if e.button() != Qt.MouseButton.LeftButton: 444 ↛ 445line 444 didn't jump to line 445 because the condition on line 444 was never true
445 return
447 t = self.effective_tool()
448 # ドラッグ塗りは、途中で Space などに切り替わっても離したときに閉じる。
449 self.finish_bucket_drag()
450 if t == "eyedropper":
451 self._finish_eyedropper_event(e)
452 self._eyedropper_sample_pixel = None
453 hide_screen_color_loupe(self)
454 if self.transform_active and self.drawing:
455 self.end_transform_drag()
456 self.drawing = False
457 self.update()
458 return
460 if t == "brush":
461 raw_release = self.widget_to_canvas(e.position())
462 if self.drawing and self.last_canvas is not None:
463 self._finish_stabilized_brush(
464 raw_release,
465 1.0,
466 )
467 self._brush_line_anchor = (
468 (self.current_frame, self.active_layer_index), QPointF(raw_release),
469 )
470 self._stop_brush_follow_timer()
471 self._finish_opaque_brush_stroke()
472 self._reset_brush_stabilizer()
473 self._cell_structure_dirty = (
474 not self._brush_started_with_content
475 and bool(self.active_layer.has_content)
476 )
477 self.cellChanged.emit(self.current_frame, self.active_layer_index)
478 elif t == "line" and self.line_curve_stage == 1:
479 self.drawing = False
480 self.line_end = self.widget_to_canvas(e.position())
481 panel = self._active_tool_panel()
482 if (panel is not None and panel.line_type.currentData() == "curve"
483 and not getattr(self, "_brush_shift_line", False)):
484 self.line_curve_stage = 2
485 self.line_control = QPointF(
486 (self.line_start.x() + self.line_end.x()) / 2.0,
487 (self.line_start.y() + self.line_end.y()) / 2.0,
488 )
489 self.status_message.emit(
490 tr("マウスを動かしてカーブを調整し、クリックで確定します。Escで取消。")
491 )
492 self.update()
493 return
494 self.commit_line_tool()
495 return
496 elif t == "shape": 496 ↛ 497line 496 didn't jump to line 497 because the condition on line 496 was never true
497 self.commit_shape_tool()
498 return
499 elif t == "lasso" and len(self.lasso) >= 3: 499 ↛ 500line 499 didn't jump to line 500 because the condition on line 499 was never true
500 self.selection_polygon = list(self.lasso)
501 self.selection_mask_override = None
502 self.selection_outline_polygons = []
503 self.selection_mask_rect = None
504 self.lasso = []
505 self.selectionChanged.emit()
506 elif t == "lasso_fill" and len(self.lasso) >= 3: 506 ↛ 507line 506 didn't jump to line 507 because the condition on line 506 was never true
507 polygon = list(self.lasso)
508 self.lasso = []
509 self.fill_lasso_polygon(polygon)
510 elif t == "rect_select" and self.rect_start is not None and self.rect_end is not None:
511 x1, x2 = sorted((self.rect_start.x(), self.rect_end.x()))
512 y1, y2 = sorted((self.rect_start.y(), self.rect_end.y()))
513 self.selection_polygon = [
514 QPointF(x1, y1), QPointF(x2, y1),
515 QPointF(x2, y2), QPointF(x1, y2),
516 ]
517 self.selection_mask_override = None
518 self.selection_outline_polygons = []
519 self.selection_mask_rect = None
520 self.rect_start = None
521 self.rect_end = None
522 self.selectionChanged.emit()
523 elif t == "mesh" and self.mesh_active >= 0: 523 ↛ 524line 523 didn't jump to line 524 because the condition on line 523 was never true
524 self.mesh_active = -1
525 self.drawing = False
526 self.update_tool_cursor()
527 self.update()
529 def tabletEvent(self,e):
530 self._brush_cursor_widget_pos = QPointF(e.position())
531 self._brush_cursor_inside = self.inside(
532 self.widget_to_canvas(e.position())
533 )
534 self.update_tool_cursor()
535 self.update()
537 # オニオン操作中はブラシ等の通常ツールを一切動作させない。
538 # ペンを離した時点で操作を確定し、元のツールへ戻る。
539 if self._onion_interaction_mode is not None: 539 ↛ 540line 539 didn't jump to line 540 because the condition on line 539 was never true
540 event_type = e.type()
541 if event_type == e.Type.TabletPress:
542 tablet_button = (
543 Qt.MouseButton.RightButton
544 if e.buttons() & Qt.MouseButton.RightButton
545 else Qt.MouseButton.LeftButton
546 )
547 self._begin_onion_interaction_drag(
548 e.position(),
549 tablet_button,
550 e.modifiers(),
551 )
552 elif (
553 event_type == e.Type.TabletMove
554 and self._onion_interaction_dragging
555 ):
556 self._update_onion_interaction_drag(e.position())
557 elif event_type == e.Type.TabletRelease:
558 if self._onion_interaction_dragging:
559 self._update_onion_interaction_drag(e.position())
560 self.cancel_onion_interaction(restore=False)
561 e.accept()
562 return
564 if self.tool == "brush" and not self.temp_tool: 564 ↛ 578line 564 didn't jump to line 578 because the condition on line 564 was always true
565 p = self.widget_to_canvas(e.position())
566 if (e.type() == e.Type.TabletPress and self.inside(p)
567 and e.modifiers() & Qt.KeyboardModifier.ShiftModifier):
568 self._begin_brush_straight_line(p)
569 e.accept()
570 return
571 if getattr(self, "_brush_shift_line", False):
572 self.line_end = QPointF(p)
573 if e.type() == e.Type.TabletRelease:
574 self.commit_line_tool()
575 self.update()
576 e.accept()
577 return
578 if self.effective_tool() != "brush":e.ignore();return 578 ↛ exitline 578 didn't return from function 'tabletEvent' because the return on line 578 wasn't executed
579 p=self.widget_to_canvas(e.position()); pressure=max(.001,float(e.pressure()))
580 if e.type()==e.Type.TabletPress and self.inside(p):
581 self._brush_started_with_content = bool(self.active_layer.has_content)
582 self.ensure_editable_key()
583 self._begin_opaque_brush_stroke()
584 self.drawing = True
585 self._reset_brush_stabilizer(p)
586 self._start_brush_follow_timer()
587 self.last_canvas = QPointF(p)
588 pressure = self._smooth_brush_pressure(pressure)
589 self._last_brush_pressure = pressure
590 self.draw_line(p, p, pressure)
591 used_color = self.opaque_paint_color()
592 if not self.is_pseudo_transparent_color(used_color): 592 ↛ 594line 592 didn't jump to line 594 because the condition on line 592 was always true
593 self.colorUsed.emit(used_color)
594 e.accept()
595 elif e.type()==e.Type.TabletMove and self.drawing:
596 # マウスと同じく、キャンバス外の点も描画へ渡してクリップさせる。
597 pressure = self._smooth_brush_pressure(
598 pressure
599 )
600 self._last_brush_pressure = pressure
601 self._draw_stabilized_brush_to(
602 p,
603 pressure,
604 )
605 e.accept()
606 elif e.type()==e.Type.TabletRelease: 606 ↛ 628line 606 didn't jump to line 628 because the condition on line 606 was always true
607 if self.drawing and self.last_canvas is not None: 607 ↛ 618line 607 didn't jump to line 618 because the condition on line 607 was always true
608 self._finish_stabilized_brush(
609 p,
610 max(
611 0.001,
612 float(self._last_brush_pressure),
613 ),
614 )
615 self._brush_line_anchor = (
616 (self.current_frame, self.active_layer_index), QPointF(p),
617 )
618 self._stop_brush_follow_timer()
619 self._finish_opaque_brush_stroke()
620 self._reset_brush_stabilizer()
621 self.drawing=False
622 self._cell_structure_dirty = (
623 not self._brush_started_with_content
624 and bool(self.active_layer.has_content)
625 )
626 self.cellChanged.emit(self.current_frame,self.active_layer_index)
627 e.accept()
628 else:e.ignore()