Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_selection_actions_panel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2025 Ross Rosales <ross.erosales@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
10#include "KisDocument.h"
11#include "KisViewManager.h"
12#include "kis_canvas2.h"
14#include "kis_icon_utils.h"
15#include "kis_painting_tweaks.h"
16#include "kis_selection.h"
21#include <QList>
22#include <QMouseEvent>
23#include <QPointF>
24#include <QPushButton>
25#include <QTabletEvent>
26#include <QTouchEvent>
27#include <QTransform>
28#include <kactioncollection.h>
29#include <kis_algebra_2d.h>
30#include <klocalizedstring.h>
31#include <ktoggleaction.h>
32
33#include <QApplication>
34#include <QPainter>
35#include <QPainterPath>
36
37static constexpr int BUTTON_SIZE = 30;
38static constexpr int BUFFER_SPACE = 5;
39
41
49
52 {
53 }
56
58 bool m_pressed = false;
59 bool m_visible = false;
60 bool m_enabled = true;
61
62 struct DragHandle {
63 QPoint position = QPoint(0, 0);
64 QPoint dragOrigin = QPoint(0, 0);
65 };
66
67 QScopedPointer<DragHandle> m_dragHandle;
68
72 {
73 static const QVector<ActionButtonData> data = {
74 {"select-all", i18n("Select All"), &KisSelectionManager::selectAll},
75 {"select-invert", i18n("Invert Selection"), &KisSelectionManager::invert},
76 {"select-clear", i18n("Deselect"), &KisSelectionManager::deselect},
77 {"krita_tool_color_fill", i18n("Fill Selection with Color"), &KisSelectionManager::fillForegroundColor},
78 {"draw-eraser", i18n("Clear Selection"), &KisSelectionManager::clear},
79 {"duplicatelayer", i18n("Copy To New Layer"), &KisSelectionManager::copySelectionToNewLayer},
80 {"tool_crop", i18n("Crop to Selection"), &KisSelectionManager::imageResizeToSelection}};
81 return data;
82 }
83 int m_buttonCount = buttonData().size() + 1; // buttons + drag handle
84
86};
87
89 : d(new Private)
90{
91 d->m_viewManager = viewManager;
92 d->m_selectionManager = viewManager->selectionManager();
93
94 // Setup buttons...
95 for (const ActionButtonData &buttonData : Private::buttonData()) {
96 KisSelectionActionsPanelButton *button = new KisSelectionActionsPanelButton(buttonData.iconName, buttonData.tooltip, BUTTON_SIZE, viewManager->canvas());
97 connect(button, &QAbstractButton::clicked, d->m_selectionManager, buttonData.slot);
98 d->m_buttons.append(button);
99 }
100
101 d->m_handleWidget = new KisSelectionActionsPanelHandle(BUTTON_SIZE, viewManager->canvas());
102}
103
107
108void KisSelectionActionsPanel::draw(QPainter &painter)
109{
110 KisSelectionSP selection = d->m_viewManager->selection();
111
112 if (!selection || !d->m_enabled || !d->m_visible) {
113 return;
114 }
115
117 Q_FOREACH(KisSelectionActionsPanelButton* button, d->m_buttons){
118 button->draw(painter);
119 }
120
121 d->m_handleWidget->draw(painter);
122}
123
125{
126 QWidget *canvasWidget = dynamic_cast<QWidget *>(d->m_viewManager->canvas());
127 if (!canvasWidget) {
128 return;
129 }
130
131 p_visible &= d->m_enabled;
132
133 const bool VISIBILITY_CHANGED = d->m_visible != p_visible;
134 if (!VISIBILITY_CHANGED) {
135 return;
136 }
137
138 if (d->m_viewManager->selection() && p_visible) { // Now visible!
139 d->m_handleWidget->installEventFilter(this);
140
141 if (!d->m_dragHandle) {
142 d->m_dragHandle.reset(new Private::DragHandle());
143 d->m_dragHandle->position = initialDragHandlePosition();
145 }
146 } else { // Now hidden!
147 d->m_handleWidget->removeEventFilter(this);
148
149 for (KisSelectionActionsPanelButton *button : d->m_buttons) {
150 button->hide();
151 }
152 d->m_handleWidget->hide();
153
154 d->m_pressed = false;
155 d->m_dragHandle.reset();
156 }
157
158 d->m_visible = p_visible;
159}
160
162{
163 bool configurationChanged = enabled != d->m_enabled;
164 d->m_enabled = enabled;
165 if (configurationChanged) {
166 // Reset visibility when configuration changes
167 setVisible(enabled);
168 }
169}
170
171bool KisSelectionActionsPanel::eventFilter(QObject *obj, QEvent *event)
172{
173 Q_UNUSED(obj);
174
175 switch (event->type()) {
176 case QEvent::MouseButtonPress: {
177 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
178 return handlePress(event, mouseEventPos(mouseEvent), mouseEvent->button());
179 }
180 case QEvent::TabletPress: {
181 const QTabletEvent *tabletEvent = static_cast<QTabletEvent *>(event);
182 return handlePress(event, tabletEventPos(tabletEvent));
183 }
184 case QEvent::TouchBegin: {
185 const QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event);
186 QPoint pos;
187 if (touchEventPos(touchEvent, pos)) {
188 return handlePress(event, pos);
189 }
190 break;
191 }
192
193 case QEvent::MouseMove:
194 if (d->m_pressed) {
195 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
196 return handleMove(event, mouseEventPos(mouseEvent));
197 }
198 break;
199 case QEvent::TabletMove:
200 if (d->m_pressed) {
201 const QTabletEvent *tabletEvent = static_cast<QTabletEvent *>(event);
202 return handleMove(event, tabletEventPos(tabletEvent));
203 }
204 break;
205 case QEvent::TouchUpdate:
206 if (d->m_pressed) {
207 const QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event);
208 QPoint pos;
209 if (touchEventPos(touchEvent, pos)) {
210 return handleMove(event, pos);
211 }
212 }
213 break;
214
215 case QEvent::MouseButtonRelease:
216 case QEvent::TabletRelease:
217 case QEvent::TouchEnd:
218 case QEvent::TouchCancel:
219 if (d->m_pressed) {
220 d->m_handleWidget->set_held(false);
221 d->m_pressed = false;
222 event->accept();
223 return true;
224 }
225 break;
226
227 default:
228 break;
229 }
230 return false;
231}
232
233
235{
236
237 Q_FOREACH(QWidget* btn, d->m_buttons) {
238 btn->setParent(canvas->widget());
239 btn->show();
240 }
241
242 d->m_handleWidget->setParent(canvas->widget());
243 d->m_handleWidget->show();
244}
245
246QPoint KisSelectionActionsPanel::updateCanvasBoundaries(QPoint position, QWidget *canvasWidget) const
247{
248 QRect canvasBounds = canvasWidget->rect();
249
250 const int ACTION_BAR_WIDTH = d->m_actionBarWidth;
251 const int ACTION_BAR_HEIGHT = BUTTON_SIZE;
252
253 int pos_x_min = canvasBounds.left() + BUFFER_SPACE;
254 int pos_x_max = canvasBounds.right() - ACTION_BAR_WIDTH - BUFFER_SPACE;
255
256 int pos_y_min = canvasBounds.top() + BUFFER_SPACE;
257 int pos_y_max = canvasBounds.bottom() - ACTION_BAR_HEIGHT - BUFFER_SPACE;
258
259 //Ensure that max is always bigger than min
260 //If the window is small enough max could be smaller than min
261 if (pos_x_max < pos_x_min) {
262 pos_x_max = pos_x_min;
263 }
264
265 //It is pretty implausible for it to happen vertically but better safe than sorry
266 if (pos_y_max < pos_y_min) {
267 pos_y_max = pos_y_min;
268 }
269
270 position.setX(qBound(pos_x_min, position.x(), pos_x_max));
271 position.setY(qBound(pos_y_min, position.y(), pos_y_max));
272
273 return position;
274}
275
277{
278 KisSelectionSP selection = d->m_viewManager->selection();
279 KisCanvasWidgetBase *canvas = dynamic_cast<KisCanvasWidgetBase*>(d->m_viewManager->canvas());
280 KIS_ASSERT(selection);
281 KIS_ASSERT(canvas);
282
283 QRectF selectionBounds = selection->selectedRect();
284 int selectionBottom = selectionBounds.bottom();
285 QPointF selectionCenter = selectionBounds.center();
286 QPointF bottomCenter(selectionCenter.x(), selectionBottom);
287
288 QPointF widgetBottomCenter = canvas->coordinatesConverter()->imageToWidget(bottomCenter); // converts current selection's QPointF into canvasWidget's QPointF space
289
290 widgetBottomCenter.setX(widgetBottomCenter.x() - (d->m_actionBarWidth / 2)); // centers toolbar midpoint with the selection center
291 widgetBottomCenter.setY(widgetBottomCenter.y() + BUFFER_SPACE);
292
293 return updateCanvasBoundaries(widgetBottomCenter.toPoint(), d->m_viewManager->canvas());
294}
295
297{
298 const int cornerRadius = 4;
299 QColor outlineColor = qApp->palette().window().color();
300
301 QColor bgColor = qApp->palette().window().color();
302
303 //Slightly lighten the color, to force `dragColor` to move it in the lighter direction, without this it will always darken the color
305 bgColor = bgColor.lighter(120);
306 }
307
308 KisPaintingTweaks::dragColor(&bgColor, outlineColor, 0.25);
309
310 QColor bgColorTrans = bgColor;
311 bgColorTrans.setAlpha(80);
312 const int outline_width = 4;
313
314 //an outer 1px wide outline for contrast against the background
315 QRectF contrastOutline(d->m_dragHandle->position - QPoint(outline_width + 1,outline_width + 1), QSize(d->m_actionBarWidth, BUTTON_SIZE) +QSize(outline_width + 1,outline_width + 1) * 2);
316 QRectF midOutline(d->m_dragHandle->position - QPoint(outline_width,outline_width), QSize(d->m_actionBarWidth, BUTTON_SIZE) +QSize(outline_width,outline_width) * 2);
317 //Add a bit of padding here for the icons
318 QRectF centerBackground(d->m_dragHandle->position - QPoint(outline_width,outline_width) / 2, QSize(d->m_actionBarWidth - BUTTON_SIZE, BUTTON_SIZE) +QSize(outline_width,outline_width));
319 QPainterPath bgPath;
320 QPainterPath outlinePath;
321 QPainterPath contrastOutlinePath;
322
323 bgPath.addRoundedRect(centerBackground, cornerRadius, cornerRadius);
324 outlinePath.addRoundedRect(midOutline, cornerRadius, cornerRadius);
325 contrastOutlinePath.addRoundedRect(contrastOutline, cornerRadius, cornerRadius);
326
327 painter.fillPath(contrastOutlinePath, bgColorTrans);
328 painter.fillPath(outlinePath, outlineColor);
329 painter.fillPath(bgPath, bgColor);
330}
331
332bool KisSelectionActionsPanel::handlePress(QEvent *event, const QPoint &pos, Qt::MouseButton button)
333{
334 if (d->m_pressed) {
335 event->accept();
336 return true;
337 }
338
339 if (button == Qt::LeftButton) {
340 d->m_pressed = true;
341 d->m_dragHandle->dragOrigin = pos - d->m_dragHandle->position;
342 d->m_handleWidget->set_held(true);
343
344 event->accept();
345 return true;
346 }
347
348 return false;
349}
350
351bool KisSelectionActionsPanel::handleMove(QEvent *event, const QPoint &pos)
352{
353 QWidget *canvasWidget = d->m_viewManager->canvas();
354 QPoint newPos = pos - d->m_dragHandle->dragOrigin;
355 d->m_dragHandle->position = updateCanvasBoundaries(newPos, canvasWidget);
357 canvasWidget->update();
358 event->accept();
359 return true;
360}
361
363{
364 d->m_handleWidget->move(d->m_dragHandle->position.x() + d->m_buttons.size() * BUTTON_SIZE, d->m_dragHandle->position.y());
365 d->m_handleWidget->show();
366
367 int i = 0;
368 Q_FOREACH(KisSelectionActionsPanelButton *button, d->m_buttons) {
369 int buttonPosition = i * BUTTON_SIZE;
370 button->move(d->m_dragHandle->position.x() + buttonPosition, d->m_dragHandle->position.y());
371 button->show();
372 i++;
373 }
374}
375
376QPoint KisSelectionActionsPanel::mouseEventPos(const QMouseEvent *mouseEvent)
377{
378#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
379 return transformHandleCoords(mouseEvent->position().toPoint());
380#else
381 return transformHandleCoords(mouseEvent->pos());
382#endif
383}
384
385QPoint KisSelectionActionsPanel::tabletEventPos(const QTabletEvent *tabletEvent)
386{
387#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
388 return transformHandleCoords(tabletEvent->position().toPoint());
389#else
390 return transformHandleCoords(tabletEvent->pos());
391#endif
392}
393
394bool KisSelectionActionsPanel::touchEventPos(const QTouchEvent *touchEvent, QPoint &outPos)
395{
396#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
397 if (touchEvent->pointCount() < 1) {
398 return false;
399 } else {
400 outPos = transformHandleCoords(touchEvent->points().first().position().toPoint());
401 return true;
402 }
403#else
404 const QList<QTouchEvent::TouchPoint> &touchPoints = touchEvent->touchPoints();
405 if (touchPoints.isEmpty()) {
406 return false;
407 } else {
408 outPos = transformHandleCoords(touchPoints.first().pos().toPoint());
409 return true;
410 }
411#endif
412}
413
415 return d->m_dragHandle->position + pos;
416}
virtual QWidget * widget()=0
KisCoordinatesConverter * coordinatesConverter() const
_Private::Traits< T >::Result imageToWidget(const T &obj) const
Custom widget for selection actions panel buttons, to prevent them being drawn over the pop-up palett...
bool handlePress(QEvent *event, const QPoint &pos, Qt::MouseButton button=Qt::LeftButton)
void movePanelWidgets()
Moves all the widgets that are a part of the panel.
QPoint updateCanvasBoundaries(QPoint position, QWidget *canvasWidget) const
bool eventFilter(QObject *obj, QEvent *event) override
QPoint tabletEventPos(const QTabletEvent *tabletEvent)
bool handleMove(QEvent *event, const QPoint &pos)
QPoint mouseEventPos(const QMouseEvent *mouseEvent)
void canvasWidgetChanged(KisCanvasWidgetBase *canvas)
bool touchEventPos(const QTouchEvent *touchEvent, QPoint &outPos)
void drawActionBarBackground(QPainter &gc) const
QWidget * canvas() const
Return the actual widget that is displaying the current image.
KisSelectionManager * selectionManager()
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
static constexpr int BUFFER_SPACE
static constexpr int BUTTON_SIZE
QString button(const QWheelEvent &ev)
void dragColor(QColor *color, const QColor &baseColor, qreal threshold)
void(KisSelectionManager::*)() TargetSlot
KisSelectionActionsPanelHandle * m_handleWidget
QList< KisSelectionActionsPanelButton * > m_buttons
static const QVector< ActionButtonData > & buttonData()
QRect selectedRect() const