Skip to content

Commit 1922d8a

Browse files
committed
feat: implement lazy loading for tab content in GUI windows
Implemented a lazy loading mechanism for tabs in PromptEditorWindow and SettingsWindow to improve initial window loading performance. Tab contents are now rendered only when a tab is first selected, reducing startup overhead. - Added `_tab_configs` to track initialization state and creation methods for each tab - Integrated `_on_tab_changed` callback with CustomTkinter's Tabview - Modified window initialization to only load the default active tab immediately - Added helper methods `_load_tab_content` to handle safe on-demand rendering
1 parent 7b24bde commit 1922d8a

File tree

3 files changed

+112
-30
lines changed

3 files changed

+112
-30
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [4.1.1] - 2026-02-10
4+
5+
### Improvements
6+
7+
- **Performance**: Implemented lazy loading for tabs in Settings and Prompt Editor windows, rendering content only upon selection to improve window opening speed.
8+
39
## [4.1.0] - 2026-02-09
410

511
### New Features
@@ -13,7 +19,7 @@
1319

1420
### Build & Deployment
1521

16-
- **Launchers**: Replaced C# launchers with Python-based cx_Freeze implementations to eliminate AV false positives and to simplify and centralize logi.
22+
- **Launchers**: Replaced C# launchers with Python-based cx_Freeze implementations to eliminate AV false positives and to simplify and centralize logic.
1723

1824
## [4.0.1] - 2026-02-08
1925

src/gui/windows/prompt_editor.py

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -659,25 +659,31 @@ def _create_main_content(self, parent):
659659
segmented_button_unselected_color=self.colors.surface0,
660660
segmented_button_unselected_hover_color=self.colors.surface1,
661661
text_color=self.colors.fg,
662-
corner_radius=8
662+
corner_radius=8,
663+
command=self._on_tab_changed
663664
)
664665
self.tabview.pack(fill="both", expand=True, pady=(0, 2))
665666

666-
# Create tabs
667-
self.tabview.add("⚡ Actions")
668-
self.tabview.add("📁 Groups")
669-
self.tabview.add("⚙️ Settings")
670-
self.tabview.add("🎛️ Modifiers")
671-
self.tabview.add("🧪 Playground")
667+
# Lazy loading configuration
668+
# Format: "Tab Name": ("method_name", is_loaded_bool)
669+
self._tab_configs = {
670+
"⚡ Actions": ("_create_actions_tab", False),
671+
"📁 Groups": ("_create_groups_tab", False),
672+
"⚙️ Settings": ("_create_settings_tab", False),
673+
"🎛️ Modifiers": ("_create_modifiers_tab", False),
674+
"🧪 Playground": ("_create_playground_tab", False)
675+
}
676+
677+
# Create tabs (empty initially)
678+
for tab_name in self._tab_configs.keys():
679+
self.tabview.add(tab_name)
672680

673681
# Upgrade tabs with images and larger font
674682
upgrade_tabview_with_icons(self.tabview)
675683

676-
self._create_actions_tab(self.tabview.tab("⚡ Actions"))
677-
self._create_groups_tab(self.tabview.tab("📁 Groups"))
678-
self._create_settings_tab(self.tabview.tab("⚙️ Settings"))
679-
self._create_modifiers_tab(self.tabview.tab("🎛️ Modifiers"))
680-
self._create_playground_tab(self.tabview.tab("🧪 Playground"))
684+
# Load the first tab immediately
685+
first_tab = "⚡ Actions"
686+
self._load_tab_content(first_tab)
681687
else:
682688
# Fallback to ttk.Notebook
683689
from tkinter import ttk
@@ -704,6 +710,40 @@ def _create_main_content(self, parent):
704710
self._create_modifiers_tab(modifiers_frame)
705711
self._create_playground_tab(playground_frame)
706712

713+
def _on_tab_changed(self):
714+
"""Handle tab change event - lazy load tab content."""
715+
if not self.use_ctk or not hasattr(self, '_tab_configs'):
716+
return
717+
718+
current_tab = self.tabview.get()
719+
self._load_tab_content(current_tab)
720+
721+
def _load_tab_content(self, tab_name: str):
722+
"""Load content for a tab if not already loaded."""
723+
if not hasattr(self, '_tab_configs') or tab_name not in self._tab_configs:
724+
return
725+
726+
method_name, is_loaded = self._tab_configs[tab_name]
727+
728+
if is_loaded:
729+
return # Already loaded
730+
731+
# Mark as loaded first to prevent re-entry
732+
self._tab_configs[tab_name] = (method_name, True)
733+
734+
# Get tab frame
735+
tab_frame = self.tabview.tab(tab_name)
736+
737+
# Call creation method
738+
create_method = getattr(self, method_name, None)
739+
if create_method and callable(create_method):
740+
try:
741+
create_method(tab_frame)
742+
except Exception as e:
743+
print(f"[PromptEditor] Error loading tab '{tab_name}': {e}")
744+
import traceback
745+
traceback.print_exc()
746+
707747
def _refresh_action_list(self):
708748
"""Refresh the action scrollable list based on current tool."""
709749
if not self.action_listbox:

src/gui/windows/settings_window.py

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,10 @@ def show(self, initial_tab: str = None):
525525
if not found:
526526
print(f"[Settings] Could not find initial tab '{initial_tab}'")
527527

528+
# Ensure content for the active tab is loaded (since explicit .set() doesn't trigger command)
529+
if self.use_ctk:
530+
self._load_tab_content(self.tabview.get())
531+
528532
# Register and bind
529533
register_window(self.window_tag)
530534
self.root.protocol("WM_DELETE_WINDOW", self._close)
@@ -657,7 +661,7 @@ def _create_title_bar(self, parent):
657661
bg=self.colors.bg, fg=self.colors.blockquote).pack(side="left", padx=(15, 0))
658662

659663
def _create_notebook(self, parent):
660-
"""Create the tabbed notebook."""
664+
"""Create the tabbed notebook with lazy tab loading for performance."""
661665
if self.use_ctk:
662666
self.tabview = ctk.CTkTabview(
663667
parent,
@@ -668,36 +672,40 @@ def _create_notebook(self, parent):
668672
segmented_button_unselected_color=self.colors.surface0,
669673
segmented_button_unselected_hover_color=self.colors.surface1,
670674
text_color=self.colors.fg,
671-
corner_radius=8
675+
corner_radius=8,
676+
command=self._on_tab_changed # Lazy loading callback
672677
)
673678
self.tabview.pack(fill="both", expand=True, pady=(0, 2))
674679

675-
# Create tabs
676-
self.tabview.add("⚙️ General")
677-
self.tabview.add("🌐 Provider")
678-
self.tabview.add("⚡ Streaming")
679-
self.tabview.add("🔧 Tools")
680-
self.tabview.add("🔑 API Keys")
681-
self.tabview.add("🔗 Endpoints")
682-
self.tabview.add("🎨 Theme")
680+
# Define tab configurations for lazy loading
681+
self._tab_configs = {
682+
"⚙️ General": ("_create_general_tab", False),
683+
"🌐 Provider": ("_create_provider_tab", False),
684+
"⚡ Streaming": ("_create_streaming_tab", False),
685+
"🔧 Tools": ("_create_tools_tab", False),
686+
"🔑 API Keys": ("_create_keys_tab", False),
687+
"🔗 Endpoints": ("_create_endpoints_tab", False),
688+
"🎨 Theme": ("_create_theme_tab", False),
689+
}
690+
691+
# Create empty tabs
692+
for tab_name in self._tab_configs.keys():
693+
self.tabview.add(tab_name)
683694

684695
# Upgrade tabs with images and larger font
685696
upgrade_tabview_with_icons(self.tabview)
686697

687-
self._create_general_tab(self.tabview.tab("⚙️ General"))
688-
self._create_provider_tab(self.tabview.tab("🌐 Provider"))
689-
self._create_streaming_tab(self.tabview.tab("⚡ Streaming"))
690-
self._create_tools_tab(self.tabview.tab("🔧 Tools"))
691-
self._create_keys_tab(self.tabview.tab("🔑 API Keys"))
692-
self._create_endpoints_tab(self.tabview.tab("🔗 Endpoints"))
693-
self._create_theme_tab(self.tabview.tab("🎨 Theme"))
698+
# Only create the first tab's content immediately
699+
first_tab = "⚙️ General"
700+
self._load_tab_content(first_tab)
694701
else:
695702
from tkinter import ttk
696703
style = ttk.Style(self.root)
697704
style.theme_use('clam')
698705
self.tabview = ttk.Notebook(parent)
699706
self.tabview.pack(fill="both", expand=True, pady=(0, 2))
700707

708+
# For ttk.Notebook, we don't do lazy loading (simpler fallback)
701709
tabs = ["General", "Provider", "Streaming", "Tools", "API Keys", "Endpoints", "Theme"]
702710
frames = {}
703711
for tab_name in tabs:
@@ -713,6 +721,34 @@ def _create_notebook(self, parent):
713721
self._create_endpoints_tab(frames["Endpoints"])
714722
self._create_theme_tab(frames["Theme"])
715723

724+
def _on_tab_changed(self):
725+
"""Handle tab change event - lazy load tab content."""
726+
if not self.use_ctk or not hasattr(self, '_tab_configs'):
727+
return
728+
729+
current_tab = self.tabview.get()
730+
self._load_tab_content(current_tab)
731+
732+
def _load_tab_content(self, tab_name: str):
733+
"""Load content for a tab if not already loaded."""
734+
if not hasattr(self, '_tab_configs') or tab_name not in self._tab_configs:
735+
return
736+
737+
method_name, is_loaded = self._tab_configs[tab_name]
738+
739+
if is_loaded:
740+
return # Already loaded
741+
742+
# Mark as loaded before creating (to prevent re-entry)
743+
self._tab_configs[tab_name] = (method_name, True)
744+
745+
# Get the tab frame and call the creation method
746+
tab_frame = self.tabview.tab(tab_name)
747+
create_method = getattr(self, method_name, None)
748+
749+
if create_method and callable(create_method):
750+
create_method(tab_frame)
751+
716752
def _create_general_tab(self, frame):
717753
"""Create the General settings tab."""
718754
content_parent = None

0 commit comments

Comments
 (0)