Skip to content

Commit

Permalink
Merge pull request #5 from niklashenning/develop
Browse files Browse the repository at this point in the history
v1.1.0
  • Loading branch information
niklashenning authored Apr 3, 2024
2 parents 2e9d074 + 36df6f2 commit d733c49
Show file tree
Hide file tree
Showing 24 changed files with 1,747 additions and 292 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit = tests/*
97 changes: 60 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
# pyqt-modern-slider
A clean and customizable PyQt5 slider widget for integer and float values
# PyQt Advanced Slider

![pyqt-modern-slider](https://github.com/niklashenning/pyqt-modern-slider/assets/58544929/b741e06c-1efa-44c8-8c7e-e35ca1c0f348)
[![PyPI](https://img.shields.io/badge/pypi-v1.1.0-blue)](https://pypi.org/project/pyqt-advanced-slider/)
[![Python](https://img.shields.io/badge/python-3.7+-blue)](https://github.com/niklashenning/pyqt-advanced-slider)
[![Build](https://img.shields.io/badge/build-passing-neon)](https://github.com/niklashenning/pyqt-advanced-slider)
[![Coverage](https://img.shields.io/badge/coverage-100%25-green)](https://github.com/niklashenning/pyqt-advanced-slider)
[![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/niklashenning/pyqt-advanced-slider/blob/master/LICENSE)

A clean and customizable int and float slider widget for PyQt and PySide

![pyqt-advanced-slider](https://github.com/niklashenning/pyqt-modern-slider/assets/58544929/b741e06c-1efa-44c8-8c7e-e35ca1c0f348)

## Features
* Supports `int` and `float`
* Supports dynamic switching between types
* Customizable decimal places
* Customizable prefix and suffix
* Customizable decimal separator and thousands separator
* Supports keyboard and mouse wheel input
* Supports keyboard and mouse wheel inputs
* Modern and customizable UI
* Works with `PyQt5`, `PyQt6`, `PySide2`, and `PySide6`

## Installation
Download the **modern_slider.py** file from the **src** folder and add it to your project
```
pip install pyqt-advanced-slider
```

## Usage
Import the `Slider` class and add it to your window like any other PyQt Widget:
```python
from PyQt5.QtWidgets import QMainWindow
from modern_slider import Slider
from PyQt6.QtWidgets import QMainWindow
from pyqt_advanced_slider import Slider


class Window(QMainWindow):
def __init__(self):
super().__init__(parent=None)

demo_slider = Slider(self) # Add slider
demo_slider.setRange(-50, 100) # Set min and max
demo_slider.setValue(25) # Set value
demo_slider.valueChanged.connect(self.slider_value_changed) # Connect change event
slider = Slider(self) # Add slider
slider.setRange(-50, 100) # Set min and max
slider.setValue(25) # Set value
slider.valueChanged.connect(self.slider_value_changed) # Connect change event

# Called every time the slider value changes
def slider_value_changed(self, value):
Expand All @@ -38,14 +48,14 @@ class Window(QMainWindow):

You can also set the minimum and maximum of the slider individually with the `setMinimum()` and `setMaximum()` methods:
```python
demo_slider.setMinimum(-50)
demo_slider.setMaximum(100)
slider.setMinimum(-50) # Default: 0
slider.setMaximum(100) # Default: 10
```

The `getValue()` method returns the current value while the `getValueFormatted()` method returns the value as the formatted string that is being displayed on the slider:
```python
value = demo_slider.getValue() # 2500.0
value_formatted = demo_slider.getValueFormatted() # '~2,500.00 €'
value = slider.getValue() # 2500.0
value_formatted = slider.getValueFormatted() # e.g. '~2,500.00 €'
```

> **NOTE:** <br>When getting the value of the slider using the `getValue()` method or by subscribing to the `valueChanged` event, it will either be an `int` or a `float`, depending on whether float values are enabled or disabled for the slider.
Expand All @@ -54,57 +64,58 @@ value_formatted = demo_slider.getValueFormatted() # '~2,500.00 €'

* **Making the slider a float slider:**
```python
demo_slider.setFloat(True) # Default: false
demo_slider.setDecimals(2) # Default: 1
slider.setFloat(True) # Default: False
slider.setDecimals(2) # Default: 1
```

* **Adding a prefix and a suffix:**
```python
demo_slider.setPrefix('~') # Default: empty string
demo_slider.setSuffix('') # Default: empty string
slider.setPrefix('~') # Default: empty string
slider.setSuffix('') # Default: empty string
```

> **EXAMPLE:** <br>The value `100` formatted with `~` as the prefix and `°` as the suffix would be shown as `~100°`

* **Customizing the formatting of the value shown on the slider:**
```python
demo_slider.setDecimalSeparator(',') # Default: '.'
demo_slider.setThousandsSeparator('.') # Default: empty string
slider.setDecimalSeparator(',') # Default: '.'
slider.setThousandsSeparator('.') # Default: empty string
```
> **EXAMPLE:** <br>The value `1052.17` formatted with `,` as the decimal separator and `.` as the thousands separator would be `1.052,17`
* **Changing how much the value is incremented or decremented on keyboard and mouse inputs:**
```python
# If left default, the single step and page step will be 1% and 5% of the slider's value range
demo_slider.setSingleStep(10) # Default: 0
demo_slider.setPageStep(25) # Default: 0
slider.setSingleStep(10) # Default: 0
slider.setPageStep(25) # Default: 0
```

> **SINGLE STEP:** Increment or decrement of the value when the slider is scrolled or the arrow keys are pressed<br>
> **PAGE STEP:** Increment or decrement of the value when the PageUp or PageDown key is pressed
* **Hiding the value on the slider completely:**
```python
demo_slider.showValue(False) # Default: True
slider.showValue(False) # Default: True
```

* **Disabling keyboard and mouse wheel input:**
* **Enabling or disabling keyboard and mouse wheel input:**
```python
demo_slider.setKeyboardInputEnabled(False) # Default: True
demo_slider.setMouseWheelInputEnabled(False) # Default: True
slider.setKeyboardInputEnabled(False) # Default: True
slider.setMouseWheelInputEnabled(False) # Default: True
```

* **Setting the text color, background color, accent color, border color, and border radius:**
* **Setting custom colors:**
```python
# Setting custom colors
demo_slider.setTextColor(QColor('#000000'))
demo_slider.setBackgroundColor(QColor('#FFFFFF'))
demo_slider.setAccentColor(QColor.fromRgb(100, 100, 100))
demo_slider.setBorderColor(QColor.fromRgb(0, 0, 0))
slider.setTextColor(QColor('#0F0F0F')) # Default: #000000
slider.setBackgroundColor(QColor('#FFFFFF')) # Default: #D6D6D6
slider.setAccentColor(QColor.fromRgb(100, 100, 100)) # Default: #0078D7
slider.setBorderColor(QColor.fromRgb(0, 0, 0)) # Default: #D1CFD3
```

# Making corners rounded
demo_slider.setBorderRadius(3) # Default: 0
* **Making the corners rounded:**
```python
slider.setBorderRadius(3) # Default: 0
```

* **Setting a custom font:**
Expand All @@ -116,10 +127,22 @@ font.setPointSize(10)
font.setBold(True)

# Set font
demo_slider.setFont(font)
slider.setFont(font)
```

More in-depth examples can be found in the [examples](examples) folder
Examples for PyQt5, PyQt6, and PySide6 can be found in the [examples](examples) folder.

## Tests
Installing the required test dependencies [pytest](https://github.com/pytest-dev/pytest), [pytest-qt](https://github.com/pytest-dev/pytest-qt), [coveragepy](https://github.com/nedbat/coveragepy), and [PyQt6](https://pypi.org/project/PyQt6):
```
pip install pytest pytest-qt coverage PyQt6
```

To run the tests with coverage, clone this repository, go into the main directory and run:
```
coverage run -m pytest
coverage report --ignore-errors -m
```

## License
This software is licensed under the [MIT license](LICENSE).
File renamed without changes.
50 changes: 24 additions & 26 deletions examples/basic/window.py → examples/pyqt5/basic/window.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,14 @@
from PyQt5.QtWidgets import QMainWindow, QLabel
from src.modern_slider import Slider
from PyQt5.QtWidgets import QMainWindow, QFormLayout, QWidget
from pyqt_advanced_slider import Slider


class Window(QMainWindow):
def __init__(self):
super().__init__(parent=None)

# Window settings
self.setFixedSize(360, 145)
self.setWindowTitle('Modern Slider Demo')

# Add labels for the hotkey pickers
self.label_1 = QLabel(self)
self.label_1.setText('Slider 1:')
self.label_1.move(48, 16)

# Label for slider 2
self.label_2 = QLabel(self)
self.label_2.setText('Slider 2:')
self.label_2.move(48, 53)

# Label for slider 3
self.label_3 = QLabel(self)
self.label_3.setText('Slider 3:')
self.label_3.move(48, 90)
self.resize(360, 145)
self.setWindowTitle('Advanced Slider Demo')

# Slider 1
self.slider_1 = Slider(self)
Expand Down Expand Up @@ -53,13 +38,26 @@ def __init__(self):
self.slider_3.setSuffix('°') # Add slider value suffix
self.slider_3.valueChanged.connect(self.slider_3_value_changed) # Connect change event

# Using fixed size and position for simplicity since this is just a demo
self.slider_1.setFixedSize(200, 18)
self.slider_1.move(110, 23)
self.slider_2.setFixedSize(200, 18)
self.slider_2.move(110, 60)
self.slider_3.setFixedSize(200, 18)
self.slider_3.move(110, 97)
# Set slider widths and heights
self.slider_1.setMinimumWidth(100)
self.slider_2.setMinimumWidth(100)
self.slider_3.setMinimumWidth(100)
self.slider_1.setFixedHeight(18)
self.slider_2.setFixedHeight(18)
self.slider_3.setFixedHeight(18)

# Form layout
form_layout = QFormLayout()
form_layout.addRow("Slider 1:", self.slider_1)
form_layout.addRow("Slider 2:", self.slider_2)
form_layout.addRow("Slider 3:", self.slider_3)
form_layout.setSpacing(20)
form_layout.setContentsMargins(20, 20, 20, 20)

# Set layout
central_widget = QWidget()
central_widget.setLayout(form_layout)
self.setCentralWidget(central_widget)

def slider_1_value_changed(self, value):
# Called when slider_1 value changes
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor, QColor
from PyQt5.QtWidgets import QMainWindow, QLabel, QWidget, QPushButton
from src.modern_slider import Slider
from pyqt_advanced_slider import Slider


class Window(QMainWindow):
Expand Down Expand Up @@ -31,7 +31,7 @@ def __init__(self):

# Window title
self.window_title = QLabel(self)
self.window_title.setText('Modern Slider Demo')
self.window_title.setText('Advanced Slider Demo')
self.window_title.setFixedSize(150, 20)
self.window_title.move(125, 6)
self.window_title.setObjectName('window_title')
Expand Down
11 changes: 11 additions & 0 deletions examples/pyqt6/basic/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import sys
from PyQt6.QtWidgets import QApplication
from window import Window


# Run example
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
72 changes: 72 additions & 0 deletions examples/pyqt6/basic/window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from PyQt6.QtWidgets import QMainWindow, QFormLayout, QWidget
from pyqt_advanced_slider import Slider


class Window(QMainWindow):
def __init__(self):
super().__init__(parent=None)

# Window settings
self.resize(360, 145)
self.setWindowTitle('Advanced Slider Demo')

# Slider 1
self.slider_1 = Slider(self)
self.slider_1.setRange(100, 500) # Set slider min and max
self.slider_1.setValue(375) # Set slider value
self.slider_1.valueChanged.connect(self.slider_1_value_changed) # Connect change event

# Slider 2
self.slider_2 = Slider(self)
self.slider_2.setRange(-500.0, 2500.0) # Set slider min and max
self.slider_2.setValue(100.0) # Set slider value
self.slider_2.setFloat(True) # Change to float slider
self.slider_2.setDecimals(2) # Show 2 decimal places
self.slider_2.setPrefix('~') # Add slider value prefix
self.slider_2.setSuffix(' €') # Add slider value suffix
self.slider_2.setThousandsSeparator(',') # Add thousands separator
self.slider_2.setSingleStep(50) # Set custom single step value
self.slider_2.setPageStep(250) # Set custom page step value
self.slider_2.valueChanged.connect(self.slider_2_value_changed) # Connect change event

# Slider 3
self.slider_3 = Slider(self)
self.slider_3.setRange(-1.0, -0.1) # Set slider min and max
self.slider_3.setValue(-0.552) # Set slider value
self.slider_3.setFloat(True) # Change to float slider
self.slider_3.setDecimals(3) # Show 3 decimal places
self.slider_3.setSuffix('°') # Add slider value suffix
self.slider_3.valueChanged.connect(self.slider_3_value_changed) # Connect change event

# Set slider widths and heights
self.slider_1.setMinimumWidth(100)
self.slider_2.setMinimumWidth(100)
self.slider_3.setMinimumWidth(100)
self.slider_1.setFixedHeight(18)
self.slider_2.setFixedHeight(18)
self.slider_3.setFixedHeight(18)

# Form layout
form_layout = QFormLayout()
form_layout.addRow("Slider 1:", self.slider_1)
form_layout.addRow("Slider 2:", self.slider_2)
form_layout.addRow("Slider 3:", self.slider_3)
form_layout.setSpacing(20)
form_layout.setContentsMargins(20, 20, 20, 20)

# Set layout
central_widget = QWidget()
central_widget.setLayout(form_layout)
self.setCentralWidget(central_widget)

def slider_1_value_changed(self, value):
# Called when slider_1 value changes
print('[Slider 1]: ' + str(value))

def slider_2_value_changed(self, value):
# Called when slider_2 value changes
print('[Slider 2]: ' + str(value))

def slider_3_value_changed(self, value):
# Called when slider_3 value changes
print('[Slider 3]: ' + str(value))
25 changes: 25 additions & 0 deletions examples/pyqt6/custom-styling/css/stylesheet.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#window {
background: #E9E9E9;
border: 1px solid #CACACA;
border-radius: 7px;
}

#window_bar {
background-color: #CBC7C7;
border-radius: 7px;
}

#window_title {
color: #333333;
font-size: 12px;
}

#close_button {
color: #333333;
background-color: #9C9C9C;
border-radius: 11px;
}

#label_1, #label_2, #label_3, #label_4 {
font-size: 12px;
}
16 changes: 16 additions & 0 deletions examples/pyqt6/custom-styling/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import sys
from PyQt6.QtWidgets import QApplication
from window import Window


# Run example
if __name__ == '__main__':
app = QApplication(sys.argv)

# Set stylesheet
with open("css/stylesheet.css", "r") as stylesheet:
app.setStyleSheet(stylesheet.read())

window = Window()
window.show()
app.exec()
Loading

0 comments on commit d733c49

Please sign in to comment.