A fluent design widgets library based on PyQt5 providing modern Windows 11-style UI components
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advanced layout managers, smooth scrolling, animated containers, and transition effects for fluid user experiences. These components provide modern animation patterns and flexible layout options.
Enhanced layout managers that provide flexible positioning and automatic resizing with fluent design principles.
class FlowLayout(QLayout):
def __init__(self, parent=None, needAni: bool = False, isTight: bool = False): ...
def addItem(self, item: QLayoutItem): ...
def addWidget(self, widget: QWidget): ...
def setAnimation(self, needAni: bool): ...
def setTightMode(self, isTight: bool): ...
def setSpacing(self, spacing: int): ...
def setContentsMargins(self, left: int, top: int, right: int, bottom: int): ...
class ExpandLayout(QVBoxLayout):
def __init__(self, parent=None): ...
def addWidget(self, widget: QWidget): ...
def setExpandingWidget(self, widget: QWidget): ...
class VBoxLayout(QVBoxLayout):
def __init__(self, parent=None): ...
def addWidget(self, widget: QWidget, stretch: int = 0, alignment: Qt.Alignment = Qt.Alignment()): ...Usage Example:
from qfluentwidgets import FlowLayout, ExpandLayout, VBoxLayout
# Flow layout with animation
flow_layout = FlowLayout(self, needAni=True, isTight=False)
flow_layout.setSpacing(10)
# Add widgets that flow horizontally then wrap
for i in range(10):
button = PushButton(f"Button {i+1}", self)
flow_layout.addWidget(button)
container = QWidget()
container.setLayout(flow_layout)
# Expanding layout for resizable content
expand_layout = ExpandLayout(self)
header = TitleLabel("Header Section")
content = QTextEdit()
footer = BodyLabel("Footer Section")
expand_layout.addWidget(header)
expand_layout.addWidget(content) # This will expand
expand_layout.addWidget(footer)
expand_layout.setExpandingWidget(content)
# Enhanced VBox layout
vbox = VBoxLayout(self)
vbox.addWidget(TitleLabel("Title"), 0, Qt.AlignCenter)
vbox.addWidget(content_widget, 1) # Stretch factor 1
vbox.addWidget(button_widget, 0, Qt.AlignRight)Enhanced scrolling components with smooth animations and fluent design integration.
class ScrollArea(QScrollArea):
def __init__(self, parent=None): ...
def setScrollAnimation(self, enabled: bool): ...
def setBorderVisible(self, visible: bool): ...
def setViewportMargins(self, left: int, top: int, right: int, bottom: int): ...
class SmoothScrollArea(ScrollArea):
def __init__(self, parent=None): ...
def setSmoothMode(self, mode: SmoothMode): ...
def smoothMode(self) -> SmoothMode: ...
def setScrollSpeed(self, speed: float): ...
class SingleDirectionScrollArea(ScrollArea):
def __init__(self, orient: Qt.Orientation = Qt.Vertical, parent=None): ...
def setOrientation(self, orient: Qt.Orientation): ...
class SmoothScrollBar(QScrollBar):
def __init__(self, parent=None): ...
def setSmoothMode(self, mode: SmoothMode): ...
def setScrollSpeed(self, speed: float): ...Usage Example:
from qfluentwidgets import SmoothScrollArea, SingleDirectionScrollArea, SmoothMode
# Smooth scrolling area
scroll_area = SmoothScrollArea(self)
scroll_area.setSmoothMode(SmoothMode.COSINE)
scroll_area.setScrollSpeed(1.2)
scroll_area.setBorderVisible(False)
# Create scrollable content
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
for i in range(50):
item = CardWidget()
item.setFixedHeight(80)
content_layout.addWidget(item)
scroll_area.setWidget(content_widget)
scroll_area.setWidgetResizable(True)
# Single direction scrolling
horizontal_scroll = SingleDirectionScrollArea(Qt.Horizontal, self)
horizontal_scroll.setFixedHeight(120)
# Horizontal content
h_content = QWidget()
h_layout = QHBoxLayout(h_content)
for i in range(20):
card = CardWidget()
card.setFixedSize(100, 80)
h_layout.addWidget(card)
horizontal_scroll.setWidget(h_content)Container widgets with smooth transitions between pages and content areas.
class PopUpAniStackedWidget(QStackedWidget):
def __init__(self, parent=None): ...
def addWidget(self, widget: QWidget) -> int: ...
def setCurrentIndex(self, index: int): ...
def setCurrentWidget(self, widget: QWidget): ...
def setPopUpAni(self, isPopUp: bool): ...
def setAniDirection(self, direction: PopUpAniDirection): ...
class OpacityAniStackedWidget(QStackedWidget):
def __init__(self, parent=None): ...
def addWidget(self, widget: QWidget) -> int: ...
def setCurrentIndex(self, index: int): ...
def setDuration(self, duration: int): ...
class PopUpAniDirection(Enum):
BOTTOM_TO_TOP = 0
TOP_TO_BOTTOM = 1
LEFT_TO_RIGHT = 2
RIGHT_TO_LEFT = 3Usage Example:
from qfluentwidgets import PopUpAniStackedWidget, OpacityAniStackedWidget, PopUpAniDirection
# Pop-up animation stacked widget
popup_stack = PopUpAniStackedWidget(self)
popup_stack.setAniDirection(PopUpAniDirection.BOTTOM_TO_TOP)
# Add pages
home_page = QWidget()
settings_page = QWidget()
about_page = QWidget()
popup_stack.addWidget(home_page)
popup_stack.addWidget(settings_page)
popup_stack.addWidget(about_page)
# Smooth transitions between pages
def switch_to_settings():
popup_stack.setCurrentWidget(settings_page)
def switch_to_home():
popup_stack.setCurrentWidget(home_page)
# Opacity animation stacked widget
fade_stack = OpacityAniStackedWidget(self)
fade_stack.setDuration(300) # 300ms fade duration
# Add content with fade transitions
for i in range(3):
page = QWidget()
label = TitleLabel(f"Page {i+1}")
layout = QVBoxLayout(page)
layout.addWidget(label)
fade_stack.addWidget(page)
# Navigate with fade effects
fade_stack.setCurrentIndex(1)Visual progress indicators with smooth animations and modern styling.
class ProgressBar(QProgressBar):
def __init__(self, parent=None): ...
def setRange(self, min: int, max: int): ...
def setValue(self, value: int): ...
def setCustomBarColor(self, color: QColor): ...
class IndeterminateProgressBar(ProgressBar):
def __init__(self, parent=None): ...
def start(self): ...
def stop(self): ...
def pause(self): ...
def resume(self): ...
def setSpeed(self, speed: float): ...
class ProgressRing(QWidget):
def __init__(self, parent=None): ...
def setRange(self, min: int, max: int): ...
def setValue(self, value: int): ...
def setStrokeWidth(self, width: int): ...
def setCustomRingColor(self, color: QColor): ...
class IndeterminateProgressRing(ProgressRing):
def __init__(self, parent=None): ...
def start(self): ...
def stop(self): ...
def setSpeed(self, speed: float): ...Usage Example:
from qfluentwidgets import ProgressBar, IndeterminateProgressBar, ProgressRing
from PyQt5.QtCore import QTimer
# Standard progress bar
progress = ProgressBar(self)
progress.setRange(0, 100)
progress.setValue(0)
progress.setFixedSize(300, 6)
# Simulate progress
timer = QTimer()
current_value = 0
def update_progress():
global current_value
current_value += 1
progress.setValue(current_value)
if current_value >= 100:
timer.stop()
timer.timeout.connect(update_progress)
timer.start(50) # Update every 50ms
# Indeterminate progress for unknown duration
loading_bar = IndeterminateProgressBar(self)
loading_bar.setFixedSize(200, 4)
loading_bar.start()
# Progress ring
ring = ProgressRing(self)
ring.setRange(0, 100)
ring.setValue(75)
ring.setStrokeWidth(6)
ring.setFixedSize(50, 50)
# Spinning progress ring
spinner = IndeterminateProgressRing(self)
spinner.setFixedSize(40, 40)
spinner.setStrokeWidth(4)
spinner.start()class SmoothMode(Enum):
NO_SMOOTH = 0
CONSTANT = 1
LINEAR = 2
QUADRATIC = 3
COSINE = 4
class SmoothScroll:
def __init__(self, widget: QAbstractScrollArea, orient: Qt.Orientation = Qt.Vertical): ...
def setSmoothMode(self, mode: SmoothMode): ...
def setScrollSpeed(self, speed: float): ...
def smoothMode(self) -> SmoothMode: ...Usage Example:
from qfluentwidgets import SmoothScroll, SmoothMode
# Apply smooth scrolling to any scroll area
list_widget = QListWidget(self)
smooth_scroll = SmoothScroll(list_widget, Qt.Vertical)
smooth_scroll.setSmoothMode(SmoothMode.COSINE)
smooth_scroll.setScrollSpeed(1.0)
# Different smooth modes:
# - NO_SMOOTH: Instant scrolling
# - CONSTANT: Constant speed
# - LINEAR: Linear acceleration
# - QUADRATIC: Quadratic easing
# - COSINE: Smooth cosine curve (recommended)
# Apply to table widget
table = QTableWidget(self)
table_smooth = SmoothScroll(table)
table_smooth.setSmoothMode(SmoothMode.LINEAR)from PyQt5.QtCore import QPropertyAnimation, QEasingCurve, QParallelAnimationGroup
class AnimatedWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupAnimations()
def setupAnimations(self):
# Fade animation
self.fade_animation = QPropertyAnimation(self, b"windowOpacity")
self.fade_animation.setDuration(300)
self.fade_animation.setEasingCurve(QEasingCurve.OutCubic)
# Scale animation
self.scale_animation = QPropertyAnimation(self, b"geometry")
self.scale_animation.setDuration(250)
self.scale_animation.setEasingCurve(QEasingCurve.OutBack)
# Parallel animation group
self.animation_group = QParallelAnimationGroup()
self.animation_group.addAnimation(self.fade_animation)
self.animation_group.addAnimation(self.scale_animation)
def show_animated(self):
# Fade in
self.fade_animation.setStartValue(0.0)
self.fade_animation.setEndValue(1.0)
# Scale from smaller
start_rect = self.geometry()
start_rect.setSize(start_rect.size() * 0.8)
end_rect = self.geometry()
self.scale_animation.setStartValue(start_rect)
self.scale_animation.setEndValue(end_rect)
self.show()
self.animation_group.start()class ResponsiveLayout:
def __init__(self, widget: QWidget):
self.widget = widget
self.breakpoints = {
'mobile': 480,
'tablet': 768,
'desktop': 1024,
'large': 1440
}
def get_current_breakpoint(self):
width = self.widget.width()
if width < self.breakpoints['mobile']:
return 'mobile'
elif width < self.breakpoints['tablet']:
return 'tablet'
elif width < self.breakpoints['desktop']:
return 'desktop'
else:
return 'large'
def apply_responsive_layout(self):
breakpoint = self.get_current_breakpoint()
if breakpoint == 'mobile':
self.apply_mobile_layout()
elif breakpoint == 'tablet':
self.apply_tablet_layout()
else:
self.apply_desktop_layout()
# Usage
responsive = ResponsiveLayout(self)
self.resizeEvent = lambda event: responsive.apply_responsive_layout()class DynamicLayoutManager:
def __init__(self, container: QWidget):
self.container = container
self.widgets = []
self.current_layout = None
def add_widget(self, widget: QWidget):
self.widgets.append(widget)
self.rebuild_layout()
def remove_widget(self, widget: QWidget):
if widget in self.widgets:
self.widgets.remove(widget)
widget.setParent(None)
self.rebuild_layout()
def rebuild_layout(self):
# Clean up old layout
if self.current_layout:
while self.current_layout.count():
item = self.current_layout.takeAt(0)
if item.widget():
item.widget().setParent(None)
# Create new layout based on widget count
if len(self.widgets) <= 3:
self.current_layout = QHBoxLayout()
else:
self.current_layout = FlowLayout()
# Add widgets to new layout
for widget in self.widgets:
self.current_layout.addWidget(widget)
self.container.setLayout(self.current_layout)# 1. Use setUpdatesEnabled to batch layout changes
widget.setUpdatesEnabled(False)
for i in range(100):
layout.addWidget(create_widget())
widget.setUpdatesEnabled(True)
# 2. Pre-calculate sizes when possible
def optimize_flow_layout():
flow_layout.setTightMode(True) # Reduce spacing calculations
# Set fixed sizes when content is known
for widget in widgets:
if widget.hasFixedSize():
widget.setFixedSize(widget.sizeHint())
# 3. Use appropriate smooth modes
if is_low_end_device():
smooth_scroll.setSmoothMode(SmoothMode.LINEAR) # Faster
else:
smooth_scroll.setSmoothMode(SmoothMode.COSINE) # Smoother# Optimize animations for performance
class OptimizedAnimation:
def __init__(self, target: QWidget):
self.target = target
self.animation = QPropertyAnimation(target, b"geometry")
# Performance optimizations
self.animation.setDuration(200) # Shorter duration
self.animation.setEasingCurve(QEasingCurve.OutCubic) # Efficient curve
# Reduce updates during animation
self.animation.started.connect(lambda: target.setUpdatesEnabled(True))
self.animation.finished.connect(lambda: target.setUpdatesEnabled(True))
def animate_to_position(self, end_rect: QRect):
self.animation.setStartValue(self.target.geometry())
self.animation.setEndValue(end_rect)
self.animation.start()Install with Tessl CLI
npx tessl i tessl/pypi-pyqt-fluent-widgets