add range slider component

This commit is contained in:
2022-12-01 17:48:05 +01:00
parent 32c7da6ee5
commit e8f9e140fd
3 changed files with 176 additions and 12 deletions

View File

@@ -3,6 +3,8 @@ import re
from PySide6.QtGui import QColor, QPixmap, QIcon
from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton, QColorDialog, QSizePolicy, QComboBox
from src.i18n import _
from src.util.color import to_qcolor
class ColorButton(QWidget):
def __init__(self, color: str, parent=None):
@@ -69,7 +71,7 @@ class ColorButton(QWidget):
self.color_drop_down.setCurrentIndex(self.color_drop_down.count() - 1)
def _update_color(self):
new_color = QColorDialog.getColor(self._to_qcolor(self.color))
new_color = QColorDialog.getColor(to_qcolor(self.color))
if new_color.isValid():
color = self._to_hex(new_color)
self.set_color(color)
@@ -80,20 +82,10 @@ class ColorButton(QWidget):
def _color_pixmap(self, color: str) -> QPixmap:
pixmap = QPixmap(40, 40)
qcolor = self._to_qcolor(color)
qcolor = to_qcolor(color)
pixmap.fill((qcolor))
return pixmap
def _to_qcolor(self, color: str):
if self._is_hex_color(color):
red = int(color[0:2], 16)
green = int(color[2:4], 16)
blue = int(color[4:6], 16)
return QColor(red, green, blue)
elif color in QColor().colorNames():
return QColor(color)
return QColor(255, 255, 255)
def _to_hex(self, color: QColor) -> str:
red = "{0:0{1}x}".format(color.red(), 2)
green = "{0:0{1}x}".format(color.green(), 2)

154
src/ui/rangeslider.py Normal file
View File

@@ -0,0 +1,154 @@
import math
from enum import Enum
import PySide6
from PySide6 import QtGui
from PySide6.QtCore import QRect, QPoint, Signal
from PySide6.QtGui import QPainter, Qt
from PySide6.QtWidgets import QWidget
from src.util.color import to_qcolor
class RangeSliderHandle():
def __init__(self, value: int):
self.value = value
class UpdateStyle(Enum):
KEEP_ABSOLUTE = 2
"""
The absolute values for lower/upper are not changed unless the values would no longer be in range
"""
KEEP_ABSOLUTE_MIN_MAX = 4
"""
Like UpdateStyle.KEEP_ABSOLUTE but if lower was at min_value (or upper was at max_value), then it will stay there.
"""
class RangeSlider(QWidget):
value_changed = Signal(float, float)
_width = 20
_handle_width = 12
def __init__(self):
super(RangeSlider, self).__init__()
self.setFixedWidth(self._width)
self.draw_ticks = False
self.min_value = 0
self.max_value = 100
self.lower_value = RangeSliderHandle(0)
self.upper_value = RangeSliderHandle(100)
self.selected_handle = None
self.selection_drag_range = (self.min_value, self.max_value)
def set_max_value(self, max_value: int, update_style=UpdateStyle.KEEP_ABSOLUTE_MIN_MAX):
old_max_value = self.max_value
self.max_value = max_value
if update_style == UpdateStyle.KEEP_ABSOLUTE_MIN_MAX:
if self.upper_value.value == old_max_value:
self.upper_value.value = self.max_value
self.lower_value.value = min(self.lower_value.value, max_value)
self.upper_value.value = min(self.upper_value.value, max_value)
self.update()
def paintEvent(self, event: PySide6.QtGui.QPaintEvent) -> None:
painter = QPainter(self)
self._draw_background(painter)
if self.draw_ticks:
self._draw_ticks(painter)
self._draw_handle(painter, self.lower_value)
self._draw_handle(painter, self.upper_value, direction=-1)
painter.end()
def _draw_background(self, painter: QPainter) -> None:
painter.setBrush(to_qcolor("50ade8"))
painter.setPen(to_qcolor("dddddd"))
rect = QRect(round(10), self._handle_width, round(1),
self.height() - 2 * self._handle_width)
painter.drawRoundedRect(rect, 3.0, 3.0)
rect = QRect(round(7),
self._value_to_pixel(self.lower_value.value) + self._handle_width,
round(6),
self._value_to_pixel(self.upper_value.value - self.lower_value.value) - 2 * self._handle_width)
painter.drawRoundedRect(rect, 3.0, 3.0)
def _draw_ticks(self, painter: QPainter) -> None:
painter.setPen(to_qcolor("333333"))
min_tick_distance = 25
full_height = self.height() - 2 * self._handle_width
ticks = math.floor(full_height / min_tick_distance)
actual_tick_distance = full_height / ticks
print(f"ticks {ticks}")
y = actual_tick_distance + self._handle_width
while y < full_height:
painter.drawLine(8, y, 12, y)
y = y + actual_tick_distance
def _draw_handle(self, painter: QPainter, handle: RangeSliderHandle, direction=1) -> None:
y_pixel = self._value_to_pixel(handle.value)
painter.setBrush(to_qcolor("dddddd"))
painter.setPen(to_qcolor("444444"))
painter.setRenderHint(PySide6.QtGui.QPainter.RenderHint.Antialiasing, True)
painter.drawLine(2, y_pixel, 18, y_pixel)
painter.drawPolygon(
(QPoint(10, y_pixel), QPoint(18, y_pixel + 12 * direction), QPoint(2, y_pixel + 12 * direction)))
def _draw_handle_circle(self, painter: QPainter, handle: RangeSliderHandle) -> None:
y_pixel = self._value_to_pixel(handle.value)
painter.setBrush(to_qcolor("dddddd"))
painter.setPen(to_qcolor("444444"))
painter.setRenderHint(PySide6.QtGui.QPainter.RenderHint.Antialiasing, True)
painter.drawEllipse(QPoint(self._width / 2, y_pixel), self._handle_width / 2 - 1, self._handle_width / 2 - 1)
def _value_to_pixel(self, value: int) -> int:
value_percent = value / (self.max_value - self.min_value)
pixel = (self.height() - 2 * self._handle_width) * value_percent + self._handle_width
return pixel
def _pixel_to_value(self, pixel: int) -> int:
pixel_percent = (pixel - self._handle_width) / (self.height() - 2 * self._handle_width)
return (self.max_value - self.min_value) * pixel_percent
def _is_on_handle(self, handle: RangeSliderHandle, y_pixel: int, direction=1) -> bool:
handle_y_pixel = self._value_to_pixel(handle.value)
if direction > 0:
return handle_y_pixel < y_pixel < handle_y_pixel + self._handle_width
else:
return handle_y_pixel - self._handle_width < y_pixel < handle_y_pixel
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
if e.buttons() == Qt.MouseButton.LeftButton and e.modifiers() == Qt.KeyboardModifier.NoModifier:
pos: QPoint = e.pos()
if self._is_on_handle(self.lower_value, pos.y(), direction=1):
self.selected_handle = self.lower_value
self.selection_drag_range = (self.min_value, self.upper_value.value)
if self._is_on_handle(self.upper_value, pos.y(), direction=-1):
self.selected_handle = self.upper_value
self.selection_drag_range = (self.lower_value.value, self.max_value)
def mouseReleaseEvent(self, event: PySide6.QtGui.QMouseEvent) -> None:
self.selected_handle = None
def mouseMoveEvent(self, e: PySide6.QtGui.QMouseEvent) -> None:
if self.selected_handle != None:
pos: QPoint = e.pos()
value = self._pixel_to_value(pos.y())
if self.selection_drag_range[0] <= value <= self.selection_drag_range[1]:
self.selected_handle.value = value
# print("%s, %s" %(self.lower_value.value, self.upper_value.value))
self.value_changed.emit(self.lower_value.value, self.upper_value.value)
self.update()

18
src/util/color.py Normal file
View File

@@ -0,0 +1,18 @@
import re
from PySide6.QtGui import QColor
def is_hex_color(color: str):
return re.match("[0-9a-f]{6}", color, flags=re.IGNORECASE)
def to_qcolor(color: str):
if is_hex_color(color):
red = int(color[0:2], 16)
green = int(color[2:4], 16)
blue = int(color[4:6], 16)
return QColor(red, green, blue)
elif color in QColor().colorNames():
return QColor(color)
return QColor(255, 255, 255)