242 lines
8.4 KiB
Python
242 lines
8.4 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
Flet‑приложение: боковая панель + рабочая область
|
||
(с учётом всех уточнений: исчезновение текста при сворачивании,
|
||
акцент только на активной группе/вкладке, иконка‑кнопка смены темы,
|
||
фиолетовый акцентный цвет).
|
||
"""
|
||
|
||
import flet as ft
|
||
|
||
# ---------- Параметры изображений ----------
|
||
LOGO_LIGHT_COLLAPSED = "logo_light_small.png" # светлый логотип, «маленький»
|
||
LOGO_LIGHT_EXPANDED = "logo_light_large.png" # светлый логотип, «большой»
|
||
LOGO_DARK_COLLAPSED = "logo_dark_small.png" # тёмный логотип, «маленький»
|
||
LOGO_DARK_EXPANDED = "logo_dark_large.png" # тёмный логотип, «большой»
|
||
|
||
# ---------- Цвета ----------
|
||
LIGHT_BG = "#e0f7fa"
|
||
DARK_BG = "#263238"
|
||
LIGHT_ACC = "#9C27B0" # фиолетовый 500
|
||
DARK_ACC = "#7B1FA2" # фиолетовый 700
|
||
|
||
# ---------- Вспомогательные функции ----------
|
||
def status_lamp(color: str) -> ft.Icon:
|
||
return ft.Icon(ft.Icons.CIRCLE, size=12, color=color)
|
||
|
||
def group_icon(name: str) -> ft.Icon:
|
||
mapping = {
|
||
"Repository": ft.Icons.ARCHIVE,
|
||
"Environment": ft.Icons.FOLDER,
|
||
"Civit": ft.Icons.HEXAGON,
|
||
"Tasks": ft.Icons.DOWNLOAD,
|
||
}
|
||
return ft.Icon(mapping.get(name, ft.Icons.FOLDER), size=20)
|
||
|
||
def tab_icon() -> ft.Icon:
|
||
return ft.Icon(ft.Icons.FILE_COPY, size=30)
|
||
|
||
# ---------- Главная функция ----------
|
||
def main(page: ft.Page):
|
||
page.title = "Flet Sidebar Demo"
|
||
page.vertical_alignment = ft.MainAxisAlignment.START
|
||
page.horizontal_alignment = ft.CrossAxisAlignment.START
|
||
|
||
# ----- Состояния -----
|
||
is_expanded = True
|
||
|
||
group_expanded = {
|
||
"Repository": True,
|
||
"Environment": False,
|
||
"Civit": False,
|
||
"Tasks": False,
|
||
}
|
||
|
||
selected_group: str | None = None
|
||
selected_tab_name: str | None = None
|
||
|
||
selected_tab = ft.Text(value="Выберите вкладку", size=24, weight=ft.FontWeight.W_400)
|
||
|
||
# ----- Логотип -----
|
||
def get_logo_path() -> str:
|
||
if page.theme_mode == ft.ThemeMode.LIGHT:
|
||
return LOGO_LIGHT_EXPANDED if is_expanded else LOGO_LIGHT_COLLAPSED
|
||
else:
|
||
return LOGO_DARK_EXPANDED if is_expanded else LOGO_DARK_COLLAPSED
|
||
|
||
# ----- Панель навигации -----
|
||
sidebar = ft.Container(
|
||
width=200 if is_expanded else 60,
|
||
bgcolor=ft.Colors.SURFACE,
|
||
padding=ft.padding.all(8),
|
||
content=ft.Column(spacing=8, controls=[]),
|
||
)
|
||
|
||
# ----- Рабочая область -----
|
||
main_area = ft.Container(
|
||
expand=True,
|
||
padding=ft.padding.all(20),
|
||
content=ft.Container(
|
||
alignment=ft.alignment.center,
|
||
content=selected_tab
|
||
)
|
||
)
|
||
|
||
# ----- Макет -----
|
||
page.add(ft.Row(controls=[sidebar, main_area], expand=True))
|
||
|
||
# ----- Пересоздание боковой панели -----
|
||
def rebuild_sidebar():
|
||
"""Пересоздаёт содержимое боковой панели."""
|
||
controls = []
|
||
|
||
# 1. Логотип (с кликом)
|
||
logo_img = ft.Image(
|
||
src=get_logo_path(),
|
||
width=50 if not is_expanded else 150,
|
||
height=50 if not is_expanded else 150,
|
||
fit=ft.ImageFit.CONTAIN
|
||
)
|
||
logo_container = ft.Container(
|
||
content=logo_img,
|
||
on_click=lambda e: toggle_sidebar()
|
||
)
|
||
controls.append(logo_container)
|
||
|
||
# 2. Группы вкладок
|
||
groups = {
|
||
"Repository": ["Create", "Upload"],
|
||
"Environment": ["Create", "Upload", "Install"],
|
||
"Civit": ["Initialize", "Overview", "Selection"],
|
||
"Tasks": ["Upload"],
|
||
}
|
||
|
||
for grp_name, tabs in groups.items():
|
||
controls.append(build_group(grp_name, tabs))
|
||
|
||
# 3. Кнопка смены темы (только иконка)
|
||
controls.append(ft.Container(height=20))
|
||
theme_icon = ft.Icons.SUNNY if page.theme_mode == ft.ThemeMode.LIGHT else ft.Icons.NIGHTLIGHT_ROUNDED
|
||
theme_btn = ft.IconButton(
|
||
icon=theme_icon,
|
||
on_click=lambda e: toggle_theme()
|
||
)
|
||
controls.append(theme_btn)
|
||
|
||
sidebar.content.controls = controls
|
||
page.update()
|
||
|
||
# ----- Группа + подменю -----
|
||
def build_group(name: str, tabs: list[str]) -> ft.Container:
|
||
"""Создаёт одну группу с подменю."""
|
||
# Фон заголовка – только для активной группы
|
||
header_bg = LIGHT_ACC if selected_group == name else ft.Colors.TRANSPARENT
|
||
|
||
# 1️⃣ Первый ряд: статус‑лампочка, иконка, название группы
|
||
title_row = ft.Row(
|
||
controls=[
|
||
status_lamp("#ffeb3b"),
|
||
group_icon(name),
|
||
ft.Text(name, weight=ft.FontWeight.BOLD, color=ft.Colors.ON_PRIMARY)
|
||
],
|
||
alignment=ft.MainAxisAlignment.START,
|
||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||
spacing=8,
|
||
)
|
||
|
||
# 2️⃣ Второй ряд: подстрока (отображается только при развёрнутой панели)
|
||
subtitle_row = ft.Row(
|
||
controls=[
|
||
ft.Text("Подстрока", size=10, color=ft.Colors.GREY)
|
||
],
|
||
alignment=ft.MainAxisAlignment.START,
|
||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||
spacing=8,
|
||
)
|
||
|
||
header_content = ft.Column(
|
||
controls=[title_row] + ([subtitle_row] if is_expanded else []),
|
||
spacing=2,
|
||
)
|
||
|
||
header = ft.Container(
|
||
padding=ft.padding.only(left=8, right=8, top=4, bottom=4),
|
||
bgcolor=header_bg,
|
||
border_radius=8,
|
||
content=header_content,
|
||
on_click=lambda e: toggle_group(name),
|
||
)
|
||
|
||
# Список вкладок
|
||
tab_items = []
|
||
for tab_name in tabs:
|
||
icon = tab_icon()
|
||
title = ft.Text(tab_name, color=ft.Colors.ON_SURFACE_VARIANT)
|
||
if selected_tab_name == tab_name:
|
||
icon.color = LIGHT_ACC if page.theme_mode == ft.ThemeMode.LIGHT else DARK_ACC
|
||
title.color = LIGHT_ACC if page.theme_mode == ft.ThemeMode.LIGHT else DARK_ACC
|
||
|
||
row = ft.Row(
|
||
controls=[icon],
|
||
alignment=ft.MainAxisAlignment.START,
|
||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||
spacing=8
|
||
)
|
||
if is_expanded:
|
||
row.controls.append(title)
|
||
|
||
item = ft.Container(
|
||
content=row,
|
||
padding=ft.padding.only(left=16),
|
||
on_click=lambda e, t=tab_name, g=name: select_tab(g, t)
|
||
)
|
||
tab_items.append(item)
|
||
|
||
sublist = ft.Container(
|
||
content=ft.Column(controls=tab_items, spacing=0),
|
||
height=0 if not group_expanded[name] else len(tabs) * 48,
|
||
)
|
||
|
||
return ft.Column(
|
||
controls=[header, sublist],
|
||
spacing=4
|
||
)
|
||
|
||
# ----- События -----
|
||
def toggle_sidebar():
|
||
nonlocal is_expanded
|
||
is_expanded = not is_expanded
|
||
sidebar.width = 200 if is_expanded else 80
|
||
rebuild_sidebar()
|
||
|
||
def toggle_group(name: str):
|
||
group_expanded[name] = not group_expanded[name]
|
||
rebuild_sidebar()
|
||
|
||
def select_tab(group: str, tab: str):
|
||
nonlocal selected_group, selected_tab_name
|
||
selected_group = group
|
||
selected_tab_name = tab
|
||
selected_tab.value = f"{tab}\n(Icon + text)"
|
||
rebuild_sidebar()
|
||
|
||
def toggle_theme():
|
||
if page.theme_mode == ft.ThemeMode.LIGHT:
|
||
page.theme_mode = ft.ThemeMode.DARK
|
||
page.bgcolor = DARK_BG
|
||
else:
|
||
page.theme_mode = ft.ThemeMode.LIGHT
|
||
page.bgcolor = LIGHT_BG
|
||
rebuild_sidebar()
|
||
page.update()
|
||
|
||
# ----- Инициализация -----
|
||
page.bgcolor = LIGHT_BG
|
||
rebuild_sidebar()
|
||
|
||
|
||
# ---------- Запуск ----------
|
||
if __name__ == "__main__":
|
||
ft.app(target=main) |