Skip to content

Commit 77e4c55

Browse files
Add forward_declarations and post_class user code regions
- Add USER_FORWARD_DECLARATIONS region for forward declarations and enums - Add USER_POST_CLASS region for helper class definitions after main class - Add USER_INCLUDES region for custom header includes - Update code generator to support new user code regions - Update code parser to extract new user code regions - Update ProjectCode dataclass with new fields - Handle dynamic size markers (-1 to -7) in canvas and preview - Update documentation for new user code regions - Add examples/ to .gitignore
1 parent 0ab4e80 commit 77e4c55

7 files changed

Lines changed: 220 additions & 67 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ cython_debug/
123123
*.sexyui
124124
*.cssexyui
125125

126+
# Example files
127+
examples/
128+
126129
# Resource files (too large)
127130
pak/
128131
Content/

core/code_generator.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ class NewLawnButton;
102102
class LawnDialog;
103103
class LawnEditWidget;
104104
105+
// [[[USER_FORWARD_DECLARATIONS]]]
106+
// Add forward declarations and enums here
107+
{iface.user_code.forward_declarations}// [[[END_USER_FORWARD_DECLARATIONS]]]
108+
105109
class {s.class_name} : public {base_class}{listener_bases}
106110
{{
107111
public:
@@ -164,6 +168,11 @@ class {s.class_name} : public {base_class}{listener_bases}
164168
header += " virtual void ButtonPress(int theId) override;\n"
165169
header += " virtual void ButtonDepress(int theId) override;\n"
166170

171+
header += "\n // [[[USER_DECLARATIONS]]]\n"
172+
header += " // 在此标记区域内添加你的成员声明\n"
173+
header += iface.user_code.declarations
174+
header += " // [[[END_USER_DECLARATIONS]]]\n"
175+
167176
header += "\nprivate:\n"
168177
header += " std::vector<std::string> mLoadedResourceNames;\n"
169178
for wid in iface.widgets.values():
@@ -173,11 +182,14 @@ class {s.class_name} : public {base_class}{listener_bases}
173182
ns = self._get_widget_namespace(wid.class_name)
174183
header += f" {ns}{wid.class_name}* {var_name};\n"
175184

176-
header += "\n // [[[USER_DECLARATIONS]]]\n"
177-
header += " // 在此标记区域内添加你的成员声明\n"
178-
header += " // [[[END_USER_DECLARATIONS]]]\n"
179-
180-
header += "};\n\n#endif // " + guard + "\n"
185+
header += "};\n\n"
186+
187+
if iface.user_code.post_class:
188+
header += f"// [[[USER_POST_CLASS]]]\n"
189+
header += iface.user_code.post_class
190+
header += "// [[[END_USER_POST_CLASS]]]\n\n"
191+
192+
header += "#endif // " + guard + "\n"
181193
return header
182194

183195
def generate_header(self, project: Project) -> str:

core/code_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def extract_from_files(cls, header_path: str, cpp_path: str) -> ProjectCode:
6666
header = f.read()
6767
blocks = cls.extract_blocks(header)
6868
code.header_includes = blocks.get('USER_INCLUDES', '')
69+
code.forward_declarations = blocks.get('USER_FORWARD_DECLARATIONS', '')
6970
code.declarations = blocks.get('USER_DECLARATIONS', '')
71+
code.post_class = blocks.get('USER_POST_CLASS', '')
7072

7173
if cpp_path and os.path.exists(cpp_path):
7274
with open(cpp_path, 'r', encoding='utf-8') as f:

core/project.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ def __post_init__(self):
3232
class ProjectCode:
3333
header_includes: str = ""
3434
cpp_includes: str = ""
35+
forward_declarations: str = ""
3536
declarations: str = ""
3637
init_code: str = ""
3738
destroy_code: str = ""
3839
draw_code: str = ""
3940
update_code: str = ""
4041
functions: str = ""
42+
post_class: str = ""
4143
event_handlers: Dict[str, str] = field(default_factory=dict)
4244

4345

@@ -292,12 +294,14 @@ def to_dict(self) -> dict:
292294
"user_code": {
293295
"header_includes": iface.user_code.header_includes,
294296
"cpp_includes": iface.user_code.cpp_includes,
297+
"forward_declarations": iface.user_code.forward_declarations,
295298
"declarations": iface.user_code.declarations,
296299
"init_code": iface.user_code.init_code,
297300
"destroy_code": iface.user_code.destroy_code,
298301
"draw_code": iface.user_code.draw_code,
299302
"update_code": iface.user_code.update_code,
300303
"functions": iface.user_code.functions,
304+
"post_class": iface.user_code.post_class,
301305
"event_handlers": iface.user_code.event_handlers,
302306
},
303307
}
@@ -371,12 +375,14 @@ def from_dict(self, data: dict):
371375
iface.user_code = ProjectCode(
372376
header_includes=uc.get("header_includes", ""),
373377
cpp_includes=uc.get("cpp_includes", ""),
378+
forward_declarations=uc.get("forward_declarations", ""),
374379
declarations=uc.get("declarations", ""),
375380
init_code=uc.get("init_code", ""),
376381
destroy_code=uc.get("destroy_code", ""),
377382
draw_code=uc.get("draw_code", ""),
378383
update_code=uc.get("update_code", ""),
379384
functions=uc.get("functions", ""),
385+
post_class=uc.get("post_class", ""),
380386
event_handlers=uc.get("event_handlers", {}),
381387
)
382388
self.interfaces[iid] = iface

docs/CORE_ARCHITECTURE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,15 +529,19 @@ SexyUIExtensions/
529529
## 用户代码区域
530530

531531
### C++ 版本
532+
- `// [[[USER_INCLUDES]]]` - 自定义头文件包含
533+
- `// [[[USER_FORWARD_DECLARATIONS]]]` - 前向声明和枚举定义(在类定义之前)
532534
- `// [[[USER_DECLARATIONS]]]` - 成员声明
533535
- `// [[[USER_INIT]]]` - 初始化代码
534536
- `// [[[USER_DESTROY]]]` - 清理代码
535537
- `// [[[USER_DRAW]]]` - 自定义绘制代码
536538
- `// [[[USER_UPDATE]]]` - 更新逻辑
537539
- `// [[[USER_FUNCTIONS]]]` - 自定义函数
540+
- `// [[[USER_POST_CLASS]]]` - 类后定义(辅助类等,在类定义之后)
538541
- `// [[[HANDLER_widget_id]]]` - 事件处理器代码
539542

540543
### C# 版本
544+
- `// [[[USER_INCLUDES]]]` - 自定义 using 语句
541545
- `// [[[USER_DECLARATIONS]]]` - 成员声明
542546
- `// [[[USER_INIT]]]` - 初始化代码
543547
- `// [[[USER_DESTROY]]]` - 清理代码

ui/canvas.py

Lines changed: 116 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -240,47 +240,97 @@ def _do_paint(self, widget):
240240

241241
painter.end()
242242

243+
def _resolve_dynamic_size(self, value: int, img_px: QPixmap, is_width: bool) -> int:
244+
if value >= 0:
245+
return value
246+
if img_px is None or img_px.isNull():
247+
return 100 if is_width else 30
248+
img_size = img_px.width() if is_width else img_px.height()
249+
special_sizes = {
250+
-1: img_size,
251+
-2: img_size + 23,
252+
-3: img_size + 33,
253+
-4: img_size + 10,
254+
-5: img_size,
255+
-6: img_size,
256+
-7: img_size,
257+
}
258+
return special_sizes.get(value, img_size)
259+
260+
def _resolve_dynamic_pos(self, value: int, img_px: QPixmap, is_x: bool) -> int:
261+
if value >= 0:
262+
return value
263+
iface = self.project.current_interface
264+
canvas_w = iface.settings.width if iface else 800
265+
canvas_h = iface.settings.height if iface else 600
266+
img_w = img_px.width() if img_px and not img_px.isNull() else 100
267+
img_h = img_px.height() if img_px and not img_px.isNull() else 30
268+
if is_x:
269+
special_positions = {
270+
-7: canvas_w - 150,
271+
-6: 20,
272+
}
273+
else:
274+
special_positions = {
275+
-7: 455,
276+
-6: canvas_h - img_h - 35,
277+
}
278+
return special_positions.get(value, value)
279+
243280
def _draw_widget(self, painter: QPainter, widget_id: str):
244281
w = self.project.get_widget(widget_id)
245282
if not w:
246283
return
247284

248285
props = w.properties
249-
x = int(props.get("mX", 0) * self._zoom)
250-
y = int(props.get("mY", 0) * self._zoom)
251-
width = int(props.get("mWidth", 100) * self._zoom)
252-
height = int(props.get("mHeight", 30) * self._zoom)
286+
raw_x = props.get("mX", 0)
287+
raw_y = props.get("mY", 0)
288+
raw_width = props.get("mWidth", 100)
289+
raw_height = props.get("mHeight", 30)
253290

254291
img_drawn = False
292+
img_px = None
255293
for img_key in ["mButtonImage", "mComponentImage", "mUncheckedImage", "mTrackImage", "mImage"]:
256294
img_ref = props.get(img_key, "")
257295
if not img_ref and img_key == "mButtonImage" and w.class_name == "NewLawnButton":
258296
img_ref = props.get("mUniformImage", "")
259297
if img_ref:
260-
px = self._load_pixmap(img_ref)
261-
if px and not px.isNull():
262-
stretch = False
263-
if w.class_name == "ImageBox":
264-
stretch = props.get("mStretch", False)
265-
266-
if stretch:
267-
scaled = px.scaled(width, height, Qt.AspectRatioMode.IgnoreAspectRatio,
268-
Qt.TransformationMode.SmoothTransformation)
269-
painter.drawPixmap(x, y, scaled)
270-
else:
271-
scale_x = props.get("mScaleX", 1.0)
272-
scale_y = props.get("mScaleY", 1.0)
273-
if scale_x != 1.0 or scale_y != 1.0:
274-
scaled = px.scaled(int(px.width() * scale_x * self._zoom),
275-
int(px.height() * scale_y * self._zoom),
276-
Qt.AspectRatioMode.IgnoreAspectRatio,
277-
Qt.TransformationMode.SmoothTransformation)
278-
painter.drawPixmap(x, y, scaled)
279-
else:
280-
painter.drawPixmap(x, y, px)
281-
img_drawn = True
298+
img_px = self._load_pixmap(img_ref)
299+
if img_px and not img_px.isNull():
282300
break
283301

302+
x = self._resolve_dynamic_pos(raw_x, img_px, True)
303+
y = self._resolve_dynamic_pos(raw_y, img_px, False)
304+
width = self._resolve_dynamic_size(raw_width, img_px, True)
305+
height = self._resolve_dynamic_size(raw_height, img_px, False)
306+
307+
x = int(x * self._zoom)
308+
y = int(y * self._zoom)
309+
width = int(width * self._zoom)
310+
height = int(height * self._zoom)
311+
312+
if img_px and not img_px.isNull():
313+
stretch = False
314+
if w.class_name == "ImageBox":
315+
stretch = props.get("mStretch", False)
316+
317+
if stretch:
318+
scaled = img_px.scaled(width, height, Qt.AspectRatioMode.IgnoreAspectRatio,
319+
Qt.TransformationMode.SmoothTransformation)
320+
painter.drawPixmap(x, y, scaled)
321+
else:
322+
scale_x = props.get("mScaleX", 1.0)
323+
scale_y = props.get("mScaleY", 1.0)
324+
if scale_x != 1.0 or scale_y != 1.0:
325+
scaled = img_px.scaled(int(img_px.width() * scale_x * self._zoom),
326+
int(img_px.height() * scale_y * self._zoom),
327+
Qt.AspectRatioMode.IgnoreAspectRatio,
328+
Qt.TransformationMode.SmoothTransformation)
329+
painter.drawPixmap(x, y, scaled)
330+
else:
331+
painter.drawPixmap(x, y, img_px)
332+
img_drawn = True
333+
284334
if not img_drawn:
285335
color = _WIDGET_COLORS.get(w.class_name, QColor(200, 200, 200, 80))
286336
painter.setBrush(QBrush(color))
@@ -368,10 +418,26 @@ def _hit_test_widget(self, pos: QPoint, widget_id: str) -> str:
368418
if result:
369419
return result
370420
props = w.properties
371-
x = int(props.get("mX", 0) * self._zoom)
372-
y = int(props.get("mY", 0) * self._zoom)
373-
width = int(props.get("mWidth", 100) * self._zoom)
374-
height = int(props.get("mHeight", 30) * self._zoom)
421+
raw_x = props.get("mX", 0)
422+
raw_y = props.get("mY", 0)
423+
raw_width = props.get("mWidth", 100)
424+
raw_height = props.get("mHeight", 30)
425+
426+
img_px = None
427+
for img_key in ["mButtonImage", "mComponentImage", "mUncheckedImage", "mTrackImage", "mImage"]:
428+
img_ref = props.get(img_key, "")
429+
if not img_ref and img_key == "mButtonImage" and w.class_name == "NewLawnButton":
430+
img_ref = props.get("mUniformImage", "")
431+
if img_ref:
432+
img_px = self._load_pixmap(img_ref)
433+
if img_px and not img_px.isNull():
434+
break
435+
436+
x = int(self._resolve_dynamic_pos(raw_x, img_px, True) * self._zoom)
437+
y = int(self._resolve_dynamic_pos(raw_y, img_px, False) * self._zoom)
438+
width = int(self._resolve_dynamic_size(raw_width, img_px, True) * self._zoom)
439+
height = int(self._resolve_dynamic_size(raw_height, img_px, False) * self._zoom)
440+
375441
if QRect(x, y, width, height).contains(pos):
376442
return w.id
377443
return ""
@@ -383,10 +449,26 @@ def _hit_handle(self, pos: QPoint) -> str:
383449
if not w:
384450
return ""
385451
props = w.properties
386-
x = int(props.get("mX", 0) * self._zoom)
387-
y = int(props.get("mY", 0) * self._zoom)
388-
width = int(props.get("mWidth", 100) * self._zoom)
389-
height = int(props.get("mHeight", 30) * self._zoom)
452+
raw_x = props.get("mX", 0)
453+
raw_y = props.get("mY", 0)
454+
raw_width = props.get("mWidth", 100)
455+
raw_height = props.get("mHeight", 30)
456+
457+
img_px = None
458+
for img_key in ["mButtonImage", "mComponentImage", "mUncheckedImage", "mTrackImage", "mImage"]:
459+
img_ref = props.get(img_key, "")
460+
if not img_ref and img_key == "mButtonImage" and w.class_name == "NewLawnButton":
461+
img_ref = props.get("mUniformImage", "")
462+
if img_ref:
463+
img_px = self._load_pixmap(img_ref)
464+
if img_px and not img_px.isNull():
465+
break
466+
467+
x = int(self._resolve_dynamic_pos(raw_x, img_px, True) * self._zoom)
468+
y = int(self._resolve_dynamic_pos(raw_y, img_px, False) * self._zoom)
469+
width = int(self._resolve_dynamic_size(raw_width, img_px, True) * self._zoom)
470+
height = int(self._resolve_dynamic_size(raw_height, img_px, False) * self._zoom)
471+
390472
handles = self._get_handles(x, y, width, height, 8)
391473
names = ["tl", "tr", "bl", "br", "tm", "bm", "ml", "mr"]
392474
for name, handle in zip(names, handles):

0 commit comments

Comments
 (0)