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

1"""Pointer / tablet / keyboard event handlers for :class:`PaintCanvas`. 

2 

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 

17 

18log = get_logger(__name__) 

19 

20 

21class InputEventMixin(CanvasMembers): 

22 _EYEDROPPER_PREVIEW_INTERVAL_NS = 33_000_000 

23 

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 

44 

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) 

54 

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) 

64 

65 def leaveEvent(self, event): 

66 self._brush_cursor_inside = False 

67 self.update_tool_cursor() 

68 self.update() 

69 super().leaveEvent(event) 

70 

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() 

85 

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 

105 

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) 

138 

139 def keyReleaseEvent(self,e): 

140 super().keyReleaseEvent(e) 

141 

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 

163 

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 

178 

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 

186 

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() 

279 

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()) 

326 

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 

334 

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 

339 

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)) 

401 

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) 

410 

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 

422 

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 

435 

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 

446 

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 

459 

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() 

528 

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() 

536 

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 

563 

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()