GradientWidget.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. #include "GradientWidget.h"
  2. #include <QPaintEvent>
  3. #include <QPainter>
  4. #include <QStyleOptionSlider>
  5. #include <QColorDialog>
  6. #include <QStyle>
  7. #include <algorithm>
  8. #include <cmath>
  9. GradientWidget::GradientWidget(QWidget* parent) :
  10. QWidget{ parent }
  11. {
  12. dragging = false;
  13. selectedHandle = -1;
  14. mouseOver = -1;
  15. maxValue = 1.0f;
  16. colorPicker = new QColorDialog(this);
  17. colorPicker->setOption(QColorDialog::NoButtons);
  18. connect(colorPicker, &QColorDialog::currentColorChanged, this, &GradientWidget::selectedColorChanged);
  19. setMouseTracking(true);
  20. }
  21. const Gradient& GradientWidget::getGradient(void) const
  22. {
  23. return gradient;
  24. }
  25. void GradientWidget::setGradient(Gradient gr)
  26. {
  27. gradient = std::move(gr);
  28. points = gradient.getPoints();
  29. maxValue = gradient.getMax();
  30. updateGradient();
  31. }
  32. void GradientWidget::updateGradient(void)
  33. {
  34. gradient = Gradient{ points, maxValue };
  35. update();
  36. emit gradientChanged();
  37. }
  38. QColor GradientWidget::colorAtY(float y)
  39. {
  40. float v = handleYToGradVal(y);
  41. return fromRGB(gradient.get(v));
  42. /*float v = handleYToGradVal(y);
  43. QColor up = QColor(QColor::Invalid);
  44. QColor down = QColor(QColor::Invalid);
  45. float upv = 0;
  46. float downv = 1;
  47. for (const auto& [color, val] : points) {
  48. if (val >= upv && val < v) {
  49. upv = val;
  50. up = QColor(color.r, color.g, color.b);
  51. }
  52. if (val <= downv && val > v) {
  53. downv = val;
  54. down = QColor(color.r, color.g, color.b);
  55. }
  56. }
  57. if (!up.isValid())
  58. return down;
  59. if (!down.isValid())
  60. return up;
  61. return lerp(up, down, (v - upv) / (downv - upv));*/
  62. }
  63. void GradientWidget::paintEvent(QPaintEvent* e)
  64. {
  65. QPainter painter{ this };
  66. QRect gradientRect = getGradientRect();
  67. QStyleOption frameOptions;
  68. frameOptions.init(this);
  69. frameOptions.rect = gradientRect;
  70. frameOptions.state |= QStyle::State_Sunken;
  71. style()->drawPrimitive(
  72. QStyle::PrimitiveElement::PE_Frame, &frameOptions, &painter, this);
  73. int fhmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameHMargin);
  74. int fvmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameVMargin);
  75. if (fhmargins == -1) {
  76. fhmargins = fvmargins = 3;
  77. }
  78. QLinearGradient linGrad;
  79. linGrad.setStart(0, gradientRect.top());
  80. linGrad.setFinalStop(0, gradientRect.bottom());
  81. for (const auto& [col, at] : points) {
  82. linGrad.setColorAt(at / maxValue, QColor{ col.r, col.g, col.b });
  83. }
  84. // adjust rect to have small margins, so the frame
  85. // around the gradient is visible
  86. gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
  87. QBrush brush{ linGrad };
  88. painter.fillRect(gradientRect, brush);
  89. int index = 0;
  90. for (auto& [color, point] : points) {
  91. QRect r = getHandleRect(index);
  92. int hs = HandleState::HANDLE_NORMAL;
  93. if (dragging && selectedHandle == index)
  94. hs |= HANDLE_DOWN;
  95. if (mouseOver == index)
  96. hs |= HANDLE_MOUSEOVER;
  97. if (selectedHandle == index)
  98. hs |= HANDLE_SELECTED;
  99. paintHandle(painter, r, fromRGB(color), hs);
  100. index++;
  101. }
  102. }
  103. void GradientWidget::paintHandle(QPainter& painter, const QRectF& pos,
  104. QColor c, int handleState)
  105. {
  106. const float lineWidth = 2;
  107. QPainterPath qpp = createSlideHandle(pos.width() - lineWidth, pos.height() - lineWidth);
  108. qpp.translate(pos.x() + lineWidth / 2, pos.y() + lineWidth / 2);
  109. if (handleState & HANDLE_SELECTED) {
  110. QColor absLighter;
  111. absLighter.setHsvF(c.hueF(), c.saturationF(), c.valueF() > 0.3 ? c.valueF() : 0.3);
  112. painter.setPen(QPen(QBrush(absLighter.lighter(130)), lineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  113. } else
  114. painter.setPen(QPen(QBrush(c.darker(200)), lineWidth / 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  115. painter.setRenderHint(QPainter::Antialiasing);
  116. QLinearGradient bevel{ 0, pos.top(), 0, pos.bottom() }; // top down linear gradient
  117. if (handleState & HANDLE_DOWN) {
  118. bevel.setColorAt(0, c.darker(120));
  119. bevel.setColorAt(1, c.lighter(120));
  120. }
  121. else if (handleState & HANDLE_MOUSEOVER) {
  122. bevel.setColorAt(0, c.lighter(130));
  123. bevel.setColorAt(1, c.darker(110));
  124. }
  125. else {
  126. bevel.setColorAt(0, c.lighter(120));
  127. bevel.setColorAt(1, c.darker(120));
  128. }
  129. painter.fillPath(qpp, QBrush(bevel));
  130. painter.drawPath(qpp);
  131. }
  132. void GradientWidget::mousePressEvent(QMouseEvent* e)
  133. {
  134. int handle = handleAtPos(e->pos());
  135. if (handle != -1) {
  136. selectedHandle = handle;
  137. dragging = true;
  138. selectOffsetY = e->y() - gradValToHandleY(
  139. points[handle].second);
  140. update();
  141. e->accept();
  142. }
  143. else {
  144. e->ignore();
  145. }
  146. }
  147. void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
  148. {
  149. if (dragging) {
  150. dragging = false;
  151. update();
  152. e->accept();
  153. }
  154. else {
  155. e->ignore();
  156. }
  157. }
  158. void GradientWidget::mouseMoveEvent(QMouseEvent* e)
  159. {
  160. if (dragging) {
  161. float newVal = handleYToGradVal(e->y() - selectOffsetY);
  162. newVal = std::clamp(newVal, 0.0f, maxValue);
  163. points[selectedHandle].second = newVal;
  164. updateGradient();
  165. emit gradientChanged();
  166. e->accept();
  167. }
  168. else {
  169. int handle = handleAtPos(e->pos());
  170. bool needsUpdate = false;
  171. if (mouseOver != handle)
  172. needsUpdate = true;
  173. mouseOver = handle;
  174. e->accept();
  175. if (needsUpdate)
  176. update();
  177. }
  178. }
  179. void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
  180. {
  181. auto torgb = [](const QColor& c) {
  182. return RGBColor{
  183. uint8_t(c.red()), uint8_t(c.green()), uint8_t(c.blue())
  184. };
  185. };
  186. QRect handleArea = getHandleArea();
  187. int handle = handleAtPos(e->pos());
  188. if (handle != -1) {
  189. RGBColor current = points.at(handle).first;
  190. /*QColor newColor = QColorDialog::getColor(current,
  191. this,
  192. tr("Pick Color"));*/
  193. selectedHandle = handle;
  194. colorPicker->setCurrentColor(fromRGB(current));
  195. colorPicker->exec();
  196. /*if (newColor.isValid()) {
  197. points[handle].first = torgb(newColor);
  198. update();
  199. emit gradientChanged();
  200. }*/
  201. e->accept();
  202. }
  203. else if (handleArea.contains(e->pos())) {
  204. float v = handleYToGradVal(e->pos().y());
  205. points.emplace_back(torgb(colorAtY(e->pos().y())), v);
  206. e->accept();
  207. updateGradient();
  208. }
  209. else {
  210. e->ignore();
  211. }
  212. }
  213. QSize GradientWidget::minimumSizeHint(void) const
  214. {
  215. int spacing = this->style()->pixelMetric(
  216. QStyle::PM_LayoutHorizontalSpacing);
  217. if (spacing == -1) {
  218. spacing = this->style()->layoutSpacing(
  219. QSizePolicy::Frame,
  220. QSizePolicy::PushButton,
  221. Qt::Horizontal);
  222. }
  223. return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
  224. }
  225. QSize GradientWidget::sizeHint(void) const
  226. {
  227. int spacing = this->style()->pixelMetric(
  228. QStyle::PM_LayoutHorizontalSpacing);
  229. if (spacing == -1) {
  230. spacing = this->style()->layoutSpacing(
  231. QSizePolicy::Frame,
  232. QSizePolicy::PushButton,
  233. Qt::Horizontal);
  234. }
  235. return QSize{ int(handleWidth * 1.1 + spacing), handleHeight };
  236. }
  237. void GradientWidget::selectedColorChanged(const QColor& newColor)
  238. {
  239. if (points.size() > selectedHandle) {
  240. points.at(selectedHandle).first = RGBColor {
  241. uint8_t(newColor.red()),
  242. uint8_t(newColor.green()),
  243. uint8_t(newColor.blue())
  244. };
  245. updateGradient();
  246. emit gradientChanged();
  247. }
  248. }
  249. void GradientWidget::removeSelectedHandle(void)
  250. {
  251. if (selectedHandle >= 0 && selectedHandle < points.size()) {
  252. points.erase(points.begin() + selectedHandle);
  253. selectedHandle = -1;
  254. updateGradient();
  255. emit gradientChanged();
  256. }
  257. }
  258. QRect GradientWidget::getGradientRect(void) const
  259. {
  260. QMargins cm = contentsMargins();
  261. int top = cm.top();
  262. int bottom = cm.bottom();
  263. int left = cm.left();
  264. int right = cm.right();
  265. int spacing = this->style()->pixelMetric(
  266. QStyle::PM_LayoutHorizontalSpacing);
  267. if (spacing == -1) {
  268. spacing = this->style()->layoutSpacing(
  269. QSizePolicy::Frame,
  270. QSizePolicy::PushButton,
  271. Qt::Horizontal);
  272. }
  273. top += handleHeight / 2;
  274. bottom += handleHeight / 2;
  275. return QRect{ left, top,
  276. width() - left - right - handleWidth - spacing,
  277. height() - bottom - top };
  278. }
  279. QRect GradientWidget::getHandleRect(int index) const
  280. {
  281. QRect handleArea = getHandleArea();
  282. float y = handleArea.top() + points.at(index).second / maxValue * handleArea.height();
  283. return QRect {
  284. handleArea.x(), int(y - handleHeight / 2),
  285. handleWidth, handleHeight
  286. };
  287. }
  288. QRect GradientWidget::getHandleArea(void) const
  289. {
  290. QMargins cm = contentsMargins();
  291. int top = cm.top();
  292. int bottom = cm.bottom();
  293. int left = cm.left();
  294. int right = cm.right();
  295. top += handleHeight / 2;
  296. bottom += handleHeight / 2;
  297. float y = top;
  298. float x = width() - handleWidth - right;
  299. return QRect {
  300. int(x), top, handleWidth, height() - top - bottom
  301. };
  302. }
  303. int GradientWidget::handleAtPos(QPoint pos) const
  304. {
  305. for (int i = points.size() - 1; i >= 0; i--) {
  306. QRect rect = getHandleRect(i);
  307. if (rect.contains(pos)) {
  308. return i;
  309. }
  310. }
  311. return -1;
  312. }
  313. float GradientWidget::handleYToGradVal(float y) const
  314. {
  315. QRect area = getHandleArea();
  316. return maxValue * (y - area.top()) / area.height();
  317. }
  318. float GradientWidget::gradValToHandleY(float v) const
  319. {
  320. QRect area = getHandleArea();
  321. return area.top() + v / maxValue * area.height();
  322. }
  323. QPainterPath GradientWidget::createSlideHandle(float w, float h)
  324. {
  325. const float rounding = 4;
  326. QPainterPath qpp;
  327. QPolygonF qpf;
  328. qpf << QPointF{ 0, 0.5 * h }
  329. << QPointF{ 0.3 * w, h };
  330. qpp.moveTo(0, 0.5 * h);
  331. qpp.lineTo(0.3 * w, h);
  332. qpp.arcTo(w - rounding, h - rounding, rounding, rounding, -90, 90);
  333. qpp.arcTo(w - rounding, 0, rounding, rounding, 0, 90);
  334. qpp.lineTo(0.3 * w, 0);
  335. qpp.lineTo(0, 0.5 * h);
  336. return qpp;
  337. }