Skip to content

Commit 923eb65

Browse files
committed
refactor(ui): 🔨 restructure layout system with fixed height constraints and grid ratios
This commit restructures the model filter GUI layout to use a grid-based system with proper weight ratios and minimum height constraints, ensuring stable proportions during window resizing. - Reduce minimum window dimensions from 850x600 to 600x400 for better flexibility - Implement 3:1 grid weight ratio between model lists (weight=3) and rule panels (weight=1) - Add grid_propagate(False) to key containers to prevent content from dictating frame sizes - Reorganize RulePanel layout: title at top, input frame at bottom (packed first to reserve space), rule list in middle - Set minimum row sizes: 200px for model lists, 55px for rule panels - Remove obsolete RuleChip class (141 lines) - functionality now handled by VirtualRuleList - Adjust padding and spacing values for more compact layout The new grid-based approach ensures consistent proportional sizing and prevents UI elements from being squished or expanding unpredictably during window resizing.
1 parent 2d9a112 commit 923eb65

File tree

1 file changed

+36
-171
lines changed

1 file changed

+36
-171
lines changed

src/proxy_app/model_filter_gui.py

Lines changed: 36 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
# Window settings
3333
WINDOW_TITLE = "Model Filter Configuration"
3434
WINDOW_DEFAULT_SIZE = "1000x750"
35-
WINDOW_MIN_WIDTH = 850
36-
WINDOW_MIN_HEIGHT = 600
35+
WINDOW_MIN_WIDTH = 600
36+
WINDOW_MIN_HEIGHT = 400
3737

3838
# Color scheme (dark mode)
3939
BG_PRIMARY = "#1a1a2e" # Main background
@@ -1377,6 +1377,9 @@ def __init__(
13771377

13781378
def _create_content(self):
13791379
"""Build the dual list layout."""
1380+
# Don't let content dictate size - let parent grid control height
1381+
self.grid_propagate(False)
1382+
13801383
# Configure grid
13811384
self.grid_columnconfigure(0, weight=1)
13821385
self.grid_columnconfigure(1, weight=1)
@@ -2017,147 +2020,6 @@ def _render(self):
20172020
)
20182021

20192022

2020-
# ════════════════════════════════════════════════════════════════════════════════
2021-
# RULE CHIP COMPONENT
2022-
# ════════════════════════════════════════════════════════════════════════════════
2023-
2024-
2025-
class RuleChip(ctk.CTkFrame):
2026-
"""
2027-
Individual rule display showing pattern, affected count, and delete button.
2028-
2029-
The pattern text is colored with the rule's assigned color.
2030-
"""
2031-
2032-
def __init__(
2033-
self,
2034-
master,
2035-
rule: FilterRule,
2036-
on_delete: Callable[[str], None],
2037-
on_click: Callable[[FilterRule], None],
2038-
):
2039-
super().__init__(
2040-
master,
2041-
fg_color=BG_TERTIARY,
2042-
corner_radius=6,
2043-
border_width=1,
2044-
border_color=BORDER_COLOR,
2045-
)
2046-
2047-
self.rule = rule
2048-
self.on_delete = on_delete
2049-
self.on_click = on_click
2050-
self._is_highlighted = False
2051-
self._tooltip = None # Store tooltip reference to avoid duplicates
2052-
2053-
self._create_content()
2054-
2055-
# Click binding
2056-
self.bind("<Button-1>", self._handle_click)
2057-
2058-
def _create_content(self):
2059-
"""Build chip content."""
2060-
# Container for horizontal layout
2061-
self.content = ctk.CTkFrame(self, fg_color="transparent")
2062-
self.content.pack(fill="x", padx=8, pady=6)
2063-
2064-
# Pattern text (colored)
2065-
self.pattern_label = ctk.CTkLabel(
2066-
self.content,
2067-
text=self.rule.pattern,
2068-
font=(FONT_FAMILY, FONT_SIZE_NORMAL),
2069-
text_color=self.rule.color,
2070-
anchor="w",
2071-
)
2072-
self.pattern_label.pack(side="left", fill="x", expand=True)
2073-
self.pattern_label.bind("<Button-1>", self._handle_click)
2074-
2075-
# Affected count
2076-
self.count_label = ctk.CTkLabel(
2077-
self.content,
2078-
text=f"({self.rule.affected_count})",
2079-
font=(FONT_FAMILY, FONT_SIZE_SMALL),
2080-
text_color=TEXT_MUTED,
2081-
width=35,
2082-
)
2083-
self.count_label.pack(side="left", padx=(5, 5))
2084-
self.count_label.bind("<Button-1>", self._handle_click)
2085-
2086-
# Delete button
2087-
delete_btn = ctk.CTkButton(
2088-
self.content,
2089-
text="×",
2090-
font=(FONT_FAMILY, FONT_SIZE_LARGE, "bold"),
2091-
fg_color="transparent",
2092-
hover_color=ACCENT_RED,
2093-
text_color=TEXT_MUTED,
2094-
width=24,
2095-
height=24,
2096-
corner_radius=4,
2097-
command=self._handle_delete,
2098-
)
2099-
delete_btn.pack(side="right")
2100-
2101-
# Tooltip showing affected models - create once, update later
2102-
self._update_tooltip()
2103-
2104-
# Bind tooltip events to child widgets (not delete button)
2105-
for widget in [self.content, self.pattern_label, self.count_label]:
2106-
widget.bind("<Enter>", self._on_tooltip_enter)
2107-
widget.bind("<Leave>", self._on_tooltip_leave)
2108-
2109-
def _on_tooltip_enter(self, event=None):
2110-
"""Forward enter event to tooltip."""
2111-
if self._tooltip:
2112-
self._tooltip._schedule_show(event)
2113-
2114-
def _on_tooltip_leave(self, event=None):
2115-
"""Forward leave event to tooltip."""
2116-
if self._tooltip:
2117-
self._tooltip._hide(event)
2118-
2119-
def _handle_click(self, event=None):
2120-
"""Handle click on rule chip."""
2121-
self.on_click(self.rule)
2122-
2123-
def _handle_delete(self):
2124-
"""Handle delete button click."""
2125-
self.on_delete(self.rule.pattern)
2126-
2127-
def update_count(self, count: int, affected_models: List[str]):
2128-
"""Update the affected count and tooltip."""
2129-
self.rule.affected_count = count
2130-
self.rule.affected_models = affected_models
2131-
self.count_label.configure(text=f"({count})")
2132-
self._update_tooltip()
2133-
2134-
def _update_tooltip(self):
2135-
"""Update tooltip with affected models."""
2136-
if self.rule.affected_models:
2137-
if len(self.rule.affected_models) <= 5:
2138-
models_text = "\n".join(self.rule.affected_models)
2139-
else:
2140-
models_text = "\n".join(self.rule.affected_models[:5])
2141-
models_text += f"\n... and {len(self.rule.affected_models) - 5} more"
2142-
text = f"Matches:\n{models_text}"
2143-
else:
2144-
text = "No models match this pattern"
2145-
2146-
# Reuse existing tooltip or create new one
2147-
if self._tooltip is None:
2148-
self._tooltip = ToolTip(self, text)
2149-
else:
2150-
self._tooltip.update_text(text)
2151-
2152-
def set_highlighted(self, highlighted: bool):
2153-
"""Set highlighted state."""
2154-
self._is_highlighted = highlighted
2155-
if highlighted:
2156-
self.configure(border_color=self.rule.color, border_width=2)
2157-
else:
2158-
self.configure(border_color=BORDER_COLOR, border_width=1)
2159-
2160-
21612023
# ════════════════════════════════════════════════════════════════════════════════
21622024
# RULE PANEL COMPONENT
21632025
# ════════════════════════════════════════════════════════════════════════════════
@@ -2191,30 +2053,18 @@ def __init__(
21912053

21922054
def _create_content(self):
21932055
"""Build panel content."""
2194-
# Title (compact)
2056+
# Title at top (compact)
21952057
title_label = ctk.CTkLabel(
21962058
self,
21972059
text=self.title,
21982060
font=(FONT_FAMILY, FONT_SIZE_SMALL, "bold"),
21992061
text_color=TEXT_PRIMARY,
22002062
)
2201-
title_label.pack(anchor="w", padx=10, pady=(6, 3))
2063+
title_label.pack(side="top", anchor="w", padx=10, pady=(4, 2))
22022064

2203-
# Virtual rule list (replaces CTkScrollableFrame + RuleChips)
2204-
self.rule_list = VirtualRuleList(
2205-
self,
2206-
rule_type=self.rule_type,
2207-
on_rule_click=self.on_rule_clicked,
2208-
on_rule_delete=self._on_rule_delete,
2209-
)
2210-
self.rule_list.pack(fill="both", expand=True, padx=6, pady=(0, 3))
2211-
2212-
# Set minimum height for rule list to ensure it's visible
2213-
self.rule_list.frame.configure(height=70)
2214-
2215-
# Input frame with fixed height (won't squish on resize)
2065+
# Input frame at BOTTOM - pack BEFORE rule_list to reserve space
22162066
input_frame = ctk.CTkFrame(self, fg_color="transparent", height=32)
2217-
input_frame.pack(fill="x", padx=6, pady=(0, 5))
2067+
input_frame.pack(side="bottom", fill="x", padx=6, pady=(2, 4))
22182068
input_frame.pack_propagate(False) # Prevent children from changing frame height
22192069

22202070
# Pattern input
@@ -2228,7 +2078,7 @@ def _create_content(self):
22282078
placeholder_text_color=TEXT_MUTED,
22292079
height=28,
22302080
)
2231-
self.input_entry.pack(side="left", fill="x", expand=True, padx=(0, 6))
2081+
self.input_entry.pack(side="left", fill="both", expand=True, padx=(0, 6))
22322082
self.input_entry.bind("<Return>", self._on_add_clicked)
22332083
self.input_entry.bind("<KeyRelease>", self._on_input_key)
22342084

@@ -2245,6 +2095,15 @@ def _create_content(self):
22452095
)
22462096
add_btn.pack(side="right")
22472097

2098+
# Virtual rule list fills REMAINING middle space - pack LAST
2099+
self.rule_list = VirtualRuleList(
2100+
self,
2101+
rule_type=self.rule_type,
2102+
on_rule_click=self.on_rule_clicked,
2103+
on_rule_delete=self._on_rule_delete,
2104+
)
2105+
self.rule_list.pack(side="top", fill="both", expand=True, padx=6, pady=(0, 2))
2106+
22482107
def _on_input_key(self, event=None):
22492108
"""Handle key release in input field - for real-time preview."""
22502109
text = self.input_entry.get().strip()
@@ -2365,20 +2224,24 @@ def __init__(self):
23652224
self.after(100, self._activate_window)
23662225

23672226
def _create_main_layout(self):
2368-
"""Create the main layout with grid for responsive sizing."""
2369-
# Main content frame using grid layout
2370-
# This allows proportional sizing between model lists and rule panels
2227+
"""Create the main layout with grid weights for 3:1 ratio."""
2228+
# Main content frame - regular frame with grid layout
23712229
self.content_frame = ctk.CTkFrame(self, fg_color="transparent")
2372-
self.content_frame.pack(fill="both", expand=True, padx=20, pady=(8, 10))
2230+
self.content_frame.pack(fill="both", expand=True, padx=15, pady=(5, 8))
23732231

2374-
# Configure grid weights for responsive layout
2375-
# Using 3:1 ratio so models get significantly more space than rules
2232+
# Configure grid with proper weights for 3:1 ratio
23762233
self.content_frame.grid_columnconfigure(0, weight=1)
2377-
self.content_frame.grid_rowconfigure(0, weight=0) # Header - fixed
2378-
self.content_frame.grid_rowconfigure(1, weight=0) # Search - fixed
2379-
self.content_frame.grid_rowconfigure(2, weight=3) # Model lists - expands most
2380-
self.content_frame.grid_rowconfigure(3, weight=1) # Rule panels - expands less
2381-
self.content_frame.grid_rowconfigure(4, weight=0) # Status bar - fixed
2234+
2235+
# Row 0: Header - fixed height
2236+
self.content_frame.grid_rowconfigure(0, weight=0)
2237+
# Row 1: Search - fixed height
2238+
self.content_frame.grid_rowconfigure(1, weight=0)
2239+
# Row 2: Model lists - weight=3 for 3:1 ratio, minimum 100px
2240+
self.content_frame.grid_rowconfigure(2, weight=3, minsize=200)
2241+
# Row 3: Rule panels - weight=1 for 3:1 ratio, minimum 55px
2242+
self.content_frame.grid_rowconfigure(3, weight=1, minsize=55)
2243+
# Row 4: Status bar - fixed height
2244+
self.content_frame.grid_rowconfigure(4, weight=0)
23822245

23832246
# Create all sections
23842247
self._create_header()
@@ -2527,6 +2390,8 @@ def _create_rule_panels(self):
25272390
"""Create the ignore and whitelist rule panels."""
25282391
self.rules_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
25292392
self.rules_frame.grid(row=3, column=0, sticky="nsew", pady=(0, 5))
2393+
# Don't let content dictate size - let parent grid control height
2394+
self.rules_frame.grid_propagate(False)
25302395
self.rules_frame.grid_columnconfigure(0, weight=1)
25312396
self.rules_frame.grid_columnconfigure(1, weight=1)
25322397
self.rules_frame.grid_rowconfigure(0, weight=1)

0 commit comments

Comments
 (0)