""" RenderPipeline Copyright (c) 2014-2016 tobspr Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from __future__ import print_function # from PyQt5 import Qt # from PyQt5.QtGui import QPainter, QColor, QPen # from PyQt5.QtWidgets import QWidget from six.moves import range # pylint: disable=import-error import math from RenderPipelineFile.rplibs.pyqt_imports import * # noqa class CurveWidget(QWidget): """ This is a resizeable Widget which shows an editable curve which can be modified. """ def __init__(self, parent): """ Constructs the CurveWidget, we start with an initial curve """ QWidget.__init__(self, parent) self.setFocusPolicy(Qt.ClickFocus) self._curves = [] # Store current display time self._current_time = 0.5 # Widget render constants self._cv_point_size = 3 self._legend_border = 52 self._bar_h = 30 # Currently dragged control point, format is: # (CurveIndex, PointIndex, Drag-Offset (x,y)) self._drag_point = None self._drag_time = 0.0 # Currently selected control point, format is: # (CurveIndex, PointIndex) self._selected_point = None self._unit_processor = lambda v: str(round(v, 2)) self._change_handler = lambda: None def set_unit_processor(self, proc): """ Sets the function which gets called to map values from 0 .. 1 to values like 10% to 30% """ self._unit_processor = proc def set_change_handler(self, handler): """ Sets a function which gets called when the data in this widget got edited """ self._change_handler = handler def paintEvent(self, e): # noqa """ Internal QT paint event, draws the entire widget """ qp = QPainter() qp.begin(self) self._draw(qp) qp.end() def mousePressEvent(self, QMouseEvent): # noqa """ Internal mouse-press handler """ self._drag_point = None self._selected_point = None mouse_pos = QMouseEvent.pos() mouse_x = mouse_pos.x() - self._legend_border mouse_y = mouse_pos.y() - self._bar_h for index, curve in enumerate(self._curves): # Check for clicks on control points for cv_index, (x, y) in enumerate(curve.control_points): point_x = self._get_x_value_for(x) point_y = self._get_y_value_for(y) - self._bar_h if abs(point_x - mouse_x) < self._cv_point_size + 6: if (abs(point_y - mouse_y)) < self._cv_point_size + 6: drag_x_offset = mouse_x - point_x drag_y_offset = mouse_y - point_y mpos_relx = float(mouse_x) / (self.width() - self._legend_border) self._drag_point = (index, cv_index, (drag_x_offset, drag_y_offset)) self._drag_time = mpos_relx self._selected_point = (index, cv_index) # If still no intersection, check if we clicked a curve if mouse_x > 0 and mouse_x < self.width() - self._legend_border: if mouse_y > 0 and mouse_y < self.height() - self._legend_border: mpos_relx = float(mouse_x) / (self.width() - self._legend_border) curve_py = curve.get_value(mpos_relx) curve_offy = self._get_y_value_for(curve_py) - self._bar_h if abs(curve_offy - mouse_y) < 8 and self._selected_point is None: # Clicked on curve, spawn new point cv_index = curve.append_cv(mpos_relx, curve_py) self._selected_point = (index, cv_index) self._drag_point = (index, cv_index, (0, 0)) self._drag_time = mpos_relx self._change_handler() self.update() def mouseReleaseEvent(self, event): # noqa """ Internal mouse-release handler """ self._drag_point = None self._drag_time = -1 self.update() def mouseMoveEvent(self, event): # noqa """ Internal mouse-move handler """ if len(self._curves) < 1: return if self._drag_point is not None: mouse_x = event.pos().x() - self._drag_point[2][0] - self._legend_border mouse_y = event.pos().y() - self._drag_point[2][1] - self._bar_h # Convert to local coordinate local_x = max(0, min(1, mouse_x / float(self.width() - self._legend_border))) local_y = mouse_y / float(self.height() - self._legend_border - self._bar_h) local_y = 1 - max(0, min(1, local_y)) self._drag_time = local_x # Set new point data self._curves[self._drag_point[0]].set_cv_value(self._drag_point[1], local_x, local_y) # Redraw curve self._curves[self._drag_point[0]].build_curve() self.update() self._change_handler() def keyPressEvent(self, event): # noqa """ Internal keypress handler """ # Delete anchor point if event.key() == Qt.Key_Delete: self.delete_current_point() def delete_current_point(self): """ Deletes the currently selected point """ if self._selected_point is not None: self._curves[self._selected_point[0]].remove_cv(self._selected_point[1]) self._selected_point = None self._drag_point = None self.update() self._change_handler() def set_curves(self, curves): """ Sets the list of displayed curves """ self._selected_point = None self._drag_point = None self._curves = curves self.update() def _get_y_value_for(self, local_value): """ Converts a value from 0 to 1 to a value from 0 .. canvas height """ local_value = max(0, min(1.0, 1.0 - local_value)) local_value *= self.height() - self._legend_border - self._bar_h local_value += self._bar_h return local_value def _get_x_value_for(self, local_value): """ Converts a value from 0 to 1 to a value from 0 .. canvas width """ local_value = max(0, min(1.0, local_value)) local_value *= self.width() - self._legend_border return local_value def set_current_time(self, local_time): """ Sets the current displayed time, should range from 0 to 1 """ self._current_time = max(0.0, min(1.0, local_time)) self.update() def _draw(self, painter): """ Internal method to draw the widget """ canvas_width = self.width() - self._legend_border canvas_height = self.height() - self._legend_border - self._bar_h # Draw field background # painter.setPen(QColor(200, 200, 200)) # painter.setBrush(QColor(230, 230, 230)) # painter.drawRect(0, 0, self.width() - 1, self.height() - 1) # Draw legend # Compute amount of horizontal / vertical lines num_vert_lines = 12 # 24 / 12 = 2, one entry per 2 hours line_spacing_x = (self.width() - self._legend_border) / num_vert_lines line_spacing_y = (self.height() - self._legend_border - self._bar_h) / 20.0 num_horiz_lines = int(math.ceil(canvas_height / float(line_spacing_y)) + 1) # Draw vertical lines painter.setPen(QColor(200, 200, 200)) for i in range(num_vert_lines + 1): line_pos = i * line_spacing_x + self._legend_border - 1 painter.drawLine(int(line_pos), self._bar_h, int(line_pos), canvas_height + self._bar_h) # Draw horizontal lines painter.setPen(QColor(200, 200, 200)) for i in range(num_horiz_lines): line_pos = canvas_height - i * line_spacing_y + self._bar_h painter.drawLine(self._legend_border, int(line_pos), self.width(), int(line_pos)) # Draw vetical legend labels painter.setPen(QColor(120, 120, 120)) for i in range(num_horiz_lines): line_pos = canvas_height - i * line_spacing_y + self._bar_h # painter.drawText(6, line_pos + 3, str(round(float(i) / (num_horiz_lines-1), 2))) painter.drawText( 6, int(line_pos + 3), self._unit_processor(float(i) / (num_horiz_lines - 1))) # Draw horizontal legend labels for i in range(num_vert_lines + 1): line_pos = i * line_spacing_x + self._legend_border offpos_x = -14 if i == 0: offpos_x = -2 elif i == num_vert_lines: offpos_x = -27 time_string = str(int(float(i) / num_vert_lines * 24)).zfill(2) + ":00" painter.drawText(int(line_pos + offpos_x), canvas_height + self._bar_h + 18, time_string) # Draw curve for index, curve in enumerate(self._curves): painter.setPen(QColor(*curve.color)) last_value = 0 for i in range(canvas_width): rel_offset = i / (canvas_width - 1.0) curve_height = self._get_y_value_for(curve.get_value(rel_offset)) if i == 0: last_value = curve_height painter.drawLine( int(self._legend_border + i - 1), int(last_value), int(self._legend_border + i), int(curve_height)) last_value = curve_height # Draw the CV points of the curve painter.setBrush(QColor(240, 240, 240)) for cv_index, (x, y) in enumerate(curve.control_points): offs_x = x * canvas_width + self._legend_border offs_y = (1 - y) * canvas_height + self._bar_h if (self._selected_point and self._selected_point[0] == index and self._selected_point[1] == cv_index): painter.setPen(QColor(255, 0, 0)) else: painter.setPen(QColor(100, 100, 100)) painter.drawRect( int(offs_x - self._cv_point_size), int(offs_y - self._cv_point_size), 2 * self._cv_point_size, 2 * self._cv_point_size) # Draw bar background bar_half_height = 4 bar_top_pos = 10 painter.setBrush(QColor(255, 0, 0)) painter.setPen(QColor(110, 110, 110)) painter.drawRect( self._legend_border - 1, bar_top_pos - 1, self.width() - self._legend_border, 2 * bar_half_height + 2) # Draw bar if len(self._curves) == 0: return if len(self._curves) == 1: bar_curve = self._curves[0] else: bar_curve = self._curves[0:3] for i in range(canvas_width - 1): xpos = self._legend_border + i relv = float(i) / float(canvas_width) if len(self._curves) == 1: val = max(0, min(255, int(bar_curve.get_value(relv) * 255.0))) painter.setPen(QColor(val, val, val)) else: r = max(0, min(255, int(bar_curve[0].get_value(relv) * 255.0))) g = max(0, min(255, int(bar_curve[1].get_value(relv) * 255.0))) b = max(0, min(255, int(bar_curve[2].get_value(relv) * 255.0))) painter.setPen(QColor(r, g, b)) painter.drawLine(int(xpos), bar_top_pos, int(xpos), bar_top_pos + 2 * bar_half_height) # Draw selected time if self._drag_point: painter.setBrush(QColor(200, 200, 200)) painter.setPen(QColor(90, 90, 90)) offs_x = max(0, min( canvas_width + 10, self._drag_time * canvas_width + self._legend_border - 19)) offs_y = self.height() - self._legend_border minutes = int(self._drag_time * 24 * 60) hours = int(minutes / 60) minutes = minutes % 60 painter.drawRect(int(offs_x), self.height() - self._legend_border + 5, 40, 20) painter.drawText(int(offs_x + 7), offs_y + 20, "{:02}:{:02}".format(int(hours), int(minutes))) painter.setPen(QColor(150, 150, 150)) painter.drawLine( int(offs_x) + 19, bar_top_pos + 15, int(offs_x + 19), self.height() - self._legend_border + 5) # Display current time pen = QPen() pen.setColor(QColor(255, 100, 100)) pen.setStyle(Qt.DashLine) painter.setPen(pen) xoffs = self._legend_border + self._current_time * (canvas_width - 1) painter.drawLine(int(xoffs), self._bar_h, int(xoffs), self._bar_h + canvas_height) # Draw usage hints painter.setPen(QColor(100, 100, 100)) painter.drawText( 5, self.height() - 2, "Click on the curve to add new control points, click and drag " "existing points to move them.")