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(<); 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(<); 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