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