Release icon and connection direction update

This commit is contained in:
Данил Ярош 2026-05-21 09:30:15 +03:00
parent 59bbad8288
commit ab46e603b9
7 changed files with 96 additions and 7 deletions

View File

@ -1,12 +1,24 @@
# -*- mode: python ; coding: utf-8 -*-
from pathlib import Path
spec_dir = Path(SPECPATH)
a = Analysis(
['main.py'],
pathex=['.'],
[str(spec_dir / 'main.py')],
pathex=[str(spec_dir), str(spec_dir.parent)],
binaries=[],
datas=[],
hiddenimports=[],
hiddenimports=[
'canvas',
'icons',
'main_window',
'models',
'panel_mapping_generator',
'unfolding',
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
@ -35,4 +47,5 @@ exe = EXE(
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=str(spec_dir / 'assets' / 'ledmapper.ico'),
)

BIN
assets/ledmapper.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -529,15 +529,21 @@ class LedCanvas(QGraphicsView):
inner_alpha = 0.9 if is_active else 0.25
bridge_width = 1.9 if is_active else 1.0
bridge_alpha = 0.65 if is_active else 0.18
arrow_width = 1.8 if is_active else 1.0
arrow_alpha = 0.95 if is_active else 0.28
inner_path = QPainterPath()
inner_arrows = QPainterPath()
for block in line.blocks:
ordered_items = [self.items_by_id[point_id] for point_id in block.point_ids if point_id in self.items_by_id]
for first, second in zip(ordered_items, ordered_items[1:]):
self._append_connection(inner_path, first.pos(), second.pos())
self._append_arrow_marker(inner_arrows, first.pos(), second.pos())
self._add_connection_path(inner_path, line.color, inner_width, inner_alpha)
self._add_connection_path(inner_arrows, line.color, arrow_width, arrow_alpha, z_value=2)
previous_block_end = None
bridge_path = QPainterPath()
bridge_arrows = QPainterPath()
for block in line.blocks:
if not block.point_ids:
continue
@ -545,22 +551,48 @@ class LedCanvas(QGraphicsView):
block_end = self.items_by_id.get(block.point_ids[-1])
if block_start is not None and previous_block_end is not None:
self._append_connection(bridge_path, previous_block_end.pos(), block_start.pos())
self._append_arrow_marker(bridge_arrows, previous_block_end.pos(), block_start.pos())
if block_end is not None:
previous_block_end = block_end
self._add_connection_path(bridge_path, line.color, bridge_width, bridge_alpha)
self._add_connection_path(bridge_arrows, line.color, arrow_width, bridge_alpha, z_value=2)
@staticmethod
def _append_connection(path: QPainterPath, start: QPointF, end: QPointF) -> None:
path.moveTo(start)
path.lineTo(end)
def _add_connection_path(self, path: QPainterPath, color: str, width: float, alpha: float) -> None:
@staticmethod
def _append_arrow_marker(path: QPainterPath, start: QPointF, end: QPointF) -> None:
dx = end.x() - start.x()
dy = end.y() - start.y()
length = math.hypot(dx, dy)
if length < 8.0:
return
unit_x = dx / length
unit_y = dy / length
mid_x = start.x() + dx * 0.55
mid_y = start.y() + dy * 0.55
arrow_length = min(7.0, length * 0.28)
arrow_width = arrow_length * 0.55
base_x = mid_x - unit_x * arrow_length
base_y = mid_y - unit_y * arrow_length
perp_x = -unit_y
perp_y = unit_x
left = QPointF(base_x + perp_x * arrow_width, base_y + perp_y * arrow_width)
right = QPointF(base_x - perp_x * arrow_width, base_y - perp_y * arrow_width)
tip = QPointF(mid_x, mid_y)
path.moveTo(left)
path.lineTo(tip)
path.lineTo(right)
def _add_connection_path(self, path: QPainterPath, color: str, width: float, alpha: float, *, z_value: float = 1) -> None:
if path.isEmpty():
return
pen_color = QColor(color)
pen_color.setAlphaF(alpha)
item = self.scene().addPath(path, QPen(pen_color, width))
item.setZValue(1)
item.setZValue(z_value)
self.connection_items.append(item)
def _selection_has_assignment(self) -> bool:

View File

@ -14,6 +14,7 @@ LUCIDE_PATHS = {
"file-plus": '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><path d="M12 18v-6"/><path d="M9 15h6"/>',
"file-code": '<path d="M14 2v6h6"/><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="m10 13-2 2 2 2"/><path d="m14 17 2-2-2-2"/>',
"folder-open": '<path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6A2 2 0 0 1 18.46 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4.9a2 2 0 0 1 1.69.9L12 6h8a2 2 0 0 1 2 2v2"/><path d="M2 13h4"/>',
"git-branch": '<line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>',
"grid-2x2": '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 12h18"/><path d="M12 3v18"/>',
"layers": '<path d="m12.83 2.18 8.34 4.66a1 1 0 0 1 0 1.74l-8.34 4.66a1.7 1.7 0 0 1-1.66 0L2.83 8.58a1 1 0 0 1 0-1.74l8.34-4.66a1.7 1.7 0 0 1 1.66 0Z"/><path d="m22 12.5-9.17 5.12a1.7 1.7 0 0 1-1.66 0L2 12.5"/><path d="m22 17.5-9.17 5.12a1.7 1.7 0 0 1-1.66 0L2 17.5"/>',
"list-plus": '<path d="M11 12H3"/><path d="M16 6H3"/><path d="M16 18H3"/><path d="M18 9v6"/><path d="M21 12h-6"/>',

15
main.py
View File

@ -1,15 +1,28 @@
from __future__ import annotations
import sys
from pathlib import Path
from PySide6.QtWidgets import QApplication
from main_window import MainWindow
APP_DIR = Path(__file__).resolve().parent
if str(APP_DIR) not in sys.path:
sys.path.insert(0, str(APP_DIR))
try:
from .icons import lucide_icon
from .main_window import MainWindow
except ImportError:
from icons import lucide_icon
from main_window import MainWindow
def main() -> int:
app = QApplication(sys.argv)
app_icon = lucide_icon("git-branch", "#e5e7eb", 256)
app.setWindowIcon(app_icon)
window = MainWindow()
window.setWindowIcon(app_icon)
window.show()
return app.exec()

View File

@ -81,6 +81,7 @@ def main() -> None:
test_canvas_rebuild_can_preserve_user_zoom,
test_grid_toggle_adds_grid_items,
test_canvas_connects_new_block_to_previous_non_empty_block,
test_canvas_draws_direction_arrows_on_connections,
test_canvas_rebuild_drops_deleted_selection_items,
test_canvas_refresh_does_not_accumulate_connection_items,
test_main_window_has_classic_menu_bar,
@ -767,6 +768,32 @@ def test_canvas_connects_new_block_to_previous_non_empty_block() -> None:
assert bridge_found
def test_canvas_draws_direction_arrows_on_connections() -> None:
if LedCanvas is None or QApplication is None:
return
app = QApplication.instance() or QApplication([])
with tempfile.TemporaryDirectory() as temp_dir:
path = Path(temp_dir) / "arrows.json"
points = [
{"id": 0, "x": 0.0, "y": 0.0, "row": 0, "col": 0},
{"id": 1, "x": 34.0, "y": 0.0, "row": 0, "col": 1},
{"id": 2, "x": 68.0, "y": 0.0, "row": 0, "col": 2},
]
path.write_text(json.dumps({"circles": points}), encoding="utf-8")
doc = MappingDocument()
doc.load(path)
line = doc.add_line()
doc.add_block(line.id, [0, 1, 2])
canvas = LedCanvas()
canvas.set_document(doc)
canvas.set_active_line(line.id)
arrow_paths = [item.path() for item in canvas.connection_items if item.zValue() == 2]
assert arrow_paths
assert sum(path.elementCount() for path in arrow_paths) >= 6
def test_canvas_rebuild_drops_deleted_selection_items() -> None:
if LedCanvas is None or QApplication is None:
return
@ -963,6 +990,7 @@ def test_lucide_icon_names_are_declared() -> None:
"file-plus",
"file-code",
"folder-open",
"git-branch",
"grid-2x2",
"layers",
"paintbrush",
@ -1356,6 +1384,7 @@ def test_main_window_restores_tree_state_after_refresh_error() -> None:
def test_package_imports_work_from_project_root() -> None:
importlib.import_module("app.main")
importlib.import_module("app.canvas")
importlib.import_module("app.main_window")

View File

@ -1 +1,2 @@
PySide6>=6.7
PySide6==6.11.1
pyinstaller==6.20.0