food-market/app/ui/theme.py

185 lines
5.4 KiB
Python
Raw Permalink Normal View History

"""Тёмная тема.
Палитра задаётся через QPalette, а не только через QSS: иначе системные
диалоги и всплывающие подсказки остались бы светлыми и резали глаза.
"""
from __future__ import annotations
Отдельный репозиторий для базы, фасовки товара, ввод истории Три правки по замечаниям. 1. База и код разъехались по разным репозиториям. Автокоммит и раньше трогал только vault.fmdb, но лежал он в той же ветке, что и исходники, поэтому пуш тащил всю историю программы. Теперь data/ — самостоятельный клон food-records со своим .git, а исходники его игнорируют целиком. При первом запуске приложение встаёт на уже существующую историю сервера, а не заводит параллельную. 2. Фасовки товара. Пачка печенья 10 шт за 200 ₽ и та же печенька поштучно за 30 ₽ — один товар с двумя фасовками. Остатки, себестоимость и FIFO считаются в базовых единицах, поэтому поштучные продажи вычитаются из купленных пачек. Размер фасовки хранится в документе слепком. Миграция схемы 1→2 не меняет поведение уже заведённых данных. 3. Быстрый ввод продаж за период: выбор диапазона дат, строка таблицы — отдельная продажа, ввод с клавиатуры, запись всех разом. Незнакомое имя заводится как контрагент, повтор периода подсвечивается. Тесты больше не имеют настроенного remote, чтобы не ходить в сеть. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 01:25:39 +07:00
from PySide6.QtCore import QLocale, Qt
from PySide6.QtGui import QColor, QPalette
BG = "#1c1f24"
BG_ALT = "#23272e"
SURFACE = "#2b3038"
BORDER = "#3a4048"
TEXT = "#e6e8ea"
TEXT_DIM = "#98a0aa"
ACCENT = "#4c9aff"
OK = "#4caf7d"
WARN = "#d9a441"
DANGER = "#e0605e"
MUTED = "#6c757d"
STATUS_COLORS = {
"settled": OK,
"open": ACCENT,
"due_soon": WARN,
"overdue": DANGER,
}
def apply(app) -> None:
app.setStyle("Fusion")
Отдельный репозиторий для базы, фасовки товара, ввод истории Три правки по замечаниям. 1. База и код разъехались по разным репозиториям. Автокоммит и раньше трогал только vault.fmdb, но лежал он в той же ветке, что и исходники, поэтому пуш тащил всю историю программы. Теперь data/ — самостоятельный клон food-records со своим .git, а исходники его игнорируют целиком. При первом запуске приложение встаёт на уже существующую историю сервера, а не заводит параллельную. 2. Фасовки товара. Пачка печенья 10 шт за 200 ₽ и та же печенька поштучно за 30 ₽ — один товар с двумя фасовками. Остатки, себестоимость и FIFO считаются в базовых единицах, поэтому поштучные продажи вычитаются из купленных пачек. Размер фасовки хранится в документе слепком. Миграция схемы 1→2 не меняет поведение уже заведённых данных. 3. Быстрый ввод продаж за период: выбор диапазона дат, строка таблицы — отдельная продажа, ввод с клавиатуры, запись всех разом. Незнакомое имя заводится как контрагент, повтор периода подсвечивается. Тесты больше не имеют настроенного remote, чтобы не ходить в сеть. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 01:25:39 +07:00
# Иначе поля ввода показывают «1,800.00», а таблицы рядом — «1 800,00».
QLocale.setDefault(QLocale(QLocale.Russian, QLocale.Russia))
palette = QPalette()
palette.setColor(QPalette.Window, QColor(BG))
palette.setColor(QPalette.WindowText, QColor(TEXT))
palette.setColor(QPalette.Base, QColor(BG_ALT))
palette.setColor(QPalette.AlternateBase, QColor(SURFACE))
palette.setColor(QPalette.Text, QColor(TEXT))
palette.setColor(QPalette.Button, QColor(SURFACE))
palette.setColor(QPalette.ButtonText, QColor(TEXT))
palette.setColor(QPalette.ToolTipBase, QColor(SURFACE))
palette.setColor(QPalette.ToolTipText, QColor(TEXT))
palette.setColor(QPalette.Highlight, QColor(ACCENT))
palette.setColor(QPalette.HighlightedText, QColor("#ffffff"))
palette.setColor(QPalette.PlaceholderText, QColor(TEXT_DIM))
palette.setColor(QPalette.Link, QColor(ACCENT))
palette.setColor(QPalette.Disabled, QPalette.Text, QColor(MUTED))
palette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(MUTED))
palette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(MUTED))
app.setPalette(palette)
app.setStyleSheet(QSS)
QSS = f"""
QWidget {{
font-size: 13px;
}}
QLabel[role="h1"] {{ font-size: 21px; font-weight: 600; }}
QLabel[role="h2"] {{ font-size: 15px; font-weight: 600; }}
QLabel[role="dim"] {{ color: {TEXT_DIM}; }}
QLabel[role="metric"] {{ font-size: 24px; font-weight: 600; }}
QFrame[role="card"] {{
background: {SURFACE};
border: 1px solid {BORDER};
border-radius: 8px;
}}
QFrame[role="banner"] {{
background: #3a2f1c;
border: 1px solid {WARN};
border-radius: 6px;
}}
QListWidget#sidebar {{
background: {BG_ALT};
border: none;
border-right: 1px solid {BORDER};
outline: none;
padding-top: 8px;
}}
QListWidget#sidebar::item {{
padding: 10px 16px;
margin: 1px 6px;
border-radius: 6px;
color: {TEXT_DIM};
}}
QListWidget#sidebar::item:selected {{
background: {SURFACE};
color: {TEXT};
font-weight: 600;
}}
QListWidget#sidebar::item:hover {{ color: {TEXT}; }}
QPushButton {{
background: {SURFACE};
border: 1px solid {BORDER};
border-radius: 6px;
padding: 6px 14px;
}}
QPushButton:hover {{ border-color: {ACCENT}; }}
QPushButton:disabled {{ color: {MUTED}; border-color: {BORDER}; }}
QPushButton[role="primary"] {{
background: {ACCENT};
border-color: {ACCENT};
color: #ffffff;
font-weight: 600;
}}
QPushButton[role="primary"]:hover {{ background: #5aa6ff; }}
QPushButton[role="danger"]:hover {{ border-color: {DANGER}; color: {DANGER}; }}
QLineEdit, QDateEdit, QComboBox, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit {{
background: {BG_ALT};
border: 1px solid {BORDER};
border-radius: 6px;
padding: 5px 8px;
selection-background-color: {ACCENT};
}}
QLineEdit:focus, QDateEdit:focus, QComboBox:focus,
QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus {{
border-color: {ACCENT};
}}
QComboBox::drop-down {{ border: none; width: 18px; }}
QComboBox QAbstractItemView {{
background: {BG_ALT};
border: 1px solid {BORDER};
selection-background-color: {ACCENT};
}}
QTableWidget, QTreeWidget {{
background: {BG_ALT};
alternate-background-color: {SURFACE};
border: 1px solid {BORDER};
border-radius: 6px;
gridline-color: {BORDER};
selection-background-color: {ACCENT};
}}
QHeaderView::section {{
background: {SURFACE};
color: {TEXT_DIM};
border: none;
border-bottom: 1px solid {BORDER};
padding: 7px 8px;
font-weight: 600;
}}
QTableWidget::item, QTreeWidget::item {{ padding: 4px 6px; }}
QProgressBar {{
background: {BG};
border: 1px solid {BORDER};
border-radius: 6px;
height: 14px;
text-align: center;
color: {TEXT};
}}
QProgressBar::chunk {{ border-radius: 5px; }}
QStatusBar {{ border-top: 1px solid {BORDER}; }}
QStatusBar::item {{ border: none; }}
QScrollBar:vertical {{ background: transparent; width: 10px; margin: 0; }}
QScrollBar::handle:vertical {{ background: {BORDER}; border-radius: 5px; min-height: 30px; }}
QScrollBar::handle:vertical:hover {{ background: {MUTED}; }}
QScrollBar::add-line, QScrollBar::sub-line {{ height: 0; width: 0; }}
QScrollBar:horizontal {{ background: transparent; height: 10px; }}
QScrollBar::handle:horizontal {{ background: {BORDER}; border-radius: 5px; min-width: 30px; }}
QTabWidget::pane {{ border: 1px solid {BORDER}; border-radius: 6px; }}
QTabBar::tab {{
background: transparent;
padding: 7px 14px;
color: {TEXT_DIM};
border-bottom: 2px solid transparent;
}}
QTabBar::tab:selected {{ color: {TEXT}; border-bottom-color: {ACCENT}; }}
"""