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
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 04:50 +0000
1"""Onion-skin drag interactions for :class:`PaintCanvas`.
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
14log = get_logger(__name__)
17class OnionInteractionMixin(CanvasMembers):
18 """Interactive drag editing of onion-skin transforms."""
20 def begin_onion_transform_interaction(self, direction):
21 """旧UI互換:前/後オニオンをXY移動モードにする。"""
22 self.begin_onion_shift_interaction(direction, "xy")
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 )
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 )
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
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()
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()
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)
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()
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 )
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"
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
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 )
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 )
318 self.onionInteractionChanged.emit()
319 self.update()