Coverage for paintmaskanimator/canvas_onion_interaction.py: 11%

137 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 04:50 +0000

1"""Onion-skin drag interactions for :class:`PaintCanvas`. 

2 

3Split out of ``canvas.py`` as a mixin. These methods drive the interactive 

4drag editing of onion-skin shift/rotation/scale and canvas view rotation 

5(started from the onion settings browser). They run against a live 

6``PaintCanvas`` instance and reuse its geometry helpers and onion state. 

7""" 

8import math 

9from PySide6.QtCore import QPointF, Qt 

10from .i18n import tr 

11from ._canvas_members import CanvasMembers 

12from .logging_setup import get_logger 

13 

14log = get_logger(__name__) 

15 

16 

17class OnionInteractionMixin(CanvasMembers): 

18 """Interactive drag editing of onion-skin transforms.""" 

19 

20 def begin_onion_transform_interaction(self, direction): 

21 """旧UI互換:前/後オニオンをXY移動モードにする。""" 

22 self.begin_onion_shift_interaction(direction, "xy") 

23 

24 def begin_onion_shift_interaction(self, direction, axis="x"): 

25 """前/後オニオンのXYシフトをキャンバス上で調整する。""" 

26 axis = str(axis).lower() 

27 if axis not in ("x", "y", "xy"): 

28 axis = "xy" 

29 self._onion_interaction_mode = "onion_shift" 

30 self._onion_interaction_direction = ( 

31 -1 if int(direction) < 0 else 1 

32 ) 

33 self._onion_interaction_axis = axis 

34 self._onion_interaction_operation = "move" 

35 self._onion_interaction_dragging = False 

36 self.setFocus(Qt.FocusReason.OtherFocusReason) 

37 self.update_tool_cursor() 

38 label = ( 

39 tr("前") if self._onion_interaction_direction < 0 else tr("後") 

40 ) 

41 axis_label = ( 

42 tr("X方向") if axis == "x" 

43 else tr("Y方向") if axis == "y" 

44 else tr("XY方向") 

45 ) 

46 self.status_message.emit( 

47 tr("{label}のオニオンスキン:ドラッグで{label2}へ移動します。Escで解除します。").format(label=label, label2=axis_label) 

48 ) 

49 

50 def begin_onion_canvas_position_interaction(self): 

51 """キャンバスの移動と回転を行い、前後の相対値を補正する。""" 

52 self._onion_interaction_mode = "canvas" 

53 self._onion_interaction_direction = 0 

54 self._onion_interaction_axis = None 

55 self._onion_interaction_operation = None 

56 self._onion_interaction_dragging = False 

57 self.setFocus(Qt.FocusReason.OtherFocusReason) 

58 self.update_tool_cursor() 

59 self.status_message.emit( 

60 tr("表示位置:左ドラッグでキャンバス移動、" 

61 "Shift+左ドラッグまたは右ドラッグで回転します。" 

62 "TU/TB拡大率は維持され、前後オニオンの相対値へ" 

63 "反映されます。Escで解除します。") 

64 ) 

65 

66 def set_onion_canvas_view_rotation(self, value): 

67 """表示位置の回転UIからキャンバス角度を変更する。""" 

68 target = self._normalized_angle(value) 

69 current = self._normalized_angle(self.rotation) 

70 delta = self._normalized_angle(target - current) 

71 if abs(delta) < 1e-9: 

72 return 

73 

74 # キャンバス回転分を前後の相対値から差し引き、 

75 # オニオンの画面上の位置・角度を保つ。 

76 previous_shift = self._rotated_vector( 

77 self.onion_previous_shift_x, 

78 self.onion_previous_shift_y, 

79 -delta, 

80 ) 

81 next_shift = self._rotated_vector( 

82 self.onion_next_shift_x, 

83 self.onion_next_shift_y, 

84 -delta, 

85 ) 

86 self.onion_previous_shift_x = previous_shift.x() 

87 self.onion_previous_shift_y = previous_shift.y() 

88 self.onion_next_shift_x = next_shift.x() 

89 self.onion_next_shift_y = next_shift.y() 

90 self.onion_previous_rotation = self._normalized_angle( 

91 self.onion_previous_rotation - delta 

92 ) 

93 self.onion_next_rotation = self._normalized_angle( 

94 self.onion_next_rotation - delta 

95 ) 

96 self.rotation = target 

97 self.viewChanged.emit( 

98 float(self.zoom), 

99 float(self.rotation), 

100 ) 

101 self.onionInteractionChanged.emit() 

102 self.update() 

103 

104 def cancel_onion_interaction(self, restore=False): 

105 if self._onion_interaction_mode is None: 105 ↛ 107line 105 didn't jump to line 107 because the condition on line 105 was always true

106 return 

107 if restore and self._onion_interaction_dragging: 

108 self.pan = QPointF(self._onion_interaction_start_pan) 

109 self.rotation = float( 

110 self._onion_interaction_start_view_rotation 

111 ) 

112 ( 

113 self.onion_previous_shift_x, 

114 self.onion_previous_shift_y, 

115 ) = self._onion_interaction_start_previous 

116 ( 

117 self.onion_next_shift_x, 

118 self.onion_next_shift_y, 

119 ) = self._onion_interaction_start_next 

120 self.onion_previous_rotation = float( 

121 self._onion_interaction_start_previous_rotation 

122 ) 

123 self.onion_next_rotation = float( 

124 self._onion_interaction_start_next_rotation 

125 ) 

126 self.onion_previous_scale = float( 

127 self._onion_interaction_start_previous_scale 

128 ) 

129 self.onion_next_scale = float( 

130 self._onion_interaction_start_next_scale 

131 ) 

132 self.onion_tu_tb_scale = float( 

133 self._onion_interaction_start_tu_tb_scale 

134 ) 

135 self.onionInteractionChanged.emit() 

136 self.viewChanged.emit( 

137 float(self.zoom), 

138 float(self.rotation), 

139 ) 

140 self._onion_interaction_mode = None 

141 self._onion_interaction_axis = None 

142 self._onion_interaction_operation = None 

143 self._onion_interaction_dragging = False 

144 self.drawing = False 

145 self.update_tool_cursor() 

146 self.update() 

147 self.onionInteractionFinished.emit() 

148 

149 def _onion_widget_delta_to_canvas(self, delta): 

150 zoom = max( 

151 0.0001, 

152 float(self.zoom) * float(self.onion_tu_tb_scale), 

153 ) 

154 dx = float(delta.x()) 

155 dy = float(delta.y()) 

156 angle = math.radians(float(self.rotation)) 

157 canvas_dx = ( 

158 dx * math.cos(angle) + dy * math.sin(angle) 

159 ) / zoom 

160 canvas_dy = ( 

161 -dx * math.sin(angle) + dy * math.cos(angle) 

162 ) / zoom 

163 return QPointF(canvas_dx, canvas_dy) 

164 

165 def _begin_onion_interaction_drag( 

166 self, 

167 widget_position, 

168 button=Qt.MouseButton.LeftButton, 

169 modifiers=Qt.KeyboardModifier.NoModifier, 

170 ): 

171 self._onion_interaction_dragging = True 

172 self._onion_interaction_start_widget = QPointF( 

173 widget_position 

174 ) 

175 self._onion_interaction_start_pan = QPointF(self.pan) 

176 self._onion_interaction_start_view_rotation = float( 

177 self.rotation 

178 ) 

179 self._onion_interaction_start_previous = ( 

180 float(self.onion_previous_shift_x), 

181 float(self.onion_previous_shift_y), 

182 ) 

183 self._onion_interaction_start_next = ( 

184 float(self.onion_next_shift_x), 

185 float(self.onion_next_shift_y), 

186 ) 

187 self._onion_interaction_start_previous_rotation = float( 

188 self.onion_previous_rotation 

189 ) 

190 self._onion_interaction_start_next_rotation = float( 

191 self.onion_next_rotation 

192 ) 

193 self._onion_interaction_start_previous_scale = float( 

194 self.onion_previous_scale 

195 ) 

196 self._onion_interaction_start_next_scale = float( 

197 self.onion_next_scale 

198 ) 

199 self._onion_interaction_start_tu_tb_scale = float( 

200 self.onion_tu_tb_scale 

201 ) 

202 if self._onion_interaction_mode == "onion_shift": 

203 self._onion_interaction_operation = "move" 

204 else: 

205 self._onion_interaction_operation = ( 

206 "rotate" 

207 if ( 

208 button == Qt.MouseButton.RightButton 

209 or modifiers 

210 & Qt.KeyboardModifier.ShiftModifier 

211 ) 

212 else "move" 

213 ) 

214 self.drawing = True 

215 self.update_tool_cursor() 

216 

217 def _update_onion_interaction_drag(self, widget_position): 

218 if not self._onion_interaction_dragging: 

219 return 

220 widget_delta = ( 

221 QPointF(widget_position) 

222 - self._onion_interaction_start_widget 

223 ) 

224 canvas_delta = self._onion_widget_delta_to_canvas( 

225 widget_delta 

226 ) 

227 

228 previous_x, previous_y = ( 

229 self._onion_interaction_start_previous 

230 ) 

231 next_x, next_y = self._onion_interaction_start_next 

232 operation = self._onion_interaction_operation or "move" 

233 

234 if self._onion_interaction_mode == "onion_shift": 

235 direction = self._onion_interaction_direction 

236 axis = self._onion_interaction_axis or "x" 

237 delta_x = ( 

238 canvas_delta.x() if axis in ("x", "xy") else 0.0 

239 ) 

240 delta_y = ( 

241 canvas_delta.y() if axis in ("y", "xy") else 0.0 

242 ) 

243 if direction < 0: 

244 self.onion_previous_shift_x = ( 

245 previous_x + delta_x 

246 ) 

247 self.onion_previous_shift_y = ( 

248 previous_y + delta_y 

249 ) 

250 else: 

251 self.onion_next_shift_x = next_x + delta_x 

252 self.onion_next_shift_y = next_y + delta_y 

253 

254 elif self._onion_interaction_mode == "canvas": 

255 if operation == "rotate": 

256 angle_delta = float(widget_delta.x()) * 0.35 

257 self.rotation = self._normalized_angle( 

258 self._onion_interaction_start_view_rotation 

259 + angle_delta 

260 ) 

261 

262 # キャンバスの回転分をシフト座標とオニオン回転から 

263 # 差し引き、オニオンスキンの画面上の位置・角度を保つ。 

264 previous_shift = self._rotated_vector( 

265 previous_x, 

266 previous_y, 

267 -angle_delta, 

268 ) 

269 next_shift = self._rotated_vector( 

270 next_x, 

271 next_y, 

272 -angle_delta, 

273 ) 

274 self.onion_previous_shift_x = previous_shift.x() 

275 self.onion_previous_shift_y = previous_shift.y() 

276 self.onion_next_shift_x = next_shift.x() 

277 self.onion_next_shift_y = next_shift.y() 

278 self.onion_previous_rotation = ( 

279 self._normalized_angle( 

280 self._onion_interaction_start_previous_rotation 

281 - angle_delta 

282 ) 

283 ) 

284 self.onion_next_rotation = ( 

285 self._normalized_angle( 

286 self._onion_interaction_start_next_rotation 

287 - angle_delta 

288 ) 

289 ) 

290 self.viewChanged.emit( 

291 float(self.zoom), 

292 float(self.rotation), 

293 ) 

294 else: 

295 # キャンバスは画面上でドラッグし、前後のオニオンは 

296 # 画面上の位置を保つよう逆向きにシフト値を補正する。 

297 self.pan = ( 

298 self._onion_interaction_start_pan 

299 + widget_delta 

300 ) 

301 self.onion_previous_shift_x = ( 

302 previous_x - canvas_delta.x() 

303 ) 

304 self.onion_previous_shift_y = ( 

305 previous_y - canvas_delta.y() 

306 ) 

307 self.onion_next_shift_x = ( 

308 next_x - canvas_delta.x() 

309 ) 

310 self.onion_next_shift_y = ( 

311 next_y - canvas_delta.y() 

312 ) 

313 self.viewChanged.emit( 

314 float(self.zoom), 

315 float(self.rotation), 

316 ) 

317 

318 self.onionInteractionChanged.emit() 

319 self.update()