CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyqt-fluent-widgets

A fluent design widgets library based on PyQt5 providing modern Windows 11-style UI components

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

layout-animation.mddocs/

Layout and Animation

Advanced layout managers, smooth scrolling, animated containers, and transition effects for fluid user experiences. These components provide modern animation patterns and flexible layout options.

Capabilities

Advanced Layout Managers

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)

Scroll Areas

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)

Animated Stacked Widgets

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 = 3

Usage 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)

Progress Indicators

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()

Animation System

Smooth Scrolling Configuration

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)

Custom Animations

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()

Layout Utilities

Responsive Layout Helpers

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()

Dynamic Layout Management

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)

Performance Optimization

Layout Performance Tips

# 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

Animation Performance

# 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()

Best Practices

Layout Guidelines

  1. Use FlowLayout for responsive, wrapping content
  2. Use SmoothScrollArea for better user experience
  3. Limit animation complexity on lower-end devices
  4. Batch layout updates when adding multiple widgets
  5. Test with different window sizes for responsive behavior

Animation Guidelines

  1. Keep animations under 300ms for responsiveness
  2. Use appropriate easing curves (OutCubic, OutQuart recommended)
  3. Avoid simultaneous complex animations
  4. Provide animation disable options for accessibility
  5. Test on various hardware for performance

Install with Tessl CLI

npx tessl i tessl/pypi-pyqt-fluent-widgets

docs

buttons.md

dialog-notification.md

display-widgets.md

index.md

input-controls.md

layout-animation.md

list-view-widgets.md

material-effects.md

menu-command.md

multimedia.md

settings-config.md

theme-styling.md

window-navigation.md

tile.json