Common commit

This commit is contained in:
2025-10-16 18:42:32 +07:00
parent 124065d2ac
commit 40c320a1ac
21 changed files with 1916 additions and 379 deletions

0
modules/ui/__init__.py Normal file
View File

View File

@@ -0,0 +1,131 @@
import os.path
from pkgutil import extend_path
import flet as ft
from Workspaces import *
MainWindowDarkTheme = {
'logo_large_path': os.path.join('assets', 'logo_dark_large.png'),
'logo_small_path': os.path.join('assets', 'logo_dark_small.png'),
'background_color': "#1f1e23",
'text_color': "#ffffff",
'accent_color': "#7B1FA2",
'icon': ft.Icons.NIGHTLIGHT_ROUNDED
}
MainWindowLightTheme = {
'logo_large_path': os.path.join('assets', 'logo_light_large.png'),
'logo_small_path': os.path.join('assets', 'logo_light_small.png'),
'background_color': "#ffffff",
'text_color': "#1f1e23",
'accent_color': "#9C27B0",
'icon': ft.Icons.SUNNY
}
class MainWindow:
def __init__(self):
self.title = 'Vaiola'
self.themes = [MainWindowDarkTheme, MainWindowLightTheme]
self.active_theme_number = 0
self.active_theme = self.themes[self.active_theme_number]
self.side_bar = MainWindowSideBar(self)
self.active_workspace = self.side_bar.get_active_workspace()
self.page: ft.Page | None = None
def build(self, page: ft.Page):
self.page = page
page.clean()
page.title = self.title
page.padding = 0
page.bgcolor = self.active_theme['background_color']
self.active_workspace = self.side_bar.get_active_workspace()
layout = ft.Row(controls=[self.side_bar.build(), ft.VerticalDivider(thickness=4, ), self.active_workspace.build()],
spacing=0,
vertical_alignment=ft.CrossAxisAlignment.START,
alignment=ft.MainAxisAlignment.START,
)
page.add(layout)
def set_theme(self):
self.active_theme_number += 1
if self.active_theme_number >= len(self.themes): self.active_theme_number = 0
self.active_theme = self.themes[self.active_theme_number]
self.side_bar.set_theme(self.active_theme)
self.rebuild()
def rebuild(self):
self.build(self.page)
self.page.update()
class MainWindowSideBar:
logo_dark_large_path = os.path.join('assets', 'logo_dark_large.png')
logo_dark_small_path = os.path.join('assets', 'logo_dark_small.png')
logo_light_large_path = os.path.join('assets', 'logo_light_large.png')
logo_light_small_path = os.path.join('assets', 'logo_light_small.png')
def __init__(self, parent):
self.tabs: list[MainWindowSideBarTab] = list()
self.active_tab: MainWindowSideBarTab = MainWindowSideBarTab()
self.extended = True
self.theme = parent.active_theme
self.parent = parent
def set_theme(self, theme): self.theme = theme
def get_active_workspace(self) -> Workspace: return self.active_tab.get_active_workspace()
def build(self) -> ft.Container:
logo = ft.Container(
content=ft.Image(
src=self.theme['logo_large_path'] if self.extended else self.theme['logo_small_path'],
width=200 if self.extended else 60,
fit=ft.ImageFit.CONTAIN
),
width=200 if self.extended else 60,
# on_click
)
theme_button = ft.Button(
content=ft.Row(
controls=[
ft.Icon(self.theme['icon'], color=self.theme['background_color']),
ft.Text('Переключить тему', color=self.theme['background_color'])
] if self.extended else [
ft.Icon(self.theme['icon'], color=self.theme['background_color'])
]
),
bgcolor=self.theme['text_color'],
on_click=self.switch_theme
)
settings = ft.Container(content=theme_button, padding=8)
layout = ft.Column(
controls=[logo, ft.Text('Область вкладок', color=self.theme['text_color']), settings]
)
return ft.Container(content=layout, width=200 if self.extended else 60,)
def switch_theme(self, e):
self.parent.set_theme()
class MainWindowSideBarTab:
def __init__(self):
self.workspace = Workspace()
def get_active_workspace(self) -> Workspace:
return self.workspace
if __name__ == "__main__":
ft.app(target=MainWindow().build)

View File

@@ -0,0 +1,98 @@
# side_menu.py
import os.path
import flet as ft
class NavPanel:
"""Пустая панель навигации. Добавьте свои пункты позже."""
def __init__(self, page: ft.Page):
self.page = page
def build(self) -> ft.Container:
"""Возвращаем контейнер с кнопками навигации."""
# Пример пунктов замените/добавьте свои
return ft.Container(
padding=ft.padding.symmetric(vertical=10, horizontal=5),
content=ft.Column(
controls=[
ft.TextButton(
text="Главная",
icon=ft.Icons.HOME,
on_click=lambda e: self.page.views.append(ft.View("/home")),
style=ft.ButtonStyle(overlay_color=ft.Colors.GREY_200)
),
ft.TextButton(
text="Настройки",
icon=ft.Icons.SETTINGS,
on_click=lambda e: self.page.views.append(ft.View("/settings")),
style=ft.ButtonStyle(overlay_color=ft.Colors.GREY_200)
),
],
spacing=5,
),
)
class SideMenu:
"""
Класс, представляющий боковое меню.
На данный момент оно «пустое», но можно легко добавить пункты в будущем.
"""
def __init__(self):
# Любые начальные данные можно хранить здесь
self.width = 200 # ширина меню
self.bgcolor = ft.Colors.SURFACE
self.logo_path = os.path.join('assets', 'side_menu_logo_dark.png')
def build(self, page: ft.Page) -> ft.Container:
"""
Возвращает контейнер, который можно вставить в страницу.
"""
logo = ft.Image(
src=self.logo_path,
width=self.width, # растягиваем до ширины меню
fit=ft.ImageFit.CONTAIN, # сохраняем пропорции
# height может быть не задан; Flet будет авто‑подбирать
)
# 2⃣ Панель навигации
nav_panel = NavPanel(page).build()
# 3⃣ Кнопка‑тогглер темы
def toggle_theme(e):
# Переключаем режим и обновляем страницу
page.theme_mode = (
ft.ThemeMode.DARK if page.theme_mode == ft.ThemeMode.LIGHT
else ft.ThemeMode.LIGHT
)
page.update()
toggle_btn = ft.TextButton(
text="Тёмная тема" if page.theme_mode == ft.ThemeMode.LIGHT else "Светлая тема",
icon=ft.Icons.BOOKMARK,
on_click=toggle_theme,
style=ft.ButtonStyle(
padding=ft.padding.all(10),
alignment=ft.alignment.center_left
)
)
# 4⃣ Ставим всё в колонку с выравниванием по краям
return ft.Container(
width=self.width,
bgcolor=self.bgcolor,
padding=ft.padding.symmetric(vertical=15, horizontal=10),
content=ft.Column(
controls=[
logo,
ft.Divider(height=15),
nav_panel,
ft.Divider(height=15),
ft.Container(
content=toggle_btn,
alignment=ft.alignment.bottom_left
),
],
spacing=10,
alignment=ft.MainAxisAlignment.START,
),
)

242
modules/ui/gui/Sample.py Normal file
View File

@@ -0,0 +1,242 @@
#!/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)

View File

@@ -0,0 +1,7 @@
import flet as ft
class Workspace:
def __init__(self): pass
def build(self) -> ft.Container:
return ft.Container(content=ft.Text("Выберете вкладку"))

View File