Compare commits

..

8 Commits

Author SHA1 Message Date
5b7ef4c951 Configuration debug vscode 2026-03-20 18:42:04 +01:00
82679a5709 Style pour le bouton en travaux 2026-03-20 18:36:31 +01:00
93c37a905b Fix server handler on .exe, add hazard stripe button 2026-03-20 11:26:56 +01:00
945abae5f1 new tool for quitting app from child windows 2026-03-19 14:22:54 +01:00
720b004eca Refacto 2026-03-19 13:39:08 +01:00
54aa7a50b2 Refacto QSS, add comments 2026-03-19 12:38:15 +01:00
1125e5ee10 Refacto constants 2026-03-19 09:31:36 +01:00
5d2282def1 Fix discord user is connected with pypresence 2026-03-18 17:33:27 +01:00
16 changed files with 421 additions and 134 deletions

30
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,30 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Attach using Process Id",
"type": "debugpy",
"request": "attach",
"processId": "${command:pickProcess}"
},
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Python Debugger: Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
}
}
]
}

View File

@@ -8,7 +8,11 @@ a = Analysis(
('.\\styles\\styles.qss', 'styles'), ('.\\styles\\styles.qss', 'styles'),
('.\\ui\\mainwindow_vertical_pager.ui', 'ui') ('.\\ui\\mainwindow_vertical_pager.ui', 'ui')
], ],
hiddenimports=[], hiddenimports=[
"asyncio",
"pypresence",
"pypresence.baseclient",
],
hookspath=[], hookspath=[],
hooksconfig={ hooksconfig={
"qt_plugins": ["platforms", "styles"] "qt_plugins": ["platforms", "styles"]
@@ -107,8 +111,8 @@ a = Analysis(
# Concurrency non utilisée dans ton code # Concurrency non utilisée dans ton code
"multiprocessing", "multiprocessing",
"concurrent", #"concurrent",
"asyncio", #"asyncio",
# REPL / terminal # REPL / terminal
"readline", "readline",
@@ -218,6 +222,15 @@ a.binaries = [
if not any(u.lower() in name.lower() for u in unwanted_dlls) if not any(u.lower() in name.lower() for u in unwanted_dlls)
] ]
# AJOUTE CECI ICI :
# On filtre la liste des fichiers de données (datas)
# On exclut tout ce qui se trouve dans le dossier 'translations' de PySide6
a.datas = [f for f in a.datas if "translations" not in f[0].lower()]
# Si tu veux aussi supprimer les traductions système de Qt (fichiers .qm)
a.datas = [f for f in a.datas if not f[0].endswith('.qm')]
pyz = PYZ(a.pure, a.zipped_data) pyz = PYZ(a.pure, a.zipped_data)
exe = EXE( exe = EXE(
@@ -243,7 +256,7 @@ exe = EXE(
"msvcp*.dll", "msvcp*.dll",
], ],
runtime_tmpdir=None, runtime_tmpdir=None,
console=True, console=False,
disable_windowed_traceback=True, disable_windowed_traceback=True,
argv_emulation=False, argv_emulation=False,
target_arch=None, target_arch=None,

View File

@@ -7,7 +7,23 @@ from PySide6.QtGui import QColor
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
NO_STAFF = True NO_STAFF = True
NO_WHITELIST = False
REDIRECT_URI = "http://localhost:5000/callback"
SCOPES = ["identify"]
CLIENT_ID = "1240007913175781508"
AUTENTICATION_SUCCESS_MESSAGE = """
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
</head>
<body>
<h1>Authentication réussie</h1>
<p>Vous pouvez maintenant fermer cette fenêtre et revenir au launcher de La Tanière.</p>
</body>
</html>
""".encode('utf-8')
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# ENUMS # ENUMS
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -3,7 +3,7 @@ from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
from config.config_manager import ConfigManager, VOLUME_KEY from config.config_manager import ConfigManager, VOLUME_KEY
from tools.constants import Resources from config.constants import Resources
class AudioController: class AudioController:
# Encapsule toute la logique audio : lecture, volume, mute. # Encapsule toute la logique audio : lecture, volume, mute.

View File

@@ -1,7 +1,7 @@
from PySide6.QtCore import QPropertyAnimation, QEasingCurve from PySide6.QtCore import QPropertyAnimation, QEasingCurve
from PySide6.QtWidgets import QGraphicsDropShadowEffect from PySide6.QtWidgets import QGraphicsDropShadowEffect
from tools.constants import Glow from config.constants import Glow
class GlowAnimator: class GlowAnimator:

View File

@@ -3,31 +3,23 @@ import webbrowser
import os import os
from urllib.parse import urlencode from urllib.parse import urlencode
from http.server import HTTPServer, BaseHTTPRequestHandler from http.server import HTTPServer, BaseHTTPRequestHandler
from tools.get_server_token import GetServerTokenForDiscord from fivemserver.get_server_token import GetServerTokenForDiscord
from config.constants import CLIENT_ID, REDIRECT_URI, SCOPES, AUTENTICATION_SUCCESS_MESSAGE
# Disable stderr output # Disable stderr output
os.environ['PYTHONWARNINGS'] = 'ignore' os.environ['PYTHONWARNINGS'] = 'ignore'
REDIRECT_URI = "http://localhost:5000/callback"
SCOPES = ["identify"]
CLIENT_ID = "1240007913175781508"
AUTENTICATION_SUCCESS_MESSAGE = """
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
</head>
<body>
<h1>Authentication réussie</h1>
<p>Vous pouvez maintenant fermer cette fenêtre et revenir au launcher de La Tanière.</p>
</body>
</html>
""".encode('utf-8')
class OAuthCallbackHandler(BaseHTTPRequestHandler): class OAuthCallbackHandler(BaseHTTPRequestHandler):
code: str | None = None code: str | None = None
# Ajoute ceci pour empêcher le serveur d'écrire dans la console/stderr
def log_message(self, format, *args):
return # Ne fait rien, donc pas de blocage sur stdout/stderr
def do_GET(self): def do_GET(self):
"""
callback pour discord auth
"""
if "/callback" in self.path: if "/callback" in self.path:
query = self.path.split("?")[1] query = self.path.split("?")[1]
params = dict(p.split("=") for p in query.split("&")) params = dict(p.split("=") for p in query.split("&"))
@@ -40,11 +32,19 @@ class OAuthCallbackHandler(BaseHTTPRequestHandler):
# return discord application id (client id) # return discord application id (client id)
def get_discord_client_id() -> str: def get_discord_client_id() -> str:
"""
return discord application id
"""
return CLIENT_ID return CLIENT_ID
# return discord user id # return discord user id
def get_discord_user_id() -> str: def get_discord_user_id() -> str:
# récupération des infos serveur lataupe """
Retourne l'id du compte discord de l'utilisateur via l'oauh discord.
"""
# récupération des infos serveur la tanière
# récupération d
session_id = GetServerTokenForDiscord.authenticate() session_id = GetServerTokenForDiscord.authenticate()
client_secret = GetServerTokenForDiscord.get_token(session_id) client_secret = GetServerTokenForDiscord.get_token(session_id)
@@ -59,9 +59,6 @@ def get_discord_user_id() -> str:
webbrowser.open(f"{auth_url}?{urlencode(params)}") webbrowser.open(f"{auth_url}?{urlencode(params)}")
server = HTTPServer(("localhost", 5000), OAuthCallbackHandler) server = HTTPServer(("localhost", 5000), OAuthCallbackHandler)
# celle ligne cache le stderr output pour ne pas afficher l'url de callback dans la console
# valable en debug mode
os.dup2(os.open(os.devnull, os.O_WRONLY), 2)
server.handle_request() server.handle_request()
if not OAuthCallbackHandler.code: if not OAuthCallbackHandler.code:

View File

@@ -0,0 +1,53 @@
import psutil
from pypresence import Presence
from fivemserver.get_server_token import GetServerTokenForDiscord
from config.constants import Urls
from discord.discord_oauth import CLIENT_ID
class DiscordToken:
"""
Décode le token discord
"""
@staticmethod
def decode_discord_token():
discord_token = GetServerTokenForDiscord.get_token(
GetServerTokenForDiscord.authenticate(Urls.API_URL.value)
)
return discord_token
class CheckDiscord:
@staticmethod
def isdiscordrunning() -> bool:
"""
Vérifie si Discord est en cours d'exécution sur l'ordinateur. (Vérifie aussi pour Linux)
"""
for process in psutil.process_iter(["name"]):
if (
process.info["name"].lower() == "discord.exe"
or process.info["name"].lower() == "discordcanary.exe"
or process.info["name"].lower() == "discord"
or process.info["name"].lower() == "discord canary"
):
return True
return False
@staticmethod
def isuserconnected() -> bool:
"""
Vérifie si l'utilisateur Discord est connecté.
ne vérifie pas le user id discord.
"""
rpc = Presence(CLIENT_ID)
try:
rpc.connect()
return True
except Exception as e:
return False
finally:
try:
rpc.close()
except Exception as e:
pass

View File

@@ -5,16 +5,16 @@ from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.hazmat.primitives.ciphers.aead import AESGCM
API_URL = 'https://prod.la-taniere.fun:30121/' from config.constants import Urls
class GetServerTokenForDiscord: class GetServerTokenForDiscord:
derived_key: bytes | None = None derived_key: bytes | None = None
@staticmethod @staticmethod
def authenticate(server = API_URL): def authenticate(server = Urls.API_URL.value):
if server is None: if server is None:
server = API_URL server = Urls.API_URL.value
# ========================== # ==========================
# Génération clé ECDH client # Génération clé ECDH client
# ========================== # ==========================
@@ -49,12 +49,12 @@ class GetServerTokenForDiscord:
return auth["session_id"] return auth["session_id"]
@staticmethod @staticmethod
def get_token(session_id: bytes, server = API_URL): def get_token(session_id: bytes, server = Urls.API_URL.value):
# ========================== # ==========================
# DISCORD TOKEN # DISCORD TOKEN
# ========================== # ==========================
if server is None: if server is None:
server = API_URL server = Urls.API_URL.value
download = requests.post(server + "/api_v2/tkn_auth", verify=False, headers={ download = requests.post(server + "/api_v2/tkn_auth", verify=False, headers={
"x-session-id": session_id "x-session-id": session_id
}).json() }).json()
@@ -65,12 +65,12 @@ class GetServerTokenForDiscord:
aesgcm = AESGCM(GetServerTokenForDiscord.derived_key) # type: ignore[arg-type] aesgcm = AESGCM(GetServerTokenForDiscord.derived_key) # type: ignore[arg-type]
return aesgcm.decrypt(nonce, encrypted_data, None) return aesgcm.decrypt(nonce, encrypted_data, None)
# @staticmethod @staticmethod
# def register_discord_user(user_id: str, server = API_URL) -> str: def register_discord_user(user_id: str, server = Urls.API_URL.value) -> str:
# if server is None: if server is None:
# server = API_URL server = Urls.API_URL.value
# registeredId = requests.post(server + "/api_v2/connection/register", verify=False, json={ registeredId = requests.post(server + "/api_v2/connection/register", verify=False, json={
# "x-session-id": user_id "x-session-id": user_id
# }).json() }).json()
#
# return registeredId["discord_id"] return registeredId["discord_id"]

View File

@@ -0,0 +1,23 @@
import requests
from urllib3 import disable_warnings
from urllib3.exceptions import InsecureRequestWarning
# Supress only InsecureRequestWarning
disable_warnings(InsecureRequestWarning)
WHITELIST_URL_ENDPOINT = f'iswhitelist/'
class WhiteList:
@staticmethod
def checkwhitelist(url, discord_user_id: str) -> bool:
print('🗒️ Vérification de la whitelist...')
response = requests.get(url + WHITELIST_URL_ENDPOINT + discord_user_id, verify=False)
api_data = response.json()
if api_data['whitelisted']:
print("👍 Vous êtes en whitelist")
return True
else:
print('🙅‍♂️ Désole mais vous n\'êtes pas whitelisté sur le serveur.')
return False

View File

@@ -7,16 +7,18 @@ from PySide6.QtWidgets import QApplication
# Imports pour la gestion de la configuration # Imports pour la gestion de la configuration
from config.config_manager import ConfigManager from config.config_manager import ConfigManager
from config.constants import Resources
# Imports pour la vérification Discord # Imports pour la vérification Discord
from tools.discord_tools import CheckDiscord from discord.discord_tools import CheckDiscord
from tools.custom_message_box import CustomMessageBox
# Import pour la partie ui
from ui.custom_message_box import CustomMessageBox
from ui.main_window import MainWindow
# Ne pas supprimer ! Enregistre les ressources Qt # Ne pas supprimer ! Enregistre les ressources Qt
import resources # noqa: F401 - required to register Qt resources import resources # noqa: F401 - required to register Qt resources
from ui.main_window import MainWindow
from tools.constants import Resources
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Bundle path resolution # Bundle path resolution
@@ -72,8 +74,8 @@ if __name__ == "__main__":
# On récupère l'ID stocké (sera "" si absent grâce au schéma) # On récupère l'ID stocké (sera "" si absent grâce au schéma)
stored_user_id = config.get_discord_user() stored_user_id = config.get_discord_user()
if stored_user_id != "" or not stored_user_id.isspace(): if stored_user_id != "" and not stored_user_id.isspace():
if not CheckDiscord.isuserconnected(stored_user_id): if not CheckDiscord.isuserconnected():
msg = CustomMessageBox( msg = CustomMessageBox(
title="La Tanière: connexion Discord", title="La Tanière: connexion Discord",
message="Tu n'est pas connecté à Discord\n\n" message="Tu n'est pas connecté à Discord\n\n"

View File

@@ -1,37 +0,0 @@
import psutil
from pypresence import Presence
from tools.get_server_token import GetServerTokenForDiscord
from tools.constants import Urls
class DiscordToken:
@staticmethod
def decode_discord_token():
discord_token = GetServerTokenForDiscord.get_token(GetServerTokenForDiscord.authenticate(Urls.API_URL.value))
return discord_token
class CheckDiscord:
@staticmethod
def isdiscordrunning() -> bool:
for process in psutil.process_iter(["name"]):
if (process.info["name"].lower() == "discord.exe" or
process.info["name"].lower() == "discordcanary.exe" or
process.info["name"].lower() == "discord" or
process.info["name"].lower() == "discord canary"):
return True
return False
@staticmethod
def isuserconnected(clientid: str) -> bool:
rpc = Presence(clientid)
try:
rpc.connect()
return True
except Exception:
return False
finally:
try:
rpc.close()
except Exception:
pass

View File

@@ -1,5 +1,6 @@
import sys import sys
from pathlib import Path from pathlib import Path
from PySide6.QtWidgets import QApplication
PROJECT_ROOT = Path(__file__).resolve().parents[2] PROJECT_ROOT = Path(__file__).resolve().parents[2]
@@ -17,3 +18,10 @@ def get_executable_dir() -> Path:
# sys.executable est le chemin complet vers l'application .exe # sys.executable est le chemin complet vers l'application .exe
return Path(sys.executable).parent.resolve() return Path(sys.executable).parent.resolve()
return Path(__file__).resolve().parents[2] return Path(__file__).resolve().parents[2]
def quit_application(exit_code: int = 0) -> None:
app = QApplication.instance()
if app is not None:
app.closeAllWindows()
app.exit(exit_code)
sys.exit(exit_code)

View File

@@ -1,4 +1,3 @@
import sys
from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, from PySide6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QWidget, QGraphicsDropShadowEffect) QLabel, QPushButton, QWidget, QGraphicsDropShadowEffect)
from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve
@@ -20,9 +19,6 @@ class CustomMessageBox(QDialog):
self.setAttribute(Qt.WA_TranslucentBackground) self.setAttribute(Qt.WA_TranslucentBackground)
self.setMinimumWidth(400) self.setMinimumWidth(400)
color_main = "#101624"
color_accent = "#248277" if icon_type == self.INFO else "#cf5b16"
# --- ANIMATION DE FONDU --- # --- ANIMATION DE FONDU ---
self.setWindowOpacity(0) self.setWindowOpacity(0)
self.fade_anim = QPropertyAnimation(self, b"windowOpacity") self.fade_anim = QPropertyAnimation(self, b"windowOpacity")
@@ -33,16 +29,10 @@ class CustomMessageBox(QDialog):
# --- UI SETUP --- # --- UI SETUP ---
self.container = QWidget(self) self.container = QWidget(self)
self.container.setObjectName("MainContainer") self.container.setObjectName("MsgBoxMainContainer")
self.container.setStyleSheet(f""" # Utilisé dans le fichier QSS comme condition dynamique de style
QWidget#MainContainer {{ self.container.setProperty("iconType", icon_type)
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, self.container.setProperty("buttonsType", buttons)
stop:0 {color_main}, stop:1 {color_accent});
border-radius: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
}}
QLabel {{ color: white; font-family: 'Segoe UI'; }}
""")
# LAYOUT PRINCIPAL DU CONTAINER (Marges à 0 pour coller le bouton au bord) # LAYOUT PRINCIPAL DU CONTAINER (Marges à 0 pour coller le bouton au bord)
layout = QVBoxLayout(self.container) layout = QVBoxLayout(self.container)
@@ -55,28 +45,13 @@ class CustomMessageBox(QDialog):
title_bar_layout.setSpacing(0) title_bar_layout.setSpacing(0)
title_label = QLabel(title.upper()) title_label = QLabel(title.upper())
title_label.setStyleSheet( title_label.setObjectName("MsgBoxTitleLabel")
"font-weight: bold; font-size: 10px; color: rgba(255,255,255,0.7); letter-spacing: 1px;")
self.close_btn = QPushButton("") self.close_btn = QPushButton("")
self.close_btn.setObjectName("MsgBoxCloseButton")
self.close_btn.setFixedSize(45, 35) self.close_btn.setFixedSize(45, 35)
self.close_btn.clicked.connect(self.reject) self.close_btn.clicked.connect(self.reject)
self.close_btn.setCursor(Qt.PointingHandCursor) self.close_btn.setCursor(Qt.PointingHandCursor)
self.close_btn.setStyleSheet("""
QPushButton {
background: transparent;
color: white;
border: none;
font-size: 14px;
/* Rayon identique au container (15px) pour épouser parfaitement le coin */
border-top-right-radius: 15px;
border-bottom-left-radius: 10px;
}
QPushButton:hover {
background-color: #e74c3c;
color: white;
}
""")
title_bar_layout.addWidget(title_label) title_bar_layout.addWidget(title_label)
title_bar_layout.addStretch() title_bar_layout.addStretch()
@@ -91,12 +66,12 @@ class CustomMessageBox(QDialog):
# Contenu central (Icône + Message) # Contenu central (Icône + Message)
content_layout = QHBoxLayout() content_layout = QHBoxLayout()
icon_label = QLabel() icon_label = QLabel()
icon_label.setObjectName("MsgBoxIconLabel")
icon_text = "" if icon_type == self.INFO else "⚠️" icon_text = "" if icon_type == self.INFO else "⚠️"
icon_label.setText(icon_text) icon_label.setText(icon_text)
icon_label.setStyleSheet("font-size: 35px; margin-right: 10px;")
msg_label = QLabel(message) msg_label = QLabel(message)
msg_label.setStyleSheet("font-size: 14px; color: #f0f0f0;") msg_label.setObjectName("MsgBoxMessageLabel")
msg_label.setWordWrap(True) msg_label.setWordWrap(True)
content_layout.addWidget(icon_label) content_layout.addWidget(icon_label)
@@ -108,24 +83,14 @@ class CustomMessageBox(QDialog):
btn_layout.setSpacing(10) btn_layout.setSpacing(10)
btn_layout.addStretch() btn_layout.addStretch()
style_btn_base = """
QPushButton {
background: #2a313d; border-radius: 6px; color: white;
padding: 8px 20px; font-weight: bold; font-size: 12px;
border: 1px solid rgba(255,255,255,0.05);
}
QPushButton:hover { background: #363d4a; border: 1px solid white; }
"""
if buttons == self.OK_CANCEL: if buttons == self.OK_CANCEL:
self.btn_cancel = QPushButton("ANNULER") self.btn_cancel = QPushButton("ANNULER")
self.btn_cancel.setStyleSheet(style_btn_base) self.btn_cancel.setObjectName("MsgBoxCancelButton")
self.btn_cancel.clicked.connect(self.reject) self.btn_cancel.clicked.connect(self.reject)
btn_layout.addWidget(self.btn_cancel) btn_layout.addWidget(self.btn_cancel)
self.btn_ok = QPushButton("COMPRIS") self.btn_ok = QPushButton("COMPRIS")
style_btn_ok = style_btn_base.replace("#2a313d", "#248277") self.btn_ok.setObjectName("MsgBoxOkButton")
self.btn_ok.setStyleSheet(style_btn_ok)
self.btn_ok.setCursor(Qt.PointingHandCursor) self.btn_ok.setCursor(Qt.PointingHandCursor)
self.btn_ok.clicked.connect(self.accept) self.btn_ok.clicked.connect(self.accept)
btn_layout.addWidget(self.btn_ok) btn_layout.addWidget(self.btn_ok)

75
src/ui/hazard_stripes.py Normal file
View File

@@ -0,0 +1,75 @@
# ui/hazard_stripes.py
from PySide6.QtWidgets import QPushButton, QStyleOptionButton, QStyle
from PySide6.QtGui import QPainter, QColor, QPainterPath, QPen, QPolygon
from PySide6.QtCore import Qt, QPoint
class HazardButton(QPushButton):
def __init__(self, parent=None):
super().__init__(parent)
self._hazard = False
def set_hazard(self, enabled: bool):
self._hazard = enabled
self.update()
def paintEvent(self, event):
if not hasattr(self, '_hazard'):
self.__dict__['_hazard'] = False
p = QPainter(self)
p.setRenderHint(QPainter.RenderHint.Antialiasing)
r = self.rect()
radius = 4
path = QPainterPath()
path.addRoundedRect(r, radius, radius)
p.setClipPath(path)
if self._hazard:
p.fillRect(r, QColor("#FFD700"))
p.setPen(Qt.PenStyle.NoPen)
p.setBrush(QColor("#000000"))
stripe_width = 20
stripe_gap = 30
period = stripe_width + stripe_gap
diag = r.width() + r.height()
for x in range(-diag, diag * 2, period):
stripe = QPolygon([
QPoint(x, r.bottom() + 10),
QPoint(x + stripe_width, r.bottom() + 10),
QPoint(x + stripe_width + r.height() + 10, r.top() - 10),
QPoint(x + r.height() + 10, r.top() - 10),
])
p.drawPolygon(stripe)
# ↓ Fond semi-transparent derrière le texte
text_bg = QColor(255, 215, 0, 230) # noir à 63% d'opacité
p.setPen(Qt.PenStyle.NoPen)
p.setBrush(text_bg)
bg_rect = r.adjusted(60, 8, -60, -8) # marges internes
p.drawRoundedRect(bg_rect, 4, 4)
else:
p.fillRect(r, QColor("#FFD700"))
p.setClipping(False)
p.setPen(QPen(QColor("#000000"), 2))
p.setBrush(Qt.BrushStyle.NoBrush)
p.drawRoundedRect(r.adjusted(1, 1, -1, -1), radius, radius)
p.setClipping(False)
opt = QStyleOptionButton()
self.initStyleOption(opt)
opt.palette.setColor(
opt.palette.ColorRole.ButtonText,
self.palette().color(self.palette().ColorRole.ButtonText)
)
self.style().drawControl(
QStyle.ControlElement.CE_PushButtonLabel, opt, p, self
)

View File

@@ -8,11 +8,14 @@ from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QMainWindow, QSizePolicy from PySide6.QtWidgets import QMainWindow, QSizePolicy
from config.config_manager import ConfigManager from config.config_manager import ConfigManager
from config.constants import NO_STAFF, Urls, NO_WHITELIST
from ui.custom_message_box import CustomMessageBox
from ui.hazard_stripes import HazardButton
from controllers.audio_controller import AudioController from controllers.audio_controller import AudioController
from controllers.glow_animator import GlowAnimator from controllers.glow_animator import GlowAnimator
from controllers.window_dragger import WindowDragger from controllers.window_dragger import WindowDragger
from tools import discord_oauth from discord import discord_oauth
from tools.constants import NO_STAFF, Urls from tools.utils import quit_application
from fake_patch_notes import patch_note from fake_patch_notes import patch_note
@@ -32,6 +35,29 @@ class MainWindow(QMainWindow):
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window) self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Window)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# Test bouton en contruction
en_chantier = True
if en_chantier:
old_btn = self.ui.connexion_btn
parent_layout = self.ui.verticalLayout_6 # layout direct du bouton dans le .ui
index = parent_layout.indexOf(old_btn)
new_btn = HazardButton(old_btn.parentWidget())
new_btn.setObjectName("connexion_btn")
new_btn.setText("EN MAINTENANCE")
new_btn.setIcon(old_btn.icon())
new_btn.setIconSize(old_btn.iconSize())
new_btn.setMinimumSize(old_btn.minimumSize())
new_btn.set_hazard(True)
parent_layout.takeAt(index)
old_btn.deleteLater()
parent_layout.insertWidget(index, new_btn)
self.ui.connexion_btn = new_btn
self.ui.connexion_btn.clicked.connect(self._on_connexion)
# centrage vertical du bouton connexion # centrage vertical du bouton connexion
if NO_STAFF: if NO_STAFF:
self.ui.staff_btn.hide() self.ui.staff_btn.hide()
@@ -61,6 +87,18 @@ class MainWindow(QMainWindow):
self._center_window() self._center_window()
self.show() self.show()
if NO_WHITELIST:
msg = CustomMessageBox(
title="La Tanière: Non whitelisté",
message="\n\nTu n'est pas whitelisté sur le serveur\n\n"
"Assure-toi de te faire whitelister.\n\n"
"Lorsque cela sera fait, relance le launcher.",
icon_type=CustomMessageBox.WARNING,
buttons=CustomMessageBox.OK
)
msg.exec()
quit_application()
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Setup # Setup
# ------------------------------------------------------------------ # ------------------------------------------------------------------

View File

@@ -45,7 +45,7 @@ QLabel#queue_lbl {
} }
QPushButton#connexion_btn { QPushButton#connexion_btn[chantier="false"] {
/* Dégradé chaleureux : Orange vers Orange-Rouge */ /* Dégradé chaleureux : Orange vers Orange-Rouge */
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop: 0 #ff9d00, stop: 0 #ff9d00,
@@ -57,7 +57,7 @@ QPushButton#connexion_btn {
padding: 10px; padding: 10px;
} }
QPushButton#connexion_btn:hover { QPushButton#connexion_btn[chantier="false"]:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop: 0 #ffb338, stop: 0 #ffb338,
stop: 1 #ff7a29); stop: 1 #ff7a29);
@@ -66,7 +66,7 @@ QPushButton#connexion_btn:hover {
outline: none; outline: none;
} }
QPushButton#connexionBtn:pressed { QPushButton#connexion_btn[chantier="false"]:pressed {
background: #cc5200; background: #cc5200;
padding-top: 12px; /* Effet d'enfoncement */ padding-top: 12px; /* Effet d'enfoncement */
} }
@@ -81,6 +81,21 @@ QPushButton#staff_btn {
padding: 5px 15px; padding: 5px 15px;
} }
HazardButton#connexion_btn {
color: #0A1A3A;
/* color: #0D2A6B;*/
font-weight: bold;
}
HazardButton#connexion_btn:hover {
/* color: #ffffff;*/
color: #0D2A6B;
}
HazardButton#connexion_btn:pressed {
color: #333333;
}
QPushButton#staff_btn:hover { QPushButton#staff_btn:hover {
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
stop:0 #FF6046, stop:1 #FF527B); stop:0 #FF6046, stop:1 #FF527B);
@@ -209,3 +224,92 @@ QSlider::handle:horizontal:pressed {
background: #E65100; background: #E65100;
border-radius: 8px; border-radius: 8px;
} }
/* ----------------------------------------------
Custom Message Box
----------------------------------------------*/
QWidget#MsgBoxMainContainer {
border-radius: 15px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
QWidget#MsgBoxMainContainer[iconType="info"] {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 1,
stop: 0 #101624,
stop: 1 #248277
);
}
QWidget#MsgBoxMainContainer[iconType="warning"] {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 1,
stop: 0 #101624,
stop: 1 #cf5b16
);
}
QWidget#MsgBoxMainContainer QLabel,
QWidget#MsgBoxMainContainer QPushButton
{
color: white;
/*font-size: 18px;
/* font-family: 'Segoe UI';*/
}
QPushButton#MsgBoxOkButton,
QPushButton#MsgBoxCancelButton {
border-radius: 6px;
/* color: white;*/
padding: 8px 20px;
font-weight: bold;
font-size: 12px;
border: 1px solid rgba(255,255,255,0.05);
}
QPushButton#MsgBoxOkButton {
background: #248277;
}
QPushButton#MsgBoxCancelButton {
background: #2a313d;
}
QPushButton#MsgBoxOkButton:hover,
QPushButton#MsgBoxCancelButton:hover{
background: #363d4a;
border: 1px solid white;
}
QPushButton#MsgBoxCloseButton {
background: transparent;
color: white;
border: none;
font-size: 14px;
/* Rayon identique au container (15px) pour épouser parfaitement le coin */
border-top-right-radius: 15px;
border-bottom-left-radius: 10px;
}
QPushButton#MsgBoxCloseButton:hover {
background-color: #e74c3c;
color: white;
}
QLabel#MsgBoxTitleLabel {
font-weight: bold;
font-size: 14px;
color: rgba(255,255,255,0.7);
letter-spacing: 1px;
}
QLabel#MsgBoxMessageLabel {
font-size: 14px;
color: #f0f0f0;
}
QLabel#MsgBoxIconLabel {
font-size: 35px;
margin-right: 10px;
}