Lemontropia-Suite/ui/attachment_selector.py

288 lines
12 KiB
Python

"""
Weapon Attachment Selector for Lemontropia Suite
Browse and search weapon attachments (scopes, sights, amplifiers, absorbers)
"""
from decimal import Decimal
from PyQt6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
QTreeWidget, QTreeWidgetItem, QHeaderView, QLabel, QDialogButtonBox,
QProgressBar, QGroupBox, QFormLayout, QComboBox, QTabWidget, QWidget
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QColor
from typing import Optional, List
from core.nexus_full_api import get_nexus_api, NexusAttachment
class AttachmentLoaderThread(QThread):
"""Background thread for loading attachments from API."""
attachments_loaded = pyqtSignal(list)
error_occurred = pyqtSignal(str)
def run(self):
try:
api = get_nexus_api()
attachments = api.get_all_attachments()
self.attachments_loaded.emit(attachments)
except Exception as e:
self.error_occurred.emit(str(e))
class AttachmentSelectorDialog(QDialog):
"""Dialog for selecting weapon attachments from Entropia Nexus API."""
attachment_selected = pyqtSignal(NexusAttachment)
def __init__(self, parent=None, attachment_type: str = ""):
super().__init__(parent)
self.preferred_type = attachment_type.lower()
type_names = {
"amplifier": "Amplifier",
"scope": "Scope",
"sight": "Sight",
"absorber": "Absorber"
}
title_type = type_names.get(self.preferred_type, "Attachment")
self.setWindowTitle(f"Select {title_type} - Entropia Nexus")
self.setMinimumSize(900, 600)
self.all_attachments: List[NexusAttachment] = []
self.selected_attachment: Optional[NexusAttachment] = None
self._setup_ui()
self._load_data()
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.setSpacing(10)
# Status
self.status_label = QLabel("Loading attachments from Entropia Nexus...")
layout.addWidget(self.status_label)
self.progress = QProgressBar()
self.progress.setRange(0, 0)
layout.addWidget(self.progress)
# Type filter tabs
self.tabs = QTabWidget()
# Create tabs for each attachment type
self.tab_all = self._create_attachment_tab("All Attachments")
self.tab_amps = self._create_attachment_tab("Amplifiers")
self.tab_scopes = self._create_attachment_tab("Scopes")
self.tab_sights = self._create_attachment_tab("Sights")
self.tab_absorbers = self._create_attachment_tab("Absorbers")
self.tabs.addTab(self.tab_all["widget"], "All")
self.tabs.addTab(self.tab_amps["widget"], "⚡ Amplifiers")
self.tabs.addTab(self.tab_scopes["widget"], "🔭 Scopes")
self.tabs.addTab(self.tab_sights["widget"], "🎯 Sights")
self.tabs.addTab(self.tab_absorbers["widget"], "🛡️ Absorbers")
# Set preferred tab
if self.preferred_type == "amplifier":
self.tabs.setCurrentIndex(1)
elif self.preferred_type == "scope":
self.tabs.setCurrentIndex(2)
elif self.preferred_type == "sight":
self.tabs.setCurrentIndex(3)
elif self.preferred_type == "absorber":
self.tabs.setCurrentIndex(4)
self.tabs.currentChanged.connect(self._on_tab_changed)
layout.addWidget(self.tabs)
# Preview panel
self.preview_group = QGroupBox("Attachment Preview")
preview_layout = QFormLayout(self.preview_group)
self.preview_name = QLabel("-")
self.preview_type = QLabel("-")
self.preview_damage = QLabel("-")
self.preview_range = QLabel("-")
self.preview_decay = QLabel("-")
self.preview_efficiency = QLabel("-")
preview_layout.addRow("Name:", self.preview_name)
preview_layout.addRow("Type:", self.preview_type)
preview_layout.addRow("Damage Bonus:", self.preview_damage)
preview_layout.addRow("Range Bonus:", self.preview_range)
preview_layout.addRow("Decay:", self.preview_decay)
preview_layout.addRow("Efficiency:", self.preview_efficiency)
layout.addWidget(self.preview_group)
# Buttons
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self._on_accept)
buttons.rejected.connect(self.reject)
self.ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok)
self.ok_button.setEnabled(False)
layout.addWidget(buttons)
def _create_attachment_tab(self, title: str) -> dict:
"""Create a tab with search and tree for attachments."""
widget = QWidget()
layout = QVBoxLayout(widget)
# Search
search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("Search:"))
search_input = QLineEdit()
search_input.setPlaceholderText(f"Search {title.lower()}...")
search_layout.addWidget(search_input)
clear_btn = QPushButton("Clear")
search_layout.addWidget(clear_btn)
layout.addLayout(search_layout)
# Tree
tree = QTreeWidget()
tree.setHeaderLabels(["Name", "Type", "Dmg+", "Rng+", "Decay", "Efficiency"])
header = tree.header()
header.setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
tree.itemSelectionChanged.connect(self._on_selection_changed)
tree.itemDoubleClicked.connect(self._on_double_click)
layout.addWidget(tree)
return {
"widget": widget,
"search": search_input,
"clear": clear_btn,
"tree": tree
}
def _load_data(self):
"""Load attachments in background thread."""
self.loader = AttachmentLoaderThread()
self.loader.attachments_loaded.connect(self._on_attachments_loaded)
self.loader.error_occurred.connect(self._on_load_error)
self.loader.start()
def _on_attachments_loaded(self, attachments: List[NexusAttachment]):
"""Handle loaded attachments."""
self.all_attachments = attachments
self.status_label.setText(f"Loaded {len(attachments)} attachments from Entropia Nexus")
self.progress.setRange(0, 100)
self.progress.setValue(100)
# Populate all tabs
self._populate_tab(self.tab_all, self.all_attachments)
self._populate_tab(self.tab_amps, [a for a in attachments if a.attachment_type == "amplifier"])
self._populate_tab(self.tab_scopes, [a for a in attachments if a.attachment_type == "scope"])
self._populate_tab(self.tab_sights, [a for a in attachments if a.attachment_type == "sight"])
self._populate_tab(self.tab_absorbers, [a for a in attachments if a.attachment_type == "absorber"])
# Connect search signals
for tab in [self.tab_all, self.tab_amps, self.tab_scopes, self.tab_sights, self.tab_absorbers]:
tab["search"].textChanged.connect(lambda text, t=tab: self._on_search_changed(t, text))
tab["clear"].clicked.connect(lambda t=tab: t["search"].clear())
def _on_load_error(self, error: str):
"""Handle load error."""
self.status_label.setText(f"Error loading attachments: {error}")
self.progress.setRange(0, 100)
self.progress.setValue(0)
def _populate_tab(self, tab: dict, attachments: List[NexusAttachment]):
"""Populate a tab's tree with attachments."""
tree = tab["tree"]
tree.clear()
# Sort by damage bonus (amplifiers) or range (scopes)
attachments = sorted(attachments, key=lambda a: a.damage_bonus + a.range_bonus, reverse=True)
for att in attachments:
item = QTreeWidgetItem()
item.setText(0, att.name)
item.setText(1, att.attachment_type.title())
item.setText(2, f"+{att.damage_bonus}")
item.setText(3, f"+{att.range_bonus}")
item.setText(4, f"{att.decay:.2f}")
item.setText(5, f"{att.efficiency_bonus:.1f}%")
# Color code by type
if att.attachment_type == "amplifier":
item.setForeground(0, QColor("#ff9800")) # Orange
elif att.attachment_type == "scope":
item.setForeground(0, QColor("#2196f3")) # Blue
elif att.attachment_type == "sight":
item.setForeground(0, QColor("#4caf50")) # Green
elif att.attachment_type == "absorber":
item.setForeground(0, QColor("#9c27b0")) # Purple
item.setData(0, Qt.ItemDataRole.UserRole, att)
tree.addTopLevelItem(item)
def _on_search_changed(self, tab: dict, text: str):
"""Handle search text change in a tab."""
tree = tab["tree"]
for i in range(tree.topLevelItemCount()):
item = tree.topLevelItem(i)
attachment = item.data(0, Qt.ItemDataRole.UserRole)
if text.lower() in attachment.name.lower():
item.setHidden(False)
else:
item.setHidden(True)
def _on_tab_changed(self, index: int):
"""Handle tab change."""
# Clear selection when changing tabs
self.selected_attachment = None
self.ok_button.setEnabled(False)
self._clear_preview()
def _on_selection_changed(self):
"""Handle selection change in current tab."""
current_tab = self._get_current_tab()
if current_tab:
items = current_tab["tree"].selectedItems()
if items:
self.selected_attachment = items[0].data(0, Qt.ItemDataRole.UserRole)
self.ok_button.setEnabled(True)
self._update_preview(self.selected_attachment)
else:
self.selected_attachment = None
self.ok_button.setEnabled(False)
self._clear_preview()
def _get_current_tab(self) -> Optional[dict]:
"""Get the currently active tab data."""
index = self.tabs.currentIndex()
tabs = [self.tab_all, self.tab_amps, self.tab_scopes, self.tab_sights, self.tab_absorbers]
if 0 <= index < len(tabs):
return tabs[index]
return None
def _update_preview(self, attachment: NexusAttachment):
"""Update preview panel."""
self.preview_name.setText(attachment.name)
self.preview_type.setText(attachment.attachment_type.title())
self.preview_damage.setText(f"+{attachment.damage_bonus}")
self.preview_range.setText(f"+{attachment.range_bonus}")
self.preview_decay.setText(f"{attachment.decay:.2f} PEC")
self.preview_efficiency.setText(f"{attachment.efficiency_bonus:.1f}%")
def _clear_preview(self):
"""Clear preview panel."""
self.preview_name.setText("-")
self.preview_type.setText("-")
self.preview_damage.setText("-")
self.preview_range.setText("-")
self.preview_decay.setText("-")
self.preview_efficiency.setText("-")
def _on_double_click(self, item, column):
"""Handle double click."""
self._on_accept()
def _on_accept(self):
"""Handle OK button."""
if self.selected_attachment:
self.attachment_selected.emit(self.selected_attachment)
self.accept()