Tab overhaul #4

Merged
Sudo-Ivan merged 3 commits from Tab-Overhaul into master 2025-11-03 17:40:43 +00:00
2 changed files with 96 additions and 5 deletions
Showing only changes of commit e77faa5105 - Show all commits

View File

@@ -29,7 +29,12 @@ class TabsManager:
self.page = page
self.manager = SimpleNamespace(tabs=[], index=0)
self.tab_bar = ft.Row(spacing=4)
self.tab_bar = ft.Row(
spacing=4,
scroll=ft.ScrollMode.AUTO
)
self.overflow_menu = None
self.max_visible_tabs = 8
self.content_container = ft.Container(
expand=True, bgcolor=ft.Colors.BLACK, padding=ft.padding.all(5)
)
@@ -46,8 +51,38 @@ class TabsManager:
self.close_btn = ft.IconButton(
ft.Icons.CLOSE, tooltip="Close Tab", on_click=self._on_close_click
)
self.tab_bar.controls.extend([self.add_btn, self.close_btn])
self.tab_bar.controls.append(self.add_btn)
self.tab_bar.controls.append(self.close_btn)
self.select_tab(0)
self._update_overflow()
def _update_overflow(self):
"""Update overflow menu based on number of tabs."""
tab_count = len(self.manager.tabs)
if self.overflow_menu and self.overflow_menu in self.tab_bar.controls:
self.tab_bar.controls.remove(self.overflow_menu)
self.overflow_menu = None
if tab_count > self.max_visible_tabs:
overflow_items = []
for i in range(self.max_visible_tabs, tab_count):
tab_data = self.manager.tabs[i]
overflow_items.append(
ft.PopupMenuItem(
text=tab_data["title"],
on_click=lambda e, idx=i: self.select_tab(idx)
)
)
self.overflow_menu = ft.PopupMenuButton(
icon=ft.Icons.MORE_HORIZ,
tooltip=f"{tab_count - self.max_visible_tabs} more tabs",
items=overflow_items
)
insert_pos = len(self.tab_bar.controls) - 2
self.tab_bar.controls.insert(insert_pos, self.overflow_menu)
def _add_tab_internal(self, title: str, content: ft.Control):
idx = len(self.manager.tabs)
@@ -78,7 +113,7 @@ class TabsManager:
"content": tab_content,
}
)
btn = ft.Container(
tab_container = ft.Container(
content=ft.Text(title),
on_click=lambda e, i=idx: self.select_tab(i),
padding=ft.padding.symmetric(horizontal=12, vertical=6),
@@ -86,7 +121,8 @@ class TabsManager:
bgcolor=ft.Colors.SURFACE_CONTAINER_HIGHEST,
)
insert_pos = max(0, len(self.tab_bar.controls) - 2)
self.tab_bar.controls.insert(insert_pos, btn)
self.tab_bar.controls.insert(insert_pos, tab_container)
self._update_overflow()
def _on_add_click(self, e):
title = f"Tab {len(self.manager.tabs) + 1}"
@@ -100,6 +136,7 @@ class TabsManager:
)
self._add_tab_internal(title, content)
self.select_tab(len(self.manager.tabs) - 1)
self._update_overflow()
self.page.update()
def _on_close_click(self, e):
@@ -112,6 +149,7 @@ class TabsManager:
control.on_click = lambda e, i=i: self.select_tab(i)
new_idx = min(idx, len(self.manager.tabs) - 1)
self.select_tab(new_idx)
self._update_overflow()
self.page.update()
def select_tab(self, idx: int):

View File

@@ -34,6 +34,9 @@ class TestTabsManager:
assert len(manager.manager.tabs) == 1
assert manager.manager.index == 0
assert isinstance(manager.tab_bar, ft.Row)
assert manager.tab_bar.scroll == ft.ScrollMode.AUTO
assert manager.overflow_menu is None
assert manager.max_visible_tabs == 8
assert isinstance(manager.content_container, ft.Container)
def test_tabs_manager_init_micron_renderer(self, mock_page):
@@ -150,7 +153,7 @@ class TestTabsManager:
"""Test that tab bar has correct controls."""
controls = tabs_manager.tab_bar.controls
# Should have: home tab, add button, close button
# Should have: home tab, add button, close button (and potentially overflow menu)
assert len(controls) >= 3
assert isinstance(controls[-2], ft.IconButton) # Add button
assert isinstance(controls[-1], ft.IconButton) # Close button
@@ -233,3 +236,53 @@ class TestTabsManager:
tabs_manager.content_container.content
== tabs_manager.manager.tabs[2]["content"]
)
def test_overflow_menu_creation(self, tabs_manager):
"""Test that overflow menu is created when tabs exceed max_visible_tabs."""
# Add tabs until we exceed max_visible_tabs (8)
for i in range(8): # Total will be 9 tabs
tabs_manager._add_tab_internal(f"Tab {i + 2}", Mock())
assert len(tabs_manager.manager.tabs) == 9
assert tabs_manager.overflow_menu is not None
assert isinstance(tabs_manager.overflow_menu, ft.PopupMenuButton)
assert tabs_manager.overflow_menu.icon == ft.Icons.MORE_HORIZ
def test_overflow_menu_items(self, tabs_manager):
"""Test that overflow menu contains correct items."""
# Add tabs to trigger overflow
for i in range(8): # Total will be 9 tabs
tabs_manager._add_tab_internal(f"Tab {i + 2}", Mock())
overflow_items = tabs_manager.overflow_menu.items
assert len(overflow_items) == 1 # Only the 9th tab should be in overflow
assert overflow_items[0].text == "Tab 9"
def test_overflow_menu_removal(self, tabs_manager):
"""Test that overflow menu is removed when tabs are reduced."""
# Add tabs to trigger overflow
for i in range(8): # Total will be 9 tabs
tabs_manager._add_tab_internal(f"Tab {i + 2}", Mock())
assert tabs_manager.overflow_menu is not None
# Remove tabs until we're below the limit
for _ in range(2):
tabs_manager.select_tab(len(tabs_manager.manager.tabs) - 1)
tabs_manager._on_close_click(None)
assert len(tabs_manager.manager.tabs) == 7
assert tabs_manager.overflow_menu is None
def test_overflow_menu_select_tab(self, tabs_manager):
"""Test selecting a tab from overflow menu."""
# Add tabs to trigger overflow
for i in range(8): # Total will be 9 tabs
tabs_manager._add_tab_internal(f"Tab {i + 2}", Mock())
# Simulate clicking overflow menu item for tab at index 8
overflow_item = tabs_manager.overflow_menu.items[0]
# The on_click handler should select tab at index 8
overflow_item.on_click(None) # This should call select_tab(8)
assert tabs_manager.manager.index == 8