Skip to content

Commit 4fae61a

Browse files
author
Earth1283
committed
skins manager, console, and server list
1 parent ccbaf35 commit 4fae61a

6 files changed

Lines changed: 859 additions & 12 deletions

File tree

pymcl/console_window.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
from PyQt6.QtCore import Qt, pyqtSlot
2+
from PyQt6.QtGui import QFont, QColor, QAction, QFontDatabase
3+
from PyQt6.QtWidgets import (
4+
QWidget,
5+
QVBoxLayout,
6+
QTextEdit,
7+
QPushButton,
8+
QHBoxLayout,
9+
QApplication,
10+
QCheckBox,
11+
QLabel,
12+
QFrame
13+
)
14+
15+
class ConsoleWindow(QWidget):
16+
def __init__(self, parent=None):
17+
super().__init__(parent, Qt.WindowType.Window)
18+
self.setWindowTitle("Game Console")
19+
self.resize(900, 650)
20+
self.auto_scroll = True
21+
22+
layout = QVBoxLayout(self)
23+
layout.setContentsMargins(0, 0, 0, 0)
24+
layout.setSpacing(0)
25+
26+
# Toolbar
27+
toolbar = QFrame()
28+
toolbar.setStyleSheet("background-color: #252526; border-bottom: 1px solid #333;")
29+
toolbar_layout = QHBoxLayout(toolbar)
30+
toolbar_layout.setContentsMargins(10, 8, 10, 8)
31+
32+
title = QLabel("Output Log")
33+
title.setStyleSheet("font-weight: bold; color: #ddd; font-size: 13px;")
34+
toolbar_layout.addWidget(title)
35+
36+
toolbar_layout.addStretch(1)
37+
38+
self.auto_scroll_check = QCheckBox("Auto-scroll")
39+
self.auto_scroll_check.setChecked(True)
40+
self.auto_scroll_check.setStyleSheet("color: #ccc;")
41+
self.auto_scroll_check.toggled.connect(self.set_auto_scroll)
42+
toolbar_layout.addWidget(self.auto_scroll_check)
43+
44+
self.copy_btn = QPushButton("Copy")
45+
self.copy_btn.setCursor(Qt.CursorShape.PointingHandCursor)
46+
self.copy_btn.clicked.connect(self.copy_logs)
47+
toolbar_layout.addWidget(self.copy_btn)
48+
49+
self.clear_btn = QPushButton("Clear")
50+
self.clear_btn.setCursor(Qt.CursorShape.PointingHandCursor)
51+
self.clear_btn.clicked.connect(self.clear_logs)
52+
toolbar_layout.addWidget(self.clear_btn)
53+
54+
layout.addWidget(toolbar)
55+
56+
# Console Output
57+
self.text_edit = QTextEdit()
58+
self.text_edit.setReadOnly(True)
59+
self.text_edit.setObjectName("console_output")
60+
self.text_edit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) # Logs are better without wrap usually
61+
62+
# Font selection
63+
preferred_fonts = ["JetBrains Mono", "Cascadia Code", "Fira Code", "Consolas", "Menlo", "Courier New"]
64+
font_family = "Monospace"
65+
for font in preferred_fonts:
66+
if font in QFontDatabase.families():
67+
font_family = font
68+
break
69+
70+
font = QFont(font_family)
71+
font.setStyleHint(QFont.StyleHint.Monospace)
72+
font.setPointSize(10)
73+
self.text_edit.setFont(font)
74+
75+
layout.addWidget(self.text_edit)
76+
77+
# Apply Dark Theme
78+
self.setStyleSheet("""
79+
QWidget {
80+
background-color: #1e1e1e;
81+
color: #d4d4d4;
82+
}
83+
QTextEdit {
84+
background-color: #1e1e1e;
85+
color: #cccccc;
86+
border: none;
87+
padding: 5px;
88+
selection-background-color: #264f78;
89+
}
90+
QPushButton {
91+
background-color: #3c3c3c;
92+
color: white;
93+
border: none;
94+
padding: 6px 12px;
95+
border-radius: 3px;
96+
font-size: 12px;
97+
}
98+
QPushButton:hover {
99+
background-color: #4a4a4a;
100+
}
101+
QPushButton:pressed {
102+
background-color: #2d2d2d;
103+
}
104+
QScrollBar:vertical {
105+
border: none;
106+
background: #1e1e1e;
107+
width: 14px;
108+
margin: 0px;
109+
}
110+
QScrollBar::handle:vertical {
111+
background: #424242;
112+
min-height: 20px;
113+
border-radius: 0px;
114+
}
115+
QScrollBar::handle:vertical:hover {
116+
background: #4f4f4f;
117+
}
118+
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
119+
height: 0px;
120+
}
121+
QCheckBox {
122+
spacing: 5px;
123+
}
124+
QCheckBox::indicator {
125+
width: 13px;
126+
height: 13px;
127+
border: 1px solid #555;
128+
border-radius: 2px;
129+
background: #1e1e1e;
130+
}
131+
QCheckBox::indicator:checked {
132+
background: #007acc;
133+
border-color: #007acc;
134+
}
135+
""")
136+
137+
def set_auto_scroll(self, checked):
138+
self.auto_scroll = checked
139+
140+
@pyqtSlot(str)
141+
def append_log(self, text):
142+
# Basic color highlighting
143+
color = "#cccccc" # default gray
144+
if "[ERROR]" in text or "Exception" in text or "at " in text or "FATAL" in text:
145+
color = "#ff6b6b" # soft red
146+
elif "[WARN]" in text:
147+
color = "#f1c40f" # yellow
148+
elif "[INFO]" in text:
149+
color = "#6ab04c" # green
150+
elif "DEBUG" in text:
151+
color = "#569cd6" # blue
152+
153+
formatted_text = f'<span style="color:{color};">{text}</span>'
154+
self.text_edit.append(formatted_text)
155+
156+
if self.auto_scroll:
157+
scrollbar = self.text_edit.verticalScrollBar()
158+
scrollbar.setValue(scrollbar.maximum())
159+
160+
def copy_logs(self):
161+
clipboard = QApplication.clipboard()
162+
clipboard.setText(self.text_edit.toPlainText())
163+
164+
def clear_logs(self):
165+
self.text_edit.clear()

pymcl/main_window.py

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
from .launch_page import LaunchPage
4242
from .settings_page import SettingsPage
4343
from .background_widget import BackgroundWidget
44+
from .console_window import ConsoleWindow
45+
from .servers_page import ServersPage
46+
from .skin_manager import SkinManagerPage
4447

4548

4649
class MainWindow(QMainWindow):
@@ -61,6 +64,9 @@ def __init__(self):
6164

6265
self.image_files = []
6366
self.current_image_index = 0
67+
68+
# New console window
69+
self.console_window = ConsoleWindow()
6470

6571
self.setWindowTitle(APP_NAME)
6672
self.setMinimumSize(900, 500)
@@ -121,9 +127,20 @@ def init_ui(self):
121127
main_layout.setContentsMargins(20, 20, 20, 20)
122128
main_layout.setSpacing(20)
123129

124-
# Left navigation
130+
# Left navigation Scroll Area
131+
left_scroll = QScrollArea()
132+
left_scroll.setWidgetResizable(True)
133+
left_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
134+
left_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
135+
left_scroll.setFrameShape(QFrame.Shape.NoFrame)
136+
left_scroll.setStyleSheet("background: transparent;") # Transparent background
137+
138+
# Left navigation container
125139
left_widget = QWidget()
126140
left_widget.setObjectName("left_title_container")
141+
# Ensure the widget itself has transparent bg if styled otherwise in stylesheet
142+
left_widget.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
143+
127144
left_layout = QVBoxLayout(left_widget)
128145
left_layout.setSpacing(5)
129146
left_layout.setContentsMargins(10, 10, 10, 10)
@@ -150,6 +167,16 @@ def init_ui(self):
150167
self.nav_launch_button.setCursor(Qt.CursorShape.PointingHandCursor)
151168
left_layout.addWidget(self.nav_launch_button)
152169

170+
self.nav_servers_button = QPushButton("Servers")
171+
self.nav_servers_button.setObjectName("nav_button")
172+
self.nav_servers_button.setCursor(Qt.CursorShape.PointingHandCursor)
173+
left_layout.addWidget(self.nav_servers_button)
174+
175+
self.nav_skins_button = QPushButton("Skins")
176+
self.nav_skins_button.setObjectName("nav_button")
177+
self.nav_skins_button.setCursor(Qt.CursorShape.PointingHandCursor)
178+
left_layout.addWidget(self.nav_skins_button)
179+
153180
self.nav_mods_button = QPushButton("Mods")
154181
self.nav_mods_button.setObjectName("nav_button")
155182
self.nav_mods_button.setCursor(Qt.CursorShape.PointingHandCursor)
@@ -165,8 +192,16 @@ def init_ui(self):
165192
self.nav_settings_button.setCursor(Qt.CursorShape.PointingHandCursor)
166193
left_layout.addWidget(self.nav_settings_button)
167194

195+
self.nav_console_button = QPushButton("Console")
196+
self.nav_console_button.setObjectName("nav_button")
197+
self.nav_console_button.setCursor(Qt.CursorShape.PointingHandCursor)
198+
self.nav_console_button.clicked.connect(self.show_console)
199+
left_layout.addWidget(self.nav_console_button)
200+
168201
left_layout.addStretch(1)
169-
main_layout.addWidget(left_widget, 2)
202+
203+
left_scroll.setWidget(left_widget)
204+
main_layout.addWidget(left_scroll, 2)
170205

171206
# Right content
172207
content_frame = QFrame()
@@ -184,16 +219,22 @@ def init_ui(self):
184219
self.settings_page.settings_saved.connect(self.reload_background_settings)
185220
self.mods_page = ModsPage()
186221
self.mod_browser_page = ModBrowserPage()
222+
self.servers_page = ServersPage()
223+
self.skin_manager_page = SkinManagerPage()
187224

188-
self.stacked_widget.addWidget(self.launch_page)
189-
self.stacked_widget.addWidget(self.mods_page)
190-
self.stacked_widget.addWidget(self.mod_browser_page)
191-
self.stacked_widget.addWidget(self.settings_page)
225+
self.stacked_widget.addWidget(self.launch_page) # Index 0
226+
self.stacked_widget.addWidget(self.servers_page) # Index 1
227+
self.stacked_widget.addWidget(self.skin_manager_page) # Index 2
228+
self.stacked_widget.addWidget(self.mods_page) # Index 3
229+
self.stacked_widget.addWidget(self.mod_browser_page) # Index 4
230+
self.stacked_widget.addWidget(self.settings_page) # Index 5
192231

193232
self.nav_launch_button.clicked.connect(lambda: self.switch_page(0, self.nav_launch_button))
194-
self.nav_mods_button.clicked.connect(lambda: self.switch_page(1, self.nav_mods_button))
195-
self.nav_browse_mods_button.clicked.connect(lambda: self.switch_page(2, self.nav_browse_mods_button))
196-
self.nav_settings_button.clicked.connect(lambda: self.switch_page(3, self.nav_settings_button))
233+
self.nav_servers_button.clicked.connect(lambda: self.switch_page(1, self.nav_servers_button))
234+
self.nav_skins_button.clicked.connect(lambda: self.switch_page(2, self.nav_skins_button))
235+
self.nav_mods_button.clicked.connect(lambda: self.switch_page(3, self.nav_mods_button))
236+
self.nav_browse_mods_button.clicked.connect(lambda: self.switch_page(4, self.nav_browse_mods_button))
237+
self.nav_settings_button.clicked.connect(lambda: self.switch_page(5, self.nav_settings_button))
197238

198239
# Connect signals from launch page to main window slots
199240
self.launch_page.username_input.textChanged.connect(self.save_settings)
@@ -240,7 +281,7 @@ def switch_page(self, index, button):
240281
self.mod_browser_page.set_launch_filters(version, loader_param)
241282

242283
# Update nav button styles
243-
for btn in [self.nav_launch_button, self.nav_mods_button, self.nav_browse_mods_button, self.nav_settings_button]:
284+
for btn in [self.nav_launch_button, self.nav_mods_button, self.nav_browse_mods_button, self.nav_settings_button, self.nav_servers_button, self.nav_skins_button]:
244285
btn.setObjectName("nav_button")
245286
button.setObjectName("nav_button_active")
246287
self.apply_styles()
@@ -301,6 +342,7 @@ def on_login_success(self, info: MicrosoftInfo):
301342
self.minecraft_info = info
302343
self.update_status(f"Logged in as {info['username']}")
303344
self.launch_page.microsoft_login_button.setText(f"Logged in as {info['username']}")
345+
self.skin_manager_page.set_microsoft_info(info)
304346

305347
def load_microsoft_info(self):
306348
info = self.microsoft_auth.load_microsoft_info()
@@ -314,6 +356,7 @@ def load_microsoft_info(self):
314356
self.update_status(f"Logged in as {info['username']}")
315357
self.launch_page.microsoft_login_button.setText(f"Logged in as {info['username']}")
316358
self.launch_page.auth_method_combo.setCurrentText("Microsoft")
359+
self.skin_manager_page.set_microsoft_info(info)
317360
else:
318361
self.update_status("Failed to refresh token. Please login again.")
319362

@@ -517,6 +560,12 @@ def save_settings(self):
517560
self.config_manager.set("last_version", self.launch_page.version_combo.currentText())
518561
self.config_manager.save()
519562

563+
@pyqtSlot()
564+
def show_console(self):
565+
self.console_window.show()
566+
self.console_window.raise_()
567+
self.console_window.activateWindow()
568+
520569
@pyqtSlot()
521570
def start_launch(self):
522571
modifiers = QApplication.keyboardModifiers()
@@ -569,6 +618,10 @@ def start_launch(self):
569618
self.launch_page.status_label.setText("Starting worker thread...")
570619
self.launch_page.progress_bar.setRange(0, 100)
571620
self.launch_page.progress_bar.setValue(0)
621+
622+
# Clear console and show it
623+
self.console_window.clear_logs()
624+
self.console_window.show()
572625

573626
self.worker_thread = QThread()
574627
self.worker = Worker(version, options, mod_loader_type)
@@ -578,6 +631,7 @@ def start_launch(self):
578631

579632
self.worker.progress.connect(self.update_progress)
580633
self.worker.status.connect(self.update_status)
634+
self.worker.log_output.connect(self.console_window.append_log) # Connect logs
581635
self.worker.finished.connect(self.on_launch_finished)
582636

583637
self.worker.finished.connect(self.worker_thread.quit)

0 commit comments

Comments
 (0)