# -*- coding: utf-8 -*-
"""
:File: EuljiroBible/gui/ui/common.py
:Author: Benjamin Jaedon Choi - https://github.com/saintbenjamin
:Affiliated Church: The Eulji-ro Presbyterian Church [대한예수교장로회(통합) 을지로교회]
:Address: The Eulji-ro Presbyterian Church, 24-10, Eulji-ro 20-gil, Jung-gu, Seoul 04549, South Korea
:Telephone: +82-2-2266-3070
:E-mail: euljirochurch [at] G.M.A.I.L. (replace [at] with @ and G.M.A.I.L as you understood.)
:License: MIT License with Attribution Requirement (see LICENSE file for details); Copyright (c) 2025 The Eulji-ro Presbyterian Church.
Provides reusable UI widgets such as buttons, checkboxes, and loading indicator.
Includes utility wrappers for `QPushButton` creation with SVG icons and dynamic sizing.
"""
from PySide6.QtWidgets import QPushButton, QCheckBox, QWidget, QApplication, QSizePolicy
from PySide6.QtCore import Qt, QTimer, QSize
from PySide6.QtGui import QPainter, QPen, QIcon
[docs]
def create_checkbox(text, checked=False, callback=None):
"""
Creates a `QCheckBox` with optional initial state and signal connection.
Args:
text (str): Label text next to the checkbox.
checked (bool): Whether the checkbox is initially checked.
callback (function, optional): Slot for stateChanged signal.
Returns:
QCheckBox: The configured checkbox.
"""
cb = QCheckBox(text)
cb.setChecked(checked)
if callback:
cb.stateChanged.connect(callback)
return cb
[docs]
def create_svg_text_button(svg_path, text, icon_size=20, tooltip="", callback=None):
"""
Creates a `QPushButton` with SVG icon and text.
Args:
svg_path (str): Path to SVG icon.
text (str): Button label.
icon_size (int): Icon size in pixels (default: 20).
tooltip (str): Tooltip text.
callback (function, optional): Slot for clicked() signal.
Returns:
QPushButton: The configured icon + text button.
"""
btn = QPushButton(text)
icon = QIcon(svg_path)
btn.setIcon(icon)
btn.setIconSize(QSize(icon_size, icon_size))
btn.setFont(QApplication.font())
btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
btn.setMinimumHeight(btn.sizeHint().height())
if tooltip:
btn.setToolTip(tooltip)
if callback:
btn.clicked.connect(callback)
return btn
[docs]
class LoadingIndicator(QWidget):
"""
A spinning arc-based loading indicator widget.
This widget displays a rotating arc animation to indicate that a background
operation (such as data loading or initialization) is in progress. It is
designed to be lightweight and self-contained, relying on a QTimer-driven
angle update and custom painting via QPainter.
The widget automatically centers itself within its parent when shown or when
the parent is resized.
Attributes:
angle (int):
Current rotation angle of the arc, in degrees. This value is incremented
periodically by the internal timer to animate the spinner.
timer (QTimer):
Timer used to update the rotation angle at a fixed interval, driving
the animation loop.
"""
[docs]
def __init__(self, parent=None):
"""
Initializes the spinning loader.
Args:
parent (QWidget, optional): Parent widget. Defaults to None.
"""
super().__init__(parent)
self.angle = 0
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_angle)
self.setFixedSize(50, 50)
[docs]
def start(self):
"""
Starts the loading animation.
"""
self.angle = 0
self.show()
self.timer.start(50)
[docs]
def stop(self):
"""
Stops the animation and hides the widget.
"""
self.timer.stop()
self.hide()
[docs]
def update_angle(self):
"""
Advances the angle for the rotating arc.
"""
self.angle += 10
if self.angle >= 360:
self.stop()
self.update()
[docs]
def paintEvent(self, event):
"""
Paints the spinning arc using QPainter.
Args:
event (QPaintEvent): Internal paint event.
"""
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
rect = self.rect().adjusted(5, 5, -5, -5)
pen = QPen(Qt.gray, 5)
painter.setPen(pen)
painter.drawArc(rect, 90 * 16, -self.angle * 16)
[docs]
def showEvent(self, event):
"""
Centers the widget within its parent on show.
Args:
event (QShowEvent): Widget shown.
"""
super().showEvent(event)
if self.parent():
self.setGeometry(
(self.parent().width() - self.width()) // 2,
(self.parent().height() - self.height()) // 2,
self.width(),
self.height()
)
[docs]
def resizeEvent(self, event):
"""
Keeps widget centered when parent resizes.
Args:
event (QResizeEvent): Resize trigger.
"""
super().resizeEvent(event)
if self.parent():
self.setGeometry(
(self.parent().width() - self.width()) // 2,
(self.parent().height() - self.height()) // 2,
self.width(),
self.height()
)