chartflow 0.1__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 (79) hide show
  1. chartflow/__init__.py +28 -0
  2. chartflow/_verify_importexport.py +169 -0
  3. chartflow/edit/__init__.py +119 -0
  4. chartflow/edit/code_editor.py +1007 -0
  5. chartflow/edit/code_folder.py +523 -0
  6. chartflow/edit/completer.py +2652 -0
  7. chartflow/edit/context_analyzer.py +521 -0
  8. chartflow/edit/demo.py +469 -0
  9. chartflow/edit/line_number_area.py +548 -0
  10. chartflow/edit/minimap.py +581 -0
  11. chartflow/edit/python_editor.py +509 -0
  12. chartflow/edit/syntax_highlighter.py +291 -0
  13. chartflow/edit/test_editor.py +190 -0
  14. chartflow/flow/__init__.py +31 -0
  15. chartflow/flow/_demo.py +98 -0
  16. chartflow/flow/chart.py +1101 -0
  17. chartflow/flow/editor.py +375 -0
  18. chartflow/flow/flow.py +6 -0
  19. chartflow/flow/qchart.py +1250 -0
  20. chartflow/flow/test/__init__.py +0 -0
  21. chartflow/flow/test/test_chart.py +537 -0
  22. chartflow/flow/test_decorator_sync.py +102 -0
  23. chartflow/fsm/__init__.py +84 -0
  24. chartflow/fsm/decorators.py +395 -0
  25. chartflow/fsm/demo.py +381 -0
  26. chartflow/fsm/demo_pyqt6.py +881 -0
  27. chartflow/fsm/demo_tree.py +46 -0
  28. chartflow/fsm/logic.py +447 -0
  29. chartflow/fsm/meta.py +569 -0
  30. chartflow/fsm/scheduler.py +427 -0
  31. chartflow/fsm/stage.py +2079 -0
  32. chartflow/fsm/stdio.py +66 -0
  33. chartflow/fsm/test/__init__.py +7 -0
  34. chartflow/fsm/test/test_decorators.py +210 -0
  35. chartflow/fsm/test/test_events.py +202 -0
  36. chartflow/fsm/test/test_integration.py +596 -0
  37. chartflow/fsm/test/test_logic.py +371 -0
  38. chartflow/fsm/test/test_meta.py +192 -0
  39. chartflow/fsm/test/test_runtime_param.py +336 -0
  40. chartflow/fsm/test/test_scheduler.py +280 -0
  41. chartflow/fsm/test/test_stage.py +314 -0
  42. chartflow/fsm/test_behavior_events.py +263 -0
  43. chartflow/fsm/test_nested_parallel.py +80 -0
  44. chartflow/fsm/test_stage_spec.py +448 -0
  45. chartflow/fsm/utils.py +34 -0
  46. chartflow/node/__init__.py +66 -0
  47. chartflow/node/_node_base.py +727 -0
  48. chartflow/node/_node_interaction.py +488 -0
  49. chartflow/node/_node_interface.py +426 -0
  50. chartflow/node/_node_markers.py +648 -0
  51. chartflow/node/_node_ports.py +536 -0
  52. chartflow/node/_port_interface.py +111 -0
  53. chartflow/node/arrow_item.py +287 -0
  54. chartflow/node/color_utils.py +113 -0
  55. chartflow/node/connection_item.py +939 -0
  56. chartflow/node/demo.py +258 -0
  57. chartflow/node/demo_arrow_drag.py +46 -0
  58. chartflow/node/demo_decorator.py +63 -0
  59. chartflow/node/demo_ports.py +69 -0
  60. chartflow/node/enums.py +35 -0
  61. chartflow/node/example.py +84 -0
  62. chartflow/node/layout_utils.py +307 -0
  63. chartflow/node/main_window.py +196 -0
  64. chartflow/node/menu_bar.py +240 -0
  65. chartflow/node/node_canvas.py +1730 -0
  66. chartflow/node/node_item.py +754 -0
  67. chartflow/node/popmenu.py +472 -0
  68. chartflow/node/port_item.py +591 -0
  69. chartflow/node/qnode_editor.py +73 -0
  70. chartflow/node/selection_rect.py +53 -0
  71. chartflow/node/style_panel.py +615 -0
  72. chartflow/node/styles.py +63 -0
  73. chartflow/node/test_qnode.py +2336 -0
  74. chartflow/showcase.py +528 -0
  75. chartflow/theme.py +508 -0
  76. chartflow-0.1.dist-info/METADATA +23 -0
  77. chartflow-0.1.dist-info/RECORD +79 -0
  78. chartflow-0.1.dist-info/WHEEL +5 -0
  79. chartflow-0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,548 @@
1
+ """
2
+ 行号边栏
3
+ 显示行号和折叠按钮 - 扁平化设计风格
4
+ """
5
+
6
+ from PyQt6.QtWidgets import QWidget
7
+ from PyQt6.QtCore import Qt, QRect, QSize, pyqtSignal, QPoint
8
+ from PyQt6.QtGui import QPainter, QColor, QFont, QMouseEvent, QPaintEvent, QFontMetrics, QPolygon, QPen
9
+ from typing import Optional, Callable, List
10
+
11
+
12
+ class LineNumberArea(QWidget):
13
+ """行号边栏控件 - 扁平化风格"""
14
+
15
+ # 信号
16
+ fold_clicked = pyqtSignal(int) # 折叠按钮点击信号 (line_number)
17
+ line_clicked = pyqtSignal(int) # 行号点击信号 (line_number)
18
+
19
+ # 颜色配置(浅色主题)
20
+ COLORS = {
21
+ 'background': '#f8f9fa',
22
+ 'fold_area_bg': '#f1f3f4',
23
+ 'line_area_bg': '#f8f9fa',
24
+ 'text': '#9aa0a6',
25
+ 'text_active': '#202124',
26
+ 'border': '#dadce0',
27
+ 'divider': '#dadce0',
28
+ 'fold_btn_bg': '#e8eaed',
29
+ 'fold_btn_bg_hover': '#dadce0',
30
+ 'fold_btn_icon': '#5f6368',
31
+ 'fold_btn_icon_active': '#1a73e8',
32
+ 'current_line': '#e8f0fe',
33
+ }
34
+
35
+ # 区域宽度配置
36
+ FOLD_AREA_WIDTH = 22 # 折叠区域宽度
37
+ LINE_NUMBER_PADDING = 8 # 行号边距
38
+ DIVIDER_WIDTH = 1 # 分割线宽度
39
+
40
+ def __init__(self, editor=None, parent=None):
41
+ super().__init__(parent)
42
+
43
+ self._editor = editor
44
+ self._line_count: int = 0
45
+ self._current_line: int = 1
46
+ self._line_height: int = 20
47
+ self._font: QFont = QFont('Consolas', 14)
48
+ self._foldable_lines: set = set() # 可折叠的行
49
+ self._folded_lines: set = set() # 已折叠的行
50
+ self._fold_markers: dict = {} # 折叠标记位置 {line: rect}
51
+ self._hovered_fold_line: int = -1 # 鼠标悬停的折叠行
52
+
53
+ # 设置固定宽度(将根据行数动态调整)
54
+ self.setFixedWidth(60)
55
+
56
+ # 设置背景色
57
+ self.setStyleSheet(f"background-color: {self.COLORS['background']};")
58
+ print(f"[LineNumberArea] __init__: parent={parent}, width={self.width()}")
59
+
60
+ # 启用鼠标跟踪
61
+ self.setMouseTracking(True)
62
+
63
+ def set_line_count(self, count: int):
64
+ """设置行数"""
65
+ self._line_count = count
66
+ self._update_width()
67
+ self.update()
68
+
69
+ def set_current_line(self, line: int):
70
+ """设置当前行"""
71
+ self._current_line = line
72
+ self.update()
73
+
74
+ def set_line_height(self, height: int):
75
+ """设置行高"""
76
+ self._line_height = height
77
+ self.update()
78
+
79
+ def set_font(self, font: QFont):
80
+ """设置字体"""
81
+ self._font = font
82
+ self._update_width()
83
+ self.update()
84
+
85
+ def set_foldable_lines(self, lines: List[int]):
86
+ """设置可折叠的行"""
87
+ self._foldable_lines = set(lines)
88
+ self.update()
89
+
90
+ def set_folded_lines(self, lines: List[int]):
91
+ """设置已折叠的行"""
92
+ self._folded_lines = set(lines)
93
+ self.update()
94
+
95
+ def add_foldable_line(self, line: int):
96
+ """添加可折叠行"""
97
+ self._foldable_lines.add(line)
98
+ self.update()
99
+
100
+ def remove_foldable_line(self, line: int):
101
+ """移除可折叠行"""
102
+ self._foldable_lines.discard(line)
103
+ self._folded_lines.discard(line)
104
+ self.update()
105
+
106
+ def toggle_fold(self, line: int):
107
+ """切换折叠状态"""
108
+ if line in self._folded_lines:
109
+ self._folded_lines.discard(line)
110
+ else:
111
+ self._folded_lines.add(line)
112
+ self.update()
113
+
114
+ def _update_width(self):
115
+ """根据行数更新宽度"""
116
+ if self._line_count <= 0:
117
+ self.setFixedWidth(self.FOLD_AREA_WIDTH + self.LINE_NUMBER_PADDING * 2 + self.DIVIDER_WIDTH)
118
+ return
119
+
120
+ # 计算所需宽度
121
+ metrics = QFontMetrics(self._font)
122
+ max_digits = len(str(self._line_count))
123
+
124
+ # 折叠区域 + 行号宽度 + 边距 + 分割线
125
+ text_width = metrics.horizontalAdvance('9' * max_digits)
126
+ line_area_width = text_width + self.LINE_NUMBER_PADDING
127
+
128
+ total_width = self.FOLD_AREA_WIDTH + self.DIVIDER_WIDTH + line_area_width
129
+ self.setFixedWidth(max(50, total_width))
130
+
131
+ def sizeHint(self):
132
+ """大小建议"""
133
+ return QSize(self.width(), self._line_count * self._line_height)
134
+
135
+ def paintEvent(self, event: QPaintEvent):
136
+ """绘制事件"""
137
+ painter = QPainter(self)
138
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
139
+
140
+ # 强制填充红色背景用于调试
141
+ painter.fillRect(self.rect(), QColor('red'))
142
+
143
+ # 绘制折叠区域背景
144
+ fold_rect = QRect(0, 0, self.FOLD_AREA_WIDTH, self.height())
145
+ painter.fillRect(fold_rect, QColor(self.COLORS['fold_area_bg']))
146
+
147
+ # 绘制行号区域背景
148
+ line_rect = QRect(self.FOLD_AREA_WIDTH + self.DIVIDER_WIDTH, 0,
149
+ self.width() - self.FOLD_AREA_WIDTH - self.DIVIDER_WIDTH, self.height())
150
+ painter.fillRect(line_rect, QColor(self.COLORS['line_area_bg']))
151
+
152
+ # 绘制分割线(在折叠区域和行号区域之间)
153
+ divider_x = self.FOLD_AREA_WIDTH
154
+ painter.setPen(QColor(self.COLORS['divider']))
155
+ painter.drawLine(divider_x, 0, divider_x, self.height())
156
+
157
+ # 绘制右边框
158
+ painter.setPen(QColor(self.COLORS['border']))
159
+ painter.drawLine(self.width() - 1, 0, self.width() - 1, self.height())
160
+
161
+ if self._line_count <= 0:
162
+ painter.end()
163
+ return
164
+
165
+ # 设置字体
166
+ painter.setFont(self._font)
167
+ metrics = QFontMetrics(self._font)
168
+
169
+ # 从父编辑器获取可见行范围
170
+ first_line = 1
171
+ last_line = self._line_count
172
+ if self._editor:
173
+ first_line = self._editor.firstVisibleBlock().blockNumber() + 1
174
+ # 计算最后一个可见行
175
+ block = self._editor.firstVisibleBlock()
176
+ viewport_height = self._editor.viewport().height()
177
+ last_line = first_line
178
+ while block.isValid():
179
+ block_rect = self._editor.blockBoundingGeometry(block)
180
+ if block_rect.top() > viewport_height:
181
+ break
182
+ last_line = block.blockNumber() + 1
183
+ block = block.next()
184
+ last_line = min(last_line, self._line_count)
185
+
186
+ # 绘制每一行 - 直接从编辑器获取实际位置
187
+ block = self._editor.document().findBlockByNumber(first_line - 1) if self._editor else None
188
+ # 获取折叠区域信息(如果有)
189
+ folded_regions = []
190
+ if hasattr(self._editor, '_folder'):
191
+ for region in self._editor._folder.get_regions():
192
+ if region.collapsed:
193
+ folded_regions.append((region.start_line, region.end_line))
194
+
195
+ # 计算折叠导致的y偏移函数
196
+ # 注意:被折叠的内部行是 (start+1) 到 end
197
+ line_height = self._line_height # 使用固定的行高
198
+ def get_fold_offset(line):
199
+ offset = 0
200
+ for start, end in folded_regions:
201
+ if line > start:
202
+ # 被折叠的内部行数 = end - start
203
+ # 这些行在折叠后会消失,所以需要减去它们的高度
204
+ if line > end:
205
+ # 完全在折叠区域之后,减去所有被折叠的内部行
206
+ offset += (end - start) * line_height
207
+ # 如果在折叠区域内(start < line <= end),正常显示,不偏移
208
+ return offset
209
+
210
+ for line in range(first_line, last_line + 1):
211
+ # 检查是否应该跳过此行(被折叠的内部行,不是起始行)
212
+ is_folded_internal = False
213
+ for start, end in folded_regions:
214
+ if start < line <= end:
215
+ # 这是折叠区域的内部行,跳过
216
+ is_folded_internal = True
217
+ break
218
+
219
+ if is_folded_internal:
220
+ # 移动到下一个块但不绘制
221
+ if block:
222
+ block = block.next()
223
+ continue
224
+
225
+ # 计算y位置:直接从编辑器获取该行的实际位置,并应用折叠偏移
226
+ y = 0
227
+ if block and block.isValid():
228
+ block_rect = self._editor.blockBoundingGeometry(block)
229
+ content_offset = self._editor.contentOffset()
230
+ raw_y = int(block_rect.y() + content_offset.y())
231
+ # 应用折叠偏移
232
+ fold_offset = get_fold_offset(line)
233
+ y = raw_y - fold_offset
234
+ # 更新_line_height为实际行高(用于后续绘制元素)
235
+ actual_height = int(block_rect.height())
236
+ if actual_height > 0:
237
+ self._line_height = actual_height
238
+
239
+ # 绘制当前行高亮(覆盖整个宽度)
240
+ if line == self._current_line:
241
+ painter.fillRect(
242
+ 0, y, self.width() - 1, self._line_height,
243
+ QColor(self.COLORS['current_line'])
244
+ )
245
+
246
+ # 绘制折叠标记
247
+ if line in self._foldable_lines:
248
+ self._draw_fold_marker(painter, line, y)
249
+
250
+ # 绘制行号
251
+ is_active = (line == self._current_line)
252
+ self._draw_line_number(painter, line, y, is_active)
253
+
254
+ # 移动到下一个块
255
+ if block:
256
+ block = block.next()
257
+
258
+ painter.end()
259
+
260
+ def _draw_fold_marker(self, painter: QPainter, line: int, y: int):
261
+ """绘制扁平化折叠标记"""
262
+ is_hovered = (line == self._hovered_fold_line)
263
+ is_collapsed = (line in self._folded_lines)
264
+
265
+ # 按钮尺寸和位置(在折叠区域中央)
266
+ btn_size = 14
267
+ btn_x = (self.FOLD_AREA_WIDTH - btn_size) // 2
268
+ btn_y = int(y + (self._line_height - btn_size) // 2)
269
+
270
+ # 存储标记位置(扩大点击区域)
271
+ self._fold_markers[line] = QRect(0, y, self.FOLD_AREA_WIDTH, self._line_height)
272
+
273
+ # 绘制按钮背景(扁平化风格)
274
+ if is_hovered:
275
+ bg_color = QColor(self.COLORS['fold_btn_bg_hover'])
276
+ else:
277
+ bg_color = QColor(self.COLORS['fold_btn_bg'])
278
+
279
+ painter.setPen(Qt.PenStyle.NoPen)
280
+ painter.setBrush(bg_color)
281
+ painter.drawRoundedRect(btn_x, btn_y, btn_size, btn_size, 3, 3)
282
+
283
+ # 绘制图标颜色
284
+ icon_color = QColor(self.COLORS['fold_btn_icon_active']) if is_hovered else QColor(self.COLORS['fold_btn_icon'])
285
+ painter.setPen(icon_color)
286
+ painter.setBrush(icon_color)
287
+
288
+ # 绘制加号/减号图标(扁平化风格)
289
+ center_x = btn_x + btn_size // 2
290
+ center_y = btn_y + btn_size // 2
291
+ line_length = 6
292
+ line_thickness = 1.5
293
+
294
+ if is_collapsed:
295
+ # 折叠状态:加号(+)
296
+ # 水平线
297
+ painter.setPen(QPen(icon_color, line_thickness))
298
+ painter.drawLine(
299
+ center_x - line_length // 2, center_y,
300
+ center_x + line_length // 2, center_y
301
+ )
302
+ # 垂直线
303
+ painter.drawLine(
304
+ center_x, center_y - line_length // 2,
305
+ center_x, center_y + line_length // 2
306
+ )
307
+ else:
308
+ # 展开状态:减号(-)
309
+ painter.setPen(QPen(icon_color, line_thickness))
310
+ painter.drawLine(
311
+ center_x - line_length // 2, center_y,
312
+ center_x + line_length // 2, center_y
313
+ )
314
+
315
+ def _draw_line_number(self, painter: QPainter, line: int, y: int, is_active: bool):
316
+ """绘制行号"""
317
+ # 设置颜色
318
+ color = self.COLORS['text_active'] if is_active else self.COLORS['text']
319
+ painter.setPen(QColor(color))
320
+
321
+ # 计算位置
322
+ text = str(line)
323
+ metrics = QFontMetrics(self._font)
324
+ text_width = metrics.horizontalAdvance(text)
325
+
326
+ # 行号右对齐,位于行号区域内
327
+ line_area_start = self.FOLD_AREA_WIDTH + self.DIVIDER_WIDTH
328
+ x = self.width() - text_width - self.LINE_NUMBER_PADDING
329
+ text_y = y + (self._line_height - metrics.height()) // 2 + metrics.ascent()
330
+
331
+ # 绘制文本
332
+ painter.drawText(x, text_y, text)
333
+
334
+ def _get_line_at_y(self, y: int) -> int:
335
+ """根据y坐标获取行号 - 使用与绘制相同的逻辑"""
336
+ if not self._editor or self._line_count <= 0:
337
+ return 1
338
+
339
+ # 从编辑器获取第一可见行
340
+ first_line = self._editor.firstVisibleBlock().blockNumber() + 1
341
+ block = self._editor.document().findBlockByNumber(first_line - 1)
342
+
343
+ # 遍历可见行,找到对应的行
344
+ for line_num in range(first_line, self._line_count + 1):
345
+ if block.isValid():
346
+ block_rect = self._editor.blockBoundingGeometry(block)
347
+ content_offset = self._editor.contentOffset()
348
+ block_y = int(block_rect.y() + content_offset.y())
349
+ block_height = int(block_rect.height())
350
+
351
+ if block_y <= y < block_y + block_height:
352
+ return line_num
353
+
354
+ block = block.next()
355
+
356
+ # 如果不在任何行内,返回最接近的行
357
+ return min(max(1, y // self._line_height + 1), self._line_count)
358
+
359
+ def mousePressEvent(self, event: QMouseEvent):
360
+ """鼠标按下事件"""
361
+ if event.button() == Qt.MouseButton.LeftButton:
362
+ y = event.pos().y()
363
+ line = self._get_line_at_y(y)
364
+
365
+ if 1 <= line <= self._line_count:
366
+ x = event.pos().x()
367
+
368
+ # 检查是否点击在折叠区域
369
+ if x < self.FOLD_AREA_WIDTH and line in self._foldable_lines:
370
+ self.fold_clicked.emit(line)
371
+ return
372
+
373
+ # 否则发送行号点击信号
374
+ self.line_clicked.emit(line)
375
+
376
+ def mouseMoveEvent(self, event: QMouseEvent):
377
+ """鼠标移动事件 - 用于hover效果"""
378
+ y = event.pos().y()
379
+ line = self._get_line_at_y(y)
380
+ x = event.pos().x()
381
+
382
+ if x < self.FOLD_AREA_WIDTH and line in self._foldable_lines and 1 <= line <= self._line_count:
383
+ if self._hovered_fold_line != line:
384
+ self._hovered_fold_line = line
385
+ self.update()
386
+ else:
387
+ if self._hovered_fold_line != -1:
388
+ self._hovered_fold_line = -1
389
+ self.update()
390
+
391
+ def leaveEvent(self, event):
392
+ """鼠标离开事件"""
393
+ if self._hovered_fold_line != -1:
394
+ self._hovered_fold_line = -1
395
+ self.update()
396
+
397
+ def get_line_at_y(self, y: int) -> int:
398
+ """获取指定y坐标对应的行号"""
399
+ return self._get_line_at_y(y)
400
+
401
+ def get_y_for_line(self, line: int) -> int:
402
+ """获取指定行号的y坐标"""
403
+ return (line - 1) * self._line_height
404
+
405
+
406
+ class FoldingIndicator:
407
+ """折叠指示器 - 用于编辑器边距(扁平化风格)"""
408
+
409
+ SIZE = 14
410
+
411
+ def __init__(self, line: int, fold_type: str = 'block'):
412
+ self.line = line
413
+ self.fold_type = fold_type
414
+ self.collapsed = False
415
+ self.rect: Optional[QRect] = None
416
+
417
+ def draw(self, painter: QPainter, x: int, y: int, height: int):
418
+ """绘制扁平化折叠指示器"""
419
+ size = self.SIZE
420
+
421
+ # 计算位置(居中)
422
+ marker_x = x + (22 - size) // 2 # 22是折叠区域宽度
423
+ marker_y = y + (height - size) // 2
424
+
425
+ # 存储矩形
426
+ self.rect = QRect(marker_x, marker_y, size, size)
427
+
428
+ # 绘制按钮背景(扁平化风格)
429
+ painter.setPen(Qt.PenStyle.NoPen)
430
+ painter.setBrush(QColor('#e8eaed'))
431
+ painter.drawRoundedRect(marker_x, marker_y, size, size, 3, 3)
432
+
433
+ # 绘制图标
434
+ icon_color = QColor('#5f6368')
435
+ painter.setPen(QPen(icon_color, 1.5))
436
+
437
+ center_x = marker_x + size // 2
438
+ center_y = marker_y + size // 2
439
+ line_length = 6
440
+
441
+ if self.collapsed:
442
+ # 折叠状态:加号(+)
443
+ # 水平线
444
+ painter.drawLine(
445
+ center_x - line_length // 2, center_y,
446
+ center_x + line_length // 2, center_y
447
+ )
448
+ # 垂直线
449
+ painter.drawLine(
450
+ center_x, center_y - line_length // 2,
451
+ center_x, center_y + line_length // 2
452
+ )
453
+ else:
454
+ # 展开状态:减号(-)
455
+ painter.drawLine(
456
+ center_x - line_length // 2, center_y,
457
+ center_x + line_length // 2, center_y
458
+ )
459
+
460
+ def contains(self, x: int, y: int) -> bool:
461
+ """检查点是否在指示器内"""
462
+ if self.rect:
463
+ return self.rect.contains(x, y)
464
+ return False
465
+
466
+
467
+ class LineNumberDelegate:
468
+ """行号绘制委托 - 用于自定义编辑器"""
469
+
470
+ def __init__(self, editor):
471
+ self._editor = editor
472
+ self._font = QFont('Consolas', 14)
473
+ self._current_line = 1
474
+ self._fold_indicators: dict = {} # {line: FoldingIndicator}
475
+
476
+ def paint(self, painter: QPainter, rect: QRect, line: int):
477
+ """绘制行号"""
478
+ fold_area_width = 22
479
+ divider_width = 1
480
+
481
+ # 绘制折叠区域背景
482
+ fold_rect = QRect(rect.left(), rect.top(), fold_area_width, rect.height())
483
+ painter.fillRect(fold_rect, QColor('#f1f3f4'))
484
+
485
+ # 绘制行号区域背景
486
+ line_rect = QRect(rect.left() + fold_area_width + divider_width, rect.top(),
487
+ rect.width() - fold_area_width - divider_width, rect.height())
488
+ painter.fillRect(line_rect, QColor('#f8f9fa'))
489
+
490
+ # 绘制分割线
491
+ divider_x = rect.left() + fold_area_width
492
+ painter.setPen(QColor('#dadce0'))
493
+ painter.drawLine(divider_x, rect.top(), divider_x, rect.bottom())
494
+
495
+ # 绘制右边框
496
+ painter.setPen(QColor('#dadce0'))
497
+ painter.drawLine(rect.right(), rect.top(), rect.right(), rect.bottom())
498
+
499
+ # 绘制当前行高亮
500
+ if line == self._current_line:
501
+ painter.fillRect(rect, QColor('#e8f0fe'))
502
+
503
+ # 绘制折叠指示器
504
+ if line in self._fold_indicators:
505
+ indicator = self._fold_indicators[line]
506
+ indicator.draw(painter, rect.left(), rect.top(), rect.height())
507
+
508
+ # 绘制行号
509
+ painter.setFont(self._font)
510
+ painter.setPen(QColor('#9aa0a6') if line != self._current_line else QColor('#202124'))
511
+
512
+ metrics = QFontMetrics(self._font)
513
+ text = str(line)
514
+ text_width = metrics.horizontalAdvance(text)
515
+
516
+ x = rect.right() - text_width - 8
517
+ y = rect.top() + (rect.height() - metrics.height()) // 2 + metrics.ascent()
518
+
519
+ painter.drawText(x, y, text)
520
+
521
+ def set_current_line(self, line: int):
522
+ """设置当前行"""
523
+ self._current_line = line
524
+
525
+ def add_fold_indicator(self, line: int, fold_type: str = 'block'):
526
+ """添加折叠指示器"""
527
+ self._fold_indicators[line] = FoldingIndicator(line, fold_type)
528
+
529
+ def remove_fold_indicator(self, line: int):
530
+ """移除折叠指示器"""
531
+ if line in self._fold_indicators:
532
+ del self._fold_indicators[line]
533
+
534
+ def toggle_fold(self, line: int) -> bool:
535
+ """切换折叠状态"""
536
+ if line in self._fold_indicators:
537
+ indicator = self._fold_indicators[line]
538
+ indicator.collapsed = not indicator.collapsed
539
+ return indicator.collapsed
540
+ return False
541
+
542
+ def get_indicator_at(self, x: int, y: int, line: int) -> Optional[FoldingIndicator]:
543
+ """获取指定位置的折叠指示器"""
544
+ if line in self._fold_indicators:
545
+ indicator = self._fold_indicators[line]
546
+ if indicator.contains(x, y):
547
+ return indicator
548
+ return None