Common commit
This commit is contained in:
0
modules/ui/__init__.py
Normal file
0
modules/ui/__init__.py
Normal file
131
modules/ui/gui/MainWindow.py
Normal file
131
modules/ui/gui/MainWindow.py
Normal 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)
|
||||
|
||||
98
modules/ui/gui/MainWindowSideMenu.py
Normal file
98
modules/ui/gui/MainWindowSideMenu.py
Normal 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
242
modules/ui/gui/Sample.py
Normal 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)
|
||||
7
modules/ui/gui/Workspaces.py
Normal file
7
modules/ui/gui/Workspaces.py
Normal 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("Выберете вкладку"))
|
||||
0
modules/ui/gui/__init__.py
Normal file
0
modules/ui/gui/__init__.py
Normal file
Reference in New Issue
Block a user