Skip to content

Button Widgets

Specialized button widgets for date selection, icon display, and loading state management.


DateButton

A QToolButton that displays the currently selected date and opens a calendar dialog when clicked.

Signals:

Signal Signature Emitted when
dateChanged (QDate) The date property changes, whether from user interaction or programmatic assignment
dateSelected (QDate) The user confirms a date in the calendar dialog

Constructor parameters:

Parameter Type Default Description
parent QWidget \| None None Parent widget
date QDate \| str \| None None Initial date; None defaults to today
date_format str "dd/MM/yyyy" Qt date format string used for display and parsing
placeholder str "Select a date" Text shown when no valid date is set
show_calendar_icon bool True Whether to show the calendar icon inside the button
icon_size QSize \| tuple[int, int] QSize(16, 16) Size of the calendar icon
min_width int \| None None Minimum width; auto-calculated if None
min_height int \| None None Minimum height; auto-calculated if None

Properties:

Property Type Description
date QDate Gets or sets the selected date; setting to None clears it
date_string str Gets or sets the date as a formatted string using date_format
date_format str Gets or sets the display/parse format
placeholder str Gets or sets the placeholder text
show_calendar_icon bool Gets or sets calendar icon visibility
icon_size QSize Gets or sets the calendar icon size
min_width int \| None Gets or sets the minimum width
min_height int \| None Gets or sets the minimum height

Methods:

Method Returns Description
clearDate() None Sets the date to an invalid QDate (shows placeholder)
setToday() None Sets the date to today's date
openCalendar() None Opens the DatePickerDialog programmatically
refreshStyle() None Re-applies the QSS stylesheet after dynamic changes

Example:

from PySide6.QtWidgets import QApplication
from ezqt_widgets import DateButton

app = QApplication([])

btn = DateButton(
    date_format="dd/MM/yyyy",
    placeholder="Pick a date",
    show_calendar_icon=True,
)
btn.dateChanged.connect(lambda d: print(d.toString("dd/MM/yyyy")))
btn.setToday()
btn.show()

app.exec()

DateButton

DateButton(parent: WidgetParent = None, date: QDate | str | None = None, date_format: str = 'dd/MM/yyyy', placeholder: str = 'Select a date', show_calendar_icon: bool = True, icon_size: SizeType = QSize(16, 16), minimum_date: QDate | None = None, maximum_date: QDate | None = None, min_width: int | None = None, min_height: int | None = None, *args: Any, **kwargs: Any)

Bases: QToolButton

Button widget for date selection with integrated calendar.

Features
  • Displays current selected date
  • Opens calendar dialog on click
  • Configurable date format
  • Placeholder text when no date selected
  • Calendar icon with customizable appearance
  • Date validation and parsing
  • Optional minimum and maximum date constraints
PARAMETER DESCRIPTION
parent

The parent widget (default: None).

TYPE: WidgetParent DEFAULT: None

date

Initial date (QDate, date string, or None for current date).

TYPE: QDate | str | None DEFAULT: None

date_format

Format for displaying the date (default: "dd/MM/yyyy").

TYPE: str DEFAULT: 'dd/MM/yyyy'

placeholder

Text to display when no date is selected (default: "Select a date").

TYPE: str DEFAULT: 'Select a date'

show_calendar_icon

Whether to show calendar icon (default: True).

TYPE: bool DEFAULT: True

icon_size

Size of the calendar icon (default: QSize(16, 16)).

TYPE: SizeType DEFAULT: QSize(16, 16)

minimum_date

Minimum selectable date (default: None, no constraint).

TYPE: QDate | None DEFAULT: None

maximum_date

Maximum selectable date (default: None, no constraint).

TYPE: QDate | None DEFAULT: None

min_width

Minimum width of the button (default: None, auto-calculated).

TYPE: int | None DEFAULT: None

min_height

Minimum height of the button (default: None, auto-calculated).

TYPE: int | None DEFAULT: None

*args

Additional arguments passed to QToolButton.

TYPE: Any DEFAULT: ()

**kwargs

Additional keyword arguments passed to QToolButton.

TYPE: Any DEFAULT: {}

Signals

dateChanged(QDate): Emitted when the date changes. dateSelected(QDate): Emitted when a date is selected from calendar.

Example

from ezqt_widgets import DateButton btn = DateButton(date_format="dd/MM/yyyy", placeholder="Pick a date") btn.dateChanged.connect(lambda d: print(d.toString("dd/MM/yyyy"))) btn.setToday() btn.show()

Initialize the date button.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def __init__(
    self,
    parent: WidgetParent = None,
    date: QDate | str | None = None,
    date_format: str = "dd/MM/yyyy",
    placeholder: str = "Select a date",
    show_calendar_icon: bool = True,
    icon_size: SizeType = QSize(16, 16),
    minimum_date: QDate | None = None,
    maximum_date: QDate | None = None,
    min_width: int | None = None,
    min_height: int | None = None,
    *args: Any,
    **kwargs: Any,
) -> None:
    """Initialize the date button."""
    super().__init__(parent, *args, **kwargs)
    self.setProperty("type", "DateButton")

    # Initialize properties
    self._date_format: str = date_format
    self._placeholder: str = placeholder
    self._show_calendar_icon: bool = show_calendar_icon
    self._icon_size: QSize = (
        QSize(*icon_size)
        if isinstance(icon_size, (tuple, list))
        else QSize(icon_size)
    )
    self._minimum_date: QDate | None = minimum_date
    self._maximum_date: QDate | None = maximum_date
    self._min_width: int | None = min_width
    self._min_height: int | None = min_height
    self._current_date: QDate = QDate()
    self._calendar_icon: ThemeIcon = _get_calendar_icon()

    # Setup UI components
    self._date_label = QLabel()
    self._icon_label = QLabel()

    # Configure labels
    self._date_label.setAlignment(
        Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
    )
    self._date_label.setStyleSheet("background-color: transparent;")

    # Setup layout
    layout = QHBoxLayout(self)
    layout.setContentsMargins(8, 2, 8, 2)
    layout.setSpacing(8)
    layout.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
    layout.addWidget(self._date_label)
    layout.addStretch()  # Push icon to the right
    layout.addWidget(self._icon_label)

    # Configure size policy
    self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)

    # Set initial values
    if date:
        self.date = date
    else:
        self.date = QDate.currentDate()

    self.show_calendar_icon = show_calendar_icon
    self._update_display()

date property writable

date: QDate

Get or set the selected date.

RETURNS DESCRIPTION
QDate

The current selected date.

date_string property writable

date_string: str

Get or set the date as formatted string.

RETURNS DESCRIPTION
str

The formatted date string.

date_format property writable

date_format: str

Get or set the date format.

RETURNS DESCRIPTION
str

The current date format string.

placeholder property writable

placeholder: str

Get or set the placeholder text.

RETURNS DESCRIPTION
str

The current placeholder text.

show_calendar_icon property writable

show_calendar_icon: bool

Get or set calendar icon visibility.

RETURNS DESCRIPTION
bool

True if calendar icon is visible, False otherwise.

icon_size property writable

icon_size: QSize

Get or set the icon size.

RETURNS DESCRIPTION
QSize

The current icon size.

minimum_date property writable

minimum_date: QDate | None

Get or set the minimum selectable date.

RETURNS DESCRIPTION
QDate | None

The minimum date, or None if no constraint is set.

maximum_date property writable

maximum_date: QDate | None

Get or set the maximum selectable date.

RETURNS DESCRIPTION
QDate | None

The maximum date, or None if no constraint is set.

min_width property writable

min_width: int | None

Get or set the minimum width of the button.

RETURNS DESCRIPTION
int | None

The minimum width, or None if not set.

min_height property writable

min_height: int | None

Get or set the minimum height of the button.

RETURNS DESCRIPTION
int | None

The minimum height, or None if not set.

clearDate

clearDate() -> None

Clear the selected date.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def clearDate(self) -> None:
    """Clear the selected date."""
    self.date = None

setToday

setToday() -> None

Set the date to today.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def setToday(self) -> None:
    """Set the date to today."""
    self.date = QDate.currentDate()

openCalendar

openCalendar() -> None

Open the calendar dialog.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def openCalendar(self) -> None:
    """Open the calendar dialog."""
    dialog = DatePickerDialog(
        self,
        self._current_date,
        min_date=self._minimum_date,
        max_date=self._maximum_date,
    )
    if dialog.exec() == QDialog.DialogCode.Accepted:
        selected_date = dialog.selectedDate()
        if selected_date and selected_date.isValid():
            self.date = selected_date
            self.dateSelected.emit(selected_date)

setTheme

setTheme(theme: str) -> None

Update the calendar icon color for the given theme.

Can be connected directly to a theme-change signal to keep the icon in sync with the application's color scheme.

PARAMETER DESCRIPTION
theme

The new theme ("dark" or "light").

TYPE: str

Source code in src/ezqt_widgets/widgets/button/date_button.py
def setTheme(self, theme: str) -> None:
    """Update the calendar icon color for the given theme.

    Can be connected directly to a theme-change signal to keep
    the icon in sync with the application's color scheme.

    Args:
        theme: The new theme (``"dark"`` or ``"light"``).
    """
    self._calendar_icon.setTheme(theme)
    if self._show_calendar_icon:
        self._icon_label.setPixmap(self._calendar_icon.pixmap(self._icon_size))

mousePressEvent

mousePressEvent(event: QMouseEvent) -> None

Handle mouse press events.

The left-button press opens the calendar dialog directly. The clicked signal is emitted only after the user confirms a date (inside openCalendar), not unconditionally on press.

PARAMETER DESCRIPTION
event

The mouse event.

TYPE: QMouseEvent

Source code in src/ezqt_widgets/widgets/button/date_button.py
def mousePressEvent(self, event: QMouseEvent) -> None:
    """Handle mouse press events.

    The left-button press opens the calendar dialog directly. The
    ``clicked`` signal is emitted only after the user confirms a date
    (inside ``openCalendar``), not unconditionally on press.

    Args:
        event: The mouse event.
    """
    if event.button() == Qt.MouseButton.LeftButton:
        self.openCalendar()
        event.accept()  # absorb — do not forward to QToolButton
    else:
        super().mousePressEvent(event)

sizeHint

sizeHint() -> QSize

Get the recommended size for the button.

RETURNS DESCRIPTION
QSize

The recommended size.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def sizeHint(self) -> QSize:
    """Get the recommended size for the button.

    Returns:
        The recommended size.
    """
    return QSize(150, 30)

minimumSizeHint

minimumSizeHint() -> QSize

Get the minimum size hint for the button.

RETURNS DESCRIPTION
QSize

The minimum size hint.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def minimumSizeHint(self) -> QSize:
    """Get the minimum size hint for the button.

    Returns:
        The minimum size hint.
    """
    base_size = super().minimumSizeHint()

    text_width = self._date_label.fontMetrics().horizontalAdvance(
        self.date_string if self._current_date.isValid() else self._placeholder
    )

    icon_width = self._icon_size.width() if self._show_calendar_icon else 0

    total_width = text_width + icon_width + 16 + 8  # margins + spacing

    min_width = self._min_width if self._min_width is not None else total_width
    min_height = (
        self._min_height
        if self._min_height is not None
        else max(base_size.height(), 30)
    )

    return QSize(max(min_width, total_width), min_height)

refreshStyle

refreshStyle() -> None

Refresh the widget's style.

Useful after dynamic stylesheet changes.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def refreshStyle(self) -> None:
    """Refresh the widget's style.

    Useful after dynamic stylesheet changes.
    """
    self.style().unpolish(self)
    self.style().polish(self)
    self.update()

DatePickerDialog

A QDialog containing a QCalendarWidget for date selection. Used internally by DateButton but also available as a standalone dialog.

Constructor parameters:

Parameter Type Default Description
parent QWidget \| None None Parent widget
current_date QDate \| None None Date pre-selected in the calendar

Methods:

Method Returns Description
selectedDate() QDate \| None Returns the date clicked in the calendar, or None if none was clicked

Example:

from PySide6.QtCore import QDate
from ezqt_widgets import DatePickerDialog

dialog = DatePickerDialog(current_date=QDate.currentDate())
if dialog.exec():
    date = dialog.selectedDate()
    if date:
        print(date.toString("dd/MM/yyyy"))

DatePickerDialog

DatePickerDialog(parent: WidgetParent = None, current_date: QDate | None = None, min_date: QDate | None = None, max_date: QDate | None = None)

Bases: QDialog

Dialog for date selection with calendar widget.

Provides a modal dialog with a calendar widget for selecting dates. The dialog emits accepted signal when a date is selected and confirmed.

PARAMETER DESCRIPTION
parent

The parent widget (default: None).

TYPE: WidgetParent DEFAULT: None

current_date

The current selected date (default: None).

TYPE: QDate | None DEFAULT: None

min_date

The minimum selectable date (default: None).

TYPE: QDate | None DEFAULT: None

max_date

The maximum selectable date (default: None).

TYPE: QDate | None DEFAULT: None

Example

from ezqt_widgets import DatePickerDialog from PySide6.QtCore import QDate dialog = DatePickerDialog(current_date=QDate.currentDate()) if dialog.exec(): ... date = dialog.selected_date() ... print(date.toString("dd/MM/yyyy"))

Initialize the date picker dialog.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def __init__(
    self,
    parent: WidgetParent = None,
    current_date: QDate | None = None,
    min_date: QDate | None = None,
    max_date: QDate | None = None,
) -> None:
    """Initialize the date picker dialog."""
    super().__init__(parent)

    # ///////////////////////////////////////////////////////////////
    # INIT
    # ///////////////////////////////////////////////////////////////

    self._selected_date: QDate | None = current_date
    self._min_date: QDate | None = min_date
    self._max_date: QDate | None = max_date

    # ///////////////////////////////////////////////////////////////
    # SETUP UI
    # ///////////////////////////////////////////////////////////////

    self._setup_ui()

    # Set current date if provided
    if current_date and current_date.isValid():
        self._calendar.setSelectedDate(current_date)

selectedDate

selectedDate() -> QDate | None

Get the selected date.

RETURNS DESCRIPTION
QDate | None

The selected date, or None if no date was selected.

Source code in src/ezqt_widgets/widgets/button/date_button.py
def selectedDate(self) -> QDate | None:
    """Get the selected date.

    Returns:
        The selected date, or None if no date was selected.
    """
    return self._selected_date

IconButton

A QToolButton that displays an icon from any supported source (file path, URL, SVG, QIcon, or QPixmap) with an optional text label.

Signals:

Signal Signature Emitted when
iconChanged (QIcon) The icon is updated
textChanged (str) The text label changes

Constructor parameters:

Parameter Type Default Description
parent QWidget \| None None Parent widget
icon QIcon \| QPixmap \| str \| None None Icon source; supports local paths, URLs, and SVG
text str "" Button label text
icon_size QSize \| tuple[int, int] QSize(20, 20) Icon display size
text_visible bool True Whether the text label is initially visible
spacing int 10 Pixels between the icon and the text
min_width int \| None None Minimum width; auto-calculated if None
min_height int \| None None Minimum height; auto-calculated if None

Properties:

Property Type Description
icon QIcon \| None Gets or sets the button icon
text str Gets or sets the button text
icon_size QSize Gets or sets the icon size
text_visible bool Gets or sets text label visibility
spacing int Gets or sets the icon-to-text spacing in pixels
min_width int \| None Gets or sets minimum width
min_height int \| None Gets or sets minimum height

Methods:

Method Signature Description
clearIcon() () -> None Removes the current icon and emits iconChanged with an empty QIcon
clearText() () -> None Sets the text to an empty string
toggleTextVisibility() () -> None Inverts text_visible
setIconColor() (color: str, opacity: float) -> None Applies a color overlay to the current icon pixmap
refreshStyle() () -> None Re-applies the QSS stylesheet after dynamic changes

URL icons

When icon is an HTTP or HTTPS URL, the fetch is asynchronous. The icon appears after the network response completes; no blocking occurs.

Example:

from PySide6.QtWidgets import QApplication
from ezqt_widgets import IconButton

app = QApplication([])

btn = IconButton(
    icon="https://img.icons8.com/?size=100&id=8329&format=png&color=000000",
    text="Open file",
    icon_size=(24, 24),
    text_visible=True,
)
btn.iconChanged.connect(lambda icon: print("icon updated"))
btn.show()

app.exec()

IconButton

IconButton(parent: WidgetParent = None, icon: IconSourceExtended = None, text: str = '', icon_size: SizeType = QSize(20, 20), text_visible: bool = True, spacing: int = 10, min_width: int | None = None, min_height: int | None = None, *args: Any, **kwargs: Any)

Bases: QToolButton

Enhanced button widget with icon and optional text support.

Features
  • Icon support from various sources (ThemeIcon, QIcon, QPixmap, path, URL, SVG)
  • Optional text display with configurable visibility
  • Customizable icon size and spacing
  • Property-based access to icon and text
  • Signals for icon and text changes
  • Hover and click effects
PARAMETER DESCRIPTION
parent

The parent widget (default: None).

TYPE: WidgetParent DEFAULT: None

icon

The icon to display (ThemeIcon, QIcon, QPixmap, path, resource, URL, or SVG).

TYPE: IconSourceExtended DEFAULT: None

text

The button text (default: "").

TYPE: str DEFAULT: ''

icon_size

Size of the icon (default: QSize(20, 20)).

TYPE: SizeType DEFAULT: QSize(20, 20)

text_visible

Whether the text is initially visible (default: True).

TYPE: bool DEFAULT: True

spacing

Spacing between icon and text in pixels (default: 10).

TYPE: int DEFAULT: 10

min_width

Minimum width of the button (default: None, auto-calculated).

TYPE: int | None DEFAULT: None

min_height

Minimum height of the button (default: None, auto-calculated).

TYPE: int | None DEFAULT: None

*args

Additional arguments passed to QToolButton.

TYPE: Any DEFAULT: ()

**kwargs

Additional keyword arguments passed to QToolButton.

TYPE: Any DEFAULT: {}

Signals

iconChanged(QIcon): Emitted when the icon changes. textChanged(str): Emitted when the text changes. iconLoadFailed(str): Emitted when an icon URL fetch fails, with the URL.

Example

from ezqt_widgets import IconButton btn = IconButton(icon="path/to/icon.png", text="Open", icon_size=(20, 20)) btn.iconChanged.connect(lambda icon: print("icon changed")) btn.text_visible = False btn.show()

Initialize the icon button.

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def __init__(
    self,
    parent: WidgetParent = None,
    icon: IconSourceExtended = None,
    text: str = "",
    icon_size: SizeType = QSize(20, 20),
    text_visible: bool = True,
    spacing: int = 10,
    min_width: int | None = None,
    min_height: int | None = None,
    *args: Any,
    **kwargs: Any,
) -> None:
    """Initialize the icon button."""
    super().__init__(parent, *args, **kwargs)
    self.setProperty("type", "IconButton")

    # Initialize properties
    self._icon_size: QSize = (
        QSize(*icon_size)
        if isinstance(icon_size, (tuple, list))
        else QSize(icon_size)
    )
    self._text_visible: bool = text_visible
    self._spacing: int = spacing
    self._current_icon: QIcon | None = None
    self._min_width: int | None = min_width
    self._min_height: int | None = min_height
    self._pending_icon_url: str | None = None
    self._url_fetcher: UrlFetcher | None = None

    # Setup UI components
    self._icon_label = QLabel()
    self._text_label = QLabel()

    # Configure text label
    self._text_label.setAlignment(
        Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
    )
    self._text_label.setWordWrap(True)
    self._text_label.setStyleSheet("background-color: transparent;")

    # Setup layout
    layout = QHBoxLayout(self)
    layout.setContentsMargins(8, 2, 8, 2)
    layout.setSpacing(spacing)
    layout.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
    layout.addWidget(self._icon_label)
    layout.addWidget(self._text_label)

    # Configure size policy
    self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)

    # Set initial values
    if icon:
        self.icon = icon
    if text:
        self.text = text
    self.text_visible = text_visible

icon property writable

icon: QIcon | None

Get or set the button icon.

RETURNS DESCRIPTION
QIcon | None

The current icon, or None if no icon is set.

text property writable

text: str

Get or set the button text.

RETURNS DESCRIPTION
str

The current button text.

icon_size property writable

icon_size: QSize

Get or set the icon size.

RETURNS DESCRIPTION
QSize

The current icon size.

text_visible property writable

text_visible: bool

Get or set text visibility.

RETURNS DESCRIPTION
bool

True if text is visible, False otherwise.

spacing property writable

spacing: int

Get or set spacing between icon and text.

RETURNS DESCRIPTION
int

The current spacing in pixels.

min_width property writable

min_width: int | None

Get or set the minimum width of the button.

RETURNS DESCRIPTION
int | None

The minimum width, or None if not set.

min_height property writable

min_height: int | None

Get or set the minimum height of the button.

RETURNS DESCRIPTION
int | None

The minimum height, or None if not set.

setTheme

setTheme(theme: str) -> None

Update the icon color for the given theme.

Can be connected directly to a theme-change signal to keep the icon in sync with the application's color scheme.

PARAMETER DESCRIPTION
theme

The new theme ("dark" or "light").

TYPE: str

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def setTheme(self, theme: str) -> None:
    """Update the icon color for the given theme.

    Can be connected directly to a theme-change signal to keep
    the icon in sync with the application's color scheme.

    Args:
        theme: The new theme (``"dark"`` or ``"light"``).
    """
    if isinstance(self._current_icon, ThemeIcon):
        self._current_icon.setTheme(theme)
        self._icon_label.setPixmap(self._current_icon.pixmap(self._icon_size))

clearIcon

clearIcon() -> None

Remove the current icon.

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def clearIcon(self) -> None:
    """Remove the current icon."""
    self._current_icon = None
    self._icon_label.clear()
    self.iconChanged.emit(QIcon())

clearText

clearText() -> None

Clear the button text.

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def clearText(self) -> None:
    """Clear the button text."""
    self.text = ""

toggleTextVisibility

toggleTextVisibility() -> None

Toggle text visibility.

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def toggleTextVisibility(self) -> None:
    """Toggle text visibility."""
    self.text_visible = not self.text_visible

setIconColor

setIconColor(color: str = '#FFFFFF', opacity: float = 0.5) -> None

Apply color and opacity to the current icon.

PARAMETER DESCRIPTION
color

The color to apply (default: "#FFFFFF").

TYPE: str DEFAULT: '#FFFFFF'

opacity

The opacity level (default: 0.5).

TYPE: float DEFAULT: 0.5

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def setIconColor(self, color: str = "#FFFFFF", opacity: float = 0.5) -> None:
    """Apply color and opacity to the current icon.

    Args:
        color: The color to apply (default: "#FFFFFF").
        opacity: The opacity level (default: 0.5).
    """
    if self._current_icon:
        pixmap = self._current_icon.pixmap(self._icon_size)
        colored_pixmap = _colorize_pixmap(pixmap, color, opacity)
        self._icon_label.setPixmap(colored_pixmap)

sizeHint

sizeHint() -> QSize

Get the recommended size for the button.

RETURNS DESCRIPTION
QSize

The recommended size.

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def sizeHint(self) -> QSize:
    """Get the recommended size for the button.

    Returns:
        The recommended size.
    """
    return QSize(100, 40)

minimumSizeHint

minimumSizeHint() -> QSize

Get the minimum size hint for the button.

RETURNS DESCRIPTION
QSize

The minimum size hint.

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def minimumSizeHint(self) -> QSize:
    """Get the minimum size hint for the button.

    Returns:
        The minimum size hint.
    """
    base_size = super().minimumSizeHint()

    icon_width = self._icon_size.width() if self._current_icon else 0

    text_width = 0
    if self._text_visible and self.text:
        text_width = self._text_label.fontMetrics().horizontalAdvance(self.text)

    total_width = icon_width + text_width + self._spacing + 20  # margins

    min_width = self._min_width if self._min_width is not None else total_width
    min_height = (
        self._min_height
        if self._min_height is not None
        else max(base_size.height(), self._icon_size.height() + 8)
    )

    return QSize(max(min_width, total_width), min_height)

refreshStyle

refreshStyle() -> None

Refresh the widget's style.

Useful after dynamic stylesheet changes.

Source code in src/ezqt_widgets/widgets/button/icon_button.py
def refreshStyle(self) -> None:
    """Refresh the widget's style.

    Useful after dynamic stylesheet changes.
    """
    self.style().unpolish(self)
    self.style().polish(self)
    self.update()

LoaderButton

A QToolButton that transitions between a normal state, an animated loading state, a success state, and an error state.

Signals:

Signal Signature Emitted when
loadingStarted () startLoading() is called
loadingFinished () stopLoading(success=True) completes
loadingFailed (str) stopLoading(success=False) completes; the string is the error message

Constructor parameters:

Parameter Type Default Description
parent QWidget \| None None Parent widget
text str "" Label in the normal state
icon QIcon \| QPixmap \| str \| None None Icon in the normal state
loading_text str "Loading..." Label shown during loading
loading_icon QIcon \| QPixmap \| str \| None None Icon during loading; auto-generated spinner if None
success_icon QIcon \| QPixmap \| str \| None None Icon shown on success; auto-generated checkmark if None
error_icon QIcon \| QPixmap \| str \| None None Icon shown on error; auto-generated X if None
animation_speed int 100 Spinner frame interval in milliseconds
auto_reset bool True Whether to return to the normal state after success/error
success_display_time int 1000 Milliseconds to show the success state before auto-reset
error_display_time int 2000 Milliseconds to show the error state before auto-reset
min_width int \| None None Minimum width; auto-calculated if None
min_height int \| None None Minimum height; auto-calculated if None

Properties:

Property Type Description
text str Normal-state label
icon QIcon \| None Normal-state icon
loading_text str Text shown during loading
loading_icon QIcon \| None Icon shown during loading
success_icon QIcon \| None Icon shown on success
error_icon QIcon \| None Icon shown on error
is_loading bool Read-only; True while loading animation is active
animation_speed int Spinner frame interval in milliseconds
auto_reset bool Whether to auto-return to normal state after completion
success_display_time int Milliseconds the success state is displayed
error_display_time int Milliseconds the error state is displayed
min_width int \| None Minimum width
min_height int \| None Minimum height

Methods:

Method Signature Description
startLoading() () -> None Enters the loading state; disables the button and starts the spinner
stopLoading() (success: bool, error_message: str) -> None Exits the loading state; shows success or error UI
refreshStyle() () -> None Re-applies the QSS stylesheet after dynamic changes

Calling stopLoading while not loading

stopLoading() is a no-op if is_loading is False.

Example:

from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QApplication
from ezqt_widgets import LoaderButton

app = QApplication([])

btn = LoaderButton(
    text="Submit",
    loading_text="Sending...",
    auto_reset=True,
    success_display_time=1500,
)
btn.loadingFinished.connect(lambda: print("done"))

def simulate_request():
    btn.startLoading()
    QTimer.singleShot(2000, lambda: btn.stopLoading(success=True))

btn.clicked.connect(simulate_request)
btn.show()

app.exec()

LoaderButton

LoaderButton(parent: WidgetParent = None, text: str = '', icon: IconSourceExtended = None, loading_text: str = 'Loading...', loading_icon: IconSourceExtended = None, success_icon: IconSourceExtended = None, error_icon: IconSourceExtended = None, success_text: str = 'Success!', error_text: str = 'Error', icon_size: QSize | tuple[int, int] = QSize(16, 16), animation_speed: AnimationDuration = 100, auto_reset: bool = True, success_display_time: AnimationDuration = 1000, error_display_time: AnimationDuration = 2000, min_width: int | None = None, min_height: int | None = None, *args: Any, **kwargs: Any)

Bases: QToolButton

Button widget with integrated loading animation.

Features
  • Loading state with animated spinner
  • Success state with checkmark icon
  • Error state with X icon
  • Configurable loading, success, and error text/icons
  • Configurable success and error result texts
  • Smooth transitions between states
  • Disabled state during loading
  • Customizable animation speed
  • Progress indication support (0-100)
  • Auto-reset after completion with configurable display times
  • Configurable spinner icon size
  • Safe timer cleanup on widget destruction
PARAMETER DESCRIPTION
parent

The parent widget (default: None).

TYPE: WidgetParent DEFAULT: None

text

Button text (default: "").

TYPE: str DEFAULT: ''

icon

Button icon (ThemeIcon, QIcon, QPixmap, or path, default: None).

TYPE: IconSourceExtended DEFAULT: None

loading_text

Text to display during loading (default: "Loading...").

TYPE: str DEFAULT: 'Loading...'

loading_icon

Icon to display during loading (ThemeIcon, QIcon, QPixmap, or path, default: None, auto-generated).

TYPE: IconSourceExtended DEFAULT: None

success_icon

Icon to display on success (ThemeIcon, QIcon, QPixmap, or path, default: None, auto-generated checkmark).

TYPE: IconSourceExtended DEFAULT: None

error_icon

Icon to display on error (ThemeIcon, QIcon, QPixmap, or path, default: None, auto-generated X mark).

TYPE: IconSourceExtended DEFAULT: None

success_text

Text to display when loading succeeds (default: "Success!").

TYPE: str DEFAULT: 'Success!'

error_text

Text to display when loading fails (default: "Error").

TYPE: str DEFAULT: 'Error'

icon_size

Size of spinner and state icons (default: QSize(16, 16)).

TYPE: QSize | tuple[int, int] DEFAULT: QSize(16, 16)

animation_speed

Animation speed in milliseconds (default: 100).

TYPE: AnimationDuration DEFAULT: 100

auto_reset

Whether to auto-reset after loading (default: True).

TYPE: bool DEFAULT: True

success_display_time

Time to display success state in milliseconds (default: 1000).

TYPE: AnimationDuration DEFAULT: 1000

error_display_time

Time to display error state in milliseconds (default: 2000).

TYPE: AnimationDuration DEFAULT: 2000

min_width

Minimum width of the button (default: None, auto-calculated).

TYPE: int | None DEFAULT: None

min_height

Minimum height of the button (default: None, auto-calculated).

TYPE: int | None DEFAULT: None

*args

Additional arguments passed to QToolButton.

TYPE: Any DEFAULT: ()

**kwargs

Additional keyword arguments passed to QToolButton.

TYPE: Any DEFAULT: {}

Signals

loadingStarted(): Emitted when loading starts. loadingFinished(): Emitted when loading finishes successfully. loadingFailed(str): Emitted when loading fails with error message. progressChanged(int): Emitted when progress value changes (0-100).

Example

from ezqt_widgets import LoaderButton btn = LoaderButton(text="Submit", loading_text="Sending...") btn.loadingFinished.connect(lambda: print("done")) btn.startLoading()

After completion:

btn.stopLoading(success=True) btn.show()

Initialize the loader button.

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def __init__(
    self,
    parent: WidgetParent = None,
    text: str = "",
    icon: IconSourceExtended = None,
    loading_text: str = "Loading...",
    loading_icon: IconSourceExtended = None,
    success_icon: IconSourceExtended = None,
    error_icon: IconSourceExtended = None,
    success_text: str = "Success!",
    error_text: str = "Error",
    icon_size: QSize | tuple[int, int] = QSize(16, 16),
    animation_speed: AnimationDuration = 100,
    auto_reset: bool = True,
    success_display_time: AnimationDuration = 1000,
    error_display_time: AnimationDuration = 2000,
    min_width: int | None = None,
    min_height: int | None = None,
    *args: Any,
    **kwargs: Any,
) -> None:
    """Initialize the loader button."""
    super().__init__(parent, *args, **kwargs)
    self.setProperty("type", "LoaderButton")

    # Initialize properties
    self._original_text = text
    self._original_icon: QIcon | None = None
    self._loading_text = loading_text
    self._loading_icon: QIcon | None = None
    self._success_icon: QIcon | None = None
    self._error_icon: QIcon | None = None
    self._success_text = success_text
    self._error_text = error_text
    self._icon_size: QSize = (
        QSize(*icon_size) if isinstance(icon_size, tuple) else QSize(icon_size)
    )
    self._is_loading = False
    self._progress: int = 0
    self._animation_speed = animation_speed
    self._auto_reset = auto_reset
    self._success_display_time = success_display_time
    self._error_display_time = error_display_time
    self._min_width = min_width
    self._min_height = min_height
    self._animation_group = None
    self._spinner_animation = None
    self._animation_timer: QTimer | None = None

    # Setup UI components
    self._text_label = QLabel()
    self._icon_label = QLabel()

    # Configure labels
    self._text_label.setAlignment(
        Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter
    )
    self._text_label.setStyleSheet("background-color: transparent;")

    # Setup layout
    layout = QHBoxLayout(self)
    layout.setContentsMargins(8, 2, 8, 2)
    layout.setSpacing(8)
    layout.setAlignment(
        Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignVCenter
    )
    layout.addWidget(self._icon_label)
    layout.addWidget(self._text_label)

    # Configure size policy
    self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)

    # Set initial values
    if icon:
        self.icon = icon
    if text:
        self.text = text

    # Setup icons using the resolved _icon_size
    _sz = self._icon_size.width()
    if loading_icon:
        self.loading_icon = loading_icon
    else:
        self._loading_icon = _create_loading_icon(_sz, "#0078d4")

    if success_icon:
        self.success_icon = success_icon
    else:
        self._success_icon = _create_success_icon(_sz, "#28a745")

    if error_icon:
        self.error_icon = error_icon
    else:
        self._error_icon = _create_error_icon(_sz, "#dc3545")

    # Setup animations
    self._setup_animations()

    # Connect destroyed signal to clean up the timer safely (fix #18)
    self.destroyed.connect(self._cleanup_timer)

    # Initial display
    self._update_display()

text property writable

text: str

Get or set the button text.

RETURNS DESCRIPTION
str

The current button text.

icon property writable

icon: QIcon | None

Get or set the button icon.

RETURNS DESCRIPTION
QIcon | None

The current button icon, or None if no icon is set.

loading_text property writable

loading_text: str

Get or set the loading text.

RETURNS DESCRIPTION
str

The current loading text.

loading_icon property writable

loading_icon: QIcon | None

Get or set the loading icon.

RETURNS DESCRIPTION
QIcon | None

The current loading icon, or None if not set.

success_icon property writable

success_icon: QIcon | None

Get or set the success icon.

RETURNS DESCRIPTION
QIcon | None

The current success icon, or None if not set.

error_icon property writable

error_icon: QIcon | None

Get or set the error icon.

RETURNS DESCRIPTION
QIcon | None

The current error icon, or None if not set.

success_text property writable

success_text: str

Get or set the text displayed on success.

RETURNS DESCRIPTION
str

The current success text.

error_text property writable

error_text: str

Get or set the base text displayed on error.

RETURNS DESCRIPTION
str

The current error text.

icon_size property writable

icon_size: QSize

Get or set the spinner and state icon size.

RETURNS DESCRIPTION
QSize

The current icon size.

progress property writable

progress: int

Get or set the current progress value (0-100).

When set during loading, the progress percentage is shown in the text label instead of the generic loading text. The spinner is kept visible. Setting this property outside of loading state is silently ignored.

RETURNS DESCRIPTION
int

The current progress value.

success_display_time property writable

success_display_time: AnimationDuration

Get or set the success display time.

RETURNS DESCRIPTION
AnimationDuration

The success display time in milliseconds.

error_display_time property writable

error_display_time: AnimationDuration

Get or set the error display time.

RETURNS DESCRIPTION
AnimationDuration

The error display time in milliseconds.

is_loading property

is_loading: bool

Get the current loading state.

RETURNS DESCRIPTION
bool

True if loading, False otherwise.

animation_speed property writable

animation_speed: AnimationDuration

Get or set the animation speed.

RETURNS DESCRIPTION
AnimationDuration

The animation speed in milliseconds.

auto_reset property writable

auto_reset: bool

Get or set auto-reset behavior.

RETURNS DESCRIPTION
bool

True if auto-reset is enabled, False otherwise.

min_width property writable

min_width: int | None

Get or set the minimum width of the button.

RETURNS DESCRIPTION
int | None

The minimum width, or None if not set.

min_height property writable

min_height: int | None

Get or set the minimum height of the button.

RETURNS DESCRIPTION
int | None

The minimum height, or None if not set.

startLoading

startLoading() -> None

Start the loading animation.

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def startLoading(self) -> None:
    """Start the loading animation."""
    if self._is_loading:
        return

    self._is_loading = True
    self._progress = 0
    self.setEnabled(False)
    self._update_display()

    # Start spinner animation using timer
    self._rotation_angle = 0
    self._animation_timer = QTimer()
    self._animation_timer.timeout.connect(self._rotate_spinner)
    self._animation_timer.start(self._animation_speed // 10)

    self.loadingStarted.emit()

stopLoading

stopLoading(success: bool = True, error_message: str = '') -> None

Stop the loading animation.

PARAMETER DESCRIPTION
success

Whether the operation succeeded (default: True).

TYPE: bool DEFAULT: True

error_message

Error message if operation failed (default: "").

TYPE: str DEFAULT: ''

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def stopLoading(self, success: bool = True, error_message: str = "") -> None:
    """Stop the loading animation.

    Args:
        success: Whether the operation succeeded (default: True).
        error_message: Error message if operation failed (default: "").
    """
    if not self._is_loading:
        return

    self._is_loading = False

    # Stop spinner animation
    if self._animation_timer is not None:
        self._animation_timer.stop()
        self._animation_timer.deleteLater()
        self._animation_timer = None

    # Show result state
    if success:
        self._show_success_state()
    else:
        self._show_error_state(error_message)

    # Enable button
    self.setEnabled(True)

    if success:
        self.loadingFinished.emit()
    else:
        self.loadingFailed.emit(error_message)

    # Auto-reset if enabled
    if self._auto_reset:
        display_time = (
            self._success_display_time if success else self._error_display_time
        )
        QTimer.singleShot(display_time, self._reset_to_original)

resetLoading

resetLoading() -> None

Reset the button to its original state.

Can be called manually when auto_reset is False.

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def resetLoading(self) -> None:
    """Reset the button to its original state.

    Can be called manually when auto_reset is False.
    """
    self._is_loading = False
    self._reset_to_original()

setTheme

setTheme(theme: str) -> None

Update all icons' color for the given theme.

Can be connected directly to a theme-change signal to keep icons in sync with the application's color scheme.

PARAMETER DESCRIPTION
theme

The new theme ("dark" or "light").

TYPE: str

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def setTheme(self, theme: str) -> None:
    """Update all icons' color for the given theme.

    Can be connected directly to a theme-change signal to keep
    icons in sync with the application's color scheme.

    Args:
        theme: The new theme (``"dark"`` or ``"light"``).
    """
    for icon in (
        self._original_icon,
        self._loading_icon,
        self._success_icon,
        self._error_icon,
    ):
        if isinstance(icon, ThemeIcon):
            icon.setTheme(theme)
    self._update_display()

mousePressEvent

mousePressEvent(event: QMouseEvent) -> None

Handle mouse press events.

PARAMETER DESCRIPTION
event

The mouse event.

TYPE: QMouseEvent

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def mousePressEvent(self, event: QMouseEvent) -> None:
    """Handle mouse press events.

    Args:
        event: The mouse event.
    """
    if not self._is_loading and event.button() == Qt.MouseButton.LeftButton:
        super().mousePressEvent(event)

sizeHint

sizeHint() -> QSize

Get the recommended size for the button.

RETURNS DESCRIPTION
QSize

The recommended size.

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def sizeHint(self) -> QSize:
    """Get the recommended size for the button.

    Returns:
        The recommended size.
    """
    return QSize(120, 30)

minimumSizeHint

minimumSizeHint() -> QSize

Get the minimum size hint for the button.

RETURNS DESCRIPTION
QSize

The minimum size hint.

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def minimumSizeHint(self) -> QSize:
    """Get the minimum size hint for the button.

    Returns:
        The minimum size hint.
    """
    base_size = super().minimumSizeHint()

    text_width = self._text_label.fontMetrics().horizontalAdvance(
        self._loading_text if self._is_loading else self._original_text
    )

    icon_width = (
        self._icon_size.width()
        if (self._loading_icon or self._original_icon)
        else 0
    )

    total_width = text_width + icon_width + 16 + 8  # margins + spacing

    min_width = self._min_width if self._min_width is not None else total_width
    min_height = (
        self._min_height
        if self._min_height is not None
        else max(base_size.height(), 30)
    )

    return QSize(max(min_width, total_width), min_height)

refreshStyle

refreshStyle() -> None

Refresh the widget's style.

Useful after dynamic stylesheet changes.

Source code in src/ezqt_widgets/widgets/button/loader_button.py
def refreshStyle(self) -> None:
    """Refresh the widget's style.

    Useful after dynamic stylesheet changes.
    """
    self.style().unpolish(self)
    self.style().polish(self)
    self.update()