xref: /haiku/src/apps/icon-o-matic/transformable/PerspectiveBox.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2006-2009, 2023, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  *		Zardshard
8  */
9 
10 #include "PerspectiveBox.h"
11 
12 #include <stdio.h>
13 
14 #include <agg_trans_affine.h>
15 #include <agg_math.h>
16 
17 #include <View.h>
18 
19 #include "CanvasView.h"
20 #include "StateView.h"
21 #include "support.h"
22 #include "PerspectiveBoxStates.h"
23 #include "PerspectiveCommand.h"
24 #include "PerspectiveTransformer.h"
25 
26 
27 #define INSET 8.0
28 
29 
30 using std::nothrow;
31 using namespace PerspectiveBoxStates;
32 
33 
34 PerspectiveBox::PerspectiveBox(CanvasView* view,
35 		PerspectiveTransformer* parent)
36 	:
37 	Manipulator(NULL),
38 
39 	fLeftTop(parent->LeftTop()),
40 	fRightTop(parent->RightTop()),
41 	fLeftBottom(parent->LeftBottom()),
42 	fRightBottom(parent->RightBottom()),
43 
44 	fCurrentCommand(NULL),
45 	fCurrentState(NULL),
46 
47 	fDragging(false),
48 	fMousePos(-10000.0, -10000.0),
49 	fModifiers(0),
50 
51 	fPreviousBox(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN),
52 
53 	fCanvasView(view),
54 	fPerspective(parent),
55 
56 	fDragLTState(new DragCornerState(this, &fLeftTop)),
57 	fDragRTState(new DragCornerState(this, &fRightTop)),
58 	fDragLBState(new DragCornerState(this, &fLeftBottom)),
59 	fDragRBState(new DragCornerState(this, &fRightBottom))
60 {
61 }
62 
63 
64 PerspectiveBox::~PerspectiveBox()
65 {
66 	_NotifyDeleted();
67 
68 	delete fCurrentCommand;
69 	delete fDragLTState;
70 	delete fDragRTState;
71 	delete fDragLBState;
72 	delete fDragRBState;
73 }
74 
75 
76 void
77 PerspectiveBox::Draw(BView* into, BRect updateRect)
78 {
79 	// convert to canvas view coordinates
80 	BPoint lt = fLeftTop;
81 	BPoint rt = fRightTop;
82 	BPoint lb = fLeftBottom;
83 	BPoint rb = fRightBottom;
84 
85 	fCanvasView->ConvertFromCanvas(&lt);
86 	fCanvasView->ConvertFromCanvas(&rt);
87 	fCanvasView->ConvertFromCanvas(&lb);
88 	fCanvasView->ConvertFromCanvas(&rb);
89 
90 	into->SetDrawingMode(B_OP_COPY);
91 	into->SetHighColor(255, 255, 255, 255);
92 	into->SetLowColor(0, 0, 0, 255);
93 
94 	_StrokeBWLine(into, lt, rt);
95 	_StrokeBWLine(into, rt, rb);
96 	_StrokeBWLine(into, rb, lb);
97 	_StrokeBWLine(into, lb, lt);
98 
99 	_StrokeBWPoint(into, lt, 0.0);
100 	_StrokeBWPoint(into, rt, 90.0);
101 	_StrokeBWPoint(into, rb, 180.0);
102 	_StrokeBWPoint(into, lb, 270.0);
103 }
104 
105 
106 // #pragma mark -
107 
108 
109 bool
110 PerspectiveBox::MouseDown(BPoint where)
111 {
112 	fCanvasView->FilterMouse(&where);
113 	fCanvasView->ConvertToCanvas(&where);
114 
115 	fDragging = true;
116 	if (fCurrentState) {
117 		fCurrentState->SetOrigin(where);
118 
119 		delete fCurrentCommand;
120 		fCurrentCommand = new (nothrow) PerspectiveCommand(this, fPerspective,
121 			fPerspective->LeftTop(), fPerspective->RightTop(),
122 			fPerspective->LeftBottom(), fPerspective->RightBottom());
123 	}
124 
125 	return true;
126 }
127 
128 
129 void
130 PerspectiveBox::MouseMoved(BPoint where)
131 {
132 	fCanvasView->FilterMouse(&where);
133 	fCanvasView->ConvertToCanvas(&where);
134 
135 	if (fMousePos != where) {
136 		fMousePos = where;
137 		if (fCurrentState) {
138 			fCurrentState->DragTo(fMousePos, fModifiers);
139 			fCurrentState->UpdateViewCursor(fCanvasView, fMousePos);
140 		}
141 	}
142 }
143 
144 
145 Command*
146 PerspectiveBox::MouseUp()
147 {
148 	fDragging = false;
149 	return FinishTransaction();
150 }
151 
152 
153 bool
154 PerspectiveBox::MouseOver(BPoint where)
155 {
156 	fCanvasView->ConvertToCanvas(&where);
157 
158 	fMousePos = where;
159 	fCurrentState = _DragStateFor(where, fCanvasView->ZoomLevel());
160 
161 	if (fCurrentState) {
162 		fCurrentState->UpdateViewCursor(fCanvasView, fMousePos);
163 		return true;
164 	}
165 
166 	return false;
167 }
168 
169 
170 // #pragma mark -
171 
172 
173 BRect
174 PerspectiveBox::Bounds()
175 {
176 	// convert from canvas view coordinates
177 	BPoint lt = fLeftTop;
178 	BPoint rt = fRightTop;
179 	BPoint lb = fLeftBottom;
180 	BPoint rb = fRightBottom;
181 
182 	fCanvasView->ConvertFromCanvas(&lt);
183 	fCanvasView->ConvertFromCanvas(&rt);
184 	fCanvasView->ConvertFromCanvas(&lb);
185 	fCanvasView->ConvertFromCanvas(&rb);
186 
187 	BRect bounds;
188 	bounds.left = min4(lt.x, rt.x, lb.x, rb.x);
189 	bounds.top = min4(lt.y, rt.y, lb.y, rb.y);
190 	bounds.right = max4(lt.x, rt.x, lb.x, rb.x);
191 	bounds.bottom = max4(lt.y, rt.y, lb.y, rb.y);
192 	return bounds;
193 }
194 
195 
196 BRect
197 PerspectiveBox::TrackingBounds(BView* withinView)
198 {
199 	return withinView->Bounds();
200 }
201 
202 
203 // #pragma mark -
204 
205 
206 void
207 PerspectiveBox::ModifiersChanged(uint32 modifiers)
208 {
209 	fModifiers = modifiers;
210 	if (fDragging && fCurrentState) {
211 		fCurrentState->DragTo(fMousePos, fModifiers);
212 	}
213 }
214 
215 
216 bool
217 PerspectiveBox::UpdateCursor()
218 {
219 	if (fCurrentState) {
220 		fCurrentState->UpdateViewCursor(fCanvasView, fMousePos);
221 		return true;
222 	}
223 	return false;
224 }
225 
226 
227 // #pragma mark -
228 
229 
230 void
231 PerspectiveBox::AttachedToView(BView* view)
232 {
233 	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
234 }
235 
236 
237 void
238 PerspectiveBox::DetachedFromView(BView* view)
239 {
240 	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
241 }
242 
243 
244 // pragma mark -
245 
246 
247 void
248 PerspectiveBox::ObjectChanged(const Observable* object)
249 {
250 }
251 
252 
253 // pragma mark -
254 
255 
256 void
257 PerspectiveBox::TransformTo(
258 	BPoint leftTop, BPoint rightTop, BPoint leftBottom, BPoint rightBottom)
259 {
260 	if (fLeftTop == leftTop
261 		&& fRightTop == rightTop
262 		&& fLeftBottom == leftBottom
263 		&& fRightBottom == rightBottom)
264 		return;
265 
266 	fLeftTop = leftTop;
267 	fRightTop = rightTop;
268 	fLeftBottom = leftBottom;
269 	fRightBottom = rightBottom;
270 
271 	Update();
272 }
273 
274 
275 void
276 PerspectiveBox::Update(bool deep)
277 {
278 	BRect r = Bounds();
279 	BRect dirty(r | fPreviousBox);
280 	dirty.InsetBy(-INSET, -INSET);
281 	fCanvasView->Invalidate(dirty);
282 	fPreviousBox = r;
283 
284 	if (deep)
285 		fPerspective->TransformTo(fLeftTop, fRightTop, fLeftBottom, fRightBottom);
286 }
287 
288 
289 Command*
290 PerspectiveBox::FinishTransaction()
291 {
292 	Command* command = fCurrentCommand;
293 	if (fCurrentCommand) {
294 		fCurrentCommand->SetNewPerspective(
295 			fPerspective->LeftTop(), fPerspective->RightTop(),
296 			fPerspective->LeftBottom(), fPerspective->RightBottom());
297 		fCurrentCommand = NULL;
298 	}
299 	return command;
300 }
301 
302 
303 // #pragma mark -
304 
305 
306 bool
307 PerspectiveBox::AddListener(PerspectiveBoxListener* listener)
308 {
309 	if (listener && !fListeners.HasItem((void*)listener))
310 		return fListeners.AddItem((void*)listener);
311 	return false;
312 }
313 
314 
315 bool
316 PerspectiveBox::RemoveListener(PerspectiveBoxListener* listener)
317 {
318 	return fListeners.RemoveItem((void*)listener);
319 }
320 
321 
322 // #pragma mark -
323 
324 
325 void
326 PerspectiveBox::_NotifyDeleted() const
327 {
328 	BList listeners(fListeners);
329 	int32 count = listeners.CountItems();
330 	for (int32 i = 0; i < count; i++) {
331 		PerspectiveBoxListener* listener
332 			= (PerspectiveBoxListener*)listeners.ItemAtFast(i);
333 		listener->PerspectiveBoxDeleted(this);
334 	}
335 }
336 
337 
338 //! where is expected in canvas view coordinates
339 DragState*
340 PerspectiveBox::_DragStateFor(BPoint where, float canvasZoom)
341 {
342 	DragState* state = NULL;
343 
344 	// convert to canvas zoom level
345 	//
346 	// the conversion is necessary, because the "hot regions"
347 	// around a point should be the same size no matter what
348 	// zoom level the canvas is displayed at
349 	float inset = INSET / canvasZoom;
350 
351 	// check if the cursor is over the corners
352 	float dLT = point_point_distance(fLeftTop, where);
353 	float dRT = point_point_distance(fRightTop, where);
354 	float dLB = point_point_distance(fLeftBottom, where);
355 	float dRB = point_point_distance(fRightBottom, where);
356 	float d = min4(dLT, dRT, dLB, dRB);
357 	if (d < inset) {
358 		if (d == dLT)
359 			state = fDragLTState;
360 		else if (d == dRT)
361 			state = fDragRTState;
362 		else if (d == dLB)
363 			state = fDragLBState;
364 		else if (d == dRB)
365 			state = fDragRBState;
366 	}
367 
368 	return state;
369 }
370 
371 
372 void
373 PerspectiveBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const
374 {
375 	// find out how to offset the second line optimally
376 	BPoint offset(0.0, 0.0);
377 	// first, do we have a more horizontal line or a more vertical line?
378 	float xDiff = to.x - from.x;
379 	float yDiff = to.y - from.y;
380 	if (fabs(xDiff) > fabs(yDiff)) {
381 		// horizontal
382 		if (xDiff > 0.0) {
383 			offset.y = -1.0;
384 		} else {
385 			offset.y = 1.0;
386 		}
387 	} else {
388 		// vertical
389 		if (yDiff < 0.0) {
390 			offset.x = -1.0;
391 		} else {
392 			offset.x = 1.0;
393 		}
394 	}
395 	// stroke two lines in high and low color of the view
396 	into->StrokeLine(from, to, B_SOLID_LOW);
397 	from += offset;
398 	to += offset;
399 	into->StrokeLine(from, to, B_SOLID_HIGH);
400 }
401 
402 
403 void
404 PerspectiveBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const
405 {
406 	double x = point.x;
407 	double y = point.y;
408 
409 	double x1 = x;
410 	double y1 = y - 5.0;
411 
412 	double x2 = x - 5.0;
413 	double y2 = y - 5.0;
414 
415 	double x3 = x - 5.0;
416 	double y3 = y;
417 
418 	agg::trans_affine m;
419 
420 	double xOffset = -x;
421 	double yOffset = -y;
422 
423 	agg::trans_affine_rotation r(angle * M_PI / 180.0);
424 
425 	r.transform(&xOffset, &yOffset);
426 	xOffset = x + xOffset;
427 	yOffset = y + yOffset;
428 
429 	m.multiply(r);
430 	m.multiply(agg::trans_affine_translation(xOffset, yOffset));
431 
432 	m.transform(&x, &y);
433 	m.transform(&x1, &y1);
434 	m.transform(&x2, &y2);
435 	m.transform(&x3, &y3);
436 
437 	BPoint p[4];
438 	p[0] = BPoint(x, y);
439 	p[1] = BPoint(x1, y1);
440 	p[2] = BPoint(x2, y2);
441 	p[3] = BPoint(x3, y3);
442 
443 	into->FillPolygon(p, 4, B_SOLID_HIGH);
444 
445 	into->StrokeLine(p[0], p[1], B_SOLID_LOW);
446 	into->StrokeLine(p[1], p[2], B_SOLID_LOW);
447 	into->StrokeLine(p[2], p[3], B_SOLID_LOW);
448 	into->StrokeLine(p[3], p[0], B_SOLID_LOW);
449 }
450