add range slider component
This commit is contained in:
@@ -3,6 +3,8 @@ import re
|
|||||||
from PySide6.QtGui import QColor, QPixmap, QIcon
|
from PySide6.QtGui import QColor, QPixmap, QIcon
|
||||||
from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton, QColorDialog, QSizePolicy, QComboBox
|
from PySide6.QtWidgets import QWidget, QHBoxLayout, QPushButton, QColorDialog, QSizePolicy, QComboBox
|
||||||
from src.i18n import _
|
from src.i18n import _
|
||||||
|
from src.util.color import to_qcolor
|
||||||
|
|
||||||
|
|
||||||
class ColorButton(QWidget):
|
class ColorButton(QWidget):
|
||||||
def __init__(self, color: str, parent=None):
|
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)
|
self.color_drop_down.setCurrentIndex(self.color_drop_down.count() - 1)
|
||||||
|
|
||||||
def _update_color(self):
|
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():
|
if new_color.isValid():
|
||||||
color = self._to_hex(new_color)
|
color = self._to_hex(new_color)
|
||||||
self.set_color(color)
|
self.set_color(color)
|
||||||
@@ -80,20 +82,10 @@ class ColorButton(QWidget):
|
|||||||
|
|
||||||
def _color_pixmap(self, color: str) -> QPixmap:
|
def _color_pixmap(self, color: str) -> QPixmap:
|
||||||
pixmap = QPixmap(40, 40)
|
pixmap = QPixmap(40, 40)
|
||||||
qcolor = self._to_qcolor(color)
|
qcolor = to_qcolor(color)
|
||||||
pixmap.fill((qcolor))
|
pixmap.fill((qcolor))
|
||||||
return pixmap
|
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:
|
def _to_hex(self, color: QColor) -> str:
|
||||||
red = "{0:0{1}x}".format(color.red(), 2)
|
red = "{0:0{1}x}".format(color.red(), 2)
|
||||||
green = "{0:0{1}x}".format(color.green(), 2)
|
green = "{0:0{1}x}".format(color.green(), 2)
|
||||||
|
|||||||
154
src/ui/rangeslider.py
Normal file
154
src/ui/rangeslider.py
Normal 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
18
src/util/color.py
Normal 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)
|
||||||
Reference in New Issue
Block a user