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
30using std::nothrow;
31using namespace PerspectiveBoxStates;
32
33
34PerspectiveBox::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
64PerspectiveBox::~PerspectiveBox()
65{
66	_NotifyDeleted();
67
68	delete fCurrentCommand;
69	delete fDragLTState;
70	delete fDragRTState;
71	delete fDragLBState;
72	delete fDragRBState;
73}
74
75
76void
77PerspectiveBox::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
109bool
110PerspectiveBox::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
129void
130PerspectiveBox::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
145Command*
146PerspectiveBox::MouseUp()
147{
148	fDragging = false;
149	return FinishTransaction();
150}
151
152
153bool
154PerspectiveBox::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
173BRect
174PerspectiveBox::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
196BRect
197PerspectiveBox::TrackingBounds(BView* withinView)
198{
199	return withinView->Bounds();
200}
201
202
203// #pragma mark -
204
205
206void
207PerspectiveBox::ModifiersChanged(uint32 modifiers)
208{
209	fModifiers = modifiers;
210	if (fDragging && fCurrentState) {
211		fCurrentState->DragTo(fMousePos, fModifiers);
212	}
213}
214
215
216bool
217PerspectiveBox::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
230void
231PerspectiveBox::AttachedToView(BView* view)
232{
233	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
234}
235
236
237void
238PerspectiveBox::DetachedFromView(BView* view)
239{
240	view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET));
241}
242
243
244// pragma mark -
245
246
247void
248PerspectiveBox::ObjectChanged(const Observable* object)
249{
250}
251
252
253// pragma mark -
254
255
256void
257PerspectiveBox::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
275void
276PerspectiveBox::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
289Command*
290PerspectiveBox::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
306bool
307PerspectiveBox::AddListener(PerspectiveBoxListener* listener)
308{
309	if (listener && !fListeners.HasItem((void*)listener))
310		return fListeners.AddItem((void*)listener);
311	return false;
312}
313
314
315bool
316PerspectiveBox::RemoveListener(PerspectiveBoxListener* listener)
317{
318	return fListeners.RemoveItem((void*)listener);
319}
320
321
322// #pragma mark -
323
324
325void
326PerspectiveBox::_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
339DragState*
340PerspectiveBox::_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
372void
373PerspectiveBox::_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
403void
404PerspectiveBox::_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