Coverage for paintmaskanimator/color_panel_mask.py: 17%
119 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"""Mask-colour checkboxes for the used-color panel.
3Split out of ``color_panel.py`` as a mixin. A masked colour is one the transform
4tools treat as protected; these methods toggle that state for one colour or all
5of them, run the press-and-sweep drag that toggles a run of checkboxes in one
6gesture, and provide the mask context menu.
7"""
8from PySide6.QtWidgets import QMenu
9from .i18n import tr
10from ._color_panel_members import UsedColorPanelMembers
11from .color_panel_widgets import CheckClickArea
12from .logging_setup import get_logger
14log = get_logger(__name__)
17class MaskColorsMixin(UsedColorPanelMembers):
18 def all_masks_enabled(self):
19 keys = set(self.mask_checks)
20 return bool(keys) and keys.issubset(self.mask_rgbs)
22 def _emit_mask_state(self):
23 self._mask_all_mode = self.all_masks_enabled()
24 self.maskColorsChanged.emit(set(self.mask_rgbs))
26 def _set_mask_color(self, rgb, checked):
27 if self._mask_sweep_active:
28 if checked:
29 self.mask_rgbs.add(rgb)
30 else:
31 self.mask_rgbs.discard(rgb)
32 self._emit_mask_state()
33 return
34 with self._history_edit(tr("マスクの切り替え")):
35 if checked:
36 self.mask_rgbs.add(rgb)
37 else:
38 self.mask_rgbs.discard(rgb)
39 self._emit_mask_state()
41 def _set_mask_checkbox_without_signal(self, rgb, checked):
42 checkbox = self.mask_checks.get(rgb)
43 if checkbox is not None and checkbox.isChecked() != bool(checked): 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true
44 checkbox.blockSignals(True)
45 checkbox.setChecked(bool(checked))
46 checkbox.blockSignals(False)
48 def _set_all_masks_on(self):
49 with self._history_edit(tr("マスクを全体ON")):
50 self.mask_rgbs = set(self.mask_checks)
51 for rgb in self.mask_checks:
52 self._set_mask_checkbox_without_signal(rgb, True)
53 self._emit_mask_state()
55 def _clear_mask_colors(self):
56 """Compatibility helper: turn every mask OFF."""
57 with self._history_edit(tr("マスクを全体OFF")):
58 self.mask_rgbs.clear()
59 for rgb in self.mask_checks:
60 self._set_mask_checkbox_without_signal(rgb, False)
61 self._emit_mask_state()
63 def _isolate_mask_color(self, rgb):
64 """Alt+click: enable only the clicked mask."""
65 with self._history_edit(tr("この色だけマスクON")):
66 self.mask_rgbs = {rgb}
67 for key in self.mask_checks:
68 self._set_mask_checkbox_without_signal(key, key == rgb)
69 self._emit_mask_state()
71 def _disable_other_masks(self, rgb):
72 """右クリック対象だけをONにし、それ以外のマスクをOFFにする。"""
73 with self._history_edit(tr("対象以外のマスクOFF")):
74 self.mask_rgbs = {rgb}
75 for key in self.mask_checks:
76 self._set_mask_checkbox_without_signal(key, key == rgb)
77 self._emit_mask_state()
79 def _set_single_mask_state(self, rgb, enabled):
80 with self._history_edit(tr("マスクの切り替え")):
81 self._set_mask_checkbox_without_signal(rgb, enabled)
82 if enabled:
83 self.mask_rgbs.add(rgb)
84 else:
85 self.mask_rgbs.discard(rgb)
86 self._emit_mask_state()
88 def _begin_mask_sweep(self, source_rgb, checked):
89 self._sweep_history_before = self.capture_history_state()
90 self._mask_sweep_active = True
91 self._mask_sweep_state = bool(checked)
92 self._mask_sweep_touched = {source_rgb}
93 old = source_rgb in self.mask_rgbs
94 self._set_mask_checkbox_without_signal(source_rgb, checked)
95 if checked:
96 self.mask_rgbs.add(source_rgb)
97 else:
98 self.mask_rgbs.discard(source_rgb)
99 self._mask_sweep_changed = old != bool(checked)
101 def _mask_checkbox_rgb_at_global(self, global_position):
102 for rgb, checkbox in self.mask_checks.items():
103 area = checkbox.parentWidget()
104 target = area if isinstance(area, CheckClickArea) else checkbox
105 if target.rect().contains(target.mapFromGlobal(global_position)):
106 return rgb
107 return None
109 def _move_mask_sweep(self, global_position):
110 if not self._mask_sweep_active:
111 return
112 rgb = self._mask_checkbox_rgb_at_global(global_position)
113 if rgb is None or rgb in self._mask_sweep_touched:
114 return
115 self._mask_sweep_touched.add(rgb)
116 old = rgb in self.mask_rgbs
117 self._set_mask_checkbox_without_signal(rgb, self._mask_sweep_state)
118 if self._mask_sweep_state:
119 self.mask_rgbs.add(rgb)
120 else:
121 self.mask_rgbs.discard(rgb)
122 self._mask_sweep_changed |= old != self._mask_sweep_state
124 def _end_mask_sweep(self):
125 if not self._mask_sweep_active:
126 return
127 self._mask_sweep_active = False
128 if self._mask_sweep_changed:
129 self._emit_mask_state()
130 self._record_history(tr("マスクの一括切り替え"), self._sweep_history_before)
131 self._sweep_history_before = None
132 self._mask_sweep_touched.clear()
133 self._mask_sweep_changed = False
135 def _show_mask_context_menu(self, rgb, global_position):
136 menu = QMenu(self)
137 action_on = menu.addAction("ON")
138 action_off = menu.addAction("OFF")
139 menu.addSeparator()
140 action_others = menu.addAction(tr("対象以外をOFF"))
141 action_all = menu.addAction(tr("全表示"))
142 chosen = menu.exec(global_position)
143 if chosen is action_on:
144 self._set_single_mask_state(rgb, True)
145 elif chosen is action_off:
146 self._set_single_mask_state(rgb, False)
147 elif chosen is action_others:
148 self._disable_other_masks(rgb)
149 elif chosen is action_all:
150 self._set_all_masks_on()