sqlshell 0.1.8__py3-none-any.whl → 0.2.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.

Potentially problematic release.


This version of sqlshell might be problematic. Click here for more details.

sqlshell/menus.py ADDED
@@ -0,0 +1,171 @@
1
+ """
2
+ Menu creation and management for SQLShell application.
3
+ This module contains functions to create and manage the application's menus.
4
+ """
5
+
6
+ def create_file_menu(main_window):
7
+ """Create the File menu with project management actions.
8
+
9
+ Args:
10
+ main_window: The SQLShell main window instance
11
+
12
+ Returns:
13
+ The created File menu
14
+ """
15
+ # Create File menu
16
+ file_menu = main_window.menuBar().addMenu('&File')
17
+
18
+ # Project management actions
19
+ new_project_action = file_menu.addAction('New Project')
20
+ new_project_action.setShortcut('Ctrl+N')
21
+ new_project_action.triggered.connect(main_window.new_project)
22
+
23
+ open_project_action = file_menu.addAction('Open Project...')
24
+ open_project_action.setShortcut('Ctrl+O')
25
+ open_project_action.triggered.connect(main_window.open_project)
26
+
27
+ # Add Recent Projects submenu
28
+ main_window.recent_projects_menu = file_menu.addMenu('Recent Projects')
29
+ main_window.update_recent_projects_menu()
30
+
31
+ # Add Quick Access submenu for files
32
+ main_window.quick_access_menu = file_menu.addMenu('Quick Access Files')
33
+ main_window.update_quick_access_menu()
34
+
35
+ save_project_action = file_menu.addAction('Save Project')
36
+ save_project_action.setShortcut('Ctrl+S')
37
+ save_project_action.triggered.connect(main_window.save_project)
38
+
39
+ save_project_as_action = file_menu.addAction('Save Project As...')
40
+ save_project_as_action.setShortcut('Ctrl+Shift+S')
41
+ save_project_as_action.triggered.connect(main_window.save_project_as)
42
+
43
+ file_menu.addSeparator()
44
+
45
+ exit_action = file_menu.addAction('Exit')
46
+ exit_action.setShortcut('Ctrl+Q')
47
+ exit_action.triggered.connect(main_window.close)
48
+
49
+ return file_menu
50
+
51
+
52
+ def create_view_menu(main_window):
53
+ """Create the View menu with window management options.
54
+
55
+ Args:
56
+ main_window: The SQLShell main window instance
57
+
58
+ Returns:
59
+ The created View menu
60
+ """
61
+ # Create View menu
62
+ view_menu = main_window.menuBar().addMenu('&View')
63
+
64
+ # Maximized window option
65
+ maximize_action = view_menu.addAction('Maximize Window')
66
+ maximize_action.setShortcut('F11')
67
+ maximize_action.triggered.connect(main_window.toggle_maximize_window)
68
+
69
+ # Zoom submenu
70
+ zoom_menu = view_menu.addMenu('Zoom')
71
+
72
+ zoom_in_action = zoom_menu.addAction('Zoom In')
73
+ zoom_in_action.setShortcut('Ctrl++')
74
+ zoom_in_action.triggered.connect(lambda: main_window.change_zoom(1.1))
75
+
76
+ zoom_out_action = zoom_menu.addAction('Zoom Out')
77
+ zoom_out_action.setShortcut('Ctrl+-')
78
+ zoom_out_action.triggered.connect(lambda: main_window.change_zoom(0.9))
79
+
80
+ reset_zoom_action = zoom_menu.addAction('Reset Zoom')
81
+ reset_zoom_action.setShortcut('Ctrl+0')
82
+ reset_zoom_action.triggered.connect(lambda: main_window.reset_zoom())
83
+
84
+ return view_menu
85
+
86
+
87
+ def create_tab_menu(main_window):
88
+ """Create the Tab menu with tab management actions.
89
+
90
+ Args:
91
+ main_window: The SQLShell main window instance
92
+
93
+ Returns:
94
+ The created Tab menu
95
+ """
96
+ # Create Tab menu
97
+ tab_menu = main_window.menuBar().addMenu('&Tab')
98
+
99
+ new_tab_action = tab_menu.addAction('New Tab')
100
+ new_tab_action.setShortcut('Ctrl+T')
101
+ new_tab_action.triggered.connect(main_window.add_tab)
102
+
103
+ duplicate_tab_action = tab_menu.addAction('Duplicate Current Tab')
104
+ duplicate_tab_action.setShortcut('Ctrl+D')
105
+ duplicate_tab_action.triggered.connect(main_window.duplicate_current_tab)
106
+
107
+ rename_tab_action = tab_menu.addAction('Rename Current Tab')
108
+ rename_tab_action.setShortcut('Ctrl+R')
109
+ rename_tab_action.triggered.connect(main_window.rename_current_tab)
110
+
111
+ close_tab_action = tab_menu.addAction('Close Current Tab')
112
+ close_tab_action.setShortcut('Ctrl+W')
113
+ close_tab_action.triggered.connect(main_window.close_current_tab)
114
+
115
+ return tab_menu
116
+
117
+
118
+ def create_preferences_menu(main_window):
119
+ """Create the Preferences menu with user settings.
120
+
121
+ Args:
122
+ main_window: The SQLShell main window instance
123
+
124
+ Returns:
125
+ The created Preferences menu
126
+ """
127
+ # Create Preferences menu
128
+ preferences_menu = main_window.menuBar().addMenu('&Preferences')
129
+
130
+ # Auto-load recent project option
131
+ auto_load_action = preferences_menu.addAction('Auto-load Most Recent Project')
132
+ auto_load_action.setCheckable(True)
133
+ auto_load_action.setChecked(main_window.auto_load_recent_project)
134
+ auto_load_action.triggered.connect(lambda checked: toggle_auto_load(main_window, checked))
135
+
136
+ return preferences_menu
137
+
138
+
139
+ def toggle_auto_load(main_window, checked):
140
+ """Toggle the auto-load recent project setting.
141
+
142
+ Args:
143
+ main_window: The SQLShell main window instance
144
+ checked: Boolean indicating whether the option is checked
145
+ """
146
+ main_window.auto_load_recent_project = checked
147
+ main_window.save_recent_projects() # Save the preference
148
+ main_window.statusBar().showMessage(
149
+ f"Auto-load most recent project {'enabled' if checked else 'disabled'}",
150
+ 2000
151
+ )
152
+
153
+
154
+ def setup_menubar(main_window):
155
+ """Set up the complete menu bar for the application.
156
+
157
+ Args:
158
+ main_window: The SQLShell main window instance
159
+ """
160
+ # Create the menu bar (in case it doesn't exist)
161
+ menubar = main_window.menuBar()
162
+
163
+ # Create menus
164
+ file_menu = create_file_menu(main_window)
165
+ view_menu = create_view_menu(main_window)
166
+ tab_menu = create_tab_menu(main_window)
167
+ preferences_menu = create_preferences_menu(main_window)
168
+
169
+ # You can add more menus here in the future
170
+
171
+ return menubar
sqlshell/query_tab.py ADDED
@@ -0,0 +1,201 @@
1
+ import os
2
+ from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
3
+ QPushButton, QFrame, QHeaderView, QTableWidget, QSplitter, QApplication,
4
+ QToolButton, QMenu)
5
+ from PyQt6.QtCore import Qt
6
+ from PyQt6.QtGui import QIcon
7
+
8
+ from sqlshell.editor import SQLEditor
9
+ from sqlshell.syntax_highlighter import SQLSyntaxHighlighter
10
+ from sqlshell.ui import FilterHeader
11
+ from sqlshell.styles import get_row_count_label_stylesheet
12
+
13
+ class QueryTab(QWidget):
14
+ def __init__(self, parent, results_title="RESULTS"):
15
+ super().__init__()
16
+ self.parent = parent
17
+ self.current_df = None
18
+ self.filter_widgets = []
19
+ self.results_title_text = results_title
20
+ self.init_ui()
21
+
22
+ def init_ui(self):
23
+ """Initialize the tab's UI components"""
24
+ # Set main layout
25
+ main_layout = QVBoxLayout(self)
26
+ main_layout.setContentsMargins(0, 0, 0, 0)
27
+ main_layout.setSpacing(0)
28
+
29
+ # Create splitter for query and results
30
+ self.splitter = QSplitter(Qt.Orientation.Vertical)
31
+ self.splitter.setHandleWidth(8)
32
+ self.splitter.setChildrenCollapsible(False)
33
+
34
+ # Top part - Query section
35
+ query_widget = QFrame()
36
+ query_widget.setObjectName("content_panel")
37
+ query_layout = QVBoxLayout(query_widget)
38
+ query_layout.setContentsMargins(16, 16, 16, 16)
39
+ query_layout.setSpacing(12)
40
+
41
+ # Query input
42
+ self.query_edit = SQLEditor()
43
+ # Apply syntax highlighting to the query editor
44
+ self.sql_highlighter = SQLSyntaxHighlighter(self.query_edit.document())
45
+
46
+ # Ensure a default completer is available
47
+ if not self.query_edit.completer:
48
+ from PyQt6.QtCore import QStringListModel
49
+ from PyQt6.QtWidgets import QCompleter
50
+
51
+ # Create a basic completer with SQL keywords if one doesn't exist
52
+ if hasattr(self.query_edit, 'all_sql_keywords'):
53
+ model = QStringListModel(self.query_edit.all_sql_keywords)
54
+ completer = QCompleter()
55
+ completer.setModel(model)
56
+ self.query_edit.set_completer(completer)
57
+
58
+ # Connect keyboard events for direct handling of Ctrl+Enter
59
+ self.query_edit.installEventFilter(self)
60
+
61
+ query_layout.addWidget(self.query_edit)
62
+
63
+ # Button row
64
+ button_layout = QHBoxLayout()
65
+ button_layout.setSpacing(8)
66
+
67
+ self.execute_btn = QPushButton('Execute Query')
68
+ self.execute_btn.setObjectName("primary_button")
69
+ self.execute_btn.setIcon(QIcon.fromTheme("media-playback-start"))
70
+ self.execute_btn.clicked.connect(self.execute_query)
71
+
72
+ self.clear_btn = QPushButton('Clear')
73
+ self.clear_btn.clicked.connect(self.clear_query)
74
+
75
+ button_layout.addWidget(self.execute_btn)
76
+ button_layout.addWidget(self.clear_btn)
77
+ button_layout.addStretch()
78
+
79
+ self.export_excel_btn = QPushButton('Export to Excel')
80
+ self.export_excel_btn.setIcon(QIcon.fromTheme("x-office-spreadsheet"))
81
+ self.export_excel_btn.clicked.connect(self.export_to_excel)
82
+
83
+ self.export_parquet_btn = QPushButton('Export to Parquet')
84
+ self.export_parquet_btn.setIcon(QIcon.fromTheme("application-octet-stream"))
85
+ self.export_parquet_btn.clicked.connect(self.export_to_parquet)
86
+
87
+ button_layout.addWidget(self.export_excel_btn)
88
+ button_layout.addWidget(self.export_parquet_btn)
89
+
90
+ query_layout.addLayout(button_layout)
91
+
92
+ # Bottom part - Results section
93
+ results_widget = QWidget()
94
+ results_layout = QVBoxLayout(results_widget)
95
+ results_layout.setContentsMargins(16, 16, 16, 16)
96
+ results_layout.setSpacing(12)
97
+
98
+ # Results header with row count
99
+ header_layout = QHBoxLayout()
100
+ self.results_title = QLabel(self.results_title_text)
101
+ self.results_title.setObjectName("header_label")
102
+ header_layout.addWidget(self.results_title)
103
+
104
+ header_layout.addStretch()
105
+
106
+ self.row_count_label = QLabel("")
107
+ self.row_count_label.setStyleSheet(get_row_count_label_stylesheet())
108
+ header_layout.addWidget(self.row_count_label)
109
+
110
+ results_layout.addLayout(header_layout)
111
+
112
+ # Results table with customized header
113
+ self.results_table = QTableWidget()
114
+ self.results_table.setAlternatingRowColors(True)
115
+
116
+ # Use custom FilterHeader for filtering
117
+ header = FilterHeader(self.results_table)
118
+ header.set_main_window(self.parent) # Set reference to main window
119
+ self.results_table.setHorizontalHeader(header)
120
+
121
+ # Set table properties for better performance with large datasets
122
+ self.results_table.setShowGrid(True)
123
+ self.results_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
124
+ self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
125
+ self.results_table.horizontalHeader().setStretchLastSection(True)
126
+ self.results_table.verticalHeader().setVisible(True)
127
+
128
+ results_layout.addWidget(self.results_table)
129
+
130
+ # Add widgets to splitter
131
+ self.splitter.addWidget(query_widget)
132
+ self.splitter.addWidget(results_widget)
133
+
134
+ # Set initial sizes - default 40% query, 60% results
135
+ # This will be better for most uses of the app
136
+ screen = QApplication.primaryScreen()
137
+ if screen:
138
+ # Get available screen height
139
+ available_height = screen.availableGeometry().height()
140
+ # Calculate reasonable query pane size (25-35% depending on screen size)
141
+ if available_height >= 1080: # Large screens
142
+ query_height = int(available_height * 0.3) # 30% for query area
143
+ self.splitter.setSizes([query_height, available_height - query_height])
144
+ else: # Smaller screens
145
+ self.splitter.setSizes([300, 500]) # Default values for smaller screens
146
+ else:
147
+ # Fallback to fixed values if screen detection fails
148
+ self.splitter.setSizes([300, 500])
149
+
150
+ main_layout.addWidget(self.splitter)
151
+
152
+ def get_query_text(self):
153
+ """Get the current query text"""
154
+ return self.query_edit.toPlainText()
155
+
156
+ def set_query_text(self, text):
157
+ """Set the query text"""
158
+ self.query_edit.setPlainText(text)
159
+
160
+ def execute_query(self):
161
+ """Execute the current query"""
162
+ if hasattr(self.parent, 'execute_query'):
163
+ self.parent.execute_query()
164
+
165
+ def clear_query(self):
166
+ """Clear the query editor"""
167
+ if hasattr(self.parent, 'clear_query'):
168
+ self.parent.clear_query()
169
+
170
+ def export_to_excel(self):
171
+ """Export results to Excel"""
172
+ if hasattr(self.parent, 'export_to_excel'):
173
+ self.parent.export_to_excel()
174
+
175
+ def export_to_parquet(self):
176
+ """Export results to Parquet"""
177
+ if hasattr(self.parent, 'export_to_parquet'):
178
+ self.parent.export_to_parquet()
179
+
180
+ def eventFilter(self, obj, event):
181
+ """Event filter to intercept Ctrl+Enter and send it to the main window"""
182
+ from PyQt6.QtCore import QEvent, Qt
183
+
184
+ # Check if it's a key press event
185
+ if event.type() == QEvent.Type.KeyPress:
186
+ # Check for Ctrl+Enter specifically
187
+ if (event.key() == Qt.Key.Key_Return and
188
+ event.modifiers() & Qt.KeyboardModifier.ControlModifier):
189
+
190
+ # Hide any autocomplete popup if it's visible
191
+ if hasattr(obj, 'completer') and obj.completer and obj.completer.popup().isVisible():
192
+ obj.completer.popup().hide()
193
+
194
+ # Execute the query via the parent (main window)
195
+ if hasattr(self.parent, 'execute_query'):
196
+ self.parent.execute_query()
197
+ # Mark event as handled
198
+ return True
199
+
200
+ # Default - let the event propagate normally
201
+ return super().eventFilter(obj, event)
@@ -1,53 +1,131 @@
1
1
  import os
2
+ import sys
2
3
  from PIL import Image, ImageDraw, ImageFont
3
4
 
4
5
  def create_sql_icon(output_path, size=256):
5
- """Create a simple SQL icon"""
6
+ """Create a professional SQL icon with improved readability"""
6
7
  # Create a new image with a transparent background
7
8
  img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
8
9
  draw = ImageDraw.Draw(img)
9
10
 
10
11
  # Define colors
11
- primary_color = (44, 62, 80, 255) # Dark blue-gray
12
+ primary_color = (44, 62, 80, 255) # Dark blue-gray
12
13
  secondary_color = (52, 152, 219, 255) # Bright blue
13
- accent_color = (26, 188, 156, 255) # Teal
14
+ text_color = (236, 240, 241, 255) # Light gray for better readability
15
+ accent_color = (26, 188, 156, 255) # Teal
14
16
 
15
17
  # Draw a rounded rectangle background
16
- radius = size // 10
17
- rect = [(size//8, size//8), (size - size//8, size - size//8)]
18
+ radius = size // 8
19
+ rect = [(size//12, size//12), (size - size//12, size - size//12)]
18
20
  draw.rounded_rectangle(rect, radius, fill=primary_color)
19
21
 
20
- # Try to load a font, fall back to default if not available
21
- try:
22
- font_size = size // 3
23
- font = ImageFont.truetype("arial.ttf", font_size)
24
- except IOError:
22
+ # Try to load fonts in order of preference
23
+ font_size = size // 4 # Slightly smaller for better readability
24
+ font = None
25
+
26
+ # Common fonts list to try
27
+ font_options = [
28
+ "Arial Bold", "Arial", "Helvetica Bold", "Helvetica",
29
+ "DejaVuSans-Bold.ttf", "DejaVuSans.ttf",
30
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
31
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
32
+ ]
33
+
34
+ for font_name in font_options:
25
35
  try:
26
- font = ImageFont.truetype("DejaVuSans.ttf", font_size)
36
+ font = ImageFont.truetype(font_name, font_size)
37
+ break
27
38
  except IOError:
28
- font = ImageFont.load_default()
39
+ continue
29
40
 
30
- # Draw SQL text
41
+ if font is None:
42
+ font = ImageFont.load_default()
43
+ font_size = size // 5 # Adjust size for default font
44
+
45
+ # Draw "SQL" text with improved visibility
31
46
  text = "SQL"
32
- text_width, text_height = draw.textsize(text, font=font) if hasattr(draw, 'textsize') else font.getsize(text)
33
- position = ((size - text_width) // 2, (size - text_height) // 2)
34
- draw.text(position, text, fill=accent_color, font=font)
47
+
48
+ # Handle different PIL versions for text size calculation
49
+ if hasattr(draw, 'textsize'):
50
+ text_width, text_height = draw.textsize(text, font=font)
51
+ elif hasattr(font, 'getsize'):
52
+ text_width, text_height = font.getsize(text)
53
+ else:
54
+ # Fallback for newer PIL versions
55
+ try:
56
+ text_width, text_height = font.getbbox(text)[2:]
57
+ except:
58
+ text_width, text_height = font_size * 3, font_size
59
+
60
+ # Position text slightly higher
61
+ position = ((size - text_width) // 2, (size - text_height) // 2 - size//12)
62
+
63
+ # Draw text with subtle shadow for better readability
64
+ shadow_offset = max(1, size // 64)
65
+ draw.text((position[0] + shadow_offset, position[1] + shadow_offset),
66
+ text, fill=(0, 0, 0, 100), font=font)
67
+ draw.text(position, text, fill=text_color, font=font)
35
68
 
36
69
  # Draw a small database icon
37
- db_size = size // 4
70
+ db_size = size // 3
38
71
  db_x = size // 2 - db_size // 2
39
- db_y = size // 2 + text_height // 2 + db_size // 2
72
+ db_y = size // 2 + text_height // 2 + db_size // 4
73
+
74
+ # Draw database cylinder with improved shape
75
+ # Top ellipse
76
+ draw.ellipse([(db_x, db_y - db_size//4), (db_x + db_size, db_y)],
77
+ fill=secondary_color)
78
+ # Bottom ellipse
79
+ draw.ellipse([(db_x, db_y + db_size//2), (db_x + db_size, db_y + db_size//1.5)],
80
+ fill=secondary_color)
81
+ # Rectangle body
82
+ draw.rectangle([(db_x, db_y), (db_x + db_size, db_y + db_size//2)],
83
+ fill=secondary_color)
40
84
 
41
- # Draw database cylinder
42
- draw.ellipse([(db_x, db_y - db_size//3), (db_x + db_size, db_y)], fill=secondary_color)
43
- draw.ellipse([(db_x, db_y + db_size//3), (db_x + db_size, db_y + db_size//1.5)], fill=secondary_color)
44
- draw.rectangle([(db_x, db_y), (db_x + db_size, db_y + db_size//3)], fill=secondary_color)
85
+ # Add subtle details to database
86
+ highlight_color = (72, 172, 240, 150) # Lighter blue for highlight
87
+ # Database line details
88
+ line_y1 = db_y + db_size//6
89
+ line_y2 = db_y + db_size//3
90
+ draw.line([(db_x + db_size//6, line_y1), (db_x + db_size - db_size//6, line_y1)],
91
+ fill=highlight_color, width=max(1, size//128))
92
+ draw.line([(db_x + db_size//6, line_y2), (db_x + db_size - db_size//6, line_y2)],
93
+ fill=highlight_color, width=max(1, size//128))
45
94
 
46
- # Save the image
47
- img.save(output_path)
48
- print(f"Icon created at {output_path}")
95
+ # Save the image with compression
96
+ img.save(output_path, optimize=True)
97
+ print(f"Professional SQL icon created at {output_path}")
98
+ return img
49
99
 
50
- if __name__ == "__main__":
100
+ def create_logo(size=512):
101
+ """Create a properly sized logo for different uses"""
51
102
  output_dir = os.path.dirname(os.path.abspath(__file__))
52
- output_path = os.path.join(output_dir, "icon.png")
53
- create_sql_icon(output_path)
103
+ project_dir = os.path.abspath(os.path.join(output_dir, '..', '..'))
104
+
105
+ # Create different sizes
106
+ sizes = {
107
+ 'icon': 128, # For app icon
108
+ 'logo_small': 256, # For UI elements
109
+ 'logo_medium': 512, # For documentation
110
+ 'logo_large': 1024 # For high-res displays
111
+ }
112
+
113
+ for name, icon_size in sizes.items():
114
+ output_path = os.path.join(output_dir, f"{name}.png")
115
+ create_sql_icon(output_path, icon_size)
116
+
117
+ # Create the main logo file at the root directory
118
+ main_logo_path = os.path.join(project_dir, "sqlshell_logo.png")
119
+ logo = create_sql_icon(main_logo_path, size)
120
+ print(f"Main logo created at {main_logo_path}")
121
+
122
+ if __name__ == "__main__":
123
+ # Default size is 512 if no argument provided
124
+ size = 512
125
+ if len(sys.argv) > 1:
126
+ try:
127
+ size = int(sys.argv[1])
128
+ except ValueError:
129
+ print(f"Invalid size argument: {sys.argv[1]}. Using default size {size}.")
130
+
131
+ create_logo(size)
@@ -2,7 +2,7 @@ from PIL import Image, ImageDraw, ImageFont
2
2
  import os
3
3
  import math
4
4
 
5
- def create_frame(size, progress, text=None): # We won't use text parameter anymore
5
+ def create_frame(size, progress, text=None):
6
6
  # Create a new image with a dark background
7
7
  img = Image.new('RGBA', size, (44, 62, 80, 255)) # Dark blue-gray background
8
8
  draw = ImageDraw.Draw(img)
@@ -12,12 +12,31 @@ def create_frame(size, progress, text=None): # We won't use text parameter anym
12
12
  center_x = width // 2
13
13
  center_y = height // 2
14
14
 
15
- # Create a subtle circular pattern
16
- radius = 60
15
+ # Create a more visible circular pattern
16
+ radius = 80 # Larger radius
17
17
  num_dots = 12
18
- dot_size = 4
19
- trail_length = 6
18
+ dot_size = 6 # Larger dots
19
+ trail_length = 8 # Longer trail
20
20
 
21
+ # Add a subtle gradient background
22
+ for y in range(height):
23
+ # Create a vertical gradient
24
+ gradient_factor = y / height
25
+ r = int(44 + (52 - 44) * gradient_factor)
26
+ g = int(62 + (152 - 62) * gradient_factor)
27
+ b = int(80 + (219 - 80) * gradient_factor)
28
+ draw.line([(0, y), (width, y)], fill=(r, g, b, 255))
29
+
30
+ # Draw a pulsing circle
31
+ pulse_size = 40 + 15 * math.sin(progress * 2 * math.pi)
32
+ draw.ellipse(
33
+ (center_x - pulse_size, center_y - pulse_size,
34
+ center_x + pulse_size, center_y + pulse_size),
35
+ outline=(52, 152, 219, 100),
36
+ width=2
37
+ )
38
+
39
+ # Draw orbiting dots with trails
21
40
  for i in range(num_dots):
22
41
  # Calculate dot position
23
42
  angle = (progress * 360 - i * (360 / num_dots)) % 360
@@ -32,21 +51,32 @@ def create_frame(size, progress, text=None): # We won't use text parameter anym
32
51
  for size_mult in [1.0, 0.8, 0.6]:
33
52
  current_size = int(dot_size * size_mult)
34
53
  current_opacity = int(opacity * size_mult)
54
+
55
+ # Use more vibrant color for dots
35
56
  draw.ellipse(
36
57
  (x - current_size, y - current_size,
37
58
  x + current_size, y + current_size),
38
- fill=(52, 152, 219, current_opacity) # Bright blue with fading opacity
59
+ fill=(41, 128, 185, current_opacity) # Brighter blue with fading opacity
39
60
  )
40
61
 
62
+ # Add a subtle glow effect in the center
63
+ glow_radius = 60 + 10 * math.sin(progress * 4 * math.pi)
64
+ for r in range(int(glow_radius), 0, -5):
65
+ opacity = int(100 * (r / glow_radius))
66
+ draw.ellipse(
67
+ (center_x - r, center_y - r, center_x + r, center_y + r),
68
+ fill=(52, 152, 219, opacity // 8) # Very transparent blue
69
+ )
70
+
41
71
  return img
42
72
 
43
73
  def create_splash_gif():
44
74
  size = (400, 300)
45
75
  frames = []
46
76
 
47
- # Create 60 frames for smooth animation
48
- for i in range(60):
49
- progress = i / 60
77
+ # Create 30 frames for smooth animation (reduced from 60 for smaller file size)
78
+ for i in range(30):
79
+ progress = i / 30
50
80
  frame = create_frame(size, progress)
51
81
  frames.append(frame)
52
82
 
@@ -56,9 +86,9 @@ def create_splash_gif():
56
86
  output_path,
57
87
  save_all=True,
58
88
  append_images=frames[1:],
59
- duration=50, # 50ms per frame = 20fps
89
+ duration=66, # 66ms per frame = ~15fps (reduced from 20fps for smaller file size)
60
90
  loop=0,
61
- optimize=False
91
+ optimize=True # Enable optimization
62
92
  )
63
93
  print(f"Created splash screen GIF at: {output_path}")
64
94
 
Binary file
Binary file
Binary file
Binary file