add burger menu

This commit is contained in:
unknown 2025-09-26 15:42:45 +03:00
parent f859211729
commit 55f033ab37
3 changed files with 439 additions and 10 deletions

View File

@ -20,5 +20,10 @@ class ThemeManager(QObject):
self.settings.setValue("theme", theme) self.settings.setValue("theme", theme)
self.theme_changed.emit(self.theme) self.theme_changed.emit(self.theme)
def toggle_theme(self):
"""Переключает тему между светлой и темной."""
new_theme = "light" if self.is_dark() else "dark"
self.set_theme(new_theme)
# Глобальный экземпляр # Глобальный экземпляр
theme_manager = ThemeManager() theme_manager = ThemeManager()

View File

@ -0,0 +1,334 @@
# app/ui/views/side_menu_view.py
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame,
QScrollArea, QSizePolicy, QGraphicsDropShadowEffect
)
from PySide6.QtCore import Qt, QSize, QPropertyAnimation, QEasingCurve, Property
from PySide6.QtGui import QIcon, QColor
from app.core.theme import theme_manager
class SideMenuButton(QPushButton):
"""Кастомная кнопка для пунктов бокового меню."""
def __init__(self, icon_path: str, text: str):
super().__init__()
self.setCursor(Qt.PointingHandCursor)
self.setObjectName("SideMenuButton")
layout = QHBoxLayout(self)
layout.setContentsMargins(10, 8, 10, 8)
layout.setSpacing(15)
icon_label = QLabel(icon_path) # Используем текстовые иконки
icon_label.setObjectName("SideMenuButtonIcon")
icon_label.setFixedSize(24, 24)
icon_label.setAlignment(Qt.AlignCenter)
text_label = QLabel(text)
text_label.setObjectName("SideMenuButtonText")
layout.addWidget(icon_label)
layout.addWidget(text_label)
layout.addStretch()
class SideMenuFooterButton(QPushButton):
"""Кастомная кнопка для футера бокового меню."""
def __init__(self, icon_path: str, text: str):
super().__init__()
self.setCursor(Qt.PointingHandCursor)
self.setObjectName("SideMenuFooterButton")
layout = QVBoxLayout(self)
layout.setContentsMargins(5, 8, 5, 8)
layout.setSpacing(4)
icon_label = QLabel(icon_path)
icon_label.setObjectName("SideMenuFooterIcon")
icon_label.setAlignment(Qt.AlignCenter)
text_label = QLabel(text)
text_label.setObjectName("SideMenuFooterText")
text_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
layout.addWidget(text_label)
class SideMenuView(QFrame):
"""Виджет бокового меню."""
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("SideMenuView")
self.setFixedWidth(280)
# --- Основной макет ---
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
# --- Компоненты ---
self.header = self._create_header()
self.account_list_container = self._create_account_list()
self.scroll_area = self._create_scroll_area()
self.footer = self._create_footer()
main_layout.addWidget(self.header)
main_layout.addWidget(self.account_list_container)
main_layout.addWidget(self.scroll_area)
main_layout.addWidget(self.footer)
# --- Анимация списка аккаунтов ---
self.account_list_container.setVisible(False)
self.account_list_animation = QPropertyAnimation(self.account_list_container, b"maximumHeight")
self.account_list_animation.setDuration(300)
self.account_list_animation.setEasingCurve(QEasingCurve.InOutQuart)
# --- Состояние анимации ---
self.is_closing_animation = False
self.account_list_animation.finished.connect(self._on_animation_finished)
self.update_styles()
theme_manager.theme_changed.connect(self.update_styles)
def _create_header(self):
"""Создает заголовок меню."""
header_widget = QWidget()
header_layout = QVBoxLayout(header_widget)
header_layout.setContentsMargins(15, 50, 15, 10)
header_layout.setSpacing(10)
# Верхняя часть: Аватар и кнопка темы
top_row_layout = QHBoxLayout()
avatar_button = QPushButton("👤")
avatar_button.setObjectName("AvatarButton")
avatar_button.setFixedSize(60, 60)
self.theme_toggle_button = QPushButton("☀️")
self.theme_toggle_button.setObjectName("ThemeToggleButton")
self.theme_toggle_button.setCursor(Qt.PointingHandCursor)
self.theme_toggle_button.clicked.connect(theme_manager.toggle_theme)
top_row_layout.addWidget(avatar_button)
top_row_layout.addStretch()
top_row_layout.addWidget(self.theme_toggle_button)
# Нижняя часть: Имя пользователя и кнопка раскрытия
self.account_toggle_button = QPushButton()
self.account_toggle_button.setObjectName("AccountToggleButton")
self.account_toggle_button.setCursor(Qt.PointingHandCursor)
self.account_toggle_button.clicked.connect(self.toggle_account_list)
bottom_row_layout = QHBoxLayout(self.account_toggle_button)
user_info_layout = QVBoxLayout()
user_info_layout.setSpacing(0)
user_info_layout.addWidget(QLabel("Your Name"))
user_info_layout.addWidget(QLabel("@yourusername"))
self.expand_icon = QLabel("")
bottom_row_layout.addLayout(user_info_layout)
bottom_row_layout.addStretch()
bottom_row_layout.addWidget(self.expand_icon)
header_layout.addLayout(top_row_layout)
header_layout.addWidget(self.account_toggle_button)
return header_widget
def _create_account_list(self):
"""Создает выпадающий список аккаунтов."""
container = QWidget()
container.setObjectName("AccountListContainer")
layout = QVBoxLayout(container)
layout.setContentsMargins(15, 10, 15, 10)
layout.setSpacing(15)
# Пример аккаунтов
accounts = [
("Your Name", "@yourusername", True),
("Second Account", "@second", False)
]
for name, username, is_current in accounts:
# Здесь можно создать более сложный виджет для каждого аккаунта
label = QLabel(f"{name} ({username}) {'' if is_current else ''}")
layout.addWidget(label)
return container
def _create_scroll_area(self):
"""Создает прокручиваемую область с пунктами меню."""
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setFrameShape(QFrame.NoFrame)
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll_area.setObjectName("ScrollArea")
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(15, 10, 15, 10)
content_layout.setSpacing(5)
# Секция 1
content_layout.addWidget(SideMenuButton("👥", "People You May Like"))
content_layout.addWidget(SideMenuButton("", "Fun Fest"))
content_layout.addWidget(SideMenuButton("💡", "Creator Center"))
content_layout.addSpacing(10)
content_layout.addWidget(self._create_divider())
content_layout.addSpacing(10)
# Секция 2
content_layout.addWidget(self._create_category_label("CATEGORY"))
content_layout.addWidget(SideMenuButton("📄", "Drafts"))
content_layout.addWidget(SideMenuButton("💬", "My Comments"))
content_layout.addStretch() # Заполняет пространство внизу
scroll_area.setWidget(content_widget)
return scroll_area
def _create_footer(self):
"""Создает футер меню."""
footer_widget = QWidget()
footer_widget.setObjectName("SideMenuFooter")
layout = QHBoxLayout(footer_widget)
layout.setContentsMargins(15, 10, 15, 20)
layout.addWidget(SideMenuFooterButton("📱", "Scan"))
layout.addWidget(SideMenuFooterButton("", "Help"))
layout.addWidget(SideMenuFooterButton("⚙️", "Settings"))
return footer_widget
def _create_divider(self):
divider = QFrame()
divider.setFrameShape(QFrame.HLine)
divider.setObjectName("Divider")
return divider
def _create_category_label(self, text):
label = QLabel(text)
label.setObjectName("CategoryLabel")
return label
def _on_animation_finished(self):
"""Срабатывает после завершения анимации."""
if self.is_closing_animation:
self.account_list_container.setVisible(False)
def toggle_account_list(self):
"""Анимирует показ/скрытие списка аккаунтов."""
self.account_list_animation.stop()
if self.account_list_container.isVisible():
# --- Закрытие ---
self.is_closing_animation = True
self.expand_icon.setText("")
end_height = 0
else:
# --- Открытие ---
self.is_closing_animation = False
self.expand_icon.setText("")
self.account_list_container.setVisible(True)
end_height = self.account_list_container.sizeHint().height()
self.account_list_animation.setStartValue(self.account_list_container.height())
self.account_list_animation.setEndValue(end_height)
self.account_list_animation.start()
def update_styles(self):
"""Обновляет стили в зависимости от темы."""
is_dark = theme_manager.is_dark()
self.theme_toggle_button.setText("🌙" if is_dark else "☀️")
self.setStyleSheet(self.get_stylesheet())
def get_stylesheet(self):
is_dark = theme_manager.is_dark()
bg_color = "#1c1c1e" if is_dark else "#ffffff"
text_color = "#f2f2f7" if is_dark else "#000000"
secondary_text_color = "#8e8e93" if is_dark else "#888888"
divider_color = "#3c3c3c" if is_dark else "#e7e7e7"
button_hover_bg = "#2c2c2e" if is_dark else "#f0f0f0"
return f"""
#SideMenuView {{
background-color: {bg_color};
}}
QPushButton {{
border: none;
background: transparent;
text-align: left;
color: {text_color};
}}
/* --- Header --- */
#AvatarButton {{
font-size: 40px;
border-radius: 30px;
background-color: {divider_color};
}}
#ThemeToggleButton {{
font-size: 24px;
}}
#AccountToggleButton {{
padding: 8px;
border-radius: 8px;
}}
#AccountToggleButton:hover {{
background-color: {button_hover_bg};
}}
#AccountToggleButton QLabel {{
color: {text_color};
}}
#AccountToggleButton > QLabel:first-child {{ /* Имя */
font-weight: bold;
}}
#AccountToggleButton > QLabel:last-child {{ /* @username */
color: {secondary_text_color};
}}
/* --- Account List --- */
#AccountListContainer {{
background-color: transparent;
color: {text_color};
}}
/* --- Menu Buttons --- */
#SideMenuButton, #SideMenuFooterButton {{
border-radius: 8px;
}}
#SideMenuButton:hover, #SideMenuFooterButton:hover {{
background-color: {button_hover_bg};
}}
#SideMenuButtonIcon, #SideMenuFooterIcon {{
font-size: 18px;
color: {text_color};
}}
#SideMenuButtonText, #SideMenuFooterText {{
font-size: 14px;
color: {text_color};
}}
#SideMenuFooterText {{
font-size: 11px;
}}
/* --- Other --- */
#Divider {{
background-color: {divider_color};
border: none;
height: 1px;
}}
#CategoryLabel {{
color: {secondary_text_color};
font-size: 12px;
font-weight: bold;
padding: 5px 10px;
}}
#ScrollArea {{
border: none;
}}
"""

View File

@ -1,41 +1,125 @@
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame,
QStackedWidget, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect QStackedWidget, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect,
QGraphicsOpacityEffect
) )
from PySide6.QtCore import Qt, QSize from PySide6.QtCore import Qt, QSize, QPropertyAnimation, QEasingCurve, Property
from PySide6.QtGui import QIcon, QColor from PySide6.QtGui import QIcon, QColor
from app.core.theme import theme_manager from app.core.theme import theme_manager
from app.ui.views.side_menu_view import SideMenuView
class YobbleHomeView(QWidget): class YobbleHomeView(QWidget):
def __init__(self, username: str): def __init__(self, username: str):
super().__init__() super().__init__()
self.username = username self.username = username
self.setWindowTitle(f"Yobble Home - {username}") self.setWindowTitle(f"Yobble Home - {username}")
self.setMinimumSize(360, 640)
# --- Основной макет --- # --- Основной макет ---
main_layout = QVBoxLayout(self) # Используем QHBoxLayout, чтобы можно было разместить меню и контент рядом
# Но на самом деле меню будет поверх контента
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0) main_layout.setSpacing(0)
# --- Виджет для основного контента ---
self.content_widget = QWidget()
content_layout = QVBoxLayout(self.content_widget)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.setSpacing(0)
# 1. Верхняя панель # 1. Верхняя панель
self.top_bar = self.create_top_bar() self.top_bar = self.create_top_bar()
main_layout.addWidget(self.top_bar) content_layout.addWidget(self.top_bar)
# 2. Центральная область контента # 2. Центральная область контента
self.content_stack = QStackedWidget() self.content_stack = QStackedWidget()
self.setup_content_pages() self.setup_content_pages()
main_layout.addWidget(self.content_stack, 1) content_layout.addWidget(self.content_stack, 1)
# 3. Нижняя панель навигации # 3. Нижняя панель навигации
self.bottom_bar = self.create_bottom_bar() self.bottom_bar = self.create_bottom_bar()
main_layout.addWidget(self.bottom_bar) content_layout.addWidget(self.bottom_bar)
self.update_styles() # Применяем стили при инициализации main_layout.addWidget(self.content_widget)
# Подключаемся к сигналу смены темы # --- Боковое меню и оверлей ---
self.setup_side_menu()
self.update_styles()
theme_manager.theme_changed.connect(self.update_styles) theme_manager.theme_changed.connect(self.update_styles)
def setup_side_menu(self):
"""Настраивает боковое меню и оверлей."""
# Оверлей для затемнения контента
self.overlay = QFrame(self)
self.overlay.setObjectName("Overlay")
self.overlay.hide()
self.overlay.mousePressEvent = lambda event: self.toggle_side_menu()
# Эффект прозрачности для оверлея
self.opacity_effect = QGraphicsOpacityEffect(self.overlay)
self.overlay.setGraphicsEffect(self.opacity_effect)
# Боковое меню
self.side_menu = SideMenuView(self)
self.side_menu.move(-self.side_menu.width(), 0) # Изначально скрыто за экраном
# Анимация для меню
self.menu_animation = QPropertyAnimation(self.side_menu, b"pos")
self.menu_animation.setEasingCurve(QEasingCurve.InOutCubic)
self.menu_animation.setDuration(300)
# Анимация для оверлея
self.opacity_animation = QPropertyAnimation(self.opacity_effect, b"opacity")
self.opacity_animation.setDuration(300)
# --- Состояние анимации ---
self.is_menu_closing = False
self.opacity_animation.finished.connect(self._on_menu_animation_finished)
def _on_menu_animation_finished(self):
"""Срабатывает после завершения анимации оверлея."""
if self.is_menu_closing:
self.overlay.hide()
def toggle_side_menu(self):
"""Показывает или скрывает боковое меню с анимацией."""
# Останавливаем текущие анимации, чтобы избежать конфликтов
self.menu_animation.stop()
self.opacity_animation.stop()
is_hidden = self.side_menu.pos().x() < 0
if is_hidden:
# --- Открытие ---
self.is_menu_closing = False
self.overlay.show()
self.menu_animation.setStartValue(self.side_menu.pos())
self.menu_animation.setEndValue(self.side_menu.pos().__class__(0, 0))
self.opacity_animation.setStartValue(self.opacity_effect.opacity())
self.opacity_animation.setEndValue(0.5)
else:
# --- Закрытие ---
self.is_menu_closing = True
self.menu_animation.setStartValue(self.side_menu.pos())
self.menu_animation.setEndValue(self.side_menu.pos().__class__(-self.side_menu.width(), 0))
self.opacity_animation.setStartValue(self.opacity_effect.opacity())
self.opacity_animation.setEndValue(0)
self.menu_animation.start()
self.opacity_animation.start()
def resizeEvent(self, event):
"""Обновляет геометрию оверлея и меню при изменении размера окна."""
super().resizeEvent(event)
self.overlay.setGeometry(self.rect())
if self.side_menu.pos().x() < 0:
self.side_menu.move(-self.side_menu.width(), 0)
self.side_menu.setFixedHeight(self.height())
def update_styles(self): def update_styles(self):
"""Обновляет стили компонента при смене темы.""" """Обновляет стили компонента при смене темы."""
self.setStyleSheet(self.get_stylesheet()) self.setStyleSheet(self.get_stylesheet())
@ -50,6 +134,8 @@ class YobbleHomeView(QWidget):
self.burger_menu_button = QPushButton("") self.burger_menu_button = QPushButton("")
self.burger_menu_button.setObjectName("BurgerMenuButton") self.burger_menu_button.setObjectName("BurgerMenuButton")
self.burger_menu_button.setFocusPolicy(Qt.NoFocus) self.burger_menu_button.setFocusPolicy(Qt.NoFocus)
self.burger_menu_button.setCursor(Qt.PointingHandCursor)
self.burger_menu_button.clicked.connect(self.toggle_side_menu) # Подключаем сигнал
top_bar_layout.addWidget(self.burger_menu_button) top_bar_layout.addWidget(self.burger_menu_button)
self.title_label = QLabel("Чаты") self.title_label = QLabel("Чаты")
@ -167,11 +253,15 @@ class YobbleHomeView(QWidget):
top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5" top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5"
top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0" top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0"
hover_color = "#d0d0d0" if not is_dark else "#444444" hover_color = "#d0d0d0" if not is_dark else "#444444"
overlay_color = "rgba(0, 0, 0, 0.5)"
return f""" return f"""
YobbleHomeView {{ #content_widget {{
background-color: {bg_color}; background-color: {bg_color};
}} }}
#Overlay {{
background-color: {overlay_color};
}}
/* Глобальные стили для кнопок */ /* Глобальные стили для кнопок */
QPushButton {{ QPushButton {{