-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathwidget_slot.py
215 lines (181 loc) · 9.58 KB
/
widget_slot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import arduboy.fxcart
import arduboy.shortcuts
import arduboy.arduhex
import arduboy.image
import gui_common
import gui_utils
import constants
import debug_actions
from widget_titleimage import *
from arduboy.constants import *
from arduboy.common import *
from PyQt6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QLabel, QFileDialog
from PyQt6.QtCore import pyqtSignal, Qt
# Info input field's length limit. Just the field, not the data (though apparently the data is truncated
# when placed in the field)
INFO_TOOLTIP = "Info"
CATEGORY_BLOCK_STYLE = "background: rgba(255,255,0,1); color: #000; font-weight: bold"
FX_STYLE = "background: rgba(0,150,255,0.45)"
class SlotWidget(QWidget):
onchange = pyqtSignal()
# A slot must always have SOME parsed data associated with it!
def __init__(self, parsed: arduboy.fxcart.FxParsedSlot):
super().__init__()
self.parsed = parsed
self.layout = QHBoxLayout()
self.mode = "category" if parsed.is_category() else "game"
# ---------------------------
# Left section (image, data)
# ---------------------------
leftlayout = QVBoxLayout()
self.leftwidget = QWidget()
self.leftwidget.setObjectName("leftwidget")
self.image = TitleImageWidget()
if self.parsed.has_image():
self.image.set_image_bytes(self.parsed.image_raw)
self.image.onimage_bytes.connect(self.set_image_bytes)
leftlayout.addWidget(self.image)
# Create it now, use it later
self.meta_label = QLabel()
if self.mode != "category":
datalayout = QHBoxLayout()
datawidget = QWidget()
self.program = gui_utils.emoji_button("💻", "Set program .hex")
self.program.clicked.connect(self.select_program)
datalayout.addWidget(self.program)
self.data = gui_utils.emoji_button("🧰", "Set data .bin")
self.data.clicked.connect(self.select_data)
datalayout.addWidget(self.data)
self.save = gui_utils.emoji_button("💾", "Set save .bin")
self.save.clicked.connect(self.select_save)
datalayout.addWidget(self.save)
datalayout.setContentsMargins(0,0,0,0)
datawidget.setLayout(datalayout)
leftlayout.addWidget(datawidget)
# This is a category then
else:
self.leftwidget.setStyleSheet(CATEGORY_BLOCK_STYLE)
self.meta_label.setStyleSheet("font-weight: bold; margin-bottom: 5px")
self.meta_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
gui_utils.mod_font_size(self.meta_label, 0.85)
leftlayout.addWidget(self.meta_label)
self.update_metalabel()
leftlayout.setContentsMargins(0,0,0,0)
self.leftwidget.setLayout(leftlayout)
self.layout.addWidget(self.leftwidget)
# And now all the editable fields!
# ---------------------------
# Right section (image, data)
# ---------------------------
fieldlayout = QVBoxLayout()
fieldsparent = QWidget()
fields = []
self.title = gui_utils.new_selflabeled_edit("Title", self.parsed.meta.title)
self.title.textChanged.connect(self.title_set_event)
fields.append(self.title)
if self.mode != "category":
self.version = gui_utils.new_selflabeled_edit("Version", self.parsed.meta.version)
self.version.textChanged.connect(lambda t: self.do_meta_change(t, "version"))
fields.append(self.version)
self.author = gui_utils.new_selflabeled_edit("Author", self.parsed.meta.developer)
self.author.textChanged.connect(lambda t: self.do_meta_change(t, "developer"))
fields.append(self.author)
self.info = gui_utils.new_selflabeled_edit(INFO_TOOLTIP, self.parsed.meta.info)
self.info.textChanged.connect(lambda t: self.do_meta_change(t, "info"))
fields.append(self.info)
self.category_bigtitle = None
if self.mode == "category":
self.category_bigtitle = QLabel(self.parsed.meta.title)
self.category_bigtitle.setAlignment(Qt.AlignmentFlag.AlignCenter)
gui_common.set_font_size(self.category_bigtitle, 16)
self.category_bigtitle.setMinimumHeight((int)(self.title.sizeHint().height() * 2.75))
self.category_bigtitle.setStyleSheet(CATEGORY_BLOCK_STYLE)
gui_utils.add_children_nostretch(fieldlayout, fields, self.category_bigtitle)
else:
for f in fields:
fieldlayout.addWidget(f)
fieldlayout.setContentsMargins(0,0,0,0)
fieldsparent.setLayout(fieldlayout)
self.layout.addWidget(fieldsparent)
self.setLayout(self.layout)
def title_set_event(self, title):
self.do_meta_change(title, "title")
if self.category_bigtitle:
self.category_bigtitle.setText(title)
# Update the metadata label for this unit with whatever new information is stored locally
def update_metalabel(self):
if self.mode == "category":
self.meta_label.setText("Category ↓")
else:
self.meta_label.setText(f"{len(self.parsed.program_raw)} | {len(self.parsed.data_raw)} | {len(self.parsed.save_raw)}")
if self.parsed.fx_enabled():
self.leftwidget.setToolTip("FX-Enabled title")
self.leftwidget.setStyleSheet(f"#leftwidget {{ {FX_STYLE} }}")
else:
self.leftwidget.setToolTip(None)
self.leftwidget.setStyleSheet("")
# Perform a simple meta field change. This is unfortunately DIFFERENT than the metalabel!
def do_meta_change(self, new_text, field):
total_limit = arduboy.fxcart.META_HEADER_SIZE - 1 # Minus 1 for the delimiter
total_length = len(self.title.text()) + len(self.info.text())
if self.mode != "category":
total_length += len(self.author.text()) + len(self.version.text())
total_limit -= 2 # Extra delimiters between two extra fields
if total_length > total_limit:
self.info.setStyleSheet(gui_common.WARNINGINPUT)
self.info.setToolTip(f"Info will be truncated; total metadata length must be <= {total_limit} (at {total_length})")
else:
self.info.setStyleSheet("")
self.info.setToolTip(INFO_TOOLTIP)
setattr(self.parsed.meta, field, new_text)
self.onchange.emit()
def get_slot_data(self):
return self.parsed
# Do a bit more work and get a computed arduboy file. Only really useful when this widget is used as
# specifically an arduboy package editor
def compute_arduboy(self, device: str):
slot = self.get_slot_data()
ardparsed = arduboy.shortcuts.arduboy_from_slot(slot, device)
return ardparsed
def select_program(self):
file_path, _ = QFileDialog.getOpenFileName(self, "Open Arduboy Hex File", "", constants.HEX_FILEFILTER)
if file_path:
parsed = arduboy.arduhex.read_hex(file_path)
bindata = arduboy.common.hex_to_bin(parsed.binaries[0].hex_raw)
analysis = arduboy.arduhex.analyze_sketch(bindata)
# Trim just in case
self.parsed.program_raw = analysis.trimmed_data
self.update_metalabel()
self.onchange.emit()
debug_actions.global_debug.add_action_str(f"Edited program for: {self.parsed.meta.title}")
def select_data(self):
file_path, _ = QFileDialog.getOpenFileName(self, "Open FX Data File", "", constants.BIN_FILEFILTER)
if file_path:
with open(file_path, "rb") as f:
data = f.read()
save_size = arduboy.fxcart.embedded_save_size(data)
if save_size:
# Ask if the user wants to create a save out of this
if gui_utils.yes_no("Split save section out",
"The data provided appears to have a save section at the end. This is normal when using the development binary. Do you want to strip the save and add it properly to the slot (recommended)?",
self):
if not len(self.parsed.save_raw) or gui_utils.yes_no("Warn: Overwrite existing save",
"Data set to truncate. However, there's already a save section, do you want to overwrite the existing save (not recommended)?", self):
self.parsed.save_raw = data[-save_size:]
data = data[:-save_size]
self.parsed.data_raw = data
self.update_metalabel()
self.onchange.emit()
debug_actions.global_debug.add_action_str(f"Edited FX data for: {self.parsed.meta.title}")
def select_save(self):
file_path, _ = QFileDialog.getOpenFileName(self, "Open FX Save File", "", constants.BIN_FILEFILTER)
if file_path:
with open(file_path, "rb") as f:
self.parsed.save_raw = f.read()
self.update_metalabel()
self.onchange.emit()
debug_actions.global_debug.add_action_str(f"Edited FX save for: {self.parsed.meta.title}")
def set_image_bytes(self, image_bytes):
self.parsed.image_raw = image_bytes
self.onchange.emit()
debug_actions.global_debug.add_action_str(f"Edited tile image for: {self.parsed.meta.title}")