pyscreeps-arena 0.3.6__py3-none-any.whl → 0.5.8.0__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.
Files changed (43) hide show
  1. pyscreeps_arena/__init__.py +59 -2
  2. pyscreeps_arena/compiler.py +616 -73
  3. pyscreeps_arena/core/config.py +1 -1
  4. pyscreeps_arena/core/const.py +6 -5
  5. pyscreeps_arena/localization.py +10 -0
  6. pyscreeps_arena/project.7z +0 -0
  7. pyscreeps_arena/ui/P2PY.py +108 -0
  8. pyscreeps_arena/ui/__init__.py +12 -0
  9. pyscreeps_arena/ui/creeplogic_edit.py +14 -0
  10. pyscreeps_arena/ui/map_render.py +705 -0
  11. pyscreeps_arena/ui/mapviewer.py +14 -0
  12. pyscreeps_arena/ui/project_ui.py +215 -0
  13. pyscreeps_arena/ui/qcreeplogic/__init__.py +3 -0
  14. pyscreeps_arena/ui/qcreeplogic/model.py +72 -0
  15. pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py +770 -0
  16. pyscreeps_arena/ui/qmapker/__init__.py +1 -0
  17. pyscreeps_arena/ui/qmapker/qmapmarker.py +339 -0
  18. pyscreeps_arena/ui/qmapker/qvariable.py +303 -0
  19. pyscreeps_arena/ui/qmapker/test_compact_variable.py +61 -0
  20. pyscreeps_arena/ui/qmapker/test_qmapmarker.py +71 -0
  21. pyscreeps_arena/ui/qmapker/test_qvariable.py +49 -0
  22. pyscreeps_arena/ui/qmapker/to_code.py +100 -0
  23. pyscreeps_arena/ui/qmapv/__init__.py +3 -0
  24. pyscreeps_arena/ui/qmapv/qcinfo.py +567 -0
  25. pyscreeps_arena/ui/qmapv/qco.py +441 -0
  26. pyscreeps_arena/ui/qmapv/qmapv.py +728 -0
  27. pyscreeps_arena/ui/qmapv/test_array_drag.py +191 -0
  28. pyscreeps_arena/ui/qmapv/test_drag.py +107 -0
  29. pyscreeps_arena/ui/qmapv/test_qcinfo.py +169 -0
  30. pyscreeps_arena/ui/qmapv/test_qco_drag.py +7 -0
  31. pyscreeps_arena/ui/qmapv/test_qmapv.py +224 -0
  32. pyscreeps_arena/ui/qmapv/test_simple_array.py +303 -0
  33. pyscreeps_arena/ui/qrecipe/__init__.py +1 -0
  34. pyscreeps_arena/ui/qrecipe/model.py +434 -0
  35. pyscreeps_arena/ui/qrecipe/qrecipe.py +914 -0
  36. pyscreeps_arena/ui/rs_icon.py +43 -0
  37. {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.8.0.dist-info}/METADATA +15 -3
  38. pyscreeps_arena-0.5.8.0.dist-info/RECORD +47 -0
  39. {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.8.0.dist-info}/WHEEL +1 -1
  40. pyscreeps_arena-0.5.8.0.dist-info/entry_points.txt +4 -0
  41. pyscreeps_arena-0.3.6.dist-info/RECORD +0 -17
  42. pyscreeps_arena-0.3.6.dist-info/entry_points.txt +0 -2
  43. {pyscreeps_arena-0.3.6.dist-info → pyscreeps_arena-0.5.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,567 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ QPSA Cell Info Component - 单元格信息组件
4
+ """
5
+ from typing import Optional, List, Dict, Any
6
+ import json
7
+ from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
8
+ QListWidget, QListWidgetItem, QFrame,
9
+ QSpacerItem, QSizePolicy)
10
+ from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, QRectF, QMimeData
11
+ from PyQt6.QtGui import QPixmap, QPainter, QPen, QColor, QBrush, QPainterPath, QMouseEvent, QDrag
12
+
13
+ from .qmapv import CellInfo
14
+
15
+
16
+ class QPSACellObjectItem(QWidget):
17
+ """Game object item widget for the list."""
18
+
19
+ def __init__(self, obj_id: str, obj_type: str, obj_name: str,
20
+ x: int, y: int, image: Optional[QPixmap] = None, method: str = "", parent=None):
21
+ super().__init__(parent)
22
+ self._id = obj_id
23
+ self._name = obj_name
24
+ self._x = x
25
+ self._y = y
26
+ self._type = obj_type
27
+ self._method = method
28
+ self._image = image
29
+ self._init_ui(obj_type)
30
+
31
+ def _init_ui(self, obj_type: str):
32
+ """Initialize UI components."""
33
+ # Main horizontal layout
34
+ layout = QHBoxLayout()
35
+ layout.setContentsMargins(3, 3, 3, 3)
36
+
37
+ # Left side - shape/icon
38
+ self._icon_label = QLabel()
39
+ self._icon_label.setFixedSize(30, 30) # Increased by 10%
40
+ self._icon_label.setStyleSheet("""
41
+ QLabel {
42
+ border: 1px solid #ccc;
43
+ border-radius: 4px;
44
+ background-color: #f0f0f0;
45
+ }
46
+ """)
47
+ self._icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
48
+ self._update_icon()
49
+ layout.addWidget(self._icon_label)
50
+
51
+ # Middle section - vertical layout for type and id
52
+ middle_layout = QVBoxLayout()
53
+ middle_layout.setSpacing(2)
54
+
55
+ # Type label (first row)
56
+ type_label = QLabel(obj_type)
57
+ type_label.setStyleSheet("font-weight: bold; font-size: 11px;") # Increased by 10%
58
+ type_label.setWordWrap(False)
59
+ middle_layout.addWidget(type_label)
60
+
61
+ # ID label (second row)
62
+ id_label = QLabel(self._id)
63
+ id_label.setStyleSheet("font-size: 10px; color: #666;") # Increased by 10%
64
+ id_label.setWordWrap(False)
65
+ middle_layout.addWidget(id_label)
66
+
67
+ layout.addLayout(middle_layout)
68
+
69
+ # Add spacer to push content to the left
70
+ layout.addStretch()
71
+
72
+ self.setLayout(layout)
73
+
74
+ def _update_icon(self):
75
+ """Update the icon based on current image."""
76
+ if self._image and not self._image.isNull():
77
+ # Get the label size and use 90% of it for the image
78
+ label_width = self._icon_label.width()
79
+ label_height = self._icon_label.height()
80
+
81
+ # Calculate 90% of container size
82
+ target_width = int(label_width * 0.9)
83
+ target_height = int(label_height * 0.9)
84
+
85
+ # Scale image to fit the label with 90% size
86
+ scaled_image = self._image.scaled(
87
+ target_width, target_height, Qt.AspectRatioMode.KeepAspectRatio,
88
+ Qt.TransformationMode.SmoothTransformation
89
+ )
90
+ self._icon_label.setPixmap(scaled_image)
91
+ self._icon_label.setText("")
92
+ print(f"[DEBUG] Set icon size to {target_width}x{target_height} (90% of {label_width}x{label_height})") # 调试输出
93
+ else:
94
+ # Use placeholder text if no image
95
+ self._icon_label.setPixmap(QPixmap())
96
+ self._icon_label.setText("◆") # Placeholder shape
97
+
98
+ # Properties
99
+ @property
100
+ def id(self) -> str:
101
+ """Get object ID."""
102
+ return self._id
103
+
104
+ @property
105
+ def name(self) -> str:
106
+ """Get object name."""
107
+ return self._name
108
+
109
+ @property
110
+ def image(self) -> Optional[QPixmap]:
111
+ """Get/set object icon image."""
112
+ return self._image
113
+
114
+ @image.setter
115
+ def image(self, value: Optional[QPixmap]):
116
+ """Set object icon image."""
117
+ self._image = value
118
+ self._update_icon()
119
+
120
+ def mousePressEvent(self, event: QMouseEvent):
121
+ """Handle mouse press event to start drag."""
122
+ if event.button() == Qt.MouseButton.LeftButton:
123
+ print(f"[DEBUG] Starting drag for object: {self._id}")
124
+ # Create drag data
125
+ drag_data = {
126
+ "x": self._x,
127
+ "y": self._y,
128
+ "type": self._type,
129
+ "method": getattr(self, '_method', ''), # Use instance method or default empty
130
+ "id": self._id,
131
+ "name": self._name
132
+ }
133
+
134
+ # Convert to JSON string
135
+ json_data = json.dumps(drag_data, ensure_ascii=False, indent=2)
136
+ print(f"[DEBUG] Drag data: {json_data}")
137
+
138
+ # Create mime data
139
+ mime_data = QMimeData()
140
+ mime_data.setText(json_data)
141
+ mime_data.setData("application/json", json_data.encode('utf-8'))
142
+
143
+ # Add pixmap if available
144
+ if self._image and not self._image.isNull():
145
+ print(f"[DEBUG] Adding image to drag data")
146
+ mime_data.setImageData(self._image)
147
+
148
+ # Create drag object
149
+ drag = QDrag(self)
150
+ drag.setMimeData(mime_data)
151
+
152
+ # Start drag with multiple drop actions
153
+ drag.exec(Qt.DropAction.CopyAction | Qt.DropAction.MoveAction)
154
+
155
+ event.accept()
156
+
157
+
158
+ class CoordinateLabel(QLabel):
159
+ """Custom coordinate label that supports drag and drop."""
160
+
161
+ def __init__(self, parent=None):
162
+ super().__init__(parent)
163
+ self._x = 0
164
+ self._y = 0
165
+
166
+ def set_coordinates(self, x: int, y: int):
167
+ """Set coordinates and update display."""
168
+ self._x = x
169
+ self._y = y
170
+ self.setText(f"{{x: {x}, y: {y}}}")
171
+
172
+ def mousePressEvent(self, event: QMouseEvent):
173
+ """Handle mouse press event to start drag."""
174
+ if event.button() == Qt.MouseButton.LeftButton:
175
+ print(f"[DEBUG] Starting drag for coordinates: ({self._x}, {self._y})")
176
+ # Create drag data for Point type
177
+ drag_data = {
178
+ "x": self._x,
179
+ "y": self._y,
180
+ "type": "Point",
181
+ "method": "Point",
182
+ "id": f"{self._x}◇{self._y}",
183
+ "name": f"p{self._x}◇{self._y}"
184
+ }
185
+
186
+ # Convert to JSON string
187
+ json_data = json.dumps(drag_data, ensure_ascii=False, indent=2)
188
+ print(f"[DEBUG] Drag data: {json_data}")
189
+
190
+ # Create mime data
191
+ mime_data = QMimeData()
192
+ mime_data.setText(json_data)
193
+ mime_data.setData("application/json", json_data.encode('utf-8'))
194
+
195
+ # Create drag object
196
+ drag = QDrag(self)
197
+ drag.setMimeData(mime_data)
198
+
199
+ # Start drag with multiple drop actions
200
+ drag.exec(Qt.DropAction.CopyAction | Qt.DropAction.MoveAction)
201
+
202
+ event.accept()
203
+
204
+
205
+ class QPSACellInfo(QWidget):
206
+ """Cell information display component."""
207
+
208
+ def __init__(self, parent=None):
209
+ super().__init__(parent)
210
+ self._data: Optional[CellInfo] = None
211
+ self._x: int = 0
212
+ self._y: int = 0
213
+ self._objects: List[Dict[str, Any]] = []
214
+ self._image: Optional[QPixmap] = None # Map image for object icons
215
+ self._init_ui()
216
+
217
+ def _init_ui(self):
218
+ """Initialize UI components."""
219
+ # Main vertical layout
220
+ layout = QVBoxLayout()
221
+ layout.setContentsMargins(6, 6, 6, 6) # Further reduced margins
222
+ layout.setSpacing(4) # Further reduced spacing
223
+
224
+ # First row - coordinates
225
+ self._coord_label = CoordinateLabel()
226
+ self._coord_label.setStyleSheet("font-family: monospace; font-size: 21px; font-weight: bold;")
227
+ self._coord_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
228
+ layout.addWidget(self._coord_label)
229
+
230
+ # Second row - terrain info with rounded rectangle
231
+ terrain_frame = QFrame()
232
+ terrain_frame.setFrameStyle(QFrame.Shape.NoFrame)
233
+ terrain_layout = QHBoxLayout()
234
+ terrain_layout.setContentsMargins(0, 0, 0, 0)
235
+
236
+ # Terrain type label
237
+ self._terrain_label = QLabel("Plain")
238
+ self._terrain_label.setFixedWidth(50) # Reduced from 60
239
+ self._terrain_label.setStyleSheet("font-size: 12px;")
240
+ terrain_layout.addWidget(self._terrain_label)
241
+
242
+ # Terrain color indicator (custom painted widget)
243
+ self._terrain_indicator = TerrainIndicator()
244
+ self._terrain_indicator.setFixedSize(30, 16) # Reduced from 40x20 (保持不变)
245
+ terrain_layout.addWidget(self._terrain_indicator)
246
+
247
+ # Spacer
248
+ terrain_layout.addSpacerItem(QSpacerItem(10, 0, QSizePolicy.Policy.Expanding)) # Reduced from 20
249
+
250
+ # Cost label
251
+ self._cost_label = QLabel("Cost: 2")
252
+ self._cost_label.setStyleSheet("font-size: 12px; color: #666;")
253
+ terrain_layout.addWidget(self._cost_label)
254
+
255
+ terrain_frame.setLayout(terrain_layout)
256
+ layout.addWidget(terrain_frame)
257
+
258
+ # Third row - objects list
259
+ self._objects_list = QListWidget()
260
+ self._objects_list.setFrameStyle(QFrame.Shape.Box)
261
+ self._objects_list.setMaximumHeight(80) # Approximately 2 items high
262
+ self._objects_list.setStyleSheet("""
263
+ QListWidget {
264
+ border: 1px solid #ccc;
265
+ border-radius: 4px;
266
+ background-color: white;
267
+ }
268
+ QListWidget::item {
269
+ border-bottom: 1px solid #eee;
270
+ }
271
+ QListWidget::item:selected {
272
+ background-color: #e3f2fd;
273
+ }
274
+ """)
275
+ # Enable drag functionality
276
+ self._objects_list.setDragEnabled(True)
277
+ self._objects_list.setSelectionMode(QListWidget.SelectionMode.SingleSelection)
278
+
279
+ # Enable drop functionality for the widget
280
+ self.setAcceptDrops(True)
281
+
282
+ layout.addWidget(self._objects_list)
283
+
284
+ self.setLayout(layout)
285
+
286
+ # Set minimum size - further reduced to match list item width
287
+ self.setMinimumWidth(180) # Further reduced from 180 (10% reduction)
288
+ self.setMaximumWidth(180)
289
+ self.setMinimumHeight(200) # Increased for larger font
290
+ self.setMaximumHeight(200)
291
+
292
+
293
+ def _update_coord_display(self):
294
+ """Update coordinate display."""
295
+ if self._data:
296
+ self._coord_label.set_coordinates(self._data.x, self._data.y)
297
+ else:
298
+ self._coord_label.set_coordinates(0, 0)
299
+
300
+ def _update_terrain_display(self):
301
+ """Update terrain display."""
302
+ if self._data:
303
+ terrain_map = {
304
+ '2': 'Plain',
305
+ 'A': 'Swamp',
306
+ 'X': 'TWall'
307
+ }
308
+ terrain_type = terrain_map.get(self._data.terrain, 'Plain')
309
+ self._terrain_label.setText(terrain_type)
310
+
311
+ # Update terrain color
312
+ terrain_colors = {
313
+ '2': QColor(144, 238, 144), # Light green for plain
314
+ 'A': QColor(160, 82, 45), # Brown for swamp
315
+ 'X': QColor(128, 128, 128) # Gray for wall
316
+ }
317
+ color = terrain_colors.get(self._data.terrain, QColor(144, 238, 144))
318
+ self._terrain_indicator.set_color(color)
319
+
320
+ # Update cost
321
+ cost = self._data.cost
322
+ self._cost_label.setText(f"Cost: {cost}")
323
+ else:
324
+ self._terrain_label.setText("Plain")
325
+ self._terrain_indicator.set_color(QColor(144, 238, 144))
326
+ self._cost_label.setText("Cost: 2")
327
+
328
+ def _update_objects_list(self):
329
+ """Update objects list display."""
330
+ self._objects_list.clear()
331
+
332
+ if self._objects:
333
+ for obj in self._objects:
334
+ obj_id = obj.get('id', 'unknown')
335
+ obj_type = obj.get('type', 'Unknown')
336
+ obj_name = obj.get('name', obj_id)
337
+
338
+ # Extract object icon from map image if available
339
+ obj_image = None
340
+ if self._image and not self._image.isNull() and self._data:
341
+ # Calculate cell size based on map image dimensions
342
+ # Assuming map is 100x100 cells (default from qmapv)
343
+ map_width = 100
344
+ map_height = 100
345
+
346
+ cell_width = self._image.width() // map_width
347
+ cell_height = self._image.height() // map_height
348
+
349
+ # Calculate the position of this cell in the image
350
+ cell_x_offset = self._data.x * cell_width
351
+ cell_y_offset = self._data.y * cell_height
352
+
353
+ # Extract the entire cell region
354
+ if (cell_x_offset + cell_width <= self._image.width() and
355
+ cell_y_offset + cell_height <= self._image.height() and
356
+ cell_x_offset >= 0 and cell_y_offset >= 0):
357
+ # Extract the full cell
358
+ cell_image = self._image.copy(cell_x_offset, cell_y_offset, cell_width, cell_height)
359
+ obj_image = cell_image
360
+
361
+ # Get method from object data if available
362
+ method = obj.get('method', '')
363
+
364
+ # Create custom widget for the object
365
+ item_widget = QPSACellObjectItem(
366
+ obj_id, obj_type, obj_name, self._x, self._y, obj_image, method
367
+ )
368
+
369
+ # Create list widget item and set our custom widget
370
+ list_item = QListWidgetItem()
371
+ list_item.setSizeHint(item_widget.sizeHint())
372
+
373
+ self._objects_list.addItem(list_item)
374
+ self._objects_list.setItemWidget(list_item, item_widget)
375
+
376
+ # Public properties
377
+ @property
378
+ def data(self) -> Optional[CellInfo]:
379
+ """Get/set cell data."""
380
+ return self._data
381
+
382
+ @data.setter
383
+ def data(self, value: Optional[CellInfo]):
384
+ """Set cell data and update display."""
385
+ self._data = value
386
+ if value:
387
+ self._x = value.x
388
+ self._y = value.y
389
+ self._objects = value.objects
390
+ else:
391
+ self._x = 0
392
+ self._y = 0
393
+ self._objects = []
394
+
395
+ self._update_coord_display()
396
+ self._update_terrain_display()
397
+ self._update_objects_list()
398
+
399
+ @property
400
+ def x(self) -> int:
401
+ """Get X coordinate."""
402
+ return self._x
403
+
404
+ @x.setter
405
+ def x(self, value: int):
406
+ """Set X coordinate."""
407
+ self._x = value
408
+ self._update_coord_display()
409
+
410
+ @property
411
+ def y(self) -> int:
412
+ """Get Y coordinate."""
413
+ return self._y
414
+
415
+ @y.setter
416
+ def y(self, value: int):
417
+ """Set Y coordinate."""
418
+ self._y = value
419
+ self._update_coord_display()
420
+
421
+ @property
422
+ def objects(self) -> List[Dict[str, Any]]:
423
+ """Get objects list."""
424
+ return self._objects.copy()
425
+
426
+ @objects.setter
427
+ def objects(self, value: List[Dict[str, Any]]):
428
+ """Set objects list."""
429
+ self._objects = value
430
+ self._update_objects_list()
431
+
432
+ @property
433
+ def image(self) -> Optional[QPixmap]:
434
+ """Get/set map image for object icons."""
435
+ return self._image
436
+
437
+ @image.setter
438
+ def image(self, value: Optional[QPixmap]):
439
+ """Set map image for object icons."""
440
+ self._image = value
441
+ self._update_objects_list()
442
+
443
+ # Drag and drop functionality
444
+ def dragEnterEvent(self, event):
445
+ """Accept drag enter events."""
446
+ print(f"[DEBUG] QPSACellInfo drag enter: hasText={event.mimeData().hasText()}")
447
+ if event.mimeData().hasText() or event.mimeData().hasFormat("application/json"):
448
+ event.acceptProposedAction()
449
+
450
+ def dropEvent(self, event):
451
+ """Handle drop event to add objects."""
452
+ mime_data = event.mimeData()
453
+ json_data = None
454
+ image = None
455
+
456
+ print(f"[DEBUG] QPSACellInfo drop received: hasText={event.mimeData().hasText()}, hasJSON={event.mimeData().hasFormat('application/json')}, hasImage={event.mimeData().hasImage()}")
457
+
458
+ # Try to get JSON data from different sources
459
+ if mime_data.hasFormat("application/json"):
460
+ try:
461
+ json_bytes = mime_data.data("application/json")
462
+ json_str = json_bytes.data().decode('utf-8')
463
+ print(f"[DEBUG] QPSACellInfo JSON data: {json_str}")
464
+ json_data = json.loads(json_str)
465
+ except Exception as e:
466
+ print(f"[DEBUG] QPSACellInfo failed to parse JSON data: {e}")
467
+ elif mime_data.hasText():
468
+ try:
469
+ json_str = mime_data.text()
470
+ print(f"[DEBUG] QPSACellInfo text data: {json_str}")
471
+ json_data = json.loads(json_str)
472
+ except Exception as e:
473
+ print(f"[DEBUG] QPSACellInfo failed to parse text as JSON: {e}")
474
+
475
+ # Try to get image data if available
476
+ if mime_data.hasImage():
477
+ try:
478
+ image = QPixmap(mime_data.imageData())
479
+ print(f"[DEBUG] QPSACellInfo image data received: {image.size()}")
480
+ except Exception as e:
481
+ print(f"[DEBUG] QPSACellInfo failed to get image data: {e}")
482
+
483
+ if json_data:
484
+ # Handle both single object and array of objects
485
+ if isinstance(json_data, list):
486
+ # Array format: [{x, y, type, method, id, name}, ...]
487
+ print(f"[DEBUG] QPSACellInfo received array of {len(json_data)} objects")
488
+ for obj in json_data:
489
+ self._add_object_to_list(obj, image)
490
+ event.acceptProposedAction()
491
+ print(f"[DEBUG] QPSACellInfo array of objects added successfully")
492
+ elif isinstance(json_data, dict):
493
+ # Single object format: {x, y, type, method, id, name}
494
+ self._add_object_to_list(json_data, image)
495
+ event.acceptProposedAction()
496
+ print(f"[DEBUG] QPSACellInfo single object added successfully: {json_data}")
497
+ else:
498
+ print(f"[DEBUG] QPSACellInfo invalid JSON format: expected dict or list, got {type(json_data)}")
499
+ else:
500
+ print(f"[DEBUG] QPSACellInfo no valid JSON data found")
501
+
502
+ def _add_object_to_list(self, obj_data: dict, image: Optional[QPixmap] = None):
503
+ """Add a single object to the objects list."""
504
+ # Extract required fields
505
+ obj_id = obj_data.get('id', 'unknown')
506
+ obj_type = obj_data.get('type', 'Unknown')
507
+ obj_name = obj_data.get('name', obj_id)
508
+ x = obj_data.get('x', self._x) # Use current cell x if not specified
509
+ y = obj_data.get('y', self._y) # Use current cell y if not specified
510
+ method = obj_data.get('method', '')
511
+
512
+ # Check for duplicates with same id
513
+ for existing_obj in self._objects:
514
+ existing_id = existing_obj.get('id', 'unknown')
515
+ if existing_id == obj_id:
516
+ print(f"[DEBUG] QPSACellInfo rejected duplicate object: {obj_id}")
517
+ return # Reject duplicate
518
+
519
+ # Add the new object
520
+ new_obj = {
521
+ 'id': obj_id,
522
+ 'type': obj_type,
523
+ 'name': obj_name,
524
+ 'x': x,
525
+ 'y': y,
526
+ 'method': method
527
+ }
528
+
529
+ self._objects.append(new_obj)
530
+ print(f"[DEBUG] QPSACellInfo added object: {new_obj}")
531
+
532
+ # Update the display
533
+ self._update_objects_list()
534
+
535
+
536
+ class TerrainIndicator(QWidget):
537
+ """Custom widget for displaying terrain color indicator."""
538
+
539
+ def __init__(self, parent=None):
540
+ super().__init__(parent)
541
+ self._color = QColor(144, 238, 144) # Default light green
542
+
543
+ def set_color(self, color: QColor):
544
+ """Set the terrain color."""
545
+ self._color = color
546
+ self.update()
547
+
548
+ def paintEvent(self, event):
549
+ """Custom paint event to draw rounded rectangle."""
550
+ painter = QPainter(self)
551
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
552
+
553
+ # Draw rounded rectangle with border
554
+ rect = self.rect()
555
+
556
+ # Border
557
+ painter.setPen(QPen(QColor(139, 115, 85), 1)) # #8B7355
558
+
559
+ # Fill
560
+ painter.setBrush(QBrush(self._color))
561
+
562
+ # Draw rounded rectangle
563
+ painter.drawRoundedRect(rect.adjusted(1, 1, -1, -1), 4, 4)
564
+
565
+ def sizeHint(self):
566
+ """Preferred size."""
567
+ return self.minimumSize()