add range slider component
This commit is contained in:
@@ -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
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()
|
||||
Reference in New Issue
Block a user