messageflight 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- message_flight/__init__.py +0 -0
- message_flight/autostart.py +41 -0
- message_flight/config.py +457 -0
- message_flight/demo_notifications.py +12 -0
- message_flight/flight_widget.py +470 -0
- message_flight/i18n.py +370 -0
- message_flight/notification_queue.py +72 -0
- message_flight/notification_worker.py +98 -0
- message_flight/plane_banner.py +359 -0
- message_flight/plane_presets/__init__.py +26 -0
- message_flight/plane_presets/airplane.py +103 -0
- message_flight/plane_presets/base.py +35 -0
- message_flight/plane_presets/bird.py +87 -0
- message_flight/plane_presets/rocket.py +94 -0
- message_flight/plane_presets/ufo.py +73 -0
- message_flight/preset_editor.py +345 -0
- message_flight/settings_dialog.py +266 -0
- message_flight/tray_app.py +280 -0
- message_flight/tts.py +298 -0
- message_flight/tts_manager.py +98 -0
- messageflight-0.2.4.dist-info/METADATA +94 -0
- messageflight-0.2.4.dist-info/RECORD +24 -0
- messageflight-0.2.4.dist-info/WHEEL +4 -0
- messageflight-0.2.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from PyQt6.QtCore import Qt
|
|
6
|
+
from PyQt6.QtGui import QColor, QPainter, QPainterPath
|
|
7
|
+
|
|
8
|
+
from .base import ParamDef, PlanePreset
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class RocketParameters:
|
|
13
|
+
body_length: int = 60
|
|
14
|
+
body_width: int = 16
|
|
15
|
+
nose_length: int = 20
|
|
16
|
+
fin_size: int = 12
|
|
17
|
+
flame_length: int = 15
|
|
18
|
+
body_color: str = "#C0C0C0"
|
|
19
|
+
nose_color: str = "#FF4500"
|
|
20
|
+
fin_color: str = "#8B0000"
|
|
21
|
+
flame_color: str = "#FFA500"
|
|
22
|
+
flame_intensity: float = 1.0
|
|
23
|
+
banner_color: str = "#FF6347"
|
|
24
|
+
text_color: str = "#FFFFFF"
|
|
25
|
+
rotation: float = 0.0
|
|
26
|
+
banner_attach_x: int = -12
|
|
27
|
+
banner_attach_y: int = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RocketPreset(PlanePreset):
|
|
31
|
+
name = "火箭"
|
|
32
|
+
icon = "🚀"
|
|
33
|
+
|
|
34
|
+
def draw(self, painter: QPainter, params: RocketParameters) -> None:
|
|
35
|
+
bl = params.body_length
|
|
36
|
+
bw = params.body_width
|
|
37
|
+
nl = params.nose_length
|
|
38
|
+
fs = params.fin_size
|
|
39
|
+
fl = params.flame_length
|
|
40
|
+
painter.save()
|
|
41
|
+
# Nose cone (triangle pointing right)
|
|
42
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
43
|
+
painter.setBrush(QColor(params.nose_color))
|
|
44
|
+
nose = QPainterPath()
|
|
45
|
+
nose.moveTo(bl, -bw // 2)
|
|
46
|
+
nose.lineTo(bl + nl, 0)
|
|
47
|
+
nose.lineTo(bl, bw // 2)
|
|
48
|
+
nose.closeSubpath()
|
|
49
|
+
painter.drawPath(nose)
|
|
50
|
+
# Body
|
|
51
|
+
painter.setBrush(QColor(params.body_color))
|
|
52
|
+
painter.drawRect(0, -bw // 2, bl, bw)
|
|
53
|
+
# Fins (two triangles at left end)
|
|
54
|
+
painter.setBrush(QColor(params.fin_color))
|
|
55
|
+
fin_top = QPainterPath()
|
|
56
|
+
fin_top.moveTo(0, -bw // 2)
|
|
57
|
+
fin_top.lineTo(-fs, -bw // 2 - fs)
|
|
58
|
+
fin_top.lineTo(0, -bw // 2 + fs // 2)
|
|
59
|
+
fin_top.closeSubpath()
|
|
60
|
+
painter.drawPath(fin_top)
|
|
61
|
+
fin_bot = QPainterPath()
|
|
62
|
+
fin_bot.moveTo(0, bw // 2)
|
|
63
|
+
fin_bot.lineTo(-fs, bw // 2 + fs)
|
|
64
|
+
fin_bot.lineTo(0, bw // 2 - fs // 2)
|
|
65
|
+
fin_bot.closeSubpath()
|
|
66
|
+
painter.drawPath(fin_bot)
|
|
67
|
+
# Flame
|
|
68
|
+
fl_actual = int(fl * params.flame_intensity)
|
|
69
|
+
if fl_actual > 0:
|
|
70
|
+
painter.setBrush(QColor(params.flame_color))
|
|
71
|
+
painter.drawEllipse(-fl_actual, -bw // 4, fl_actual, bw // 2)
|
|
72
|
+
painter.restore()
|
|
73
|
+
|
|
74
|
+
def get_parameters(self) -> list[ParamDef]:
|
|
75
|
+
return [
|
|
76
|
+
ParamDef("body_length", "机身长度", "int", 60, 30, 100),
|
|
77
|
+
ParamDef("body_width", "机身宽度", "int", 16, 8, 32),
|
|
78
|
+
ParamDef("nose_length", "机头长度", "int", 20, 10, 50),
|
|
79
|
+
ParamDef("fin_size", "尾翼大小", "int", 12, 4, 24),
|
|
80
|
+
ParamDef("flame_length", "火焰长度", "int", 15, 5, 40),
|
|
81
|
+
ParamDef("body_color", "机身颜色", "color", "#C0C0C0"),
|
|
82
|
+
ParamDef("nose_color", "机头颜色", "color", "#FF4500"),
|
|
83
|
+
ParamDef("fin_color", "尾翼颜色", "color", "#8B0000"),
|
|
84
|
+
ParamDef("flame_color", "火焰颜色", "color", "#FFA500"),
|
|
85
|
+
ParamDef("flame_intensity", "火焰强度", "float", 1.0, 0.0, 2.0, 0.1),
|
|
86
|
+
ParamDef("banner_color", "横幅颜色", "color", "#FF6347"),
|
|
87
|
+
ParamDef("rotation", "旋转角度", "float", 0.0, -45.0, 45.0, 1.0),
|
|
88
|
+
ParamDef("text_color", "文字颜色", "color", "#FFFFFF"),
|
|
89
|
+
ParamDef("banner_attach_x", "横幅挂载X", "int", -12, -50, 100),
|
|
90
|
+
ParamDef("banner_attach_y", "横幅挂载Y", "int", 0, -50, 100),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
def get_default_params(self) -> RocketParameters:
|
|
94
|
+
return RocketParameters()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from PyQt6.QtCore import Qt
|
|
6
|
+
from PyQt6.QtGui import QColor, QPainter, QPainterPath
|
|
7
|
+
|
|
8
|
+
from .base import ParamDef, PlanePreset
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class UFOParameters:
|
|
13
|
+
disc_radius: int = 30
|
|
14
|
+
dome_radius: int = 15
|
|
15
|
+
beam_width: int = 20
|
|
16
|
+
beam_length: int = 25
|
|
17
|
+
disc_color: str = "#808080"
|
|
18
|
+
dome_color: str = "#C0C0C0"
|
|
19
|
+
beam_color: str = "#00FF00"
|
|
20
|
+
glow_intensity: float = 0.8
|
|
21
|
+
banner_color: str = "#9370DB"
|
|
22
|
+
text_color: str = "#FFFFFF"
|
|
23
|
+
rotation: float = 0.0
|
|
24
|
+
banner_attach_x: int = -30
|
|
25
|
+
banner_attach_y: int = 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UFOPreset(PlanePreset):
|
|
29
|
+
name = "UFO"
|
|
30
|
+
icon = "🛸"
|
|
31
|
+
|
|
32
|
+
def draw(self, painter: QPainter, params: UFOParameters) -> None:
|
|
33
|
+
dr = params.disc_radius
|
|
34
|
+
dor = params.dome_radius
|
|
35
|
+
bw = params.beam_width
|
|
36
|
+
bl = params.beam_length
|
|
37
|
+
painter.save()
|
|
38
|
+
beam_color = QColor(params.beam_color)
|
|
39
|
+
beam_color.setAlphaF(0.3 * params.glow_intensity)
|
|
40
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
41
|
+
painter.setBrush(beam_color)
|
|
42
|
+
beam = QPainterPath()
|
|
43
|
+
beam.moveTo(-bw // 2, 0)
|
|
44
|
+
beam.lineTo(bw // 2, 0)
|
|
45
|
+
beam.lineTo(bw, bl)
|
|
46
|
+
beam.lineTo(-bw, bl)
|
|
47
|
+
beam.closeSubpath()
|
|
48
|
+
painter.drawPath(beam)
|
|
49
|
+
painter.setBrush(QColor(params.disc_color))
|
|
50
|
+
painter.drawEllipse(-dr, -dr // 2, dr * 2, dr)
|
|
51
|
+
painter.setBrush(QColor(params.dome_color))
|
|
52
|
+
painter.drawEllipse(-dor // 2, -dr - dor // 2, dor, dor)
|
|
53
|
+
painter.restore()
|
|
54
|
+
|
|
55
|
+
def get_parameters(self) -> list[ParamDef]:
|
|
56
|
+
return [
|
|
57
|
+
ParamDef("disc_radius", "圆盘半径", "int", 30, 10, 60),
|
|
58
|
+
ParamDef("dome_radius", "圆顶半径", "int", 15, 5, 30),
|
|
59
|
+
ParamDef("beam_width", "光束宽度", "int", 20, 5, 40),
|
|
60
|
+
ParamDef("beam_length", "光束长度", "int", 25, 5, 50),
|
|
61
|
+
ParamDef("disc_color", "圆盘颜色", "color", "#808080"),
|
|
62
|
+
ParamDef("dome_color", "圆顶颜色", "color", "#C0C0C0"),
|
|
63
|
+
ParamDef("beam_color", "光束颜色", "color", "#00FF00"),
|
|
64
|
+
ParamDef("glow_intensity", "发光强度", "float", 0.8, 0.0, 1.0, 0.1),
|
|
65
|
+
ParamDef("banner_color", "横幅颜色", "color", "#9370DB"),
|
|
66
|
+
ParamDef("rotation", "旋转角度", "float", 0.0, -45.0, 45.0, 1.0),
|
|
67
|
+
ParamDef("text_color", "文字颜色", "color", "#FFFFFF"),
|
|
68
|
+
ParamDef("banner_attach_x", "横幅挂载X", "int", -30, -50, 100),
|
|
69
|
+
ParamDef("banner_attach_y", "横幅挂载Y", "int", 0, -50, 100),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
def get_default_params(self) -> UFOParameters:
|
|
73
|
+
return UFOParameters()
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
import json
|
|
5
|
+
from typing import Optional, cast
|
|
6
|
+
|
|
7
|
+
from PyQt6.QtCore import Qt
|
|
8
|
+
from PyQt6.QtGui import QColor, QFont, QPainter, QPen
|
|
9
|
+
from PyQt6.QtWidgets import (
|
|
10
|
+
QCheckBox,
|
|
11
|
+
QColorDialog,
|
|
12
|
+
QComboBox,
|
|
13
|
+
QDialog,
|
|
14
|
+
QDialogButtonBox,
|
|
15
|
+
QDoubleSpinBox,
|
|
16
|
+
QFormLayout,
|
|
17
|
+
QHBoxLayout,
|
|
18
|
+
QLabel,
|
|
19
|
+
QLineEdit,
|
|
20
|
+
QPushButton,
|
|
21
|
+
QScrollArea,
|
|
22
|
+
QSpinBox,
|
|
23
|
+
QVBoxLayout,
|
|
24
|
+
QWidget,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from message_flight.config import AppConfig
|
|
28
|
+
from message_flight.i18n import tr
|
|
29
|
+
from message_flight.plane_presets import get_preset, list_presets
|
|
30
|
+
from message_flight.plane_presets.base import PlanePreset
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PresetPreviewWidget(QWidget):
|
|
34
|
+
_CENTER_X = 100
|
|
35
|
+
_CENTER_Y = 75
|
|
36
|
+
_GRAB_RADIUS = 10
|
|
37
|
+
|
|
38
|
+
def __init__(self, preset: PlanePreset, params, on_mount_changed=None, parent=None):
|
|
39
|
+
super().__init__(parent)
|
|
40
|
+
self._preset = preset
|
|
41
|
+
self._params = params
|
|
42
|
+
self._on_mount_changed = on_mount_changed
|
|
43
|
+
self._dragging = False
|
|
44
|
+
self.setFixedSize(200, 150)
|
|
45
|
+
self.setCursor(Qt.CursorShape.CrossCursor)
|
|
46
|
+
|
|
47
|
+
def update_preset(self, preset) -> None:
|
|
48
|
+
"""Replace the active preset and request a repaint."""
|
|
49
|
+
self._preset = preset
|
|
50
|
+
self.update()
|
|
51
|
+
|
|
52
|
+
def update_params(self, params) -> None:
|
|
53
|
+
self._params = params
|
|
54
|
+
self.update()
|
|
55
|
+
|
|
56
|
+
def _get_mount(self) -> tuple[int, int]:
|
|
57
|
+
return (
|
|
58
|
+
getattr(self._params, "banner_attach_x", 0),
|
|
59
|
+
getattr(self._params, "banner_attach_y", 0),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _set_mount(self, mx: int, my: int) -> None:
|
|
63
|
+
if hasattr(self._params, "banner_attach_x"):
|
|
64
|
+
self._params.banner_attach_x = int(mx)
|
|
65
|
+
if hasattr(self._params, "banner_attach_y"):
|
|
66
|
+
self._params.banner_attach_y = int(my)
|
|
67
|
+
if self._on_mount_changed is not None:
|
|
68
|
+
self._on_mount_changed(mx, my)
|
|
69
|
+
self.update()
|
|
70
|
+
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
# 鼠标事件
|
|
73
|
+
# ------------------------------------------------------------------
|
|
74
|
+
def mousePressEvent(self, event):
|
|
75
|
+
mx, my = self._world_to_mount(event.pos())
|
|
76
|
+
cur_mx, cur_my = self._get_mount()
|
|
77
|
+
dist = ((mx - cur_mx) ** 2 + (my - cur_my) ** 2) ** 0.5
|
|
78
|
+
if dist < self._GRAB_RADIUS:
|
|
79
|
+
self._dragging = True
|
|
80
|
+
self.setCursor(Qt.CursorShape.ClosedHandCursor)
|
|
81
|
+
else:
|
|
82
|
+
self._set_mount(mx, my)
|
|
83
|
+
|
|
84
|
+
def mouseMoveEvent(self, event):
|
|
85
|
+
if self._dragging:
|
|
86
|
+
mx, my = self._world_to_mount(event.pos())
|
|
87
|
+
self._set_mount(mx, my)
|
|
88
|
+
|
|
89
|
+
def mouseReleaseEvent(self, event):
|
|
90
|
+
if self._dragging:
|
|
91
|
+
self._dragging = False
|
|
92
|
+
self.setCursor(Qt.CursorShape.CrossCursor)
|
|
93
|
+
|
|
94
|
+
def _world_to_mount(self, pos) -> tuple[int, int]:
|
|
95
|
+
return int(pos.x() - self._CENTER_X), int(pos.y() - self._CENTER_Y)
|
|
96
|
+
|
|
97
|
+
# ------------------------------------------------------------------
|
|
98
|
+
# 绘制
|
|
99
|
+
# ------------------------------------------------------------------
|
|
100
|
+
def paintEvent(self, event):
|
|
101
|
+
painter = QPainter(self)
|
|
102
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
103
|
+
painter.fillRect(self.rect(), QColor("#f5f5f5"))
|
|
104
|
+
|
|
105
|
+
# 轻微网格线
|
|
106
|
+
pen = QPen(QColor("#e0e0e0"))
|
|
107
|
+
pen.setWidth(1)
|
|
108
|
+
painter.setPen(pen)
|
|
109
|
+
for x in range(0, 201, 20):
|
|
110
|
+
painter.drawLine(x, 0, x, 150)
|
|
111
|
+
for y in range(0, 151, 20):
|
|
112
|
+
painter.drawLine(0, y, 200, y)
|
|
113
|
+
|
|
114
|
+
# 飞船原点(中心点)
|
|
115
|
+
painter.setPen(QPen(QColor("#888888"), 1))
|
|
116
|
+
painter.drawEllipse(self._CENTER_X - 2, self._CENTER_Y - 2, 4, 4)
|
|
117
|
+
|
|
118
|
+
# 绘制飞船(translate 到中心)
|
|
119
|
+
painter.translate(self._CENTER_X, self._CENTER_Y)
|
|
120
|
+
self._preset.draw(painter, self._params)
|
|
121
|
+
painter.translate(-self._CENTER_X, -self._CENTER_Y)
|
|
122
|
+
|
|
123
|
+
# 挂载点
|
|
124
|
+
mx, my = self._get_mount()
|
|
125
|
+
wx = self._CENTER_X + mx
|
|
126
|
+
wy = self._CENTER_Y + my
|
|
127
|
+
|
|
128
|
+
# 虚线连接
|
|
129
|
+
dash = QPen(QColor("#FF5722"), 1, Qt.PenStyle.DotLine)
|
|
130
|
+
painter.setPen(dash)
|
|
131
|
+
painter.drawLine(self._CENTER_X, self._CENTER_Y, wx, wy)
|
|
132
|
+
|
|
133
|
+
# 挂载点手柄
|
|
134
|
+
painter.setPen(QPen(QColor("#D32F2F"), 2))
|
|
135
|
+
painter.setBrush(QColor("#FF5252"))
|
|
136
|
+
painter.drawEllipse(wx - 5, wy - 5, 10, 10)
|
|
137
|
+
|
|
138
|
+
# 坐标标签
|
|
139
|
+
painter.setPen(QColor("#333333"))
|
|
140
|
+
font = QFont("Microsoft YaHei", 8)
|
|
141
|
+
painter.setFont(font)
|
|
142
|
+
label = f"({mx}, {my})"
|
|
143
|
+
painter.drawText(wx + 8, wy + 4, label)
|
|
144
|
+
|
|
145
|
+
painter.end()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class PresetEditorWindow(QDialog):
|
|
149
|
+
def __init__(self, cfg: AppConfig, parent: Optional[QWidget] = None) -> None:
|
|
150
|
+
super().__init__(parent)
|
|
151
|
+
self._language = cfg.language
|
|
152
|
+
self.setWindowTitle(tr("preset_editor.title", self._language))
|
|
153
|
+
self.setModal(True)
|
|
154
|
+
self._cfg = cfg
|
|
155
|
+
self._preset_key = cfg.plane_preset_key or "airplane"
|
|
156
|
+
preset_obj = get_preset(self._preset_key)
|
|
157
|
+
if cfg.plane_preset_params_json:
|
|
158
|
+
try:
|
|
159
|
+
data = json.loads(cfg.plane_preset_params_json)
|
|
160
|
+
default = preset_obj.get_default_params()
|
|
161
|
+
self._params = dataclasses.replace(
|
|
162
|
+
default,
|
|
163
|
+
**{k: v for k, v in data.items() if hasattr(default, k)},
|
|
164
|
+
)
|
|
165
|
+
except (json.JSONDecodeError, TypeError):
|
|
166
|
+
self._params = preset_obj.get_default_params()
|
|
167
|
+
else:
|
|
168
|
+
self._params = preset_obj.get_default_params()
|
|
169
|
+
self._param_widgets: dict[str, QWidget] = {}
|
|
170
|
+
|
|
171
|
+
root = QVBoxLayout(self)
|
|
172
|
+
top_row = QHBoxLayout()
|
|
173
|
+
top_row.addWidget(QLabel(tr("preset_editor.preset", self._language)))
|
|
174
|
+
self._preset_combo = QComboBox()
|
|
175
|
+
for key, name, icon in list_presets():
|
|
176
|
+
self._preset_combo.addItem(f"{icon} {name}", key)
|
|
177
|
+
idx = next(
|
|
178
|
+
(i for i in range(self._preset_combo.count())
|
|
179
|
+
if self._preset_combo.itemData(i) == self._preset_key),
|
|
180
|
+
0,
|
|
181
|
+
)
|
|
182
|
+
self._preset_combo.setCurrentIndex(idx)
|
|
183
|
+
self._preset_combo.currentIndexChanged.connect(self._on_preset_changed)
|
|
184
|
+
top_row.addWidget(self._preset_combo)
|
|
185
|
+
top_row.addStretch(1)
|
|
186
|
+
root.addLayout(top_row)
|
|
187
|
+
|
|
188
|
+
middle = QHBoxLayout()
|
|
189
|
+
self._param_panel = QWidget()
|
|
190
|
+
self._param_layout = QFormLayout(self._param_panel)
|
|
191
|
+
scroll = QScrollArea()
|
|
192
|
+
scroll.setWidget(self._param_panel)
|
|
193
|
+
scroll.setWidgetResizable(True)
|
|
194
|
+
scroll.setFixedWidth(280)
|
|
195
|
+
middle.addWidget(scroll)
|
|
196
|
+
|
|
197
|
+
# 预览 + 拖拽回调
|
|
198
|
+
self._preview = PresetPreviewWidget(
|
|
199
|
+
preset_obj, self._params,
|
|
200
|
+
on_mount_changed=self._on_preview_mount_changed,
|
|
201
|
+
)
|
|
202
|
+
middle.addWidget(self._preview)
|
|
203
|
+
root.addLayout(middle)
|
|
204
|
+
|
|
205
|
+
self._button_box = QDialogButtonBox(
|
|
206
|
+
QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Cancel
|
|
207
|
+
)
|
|
208
|
+
self._button_box.accepted.connect(self.accept)
|
|
209
|
+
self._button_box.rejected.connect(self.reject)
|
|
210
|
+
root.addWidget(self._button_box)
|
|
211
|
+
|
|
212
|
+
self._build_param_panel()
|
|
213
|
+
|
|
214
|
+
def _on_preview_mount_changed(self, mx: int, my: int) -> None:
|
|
215
|
+
"""预览拖拽时同步更新参数面板中的 SpinBox。"""
|
|
216
|
+
for name in ("banner_attach_x", "banner_attach_y"):
|
|
217
|
+
spin = self._param_widgets.get(name)
|
|
218
|
+
if spin is not None:
|
|
219
|
+
value = mx if name == "banner_attach_x" else my
|
|
220
|
+
spin_box = cast(QSpinBox, spin)
|
|
221
|
+
spin_box.blockSignals(True)
|
|
222
|
+
spin_box.setValue(value)
|
|
223
|
+
spin_box.blockSignals(False)
|
|
224
|
+
|
|
225
|
+
def _refresh_preview(self) -> None:
|
|
226
|
+
self._preview.update_params(self._params)
|
|
227
|
+
|
|
228
|
+
def _on_preset_changed(self, index: int) -> None:
|
|
229
|
+
key = self._preset_combo.itemData(index)
|
|
230
|
+
self._preset_key = key
|
|
231
|
+
preset_obj = get_preset(key)
|
|
232
|
+
self._params = preset_obj.get_default_params()
|
|
233
|
+
self._build_param_panel()
|
|
234
|
+
self._preview.update_preset(preset_obj)
|
|
235
|
+
self._refresh_preview()
|
|
236
|
+
|
|
237
|
+
def _build_param_panel(self) -> None:
|
|
238
|
+
while self._param_layout.count():
|
|
239
|
+
item = self._param_layout.takeAt(0)
|
|
240
|
+
if item is None:
|
|
241
|
+
continue
|
|
242
|
+
w = item.widget()
|
|
243
|
+
if w is not None:
|
|
244
|
+
w.deleteLater()
|
|
245
|
+
self._param_widgets.clear()
|
|
246
|
+
preset_obj = get_preset(self._preset_key)
|
|
247
|
+
for param_def in preset_obj.get_parameters():
|
|
248
|
+
value = getattr(self._params, param_def.name)
|
|
249
|
+
if param_def.type == "color":
|
|
250
|
+
row_widget = QWidget()
|
|
251
|
+
row_layout = QHBoxLayout(row_widget)
|
|
252
|
+
row_layout.setContentsMargins(0, 0, 0, 0)
|
|
253
|
+
edit = QLineEdit(str(value))
|
|
254
|
+
swatch = QLabel()
|
|
255
|
+
swatch.setFixedSize(24, 18)
|
|
256
|
+
qc = QColor(str(value))
|
|
257
|
+
swatch.setStyleSheet(
|
|
258
|
+
f"background-color: {qc.name() if qc.isValid() else '#888888'};"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def make_picker(_edit=edit, _swatch=swatch, _n=param_def.name, _label=param_def.label):
|
|
262
|
+
def open_picker():
|
|
263
|
+
current = QColor(_edit.text())
|
|
264
|
+
chosen = QColorDialog.getColor(
|
|
265
|
+
current,
|
|
266
|
+
self,
|
|
267
|
+
tr("preset_editor.choose_color", self._language, label=_label),
|
|
268
|
+
)
|
|
269
|
+
if chosen.isValid():
|
|
270
|
+
_edit.setText(chosen.name())
|
|
271
|
+
_swatch.setStyleSheet(
|
|
272
|
+
f"background-color: {chosen.name()};"
|
|
273
|
+
)
|
|
274
|
+
setattr(self._params, _n, chosen.name())
|
|
275
|
+
self._refresh_preview()
|
|
276
|
+
return open_picker
|
|
277
|
+
picker_btn = QPushButton("…")
|
|
278
|
+
picker_btn.setFixedWidth(30)
|
|
279
|
+
picker_btn.clicked.connect(make_picker())
|
|
280
|
+
edit.textChanged.connect(
|
|
281
|
+
lambda text, _e=edit, _s=swatch, _n=param_def.name:
|
|
282
|
+
self._on_color_text_changed(_n, text, _e, _s)
|
|
283
|
+
)
|
|
284
|
+
row_layout.addWidget(edit)
|
|
285
|
+
row_layout.addWidget(swatch)
|
|
286
|
+
row_layout.addWidget(picker_btn)
|
|
287
|
+
self._param_layout.addRow(param_def.label, row_widget)
|
|
288
|
+
self._param_widgets[param_def.name] = edit
|
|
289
|
+
elif param_def.type == "int":
|
|
290
|
+
spin = QSpinBox()
|
|
291
|
+
spin.setRange(
|
|
292
|
+
int(param_def.min) if param_def.min is not None else 0,
|
|
293
|
+
int(param_def.max) if param_def.max is not None else 999,
|
|
294
|
+
)
|
|
295
|
+
spin.setValue(int(value))
|
|
296
|
+
spin.valueChanged.connect(
|
|
297
|
+
lambda v, _n=param_def.name: self._on_int_changed(_n, v)
|
|
298
|
+
)
|
|
299
|
+
self._param_layout.addRow(param_def.label, spin)
|
|
300
|
+
self._param_widgets[param_def.name] = spin
|
|
301
|
+
elif param_def.type == "float":
|
|
302
|
+
double_spin = QDoubleSpinBox()
|
|
303
|
+
double_spin.setRange(
|
|
304
|
+
float(param_def.min) if param_def.min is not None else 0.0,
|
|
305
|
+
float(param_def.max) if param_def.max is not None else 999.0,
|
|
306
|
+
)
|
|
307
|
+
if param_def.step is not None:
|
|
308
|
+
double_spin.setSingleStep(float(param_def.step))
|
|
309
|
+
double_spin.setValue(float(value))
|
|
310
|
+
double_spin.valueChanged.connect(
|
|
311
|
+
lambda v, _n=param_def.name: self._on_float_changed(_n, v)
|
|
312
|
+
)
|
|
313
|
+
self._param_layout.addRow(param_def.label, double_spin)
|
|
314
|
+
self._param_widgets[param_def.name] = double_spin
|
|
315
|
+
elif param_def.type == "bool":
|
|
316
|
+
cb = QCheckBox()
|
|
317
|
+
cb.setChecked(bool(value))
|
|
318
|
+
cb.toggled.connect(
|
|
319
|
+
lambda checked, _n=param_def.name: self._on_bool_changed(_n, checked)
|
|
320
|
+
)
|
|
321
|
+
self._param_layout.addRow(param_def.label, cb)
|
|
322
|
+
self._param_widgets[param_def.name] = cb
|
|
323
|
+
|
|
324
|
+
def _on_color_text_changed(self, name, text, edit, swatch) -> None:
|
|
325
|
+
qc = QColor(text)
|
|
326
|
+
if qc.isValid():
|
|
327
|
+
swatch.setStyleSheet(f"background-color: {qc.name()};")
|
|
328
|
+
setattr(self._params, name, text)
|
|
329
|
+
self._refresh_preview()
|
|
330
|
+
|
|
331
|
+
def _on_int_changed(self, name, value) -> None:
|
|
332
|
+
setattr(self._params, name, int(value))
|
|
333
|
+
self._refresh_preview()
|
|
334
|
+
|
|
335
|
+
def _on_float_changed(self, name, value) -> None:
|
|
336
|
+
setattr(self._params, name, float(value))
|
|
337
|
+
self._refresh_preview()
|
|
338
|
+
|
|
339
|
+
def _on_bool_changed(self, name, value) -> None:
|
|
340
|
+
setattr(self._params, name, bool(value))
|
|
341
|
+
self._refresh_preview()
|
|
342
|
+
|
|
343
|
+
def get_result(self) -> tuple[str, str]:
|
|
344
|
+
data = dataclasses.asdict(self._params)
|
|
345
|
+
return (self._preset_key, json.dumps(data, ensure_ascii=False))
|