corrosim 1.0.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.
- corrosim/__init__.py +19 -0
- corrosim/app.py +206 -0
- corrosim/database.py +85 -0
- corrosim/main.py +37 -0
- corrosim/splash_screen.py +234 -0
- corrosim/tabs/__init__.py +8 -0
- corrosim/tabs/comparison_tab.py +136 -0
- corrosim/tabs/import_tab.py +225 -0
- corrosim/tabs/prediction_tab.py +177 -0
- corrosim/tabs/tafel_tab.py +249 -0
- corrosim/tafel_engine.py +235 -0
- corrosim/theme.py +151 -0
- corrosim/utils/__init__.py +11 -0
- corrosim/utils/constants.py +24 -0
- corrosim-1.0.0.dist-info/METADATA +258 -0
- corrosim-1.0.0.dist-info/RECORD +22 -0
- corrosim-1.0.0.dist-info/WHEEL +5 -0
- corrosim-1.0.0.dist-info/entry_points.txt +2 -0
- corrosim-1.0.0.dist-info/licenses/LICENSE +21 -0
- corrosim-1.0.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_tafel.py +74 -0
corrosim/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CorroSim - Professional Corrosion Analysis Platform
|
|
3
|
+
|
|
4
|
+
A comprehensive tool for electrochemical corrosion analysis including:
|
|
5
|
+
- Tafel polarization analysis
|
|
6
|
+
- Lifetime prediction
|
|
7
|
+
- Multi-sample comparison
|
|
8
|
+
- Data import/export
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.0"
|
|
12
|
+
__author__ = "Your Name"
|
|
13
|
+
__description__ = "Professional Corrosion Analysis Platform"
|
|
14
|
+
|
|
15
|
+
from .tafel_engine import TafelEngine
|
|
16
|
+
from .database import Database
|
|
17
|
+
from .theme import Theme
|
|
18
|
+
|
|
19
|
+
__all__ = ['TafelEngine', 'Database', 'Theme']
|
corrosim/app.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Main Application Window"""
|
|
2
|
+
|
|
3
|
+
from PyQt6.QtWidgets import *
|
|
4
|
+
from PyQt6.QtCore import *
|
|
5
|
+
from PyQt6.QtGui import *
|
|
6
|
+
|
|
7
|
+
from .theme import Theme, GLOBAL_STYLE, SIDEBAR_STYLE
|
|
8
|
+
from .database import Database
|
|
9
|
+
from .tabs import ImportTab, TafelTab, PredictionTab, ComparisonTab
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MainWindow(QMainWindow):
|
|
13
|
+
"""Main application window for CorroSim"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.setWindowTitle("CorroSim Analysis Platform")
|
|
18
|
+
self.setMinimumSize(1200, 800)
|
|
19
|
+
self.resize(1400, 900)
|
|
20
|
+
|
|
21
|
+
# Initialize components
|
|
22
|
+
self.db = Database()
|
|
23
|
+
self.nav_buttons = []
|
|
24
|
+
|
|
25
|
+
self.setStyleSheet(GLOBAL_STYLE)
|
|
26
|
+
self._setup_ui()
|
|
27
|
+
self._setup_menu()
|
|
28
|
+
self.statusBar().showMessage("Ready - Import data to begin")
|
|
29
|
+
|
|
30
|
+
def _setup_ui(self):
|
|
31
|
+
"""Setup the user interface"""
|
|
32
|
+
central = QWidget()
|
|
33
|
+
self.setCentralWidget(central)
|
|
34
|
+
main_layout = QHBoxLayout(central)
|
|
35
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
36
|
+
main_layout.setSpacing(0)
|
|
37
|
+
|
|
38
|
+
self._create_sidebar()
|
|
39
|
+
self._create_tabs()
|
|
40
|
+
|
|
41
|
+
main_layout.addWidget(self.sidebar)
|
|
42
|
+
main_layout.addWidget(self.tabs, 1)
|
|
43
|
+
|
|
44
|
+
self.switch_tab(0)
|
|
45
|
+
|
|
46
|
+
def _create_sidebar(self):
|
|
47
|
+
"""Create navigation sidebar"""
|
|
48
|
+
self.sidebar = QWidget()
|
|
49
|
+
self.sidebar.setObjectName("sidebar")
|
|
50
|
+
self.sidebar.setFixedWidth(200)
|
|
51
|
+
self.sidebar.setStyleSheet(SIDEBAR_STYLE)
|
|
52
|
+
|
|
53
|
+
layout = QVBoxLayout(self.sidebar)
|
|
54
|
+
layout.setContentsMargins(12, 20, 12, 20)
|
|
55
|
+
layout.setSpacing(6)
|
|
56
|
+
|
|
57
|
+
# Logo
|
|
58
|
+
logo = QLabel("⚡ CorroSim")
|
|
59
|
+
logo.setStyleSheet("font-size: 18px; font-weight: 700; color: white; background: transparent;")
|
|
60
|
+
layout.addWidget(logo)
|
|
61
|
+
layout.addSpacing(20)
|
|
62
|
+
|
|
63
|
+
# Navigation label
|
|
64
|
+
nav_label = QLabel("NAVIGATION")
|
|
65
|
+
nav_label.setStyleSheet("color: #94A3B8; font-size: 10px; font-weight: 600; background: transparent;")
|
|
66
|
+
layout.addWidget(nav_label)
|
|
67
|
+
|
|
68
|
+
# Navigation buttons
|
|
69
|
+
tabs = [
|
|
70
|
+
("📁", "Import", 0),
|
|
71
|
+
("⚡", "Tafel", 1),
|
|
72
|
+
("🔮", "Prediction", 2),
|
|
73
|
+
("📊", "Compare", 3)
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
for icon, text, idx in tabs:
|
|
77
|
+
btn = QPushButton(f" {icon} {text}")
|
|
78
|
+
btn.setProperty("active", "false")
|
|
79
|
+
btn.clicked.connect(lambda checked, i=idx: self.switch_tab(i))
|
|
80
|
+
self.nav_buttons.append(btn)
|
|
81
|
+
layout.addWidget(btn)
|
|
82
|
+
|
|
83
|
+
layout.addStretch()
|
|
84
|
+
|
|
85
|
+
# Status indicator
|
|
86
|
+
sep = QFrame()
|
|
87
|
+
sep.setFrameShape(QFrame.Shape.HLine)
|
|
88
|
+
sep.setStyleSheet("background-color: #334155; max-height: 1px;")
|
|
89
|
+
layout.addWidget(sep)
|
|
90
|
+
|
|
91
|
+
status = QLabel("● Database Connected")
|
|
92
|
+
status.setStyleSheet("color: #10B981; font-size: 10px; background: transparent; padding: 8px;")
|
|
93
|
+
layout.addWidget(status)
|
|
94
|
+
|
|
95
|
+
def _create_tabs(self):
|
|
96
|
+
"""Create and setup all tabs"""
|
|
97
|
+
self.tabs = QTabWidget()
|
|
98
|
+
self.tabs.tabBar().setVisible(False)
|
|
99
|
+
|
|
100
|
+
# Create tab widgets
|
|
101
|
+
self.import_widget = QWidget()
|
|
102
|
+
self.tafel_widget = QWidget()
|
|
103
|
+
self.prediction_widget = QWidget()
|
|
104
|
+
self.comparison_widget = QWidget()
|
|
105
|
+
|
|
106
|
+
self.tabs.addTab(self.import_widget, "Import")
|
|
107
|
+
self.tabs.addTab(self.tafel_widget, "Tafel")
|
|
108
|
+
self.tabs.addTab(self.prediction_widget, "Prediction")
|
|
109
|
+
self.tabs.addTab(self.comparison_widget, "Compare")
|
|
110
|
+
|
|
111
|
+
# Setup Import Tab
|
|
112
|
+
self.import_tab = ImportTab()
|
|
113
|
+
self.import_tab.setup(
|
|
114
|
+
parent=self.import_widget,
|
|
115
|
+
db=self.db,
|
|
116
|
+
switch_tab_callback=self.switch_tab,
|
|
117
|
+
load_tafel_callback=self._load_tafel_data
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Setup Tafel Tab
|
|
121
|
+
self.tafel_tab = TafelTab()
|
|
122
|
+
self.tafel_tab.setup(
|
|
123
|
+
parent=self.tafel_widget,
|
|
124
|
+
db=self.db
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Setup Prediction Tab
|
|
128
|
+
self.prediction_tab = PredictionTab()
|
|
129
|
+
self.prediction_tab.setup(
|
|
130
|
+
parent=self.prediction_widget
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Setup Comparison Tab
|
|
134
|
+
self.comparison_tab = ComparisonTab()
|
|
135
|
+
self.comparison_tab.setup(
|
|
136
|
+
parent=self.comparison_widget,
|
|
137
|
+
db=self.db
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def switch_tab(self, index):
|
|
141
|
+
"""Switch active tab"""
|
|
142
|
+
self.tabs.setCurrentIndex(index)
|
|
143
|
+
for i, btn in enumerate(self.nav_buttons):
|
|
144
|
+
btn.setProperty("active", "true" if i == index else "false")
|
|
145
|
+
btn.style().unpolish(btn)
|
|
146
|
+
btn.style().polish(btn)
|
|
147
|
+
|
|
148
|
+
def _load_tafel_data(self):
|
|
149
|
+
"""Load latest data into Tafel tab"""
|
|
150
|
+
if hasattr(self, 'tafel_tab'):
|
|
151
|
+
self.tafel_tab._load_data()
|
|
152
|
+
|
|
153
|
+
def _setup_menu(self):
|
|
154
|
+
"""Setup menu bar"""
|
|
155
|
+
menubar = self.menuBar()
|
|
156
|
+
menubar.setStyleSheet(f"""
|
|
157
|
+
QMenuBar {{
|
|
158
|
+
background-color: {Theme.BG_WHITE};
|
|
159
|
+
border-bottom: 1px solid {Theme.BORDER};
|
|
160
|
+
padding: 2px;
|
|
161
|
+
}}
|
|
162
|
+
QMenuBar::item {{
|
|
163
|
+
padding: 6px 12px;
|
|
164
|
+
border-radius: 4px;
|
|
165
|
+
}}
|
|
166
|
+
QMenuBar::item:selected {{
|
|
167
|
+
background-color: {Theme.PRIMARY_LIGHT};
|
|
168
|
+
}}
|
|
169
|
+
""")
|
|
170
|
+
|
|
171
|
+
file_menu = menubar.addMenu("File")
|
|
172
|
+
|
|
173
|
+
exit_action = QAction("Exit", self)
|
|
174
|
+
exit_action.setShortcut("Ctrl+Q")
|
|
175
|
+
exit_action.triggered.connect(self.close)
|
|
176
|
+
file_menu.addAction(exit_action)
|
|
177
|
+
|
|
178
|
+
help_menu = menubar.addMenu("Help")
|
|
179
|
+
|
|
180
|
+
about_action = QAction("About", self)
|
|
181
|
+
about_action.triggered.connect(self._show_about)
|
|
182
|
+
help_menu.addAction(about_action)
|
|
183
|
+
|
|
184
|
+
def _show_about(self):
|
|
185
|
+
"""Show about dialog"""
|
|
186
|
+
QMessageBox.about(
|
|
187
|
+
self,
|
|
188
|
+
"About CorroSim",
|
|
189
|
+
"<h3>⚡ CorroSim Analysis Platform</h3>"
|
|
190
|
+
"<p>Version 1.0.0</p>"
|
|
191
|
+
"<p>Professional Corrosion Analysis Tool</p>"
|
|
192
|
+
"<hr>"
|
|
193
|
+
"<p><b>Features:</b></p>"
|
|
194
|
+
"<ul>"
|
|
195
|
+
"<li>Tafel Polarization Analysis</li>"
|
|
196
|
+
"<li>Lifetime Prediction Models</li>"
|
|
197
|
+
"<li>Multi-Sample Comparison</li>"
|
|
198
|
+
"<li>Data Export (PNG, PDF, Excel)</li>"
|
|
199
|
+
"</ul>"
|
|
200
|
+
"<p>© 2026 CorroSim By NanoStack-Lab</p>"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def closeEvent(self, event):
|
|
204
|
+
"""Handle window close event"""
|
|
205
|
+
self.db.close()
|
|
206
|
+
event.accept()
|
corrosim/database.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Database module for CorroSim"""
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
import uuid
|
|
5
|
+
import datetime
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from io import StringIO
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Database:
|
|
11
|
+
"""SQLite database for storing corrosion analysis samples"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, db_path='corrosion.db'):
|
|
14
|
+
self.db_path = db_path
|
|
15
|
+
self.conn = sqlite3.connect(db_path)
|
|
16
|
+
self.cursor = self.conn.cursor()
|
|
17
|
+
self._create_tables()
|
|
18
|
+
|
|
19
|
+
def _create_tables(self):
|
|
20
|
+
"""Create database tables if they don't exist"""
|
|
21
|
+
self.cursor.execute('''
|
|
22
|
+
CREATE TABLE IF NOT EXISTS samples (
|
|
23
|
+
id TEXT PRIMARY KEY,
|
|
24
|
+
name TEXT,
|
|
25
|
+
test_type TEXT,
|
|
26
|
+
date TEXT,
|
|
27
|
+
data TEXT,
|
|
28
|
+
ecorr REAL,
|
|
29
|
+
icorr REAL,
|
|
30
|
+
cr REAL,
|
|
31
|
+
ba REAL,
|
|
32
|
+
bc REAL
|
|
33
|
+
)
|
|
34
|
+
''')
|
|
35
|
+
self.conn.commit()
|
|
36
|
+
|
|
37
|
+
def save(self, name, test_type, data):
|
|
38
|
+
"""Save a new sample to the database"""
|
|
39
|
+
sid = str(uuid.uuid4())[:8]
|
|
40
|
+
data_csv = data.to_csv(index=False)
|
|
41
|
+
date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
|
|
42
|
+
|
|
43
|
+
self.cursor.execute(
|
|
44
|
+
"INSERT INTO samples VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
45
|
+
(sid, name, test_type, date, data_csv, None, None, None, None, None)
|
|
46
|
+
)
|
|
47
|
+
self.conn.commit()
|
|
48
|
+
return sid
|
|
49
|
+
|
|
50
|
+
def update(self, sid, ecorr, icorr, cr, ba, bc):
|
|
51
|
+
"""Update analysis results for a sample"""
|
|
52
|
+
self.cursor.execute(
|
|
53
|
+
"UPDATE samples SET ecorr=?, icorr=?, cr=?, ba=?, bc=? WHERE id=?",
|
|
54
|
+
(ecorr, icorr, cr, ba, bc, sid)
|
|
55
|
+
)
|
|
56
|
+
self.conn.commit()
|
|
57
|
+
|
|
58
|
+
def get_all(self):
|
|
59
|
+
"""Get all samples ordered by date"""
|
|
60
|
+
self.cursor.execute("SELECT * FROM samples ORDER BY date DESC")
|
|
61
|
+
return self.cursor.fetchall()
|
|
62
|
+
|
|
63
|
+
def get_latest(self):
|
|
64
|
+
"""Get the most recent sample"""
|
|
65
|
+
self.cursor.execute("SELECT * FROM samples ORDER BY date DESC LIMIT 1")
|
|
66
|
+
return self.cursor.fetchone()
|
|
67
|
+
|
|
68
|
+
def get_by_id(self, sid):
|
|
69
|
+
"""Get a specific sample by ID"""
|
|
70
|
+
self.cursor.execute("SELECT * FROM samples WHERE id=?", (sid,))
|
|
71
|
+
return self.cursor.fetchone()
|
|
72
|
+
|
|
73
|
+
def delete(self, sid):
|
|
74
|
+
"""Delete a sample by ID"""
|
|
75
|
+
self.cursor.execute("DELETE FROM samples WHERE id=?", (sid,))
|
|
76
|
+
self.conn.commit()
|
|
77
|
+
|
|
78
|
+
def get_count(self):
|
|
79
|
+
"""Get total number of samples"""
|
|
80
|
+
self.cursor.execute("SELECT COUNT(*) FROM samples")
|
|
81
|
+
return self.cursor.fetchone()[0]
|
|
82
|
+
|
|
83
|
+
def close(self):
|
|
84
|
+
"""Close database connection"""
|
|
85
|
+
self.conn.close()
|
corrosim/main.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Main entry point for CorroSim"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from PyQt6.QtWidgets import QApplication
|
|
6
|
+
|
|
7
|
+
from .splash_screen import SplashScreen
|
|
8
|
+
from .app import MainWindow
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
"""Application entry point"""
|
|
13
|
+
app = QApplication(sys.argv)
|
|
14
|
+
app.setStyle('Fusion')
|
|
15
|
+
|
|
16
|
+
# Show splash screen with loading sequence
|
|
17
|
+
messages = [
|
|
18
|
+
"Initializing database...",
|
|
19
|
+
"Loading analysis modules...",
|
|
20
|
+
"Setting up user interface...",
|
|
21
|
+
"Preparing Tafel engine...",
|
|
22
|
+
"Starting application..."
|
|
23
|
+
]
|
|
24
|
+
delays = [0.3, 0.3, 0.3, 0.3, 0.2]
|
|
25
|
+
|
|
26
|
+
splash = SplashScreen.show_loading_sequence(messages, delays)
|
|
27
|
+
|
|
28
|
+
# Create and show main window
|
|
29
|
+
window = MainWindow()
|
|
30
|
+
splash.close()
|
|
31
|
+
window.show()
|
|
32
|
+
|
|
33
|
+
sys.exit(app.exec())
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
main()
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""Professional Splash Screen for CorroSim"""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from PyQt6.QtWidgets import QSplashScreen, QApplication
|
|
5
|
+
from PyQt6.QtCore import Qt, QRect, QRectF
|
|
6
|
+
from PyQt6.QtGui import QPixmap, QPainter, QColor, QFont, QLinearGradient, QPen, QBrush
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SplashScreen(QSplashScreen):
|
|
10
|
+
"""Professional splash screen with gradient design and progress bar"""
|
|
11
|
+
|
|
12
|
+
# Configuration
|
|
13
|
+
WIDTH = 600
|
|
14
|
+
HEIGHT = 400
|
|
15
|
+
BRAND_NAME = "CorroSim"
|
|
16
|
+
TAGLINE = "Advanced Corrosion Analysis Platform"
|
|
17
|
+
VERSION = "Version 1.0.0"
|
|
18
|
+
COPYRIGHT = "© 2026 CorroSim • NanoStack-Lab"
|
|
19
|
+
WEBSITE = "github.com/khadev/corrosim"
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
pixmap = self._create_pixmap(0, "Initializing...")
|
|
23
|
+
super().__init__(pixmap)
|
|
24
|
+
self.setWindowFlags(
|
|
25
|
+
Qt.WindowType.WindowStaysOnTopHint |
|
|
26
|
+
Qt.WindowType.FramelessWindowHint |
|
|
27
|
+
Qt.WindowType.SplashScreen
|
|
28
|
+
)
|
|
29
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
|
|
30
|
+
self._center_on_screen()
|
|
31
|
+
self.show()
|
|
32
|
+
QApplication.processEvents()
|
|
33
|
+
|
|
34
|
+
def _draw_gradient_background(self, painter, pixmap):
|
|
35
|
+
"""Draw professional gradient background"""
|
|
36
|
+
gradient = QLinearGradient(0, 0, self.WIDTH, self.HEIGHT)
|
|
37
|
+
gradient.setColorAt(0.0, QColor("#0F172A")) # Dark navy top
|
|
38
|
+
gradient.setColorAt(0.4, QColor("#1E293B")) # Slate middle
|
|
39
|
+
gradient.setColorAt(0.7, QColor("#1E3A5F")) # Blue-tinted
|
|
40
|
+
gradient.setColorAt(1.0, QColor("#0F172A")) # Dark navy bottom
|
|
41
|
+
|
|
42
|
+
painter.setBrush(QBrush(gradient))
|
|
43
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
44
|
+
painter.drawRect(pixmap.rect())
|
|
45
|
+
|
|
46
|
+
def _draw_decorative_circles(self, painter):
|
|
47
|
+
"""Draw decorative background circles"""
|
|
48
|
+
# Large subtle circle top-right
|
|
49
|
+
painter.setBrush(QColor(37, 99, 235, 15)) # Theme blue, very transparent
|
|
50
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
51
|
+
painter.drawEllipse(QRect(self.WIDTH - 150, -80, 300, 300))
|
|
52
|
+
|
|
53
|
+
# Small circle bottom-left
|
|
54
|
+
painter.setBrush(QColor(5, 150, 105, 10)) # Theme green, very transparent
|
|
55
|
+
painter.drawEllipse(QRect(-60, self.HEIGHT - 120, 200, 200))
|
|
56
|
+
|
|
57
|
+
# Medium circle center accent
|
|
58
|
+
painter.setBrush(QColor(124, 58, 237, 8)) # Purple accent
|
|
59
|
+
painter.drawEllipse(QRect(self.WIDTH//2 - 100, 50, 250, 250))
|
|
60
|
+
|
|
61
|
+
def _draw_logo_icon(self, painter):
|
|
62
|
+
"""Draw modern logo icon"""
|
|
63
|
+
# Outer circle
|
|
64
|
+
painter.setBrush(QColor(37, 99, 235, 30))
|
|
65
|
+
painter.setPen(QPen(QColor("#2563EB"), 2))
|
|
66
|
+
painter.drawEllipse(QRect(self.WIDTH//2 - 35, 70, 70, 70))
|
|
67
|
+
|
|
68
|
+
# Lightning bolt symbol
|
|
69
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
70
|
+
painter.setBrush(QColor("#3B82F6"))
|
|
71
|
+
|
|
72
|
+
# Draw a simple lightning bolt using polygons
|
|
73
|
+
bolt = [
|
|
74
|
+
(self.WIDTH//2 + 8, 78),
|
|
75
|
+
(self.WIDTH//2 - 8, 102),
|
|
76
|
+
(self.WIDTH//2 + 2, 102),
|
|
77
|
+
(self.WIDTH//2 - 5, 120),
|
|
78
|
+
(self.WIDTH//2 + 12, 96),
|
|
79
|
+
(self.WIDTH//2, 96),
|
|
80
|
+
(self.WIDTH//2 + 10, 78)
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
from PyQt6.QtGui import QPolygon
|
|
84
|
+
from PyQt6.QtCore import QPoint
|
|
85
|
+
|
|
86
|
+
points = [QPoint(x, y) for x, y in bolt]
|
|
87
|
+
polygon = QPolygon(points)
|
|
88
|
+
painter.drawPolygon(polygon)
|
|
89
|
+
|
|
90
|
+
def _draw_brand_text(self, painter):
|
|
91
|
+
"""Draw brand name and tagline"""
|
|
92
|
+
# Brand name
|
|
93
|
+
painter.setPen(QColor("#F8FAFC"))
|
|
94
|
+
font_title = QFont("Segoe UI", 36, QFont.Weight.Bold)
|
|
95
|
+
font_title.setLetterSpacing(QFont.SpacingType.AbsoluteSpacing, 2)
|
|
96
|
+
painter.setFont(font_title)
|
|
97
|
+
painter.drawText(
|
|
98
|
+
QRect(0, 145, self.WIDTH, 50),
|
|
99
|
+
Qt.AlignmentFlag.AlignCenter,
|
|
100
|
+
self.BRAND_NAME
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Tagline
|
|
104
|
+
painter.setPen(QColor("#94A3B8"))
|
|
105
|
+
font_tag = QFont("Segoe UI", 13, QFont.Weight.Normal)
|
|
106
|
+
painter.setFont(font_tag)
|
|
107
|
+
painter.drawText(
|
|
108
|
+
QRect(0, 200, self.WIDTH, 25),
|
|
109
|
+
Qt.AlignmentFlag.AlignCenter,
|
|
110
|
+
self.TAGLINE
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Version badge
|
|
114
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
115
|
+
painter.setBrush(QColor(37, 99, 235, 40))
|
|
116
|
+
badge_rect = QRect(self.WIDTH//2 - 50, 232, 100, 24)
|
|
117
|
+
painter.drawRoundedRect(QRectF(badge_rect), 12, 12)
|
|
118
|
+
|
|
119
|
+
painter.setPen(QColor("#60A5FA"))
|
|
120
|
+
font_ver = QFont("Segoe UI", 10, QFont.Weight.Medium)
|
|
121
|
+
painter.setFont(font_ver)
|
|
122
|
+
painter.drawText(badge_rect, Qt.AlignmentFlag.AlignCenter, self.VERSION)
|
|
123
|
+
|
|
124
|
+
def _draw_progress_section(self, painter, progress, text):
|
|
125
|
+
"""Draw modern progress bar and status text"""
|
|
126
|
+
# Section separator line
|
|
127
|
+
painter.setPen(QPen(QColor("#334155"), 1))
|
|
128
|
+
painter.drawLine(80, 285, self.WIDTH - 80, 285)
|
|
129
|
+
|
|
130
|
+
# Status text
|
|
131
|
+
painter.setPen(QColor("#10B981"))
|
|
132
|
+
font_status = QFont("Segoe UI", 10, QFont.Weight.Normal)
|
|
133
|
+
painter.setFont(font_status)
|
|
134
|
+
painter.drawText(
|
|
135
|
+
QRect(0, 290, self.WIDTH, 25),
|
|
136
|
+
Qt.AlignmentFlag.AlignCenter,
|
|
137
|
+
text
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Progress bar background
|
|
141
|
+
bar_x = 80
|
|
142
|
+
bar_y = 322
|
|
143
|
+
bar_width = self.WIDTH - 160
|
|
144
|
+
bar_height = 5
|
|
145
|
+
|
|
146
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
147
|
+
painter.setBrush(QColor("#1E293B"))
|
|
148
|
+
painter.drawRoundedRect(QRectF(bar_x, bar_y, bar_width, bar_height), 3, 3)
|
|
149
|
+
|
|
150
|
+
# Progress bar fill with gradient
|
|
151
|
+
if progress > 0:
|
|
152
|
+
fill_width = int(bar_width * progress / 100)
|
|
153
|
+
fill_gradient = QLinearGradient(bar_x, 0, bar_x + fill_width, 0)
|
|
154
|
+
fill_gradient.setColorAt(0.0, QColor("#2563EB"))
|
|
155
|
+
fill_gradient.setColorAt(1.0, QColor("#059669"))
|
|
156
|
+
|
|
157
|
+
painter.setBrush(QBrush(fill_gradient))
|
|
158
|
+
painter.drawRoundedRect(QRectF(bar_x, bar_y, fill_width, bar_height), 3, 3)
|
|
159
|
+
|
|
160
|
+
# Glow dot at progress end
|
|
161
|
+
if fill_width > 5:
|
|
162
|
+
painter.setBrush(QColor("#F8FAFC"))
|
|
163
|
+
painter.drawEllipse(QRect(bar_x + fill_width - 5, bar_y - 3, 10, 10))
|
|
164
|
+
|
|
165
|
+
def _draw_footer(self, painter):
|
|
166
|
+
"""Draw footer with copyright and website"""
|
|
167
|
+
# Copyright
|
|
168
|
+
painter.setPen(QColor("#64748B"))
|
|
169
|
+
font_footer = QFont("Segoe UI", 9, QFont.Weight.Normal)
|
|
170
|
+
painter.setFont(font_footer)
|
|
171
|
+
painter.drawText(
|
|
172
|
+
QRect(0, self.HEIGHT - 35, self.WIDTH, 20),
|
|
173
|
+
Qt.AlignmentFlag.AlignCenter,
|
|
174
|
+
self.COPYRIGHT
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Website
|
|
178
|
+
painter.setPen(QColor("#475569"))
|
|
179
|
+
font_web = QFont("Segoe UI", 8, QFont.Weight.Light)
|
|
180
|
+
painter.setFont(font_web)
|
|
181
|
+
painter.drawText(
|
|
182
|
+
QRect(0, self.HEIGHT - 18, self.WIDTH, 15),
|
|
183
|
+
Qt.AlignmentFlag.AlignCenter,
|
|
184
|
+
self.WEBSITE
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def _create_pixmap(self, progress, text):
|
|
188
|
+
"""Create complete splash screen pixmap"""
|
|
189
|
+
pixmap = QPixmap(self.WIDTH, self.HEIGHT)
|
|
190
|
+
|
|
191
|
+
painter = QPainter(pixmap)
|
|
192
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
193
|
+
|
|
194
|
+
# Draw all layers
|
|
195
|
+
self._draw_gradient_background(painter, pixmap)
|
|
196
|
+
self._draw_decorative_circles(painter)
|
|
197
|
+
self._draw_logo_icon(painter)
|
|
198
|
+
self._draw_brand_text(painter)
|
|
199
|
+
self._draw_progress_section(painter, progress, text)
|
|
200
|
+
self._draw_footer(painter)
|
|
201
|
+
|
|
202
|
+
painter.end()
|
|
203
|
+
return pixmap
|
|
204
|
+
|
|
205
|
+
def _center_on_screen(self):
|
|
206
|
+
"""Center splash on screen"""
|
|
207
|
+
screen = QApplication.primaryScreen().geometry()
|
|
208
|
+
x = (screen.width() - self.WIDTH) // 2
|
|
209
|
+
y = (screen.height() - self.HEIGHT) // 2
|
|
210
|
+
self.move(x, y)
|
|
211
|
+
|
|
212
|
+
def update_progress(self, value, text):
|
|
213
|
+
"""Update progress bar and status text"""
|
|
214
|
+
pixmap = self._create_pixmap(value, text)
|
|
215
|
+
self.setPixmap(pixmap)
|
|
216
|
+
QApplication.processEvents()
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def show_loading_sequence(messages, delays):
|
|
220
|
+
"""
|
|
221
|
+
Show animated loading sequence.
|
|
222
|
+
|
|
223
|
+
Parameters:
|
|
224
|
+
- messages: List of status messages
|
|
225
|
+
- delays: List of delays (seconds) between messages
|
|
226
|
+
"""
|
|
227
|
+
splash = SplashScreen()
|
|
228
|
+
|
|
229
|
+
for i, (msg, delay) in enumerate(zip(messages, delays)):
|
|
230
|
+
progress = int((i + 1) / len(messages) * 100)
|
|
231
|
+
splash.update_progress(progress, msg)
|
|
232
|
+
time.sleep(delay)
|
|
233
|
+
|
|
234
|
+
return splash
|