Mirrowel commited on
Commit
923eb65
Β·
1 Parent(s): 2d9a112

refactor(ui): πŸ”¨ restructure layout system with fixed height constraints and grid ratios

Browse files

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.

Files changed (1) hide show
  1. src/proxy_app/model_filter_gui.py +36 -171
src/proxy_app/model_filter_gui.py CHANGED
@@ -32,8 +32,8 @@ from dotenv import load_dotenv, set_key, unset_key
32
  # Window settings
33
  WINDOW_TITLE = "Model Filter Configuration"
34
  WINDOW_DEFAULT_SIZE = "1000x750"
35
- WINDOW_MIN_WIDTH = 850
36
- WINDOW_MIN_HEIGHT = 600
37
 
38
  # Color scheme (dark mode)
39
  BG_PRIMARY = "#1a1a2e" # Main background
@@ -1377,6 +1377,9 @@ class VirtualSyncModelLists(ctk.CTkFrame):
1377
 
1378
  def _create_content(self):
1379
  """Build the dual list layout."""
 
 
 
1380
  # Configure grid
1381
  self.grid_columnconfigure(0, weight=1)
1382
  self.grid_columnconfigure(1, weight=1)
@@ -2017,147 +2020,6 @@ class VirtualRuleList:
2017
  )
2018
 
2019
 
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
-
2161
  # ════════════════════════════════════════════════════════════════════════════════
2162
  # RULE PANEL COMPONENT
2163
  # ════════════════════════════════════════════════════════════════════════════════
@@ -2191,30 +2053,18 @@ class RulePanel(ctk.CTkFrame):
2191
 
2192
  def _create_content(self):
2193
  """Build panel content."""
2194
- # Title (compact)
2195
  title_label = ctk.CTkLabel(
2196
  self,
2197
  text=self.title,
2198
  font=(FONT_FAMILY, FONT_SIZE_SMALL, "bold"),
2199
  text_color=TEXT_PRIMARY,
2200
  )
2201
- title_label.pack(anchor="w", padx=10, pady=(6, 3))
2202
 
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)
2216
  input_frame = ctk.CTkFrame(self, fg_color="transparent", height=32)
2217
- input_frame.pack(fill="x", padx=6, pady=(0, 5))
2218
  input_frame.pack_propagate(False) # Prevent children from changing frame height
2219
 
2220
  # Pattern input
@@ -2228,7 +2078,7 @@ class RulePanel(ctk.CTkFrame):
2228
  placeholder_text_color=TEXT_MUTED,
2229
  height=28,
2230
  )
2231
- self.input_entry.pack(side="left", fill="x", expand=True, padx=(0, 6))
2232
  self.input_entry.bind("<Return>", self._on_add_clicked)
2233
  self.input_entry.bind("<KeyRelease>", self._on_input_key)
2234
 
@@ -2245,6 +2095,15 @@ class RulePanel(ctk.CTkFrame):
2245
  )
2246
  add_btn.pack(side="right")
2247
 
 
 
 
 
 
 
 
 
 
2248
  def _on_input_key(self, event=None):
2249
  """Handle key release in input field - for real-time preview."""
2250
  text = self.input_entry.get().strip()
@@ -2365,20 +2224,24 @@ class ModelFilterGUI(ctk.CTk):
2365
  self.after(100, self._activate_window)
2366
 
2367
  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
2371
  self.content_frame = ctk.CTkFrame(self, fg_color="transparent")
2372
- self.content_frame.pack(fill="both", expand=True, padx=20, pady=(8, 10))
2373
 
2374
- # Configure grid weights for responsive layout
2375
- # Using 3:1 ratio so models get significantly more space than rules
2376
  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
 
 
 
 
 
 
2382
 
2383
  # Create all sections
2384
  self._create_header()
@@ -2527,6 +2390,8 @@ class ModelFilterGUI(ctk.CTk):
2527
  """Create the ignore and whitelist rule panels."""
2528
  self.rules_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
2529
  self.rules_frame.grid(row=3, column=0, sticky="nsew", pady=(0, 5))
 
 
2530
  self.rules_frame.grid_columnconfigure(0, weight=1)
2531
  self.rules_frame.grid_columnconfigure(1, weight=1)
2532
  self.rules_frame.grid_rowconfigure(0, weight=1)
 
32
  # Window settings
33
  WINDOW_TITLE = "Model Filter Configuration"
34
  WINDOW_DEFAULT_SIZE = "1000x750"
35
+ WINDOW_MIN_WIDTH = 600
36
+ WINDOW_MIN_HEIGHT = 400
37
 
38
  # Color scheme (dark mode)
39
  BG_PRIMARY = "#1a1a2e" # Main background
 
1377
 
1378
  def _create_content(self):
1379
  """Build the dual list layout."""
1380
+ # Don't let content dictate size - let parent grid control height
1381
+ self.grid_propagate(False)
1382
+
1383
  # Configure grid
1384
  self.grid_columnconfigure(0, weight=1)
1385
  self.grid_columnconfigure(1, weight=1)
 
2020
  )
2021
 
2022
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2023
  # ════════════════════════════════════════════════════════════════════════════════
2024
  # RULE PANEL COMPONENT
2025
  # ════════════════════════════════════════════════════════════════════════════════
 
2053
 
2054
  def _create_content(self):
2055
  """Build panel content."""
2056
+ # Title at top (compact)
2057
  title_label = ctk.CTkLabel(
2058
  self,
2059
  text=self.title,
2060
  font=(FONT_FAMILY, FONT_SIZE_SMALL, "bold"),
2061
  text_color=TEXT_PRIMARY,
2062
  )
2063
+ title_label.pack(side="top", anchor="w", padx=10, pady=(4, 2))
2064
 
2065
+ # Input frame at BOTTOM - pack BEFORE rule_list to reserve space
 
 
 
 
 
 
 
 
 
 
 
 
2066
  input_frame = ctk.CTkFrame(self, fg_color="transparent", height=32)
2067
+ input_frame.pack(side="bottom", fill="x", padx=6, pady=(2, 4))
2068
  input_frame.pack_propagate(False) # Prevent children from changing frame height
2069
 
2070
  # Pattern input
 
2078
  placeholder_text_color=TEXT_MUTED,
2079
  height=28,
2080
  )
2081
+ self.input_entry.pack(side="left", fill="both", expand=True, padx=(0, 6))
2082
  self.input_entry.bind("<Return>", self._on_add_clicked)
2083
  self.input_entry.bind("<KeyRelease>", self._on_input_key)
2084
 
 
2095
  )
2096
  add_btn.pack(side="right")
2097
 
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
+
2107
  def _on_input_key(self, event=None):
2108
  """Handle key release in input field - for real-time preview."""
2109
  text = self.input_entry.get().strip()
 
2224
  self.after(100, self._activate_window)
2225
 
2226
  def _create_main_layout(self):
2227
+ """Create the main layout with grid weights for 3:1 ratio."""
2228
+ # Main content frame - regular frame with grid layout
 
2229
  self.content_frame = ctk.CTkFrame(self, fg_color="transparent")
2230
+ self.content_frame.pack(fill="both", expand=True, padx=15, pady=(5, 8))
2231
 
2232
+ # Configure grid with proper weights for 3:1 ratio
 
2233
  self.content_frame.grid_columnconfigure(0, weight=1)
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)
2245
 
2246
  # Create all sections
2247
  self._create_header()
 
2390
  """Create the ignore and whitelist rule panels."""
2391
  self.rules_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent")
2392
  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)
2395
  self.rules_frame.grid_columnconfigure(0, weight=1)
2396
  self.rules_frame.grid_columnconfigure(1, weight=1)
2397
  self.rules_frame.grid_rowconfigure(0, weight=1)