desktop_app/app/ui/views/yobble_home_view.py
2025-09-26 15:42:45 +03:00

358 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame,
QStackedWidget, QSpacerItem, QSizePolicy, QGraphicsDropShadowEffect,
QGraphicsOpacityEffect
)
from PySide6.QtCore import Qt, QSize, QPropertyAnimation, QEasingCurve, Property
from PySide6.QtGui import QIcon, QColor
from app.core.theme import theme_manager
from app.ui.views.side_menu_view import SideMenuView
class YobbleHomeView(QWidget):
def __init__(self, username: str):
super().__init__()
self.username = username
self.setWindowTitle(f"Yobble Home - {username}")
self.setMinimumSize(360, 640)
# --- Основной макет ---
# Используем QHBoxLayout, чтобы можно было разместить меню и контент рядом
# Но на самом деле меню будет поверх контента
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 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. Верхняя панель
self.top_bar = self.create_top_bar()
content_layout.addWidget(self.top_bar)
# 2. Центральная область контента
self.content_stack = QStackedWidget()
self.setup_content_pages()
content_layout.addWidget(self.content_stack, 1)
# 3. Нижняя панель навигации
self.bottom_bar = self.create_bottom_bar()
content_layout.addWidget(self.bottom_bar)
main_layout.addWidget(self.content_widget)
# --- Боковое меню и оверлей ---
self.setup_side_menu()
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):
"""Обновляет стили компонента при смене темы."""
self.setStyleSheet(self.get_stylesheet())
def create_top_bar(self):
"""Создает верхнюю панель с меню и заголовком."""
top_bar_widget = QWidget()
top_bar_widget.setObjectName("TopBar")
top_bar_layout = QHBoxLayout(top_bar_widget)
top_bar_layout.setContentsMargins(10, 5, 10, 5)
self.burger_menu_button = QPushButton("")
self.burger_menu_button.setObjectName("BurgerMenuButton")
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)
self.title_label = QLabel("Чаты")
self.title_label.setObjectName("TitleLabel")
top_bar_layout.addWidget(self.title_label)
top_bar_layout.addStretch()
return top_bar_widget
def create_bottom_bar(self):
"""Создает нижнюю панель навигации в стиле SwiftUI."""
bottom_bar_widget = QWidget()
bottom_bar_widget.setObjectName("BottomBar")
bottom_bar_layout = QHBoxLayout(bottom_bar_widget)
bottom_bar_layout.setContentsMargins(10, 0, 10, 0)
bottom_bar_layout.setSpacing(10)
btn_feed = self.create_tab_button("", "Лента", 0)
btn_search = self.create_tab_button("🔍", "Поиск", 1)
btn_create = self.create_create_button()
btn_chats = self.create_tab_button("💬", "Чаты", 2)
btn_profile = self.create_tab_button("👤", "Лицо", 3)
bottom_bar_layout.addWidget(btn_feed)
bottom_bar_layout.addWidget(btn_search)
bottom_bar_layout.addWidget(btn_create)
bottom_bar_layout.addWidget(btn_chats)
bottom_bar_layout.addWidget(btn_profile)
for btn in [btn_feed, btn_search, btn_chats, btn_profile]:
btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
btn.setFocusPolicy(Qt.NoFocus)
btn_create.setFocusPolicy(Qt.NoFocus)
self.update_tab_selection(2)
return bottom_bar_widget
def create_tab_button(self, icon_text, text, index):
button = QPushButton()
button.setObjectName("TabButton")
button.setCursor(Qt.PointingHandCursor)
button.setFocusPolicy(Qt.NoFocus)
layout = QVBoxLayout(button)
layout.setContentsMargins(0, 5, 0, 5)
layout.setSpacing(4)
icon_label = QLabel(icon_text)
icon_label.setAlignment(Qt.AlignCenter)
icon_label.setObjectName("TabIcon")
text_label = QLabel(text)
text_label.setAlignment(Qt.AlignCenter)
text_label.setObjectName("TabText")
layout.addWidget(icon_label)
layout.addWidget(text_label)
button.setProperty("tab_index", index)
button.clicked.connect(lambda: self.on_tab_button_clicked(index))
return button
def create_create_button(self):
button = QPushButton("+")
button.setObjectName("CreateButton")
button.setFixedSize(56, 56)
button.setCursor(Qt.PointingHandCursor)
button.setFocusPolicy(Qt.NoFocus)
shadow = QGraphicsDropShadowEffect(self)
shadow.setBlurRadius(18)
shadow.setColor(QColor(0, 0, 0, 100))
shadow.setOffset(0, 3)
button.setGraphicsEffect(shadow)
return button
def setup_content_pages(self):
self.content_stack.addWidget(QLabel("Контент Ленты"))
self.content_stack.addWidget(QLabel("Контент Поиска"))
self.content_stack.addWidget(QLabel("Контент Чатов"))
self.content_stack.addWidget(QLabel("Контент Профиля"))
def on_tab_button_clicked(self, index):
self.content_stack.setCurrentIndex(index)
self.update_tab_selection(index)
titles = ["Лента", "Поиск", "Чаты", "Лицо"]
self.title_label.setText(titles[index])
def update_tab_selection(self, selected_index):
if not hasattr(self, 'bottom_bar'):
return
for button in self.bottom_bar.findChildren(QPushButton):
is_tab_button = button.property("tab_index") is not None
if is_tab_button:
button.setProperty("selected", button.property("tab_index") == selected_index)
button.style().unpolish(button)
button.style().polish(button)
def get_stylesheet(self):
"""Возвращает QSS стили для компонента в зависимости от темы."""
is_dark = theme_manager.is_dark()
# Цветовая палитра
bg_color = "#1c1c1e" if is_dark else "white"
bar_bg_color = "#2c2c2e" if is_dark else "#f8f8f8"
bar_border_color = "#3c3c3c" if is_dark else "#e7e7e7"
text_color = "#8e8e93" if is_dark else "#888888"
title_color = "white" if is_dark else "black"
active_color = "#0A84FF"
top_bar_bg = "#2c2c2e" if is_dark else "#f5f5f5"
top_bar_border = "#3c3c3c" if is_dark else "#e0e0e0"
hover_color = "#d0d0d0" if not is_dark else "#444444"
overlay_color = "rgba(0, 0, 0, 0.5)"
return f"""
#content_widget {{
background-color: {bg_color};
}}
#Overlay {{
background-color: {overlay_color};
}}
/* Глобальные стили для кнопок */
QPushButton {{
background: transparent;
border: none;
outline: none;
}}
QPushButton:focus,
QPushButton:pressed,
QPushButton:checked {{
background: transparent;
border: none;
outline: none;
}}
/* Верхняя панель */
#TopBar {{
background-color: {top_bar_bg};
border-bottom: 1px solid {top_bar_border};
}}
#BurgerMenuButton {{
font-size: 24px;
border: none;
padding: 5px;
color: {title_color};
background: transparent;
}}
#TitleLabel {{
font-size: 18px;
font-weight: bold;
color: {title_color};
}}
/* Нижняя панель */
#BottomBar {{
background-color: {bar_bg_color};
border-top: 1px solid {bar_border_color};
padding-top: 5px;
padding-bottom: 15px;
}}
/* Кнопки вкладок */
#TabButton {{
background: transparent;
border: none;
outline: none;
padding: 5px;
}}
#TabButton:hover {{
background-color: {hover_color};
border-radius: 6px;
}}
#TabButton #TabIcon {{ color: {text_color}; }}
#TabButton #TabText {{ color: {text_color}; }}
#TabButton[selected="true"] #TabIcon,
#TabButton[selected="true"] #TabText {{
color: {active_color};
}}
#TabIcon, #TabText {{
border: none;
outline: none;
background-color: transparent;
}}
#TabIcon {{ font-size: 22px; }}
#TabText {{ font-size: 12px; }}
/* Центральная кнопка "Создать" */
#CreateButton {{
color: white;
font-size: 30px;
font-weight: 300;
border: none;
border-radius: 28px;
background-color: qlineargradient(
x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #007AFF, stop: 1 #0056b3
);
margin-bottom: 20px;
}}
#CreateButton:hover {{
background-color: qlineargradient(
x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #0088FF, stop: 1 #0066c3
);
}}
#CreateButton:pressed {{
background-color: qlineargradient(
x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #0056b3, stop: 1 #004493
);
}}
"""