mirror of
https://github.com/pyapp-kit/superqt.git
synced 2026-01-05 03:40:47 +01:00
more readme
This commit is contained in:
4
.github/workflows/test_and_deploy.yml
vendored
4
.github/workflows/test_and_deploy.yml
vendored
@@ -106,11 +106,11 @@ jobs:
|
|||||||
if: runner.os == 'Linux' && matrix.screenshot
|
if: runner.os == 'Linux' && matrix.screenshot
|
||||||
uses: GabrielBB/xvfb-action@v1
|
uses: GabrielBB/xvfb-action@v1
|
||||||
with:
|
with:
|
||||||
run: python examples/screenshots.py
|
run: python examples/demo_widget.py
|
||||||
|
|
||||||
- name: Screenshots
|
- name: Screenshots
|
||||||
if: runner.os != 'Linux' && matrix.screenshot
|
if: runner.os != 'Linux' && matrix.screenshot
|
||||||
run: python examples/screenshots.py
|
run: python examples/demo_widget.py
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: matrix.screenshot
|
if: matrix.screenshot
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -77,3 +77,4 @@ target/
|
|||||||
|
|
||||||
# written by setuptools_scm
|
# written by setuptools_scm
|
||||||
*/_version.py
|
*/_version.py
|
||||||
|
.vscode/settings.json
|
||||||
|
|||||||
79
README.md
79
README.md
@@ -7,7 +7,7 @@ Version](https://img.shields.io/pypi/pyversions/QtRangeSlider.svg?color=green)](
|
|||||||
[](https://github.com/tlambert03/QtRangeSlider/actions/workflows/test_and_deploy.yml)
|
[](https://github.com/tlambert03/QtRangeSlider/actions/workflows/test_and_deploy.yml)
|
||||||
[](https://codecov.io/gh/tlambert03/QtRangeSlider)
|
[](https://codecov.io/gh/tlambert03/QtRangeSlider)
|
||||||
|
|
||||||
**Multi-handle range slider widget for PyQt/PySide**
|
**The missing multi-handle range slider widget for PyQt & PySide**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -31,24 +31,78 @@ You can install `QtRangeSlider` via pip:
|
|||||||
```sh
|
```sh
|
||||||
pip install qtrangeslider
|
pip install qtrangeslider
|
||||||
|
|
||||||
# note: you must also install a Qt Backend
|
# NOTE: you must also install a Qt Backend.
|
||||||
# supports PyQt5, PySide2, PyQt6, and PySide6
|
# PyQt5, PySide2, PyQt6, and PySide6 are supported
|
||||||
# as a convenience you can install via extras:
|
# As a convenience you can install them as extras:
|
||||||
pip install qtrangeslider[pyqt5]
|
pip install qtrangeslider[pyqt5]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And then to use it:
|
|
||||||
|
|
||||||
```python
|
------
|
||||||
from qtrangeslider import QRangeSlider
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
As `QRangeSlider` inherits from `QtWidgets.QSlider`, you can use all of the
|
To create a slider:
|
||||||
same methods available in the [QSlider API](https://doc.qt.io/qt-5/qslider.html)
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
from qtrangeslider import QRangeSlider
|
||||||
|
|
||||||
|
# as usual:
|
||||||
|
# you must create a QApplication before create a widget.
|
||||||
|
range_slider = QRangeSlider()
|
||||||
|
```
|
||||||
|
|
||||||
|
As `QRangeSlider` inherits from `QtWidgets.QSlider`, you can use all of the
|
||||||
|
same methods available in the [QSlider API](https://doc.qt.io/qt-5/qslider.html). The major difference is that `value` and `sliderPosition` are reimplemented as `tuples` of `int` (where the length of the tuple is equal to the number of handles in the slider.)
|
||||||
|
|
||||||
|
### value: Tuple[int, ...]
|
||||||
|
|
||||||
|
This property holds the current value of all handles in the slider.
|
||||||
|
|
||||||
|
The slider forces all values to be within the legal range:
|
||||||
|
`minimum <= value <= maximum`.
|
||||||
|
|
||||||
|
Changing the value also changes the sliderPosition.
|
||||||
|
|
||||||
|
##### Access Functions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
range_slider.value() -> Tuple[int, ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
range_slider.setValue(val: Sequence[int]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Notifier Signal:
|
||||||
|
|
||||||
|
```python
|
||||||
|
valueChanged(Tuple[int, ...])
|
||||||
|
```
|
||||||
|
|
||||||
|
### sliderPosition: Tuple[int, ...]
|
||||||
|
|
||||||
|
This property holds the current slider positions. It is a `tuple` with length equal to the number of handles.
|
||||||
|
|
||||||
|
If [tracking](https://doc.qt.io/qt-5/qabstractslider.html#tracking-prop) is enabled (the default), this is identical to [`value`](#value--tupleint-).
|
||||||
|
|
||||||
|
##### Access Functions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
range_slider.sliderPosition() -> Tuple[int, ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
range_slider.setSliderPosition(val: Sequence[int]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Notifier Signal:
|
||||||
|
|
||||||
|
```python
|
||||||
|
sliderMoved(Tuple[int, ...])
|
||||||
|
```
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -59,6 +113,8 @@ using [Qt Style Sheets](https://doc.qt.io/qt-5/stylesheet-reference.html), then
|
|||||||
`QRangeSlider` will inherit any styles applied to `QSlider` (since it inherits
|
`QRangeSlider` will inherit any styles applied to `QSlider` (since it inherits
|
||||||
from QSlider).
|
from QSlider).
|
||||||
|
|
||||||
|
> The code for these example widgets is [here](examples/demo_widget.py)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
<summary><em>See style sheet used for this example</em></summary>
|
<summary><em>See style sheet used for this example</em></summary>
|
||||||
@@ -112,5 +168,4 @@ QSlider::sub-page:horizontal {
|
|||||||
If you encounter any problems, please [file an issue] along with a detailed
|
If you encounter any problems, please [file an issue] along with a detailed
|
||||||
description.
|
description.
|
||||||
|
|
||||||
|
|
||||||
[file an issue]: https://github.com/tlambert03/QtRangeSlider/issues
|
[file an issue]: https://github.com/tlambert03/QtRangeSlider/issues
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ from qtrangeslider.qtcompat.QtWidgets import QApplication
|
|||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
|
|
||||||
slider = QRangeSlider()
|
slider = QRangeSlider()
|
||||||
slider.setMinimum(0)
|
|
||||||
slider.setMaximum(100)
|
|
||||||
slider.setValue((20, 80))
|
slider.setValue((20, 80))
|
||||||
slider.show()
|
slider.show()
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ from .qtcompat.QtWidgets import (
|
|||||||
Control = Tuple[str, int]
|
Control = Tuple[str, int]
|
||||||
|
|
||||||
|
|
||||||
def _bound(min_: int, max_: int, value: int) -> int:
|
|
||||||
"""Return value bounded by min_ and max_."""
|
|
||||||
return max(min_, min(max_, value))
|
|
||||||
|
|
||||||
|
|
||||||
class QRangeSlider(QSlider):
|
class QRangeSlider(QSlider):
|
||||||
# Emitted when the slider value has changed, with the new slider values
|
# Emitted when the slider value has changed, with the new slider values
|
||||||
valueChanged = Signal(tuple)
|
valueChanged = Signal(tuple)
|
||||||
@@ -85,6 +80,9 @@ class QRangeSlider(QSlider):
|
|||||||
return tuple(self._position)
|
return tuple(self._position)
|
||||||
|
|
||||||
def setSliderPosition(self, sld_idx: int, pos: int) -> None:
|
def setSliderPosition(self, sld_idx: int, pos: int) -> None:
|
||||||
|
|
||||||
|
# TODO: make it take a tuple, and assert that the length is correct
|
||||||
|
# only setting `value` is allowed to change the number of handles
|
||||||
pos = self._min_max_bound(pos)
|
pos = self._min_max_bound(pos)
|
||||||
# prevent sliders from moving beyond their neighbors
|
# prevent sliders from moving beyond their neighbors
|
||||||
pos = self._neighbor_bound(pos, sld_idx, self._position)
|
pos = self._neighbor_bound(pos, sld_idx, self._position)
|
||||||
@@ -192,8 +190,11 @@ class QRangeSlider(QSlider):
|
|||||||
self.update()
|
self.update()
|
||||||
self.setSliderDown(True)
|
self.setSliderDown(True)
|
||||||
elif self._pressedControl[0] == "bar":
|
elif self._pressedControl[0] == "bar":
|
||||||
|
self.setRepeatAction(QSlider.SliderNoAction) # why again?
|
||||||
self._clickOffset = self._pixelPosToRangeValue(self._pick(ev.pos()))
|
self._clickOffset = self._pixelPosToRangeValue(self._pick(ev.pos()))
|
||||||
self._sldPosAtPress = tuple(self._position)
|
self._sldPosAtPress = tuple(self._position)
|
||||||
|
self.update()
|
||||||
|
self.setSliderDown(True)
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev: QtGui.QMouseEvent) -> None:
|
def mouseMoveEvent(self, ev: QtGui.QMouseEvent) -> None:
|
||||||
# TODO: add pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this);
|
# TODO: add pixelMetric(QStyle::PM_MaximumDragDistance, &opt, this);
|
||||||
@@ -230,7 +231,7 @@ class QRangeSlider(QSlider):
|
|||||||
old_pressed = self._pressedControl
|
old_pressed = self._pressedControl
|
||||||
self._pressedControl = self.NULL_CTRL
|
self._pressedControl = self.NULL_CTRL
|
||||||
self.setRepeatAction(QSlider.SliderNoAction)
|
self.setRepeatAction(QSlider.SliderNoAction)
|
||||||
if old_pressed[0] == "handle":
|
if old_pressed[0] in ("handle", "bar"):
|
||||||
self.setSliderDown(False)
|
self.setSliderDown(False)
|
||||||
self.update() # TODO: restrict to the rect of old_pressed
|
self.update() # TODO: restrict to the rect of old_pressed
|
||||||
|
|
||||||
@@ -489,6 +490,13 @@ class QRangeSlider(QSlider):
|
|||||||
setattr(self, f"_bar_{dim}", float(bgrd.groups()[-1]))
|
setattr(self, f"_bar_{dim}", float(bgrd.groups()[-1]))
|
||||||
|
|
||||||
|
|
||||||
|
def _bound(min_: int, max_: int, value: int) -> int:
|
||||||
|
"""Return value bounded by min_ and max_."""
|
||||||
|
return max(min_, min(max_, value))
|
||||||
|
|
||||||
|
|
||||||
|
# Styles Parsing ##############
|
||||||
|
|
||||||
qlineargrad_pattern = re.compile(
|
qlineargrad_pattern = re.compile(
|
||||||
r"""
|
r"""
|
||||||
qlineargradient\(
|
qlineargradient\(
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ QT_API = "QT_API"
|
|||||||
# Names of the expected PyQt5 api
|
# Names of the expected PyQt5 api
|
||||||
PYQT5_API = ["pyqt5"]
|
PYQT5_API = ["pyqt5"]
|
||||||
|
|
||||||
# Names of the expected PyQt5 api
|
# Names of the expected PyQt6 api
|
||||||
PYQT6_API = ["pyqt6"]
|
PYQT6_API = ["pyqt6"]
|
||||||
|
|
||||||
# Names of the expected PySide2 api
|
# Names of the expected PySide2 api
|
||||||
PYSIDE2_API = ["pyside2"]
|
PYSIDE2_API = ["pyside2"]
|
||||||
|
|
||||||
# Names of the expected PySide2 api
|
# Names of the expected PySide6 api
|
||||||
PYSIDE6_API = ["pyside6"]
|
PYSIDE6_API = ["pyside6"]
|
||||||
|
|
||||||
# Detecting if a binding was specified by the user
|
# Detecting if a binding was specified by the user
|
||||||
@@ -142,7 +142,13 @@ if API in PYSIDE6_API:
|
|||||||
PYSIDE6 = True
|
PYSIDE6 = True
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise PythonQtError("No Qt bindings could be found")
|
API = None
|
||||||
|
|
||||||
|
if API is None:
|
||||||
|
raise PythonQtError(
|
||||||
|
"No Qt bindings could be found.\nYou must install one of the following packages "
|
||||||
|
"to use QtRangeSlider: PyQt5, PyQt6, PySide2, or PySide6"
|
||||||
|
)
|
||||||
|
|
||||||
# If a correct API name is passed to QT_API and it could not be found,
|
# If a correct API name is passed to QT_API and it could not be found,
|
||||||
# switches to another and informs through the warning
|
# switches to another and informs through the warning
|
||||||
|
|||||||
Reference in New Issue
Block a user