355 lines
8.1 KiB
Python
355 lines
8.1 KiB
Python
import sys
|
|
|
|
from PySide6.QtCore import Qt
|
|
from PySide6.QtGui import QFont
|
|
from PySide6.QtWidgets import (
|
|
QApplication,
|
|
QFrame,
|
|
QHBoxLayout,
|
|
QLabel,
|
|
QLineEdit,
|
|
QListWidget,
|
|
QMainWindow,
|
|
QPushButton,
|
|
QSplitter,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
from abacus_core import AbacusCore
|
|
|
|
|
|
STYLE = """
|
|
QMainWindow, QWidget {
|
|
background: #111318;
|
|
color: #e7e9ee;
|
|
font-family: "Segoe UI";
|
|
font-size: 13px;
|
|
}
|
|
|
|
QFrame#topbar {
|
|
background: #171a21;
|
|
border-bottom: 1px solid #292e38;
|
|
}
|
|
|
|
QLabel#title {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: #f5f7fb;
|
|
}
|
|
|
|
QLabel#subtitle {
|
|
color: #858c99;
|
|
}
|
|
|
|
QFrame#panel {
|
|
background: #171a21;
|
|
border: 1px solid #292e38;
|
|
border-radius: 12px;
|
|
}
|
|
|
|
QLabel#panelTitle {
|
|
color: #9da5b3;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
QLineEdit#input {
|
|
background: #0d0f13;
|
|
border: 1px solid #343a46;
|
|
border-radius: 10px;
|
|
padding: 12px 14px;
|
|
color: #ffffff;
|
|
font-size: 16px;
|
|
selection-background-color: #3b82f6;
|
|
}
|
|
|
|
QLineEdit#input:focus {
|
|
border: 1px solid #4b8cff;
|
|
}
|
|
|
|
QPushButton {
|
|
background: #2864d7;
|
|
border: none;
|
|
border-radius: 9px;
|
|
padding: 11px 18px;
|
|
color: white;
|
|
font-weight: 700;
|
|
}
|
|
|
|
QPushButton:hover {
|
|
background: #3475ef;
|
|
}
|
|
|
|
QPushButton:pressed {
|
|
background: #1f55ba;
|
|
}
|
|
|
|
QPushButton#secondary {
|
|
background: #242933;
|
|
color: #cbd1db;
|
|
}
|
|
|
|
QPushButton#secondary:hover {
|
|
background: #2d333f;
|
|
}
|
|
|
|
QListWidget {
|
|
background: transparent;
|
|
border: none;
|
|
outline: none;
|
|
padding: 4px;
|
|
}
|
|
|
|
QListWidget::item {
|
|
padding: 8px 7px;
|
|
border-radius: 7px;
|
|
}
|
|
|
|
QListWidget::item:hover {
|
|
background: #20242d;
|
|
}
|
|
|
|
QListWidget::item:selected {
|
|
background: #202a3d;
|
|
color: #ffffff;
|
|
}
|
|
|
|
QLabel#result {
|
|
color: #66d9a8;
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
}
|
|
|
|
QLabel#status {
|
|
color: #7e8795;
|
|
}
|
|
|
|
QSplitter::handle {
|
|
background: #111318;
|
|
width: 8px;
|
|
}
|
|
"""
|
|
|
|
|
|
class AbacusWindow(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.abacus = AbacusCore()
|
|
self.step = 0
|
|
|
|
self.setWindowTitle("Abacus")
|
|
self.resize(1100, 720)
|
|
self.setMinimumSize(850, 560)
|
|
|
|
self._build_ui()
|
|
self._update_lists()
|
|
|
|
def _build_ui(self):
|
|
root = QWidget()
|
|
root_layout = QVBoxLayout(root)
|
|
root_layout.setContentsMargins(18, 14, 18, 18)
|
|
root_layout.setSpacing(14)
|
|
|
|
# Header
|
|
topbar = QFrame()
|
|
topbar.setObjectName("topbar")
|
|
top_layout = QHBoxLayout(topbar)
|
|
top_layout.setContentsMargins(4, 4, 4, 12)
|
|
|
|
title_box = QVBoxLayout()
|
|
title_box.setSpacing(1)
|
|
|
|
title = QLabel("Abacus")
|
|
title.setObjectName("title")
|
|
|
|
subtitle = QLabel("Expression calculator")
|
|
subtitle.setObjectName("subtitle")
|
|
|
|
title_box.addWidget(title)
|
|
title_box.addWidget(subtitle)
|
|
top_layout.addLayout(title_box)
|
|
top_layout.addStretch()
|
|
|
|
self.status = QLabel("Ready")
|
|
self.status.setObjectName("status")
|
|
top_layout.addWidget(self.status)
|
|
|
|
root_layout.addWidget(topbar)
|
|
|
|
splitter = QSplitter(Qt.Horizontal)
|
|
splitter.setChildrenCollapsible(False)
|
|
|
|
# Left: history
|
|
history_panel = self._make_panel("History")
|
|
history_layout = history_panel.layout()
|
|
|
|
self.history = QListWidget()
|
|
self.history.setAlternatingRowColors(False)
|
|
history_layout.addWidget(self.history)
|
|
|
|
clear_btn = QPushButton("Clear history")
|
|
clear_btn.setObjectName("secondary")
|
|
clear_btn.clicked.connect(self.history.clear)
|
|
history_layout.addWidget(clear_btn)
|
|
|
|
splitter.addWidget(history_panel)
|
|
|
|
# Center
|
|
center = QWidget()
|
|
center_layout = QVBoxLayout(center)
|
|
center_layout.setContentsMargins(0, 0, 0, 0)
|
|
center_layout.setSpacing(14)
|
|
|
|
calc_panel = self._make_panel("Calculator")
|
|
calc_layout = calc_panel.layout()
|
|
|
|
self.result = QLabel("0")
|
|
self.result.setObjectName("result")
|
|
self.result.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
calc_layout.addWidget(self.result)
|
|
|
|
self.input = QLineEdit()
|
|
self.input.setObjectName("input")
|
|
self.input.setPlaceholderText("Enter expression, e.g. 2 + 3 * alpha")
|
|
self.input.returnPressed.connect(self.calculate)
|
|
calc_layout.addWidget(self.input)
|
|
|
|
buttons = QHBoxLayout()
|
|
buttons.setSpacing(8)
|
|
|
|
enter = QPushButton("Calculate")
|
|
enter.clicked.connect(self.calculate)
|
|
|
|
clear = QPushButton("Clear")
|
|
clear.setObjectName("secondary")
|
|
clear.clicked.connect(self._clear_input)
|
|
|
|
buttons.addWidget(enter)
|
|
buttons.addWidget(clear)
|
|
calc_layout.addLayout(buttons)
|
|
|
|
center_layout.addWidget(calc_panel)
|
|
|
|
help_panel = self._make_panel("Quick reference")
|
|
help_layout = help_panel.layout()
|
|
|
|
help_text = QLabel(
|
|
"Variable: alpha = 10\n"
|
|
"Function: double: alpha * 2\n"
|
|
"Comment: 2 + 3 # test\n"
|
|
"Commands from the original console are available through the UI."
|
|
)
|
|
help_text.setStyleSheet("color: #9da5b3; line-height: 1.5;")
|
|
help_text.setWordWrap(True)
|
|
help_layout.addWidget(help_text)
|
|
center_layout.addWidget(help_panel)
|
|
center_layout.addStretch()
|
|
|
|
splitter.addWidget(center)
|
|
|
|
# Right: variables/functions
|
|
right = QWidget()
|
|
right_layout = QVBoxLayout(right)
|
|
right_layout.setContentsMargins(0, 0, 0, 0)
|
|
right_layout.setSpacing(14)
|
|
|
|
var_panel = self._make_panel("Variables")
|
|
var_panel.layout().addWidget(QLabel("Stored values"))
|
|
self.variables = QListWidget()
|
|
var_panel.layout().addWidget(self.variables)
|
|
right_layout.addWidget(var_panel, 1)
|
|
|
|
func_panel = self._make_panel("Functions")
|
|
func_panel.layout().addWidget(QLabel("Definitions"))
|
|
self.functions = QListWidget()
|
|
func_panel.layout().addWidget(self.functions)
|
|
right_layout.addWidget(func_panel, 1)
|
|
|
|
splitter.addWidget(right)
|
|
|
|
splitter.setSizes([260, 500, 280])
|
|
root_layout.addWidget(splitter, 1)
|
|
|
|
self.setCentralWidget(root)
|
|
|
|
def _make_panel(self, title):
|
|
panel = QFrame()
|
|
panel.setObjectName("panel")
|
|
|
|
layout = QVBoxLayout(panel)
|
|
layout.setContentsMargins(14, 13, 14, 14)
|
|
layout.setSpacing(9)
|
|
|
|
label = QLabel(title)
|
|
label.setObjectName("panelTitle")
|
|
layout.addWidget(label)
|
|
|
|
return panel
|
|
|
|
def calculate(self):
|
|
text = self.input.text().strip()
|
|
if not text:
|
|
return
|
|
|
|
self.step += 1
|
|
|
|
try:
|
|
result = self.abacus.parse_input(text)
|
|
except Exception as exc:
|
|
self.status.setText(f"Error: {exc}")
|
|
self.result.setText("Error")
|
|
return
|
|
|
|
if not result:
|
|
self.input.clear()
|
|
self.status.setText("Comment / empty input")
|
|
return
|
|
|
|
original, _, value, comment = result
|
|
value_text = str(value)
|
|
|
|
line = f"{self.step}. {original} = {value_text}"
|
|
if comment:
|
|
line += f" #{comment}"
|
|
|
|
self.history.addItem(line)
|
|
self.history.scrollToBottom()
|
|
|
|
self.result.setText(value_text)
|
|
self.input.setText(value_text)
|
|
self.status.setText("Calculated")
|
|
self._update_lists()
|
|
|
|
def _clear_input(self):
|
|
self.input.clear()
|
|
self.result.setText("0")
|
|
self.status.setText("Ready")
|
|
self.input.setFocus()
|
|
|
|
def _update_lists(self):
|
|
self.variables.clear()
|
|
for key, value in self.abacus.get_vars().items():
|
|
self.variables.addItem(f"{key} = {value}")
|
|
|
|
self.functions.clear()
|
|
for key, value in self.abacus.get_funcs().items():
|
|
self.functions.addItem(f"{key} = {value}")
|
|
|
|
|
|
def main():
|
|
app = QApplication(sys.argv)
|
|
app.setStyle("Fusion")
|
|
app.setFont(QFont("Segoe UI", 10))
|
|
app.setStyleSheet(STYLE)
|
|
|
|
window = AbacusWindow()
|
|
window.show()
|
|
|
|
sys.exit(app.exec())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|