xref: /haiku/src/apps/icon-o-matic/CanvasView.cpp (revision 55b40aa53a835472ec7952b138ae4256203d02e4)
1 /*
2  * Copyright 2006, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  */
8 
9 #include "CanvasView.h"
10 
11 #include <Bitmap.h>
12 #include <Region.h>
13 
14 #include <stdio.h>
15 
16 #include "ui_defines.h"
17 
18 #include "CommandStack.h"
19 #include "IconRenderer.h"
20 
21 using std::nothrow;
22 
23 // constructor
24 CanvasView::CanvasView(BRect frame)
25 	: StateView(frame, "canvas view", B_FOLLOW_ALL,
26 				B_WILL_DRAW | B_FRAME_EVENTS),
27 	  fBitmap(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)),
28 	  fBackground(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)),
29 	  fIcon(NULL),
30 	  fRenderer(new IconRenderer(fBitmap)),
31 	  fDirtyIconArea(fBitmap->Bounds()),
32 
33 	  fCanvasOrigin(0.0, 0.0),
34 	  fZoomLevel(1.0),
35 
36 	  fMouseFilterMode(SNAPPING_OFF),
37 
38 	  fOffsreenBitmap(NULL),
39 	  fOffsreenView(NULL)
40 {
41 	#if __HAIKU__
42 	SetFlags(Flags() | B_SUBPIXEL_PRECISE);
43 	#endif // __HAIKU__
44 
45 	_MakeBackground();
46 	fRenderer->SetBackground(fBackground);
47 }
48 
49 // destructor
50 CanvasView::~CanvasView()
51 {
52 	SetIcon(NULL);
53 	delete fRenderer;
54 	delete fBitmap;
55 	delete fBackground;
56 
57 	_FreeBackBitmap();
58 }
59 
60 // #pragma mark -
61 
62 // AttachedToWindow
63 void
64 CanvasView::AttachedToWindow()
65 {
66 	StateView::AttachedToWindow();
67 
68 	SetViewColor(B_TRANSPARENT_COLOR);
69 	SetLowColor(kStripesHigh);
70 	SetHighColor(kStripesLow);
71 
72 	_AllocBackBitmap(Bounds().Width(), Bounds().Height());
73 
74 	_SetZoom(8.0);
75 }
76 
77 // FrameResized
78 void
79 CanvasView::FrameResized(float width, float height)
80 {
81 	_AllocBackBitmap(width, height);
82 }
83 
84 // Draw
85 void
86 CanvasView::Draw(BRect updateRect)
87 {
88 	if (!fOffsreenView) {
89 		_DrawInto(this, updateRect);
90 	} else {
91 		BPoint boundsLeftTop = Bounds().LeftTop();
92 		if (fOffsreenBitmap->Lock()) {
93 			fOffsreenView->PushState();
94 
95 			// apply scrolling offset to offscreen view
96 			fOffsreenView->SetOrigin(-boundsLeftTop.x, -boundsLeftTop.y);
97 			// mirror the clipping of this view
98 			// to the offscreen view for performance
99 			BRegion clipping;
100 			GetClippingRegion(&clipping);
101 			fOffsreenView->ConstrainClippingRegion(&clipping);
102 			fOffsreenView->FillRect(updateRect, B_SOLID_LOW);
103 
104 			_DrawInto(fOffsreenView, updateRect);
105 
106 			fOffsreenView->PopState();
107 			fOffsreenView->Sync();
108 
109 			fOffsreenBitmap->Unlock();
110 		}
111 		// compensate scrolling offset in BView
112 		BRect bitmapRect = updateRect;
113 		bitmapRect.OffsetBy(-boundsLeftTop.x, -boundsLeftTop.y);
114 
115 		SetDrawingMode(B_OP_COPY);
116 		DrawBitmap(fOffsreenBitmap, bitmapRect, updateRect);
117 	}
118 }
119 
120 // #pragma mark -
121 
122 // MouseDown
123 void
124 CanvasView::MouseDown(BPoint where)
125 {
126 	if (!IsFocus())
127 		MakeFocus(true);
128 
129 	_FilterMouse(&where);
130 
131 	StateView::MouseDown(where);
132 }
133 
134 
135 // MouseMoved
136 void
137 CanvasView::MouseMoved(BPoint where, uint32 transit,
138 					   const BMessage* dragMessage)
139 {
140 	_FilterMouse(&where);
141 
142 	StateView::MouseMoved(where, transit, dragMessage);
143 }
144 
145 // #pragma mark -
146 
147 // ScrollOffsetChanged
148 void
149 CanvasView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset)
150 {
151 	BPoint offset = newOffset - oldOffset;
152 	ScrollBy(offset.x, offset.y);
153 
154 	MouseMoved(fMouseInfo.position + offset, fMouseInfo.transit, NULL);
155 }
156 
157 // VisibleSizeChanged
158 void
159 CanvasView::VisibleSizeChanged(float oldWidth, float oldHeight,
160 							   float newWidth, float newHeight)
161 {
162 }
163 
164 // #pragma mark -
165 
166 // AreaInvalidated
167 void
168 CanvasView::AreaInvalidated(const BRect& area)
169 {
170 	if (fDirtyIconArea.Contains(area))
171 		return;
172 
173 	fDirtyIconArea = fDirtyIconArea | area;
174 
175 	BRect viewArea(area);
176 	ConvertFromCanvas(&viewArea);
177 	Invalidate(viewArea);
178 }
179 
180 
181 // #pragma mark -
182 
183 // SetIcon
184 void
185 CanvasView::SetIcon(Icon* icon)
186 {
187 	if (fIcon == icon)
188 		return;
189 
190 	if (fIcon)
191 		fIcon->RemoveListener(this);
192 
193 	fIcon = icon;
194 	fRenderer->SetIcon(icon);
195 
196 	if (fIcon)
197 		fIcon->AddListener(this);
198 }
199 
200 // SetMouseFilterMode
201 void
202 CanvasView::SetMouseFilterMode(uint32 mode)
203 {
204 	if (fMouseFilterMode == mode)
205 		return;
206 
207 	fMouseFilterMode = mode;
208 	Invalidate(_CanvasRect());
209 }
210 
211 // ConvertFromCanvas
212 void
213 CanvasView::ConvertFromCanvas(BPoint* point) const
214 {
215 	point->x = point->x * fZoomLevel + fCanvasOrigin.x;
216 	point->y = point->y * fZoomLevel + fCanvasOrigin.y;
217 }
218 
219 // ConvertToCanvas
220 void
221 CanvasView::ConvertToCanvas(BPoint* point) const
222 {
223 	point->x = (point->x - fCanvasOrigin.x) / fZoomLevel;
224 	point->y = (point->y - fCanvasOrigin.y) / fZoomLevel;
225 }
226 
227 // ConvertFromCanvas
228 void
229 CanvasView::ConvertFromCanvas(BRect* r) const
230 {
231 	r->left = r->left * fZoomLevel + fCanvasOrigin.x;
232 	r->top = r->top * fZoomLevel + fCanvasOrigin.y;
233 	r->right++;
234 	r->bottom++;
235 	r->right = r->right * fZoomLevel + fCanvasOrigin.x;
236 	r->bottom = r->bottom * fZoomLevel + fCanvasOrigin.y;
237 	r->right--;
238 	r->bottom--;
239 }
240 
241 // ConvertToCanvas
242 void
243 CanvasView::ConvertToCanvas(BRect* r) const
244 {
245 	r->left = (r->left - fCanvasOrigin.x) / fZoomLevel;
246 	r->top = (r->top - fCanvasOrigin.y) / fZoomLevel;
247 	r->right = (r->right - fCanvasOrigin.x) / fZoomLevel;
248 	r->bottom = (r->bottom - fCanvasOrigin.y) / fZoomLevel;
249 }
250 
251 // #pragma mark -
252 
253 // _HandleKeyDown
254 bool
255 CanvasView::_HandleKeyDown(uint32 key, uint32 modifiers)
256 {
257 	switch (key) {
258 		case 'z':
259 		case 'y':
260 			if (modifiers & B_SHIFT_KEY)
261 				CommandStack()->Redo();
262 			else
263 				CommandStack()->Undo();
264 			break;
265 
266 		case '+':
267 			_SetZoom(_NextZoomInLevel(fZoomLevel));
268 			break;
269 		case '-':
270 			_SetZoom(_NextZoomOutLevel(fZoomLevel));
271 			break;
272 
273 		default:
274 			return StateView::_HandleKeyDown(key, modifiers);
275 	}
276 
277 	return true;
278 }
279 
280 // _CanvasRect()
281 BRect
282 CanvasView::_CanvasRect() const
283 {
284 	BRect r = fBitmap->Bounds();
285 	ConvertFromCanvas(&r);
286 	return r;
287 }
288 
289 // _AllocBackBitmap
290 void
291 CanvasView::_AllocBackBitmap(float width, float height)
292 {
293 	// sanity check
294 	if (width <= 0.0 || height <= 0.0)
295 		return;
296 
297 	if (fOffsreenBitmap) {
298 		// see if the bitmap needs to be expanded
299 		BRect b = fOffsreenBitmap->Bounds();
300 		if (b.Width() >= width && b.Height() >= height)
301 			return;
302 
303 		// it does; clean up:
304 		_FreeBackBitmap();
305 	}
306 
307 	BRect b(0.0, 0.0, width, height);
308 	fOffsreenBitmap = new (nothrow) BBitmap(b, B_RGB32, true);
309 	if (!fOffsreenBitmap) {
310 		fprintf(stderr, "CanvasView::_AllocBackBitmap(): failed to allocate\n");
311 		return;
312 	}
313 	if (fOffsreenBitmap->IsValid()) {
314 		fOffsreenView = new BView(b, 0, B_FOLLOW_NONE, B_WILL_DRAW);
315 		BFont font;
316 		GetFont(&font);
317 		fOffsreenView->SetFont(&font);
318 		fOffsreenView->SetHighColor(HighColor());
319 		fOffsreenView->SetLowColor(LowColor());
320 		fOffsreenView->SetFlags(Flags());
321 		fOffsreenBitmap->AddChild(fOffsreenView);
322 	} else {
323 		_FreeBackBitmap();
324 		fprintf(stderr, "CanvasView::_AllocBackBitmap(): bitmap invalid\n");
325 	}
326 }
327 
328 // _FreeBackBitmap
329 void
330 CanvasView::_FreeBackBitmap()
331 {
332 	if (fOffsreenBitmap) {
333 		delete fOffsreenBitmap;
334 		fOffsreenBitmap = NULL;
335 		fOffsreenView = NULL;
336 	}
337 }
338 
339 // _DrawInto
340 void
341 CanvasView::_DrawInto(BView* view, BRect updateRect)
342 {
343 	if (fDirtyIconArea.IsValid()) {
344 		fRenderer->Render(fDirtyIconArea);
345 		fDirtyIconArea.Set(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN);
346 	}
347 
348 	// icon
349 	BRect canvas(_CanvasRect());
350 	view->DrawBitmap(fBitmap, fBitmap->Bounds(), canvas);
351 
352 	// grid
353 	int32 gridLines = 0;
354 	int32 scale = 1;
355 	switch (fMouseFilterMode) {
356 		case SNAPPING_64:
357 			gridLines = 63;
358 			break;
359 		case SNAPPING_32:
360 			gridLines = 31;
361 			scale = 2;
362 			break;
363 		case SNAPPING_16:
364 			gridLines = 15;
365 			scale = 4;
366 			break;
367 		case SNAPPING_OFF:
368 		default:
369 			break;
370 	}
371 	view->SetDrawingMode(B_OP_BLEND);
372 	for (int32 i = 1; i <= gridLines; i++) {
373 		BPoint cross(i * scale, i * scale);
374 		ConvertFromCanvas(&cross);
375 		view->StrokeLine(BPoint(canvas.left, cross.y),
376 						 BPoint(canvas.right, cross.y));
377 		view->StrokeLine(BPoint(cross.x, canvas.top),
378 						 BPoint(cross.x, canvas.bottom));
379 	}
380 	view->SetDrawingMode(B_OP_COPY);
381 
382 	// outside icon
383 	BRegion outside(Bounds() & updateRect);
384 	outside.Exclude(canvas);
385 	view->FillRegion(&outside, kStripes);
386 
387 	StateView::Draw(view, updateRect);
388 }
389 
390 // _FilterMouse
391 void
392 CanvasView::_FilterMouse(BPoint* where) const
393 {
394 	switch (fMouseFilterMode) {
395 
396 		case SNAPPING_64:
397 			ConvertToCanvas(where);
398 			where->x = floorf(where->x + 0.5);
399 			where->y = floorf(where->y + 0.5);
400 			ConvertFromCanvas(where);
401 			break;
402 
403 		case SNAPPING_32:
404 			ConvertToCanvas(where);
405 			where->x /= 2.0;
406 			where->y /= 2.0;
407 			where->x = floorf(where->x + 0.5);
408 			where->y = floorf(where->y + 0.5);
409 			where->x *= 2.0;
410 			where->y *= 2.0;
411 			ConvertFromCanvas(where);
412 			break;
413 
414 		case SNAPPING_16:
415 			ConvertToCanvas(where);
416 			where->x /= 4.0;
417 			where->y /= 4.0;
418 			where->x = floorf(where->x + 0.5);
419 			where->y = floorf(where->y + 0.5);
420 			where->x *= 4.0;
421 			where->y *= 4.0;
422 			ConvertFromCanvas(where);
423 			break;
424 
425 		case SNAPPING_OFF:
426 		default:
427 			break;
428 	}
429 }
430 
431 // _MakeBackground
432 void
433 CanvasView::_MakeBackground()
434 {
435 	uint8* row = (uint8*)fBackground->Bits();
436 	uint32 bpr = fBackground->BytesPerRow();
437 	uint32 width = fBackground->Bounds().IntegerWidth() + 1;
438 	uint32 height = fBackground->Bounds().IntegerHeight() + 1;
439 
440 	const GammaTable& lut = fRenderer->GammaTable();
441 	uint8 redLow = lut.dir(kAlphaLow.red);
442 	uint8 greenLow = lut.dir(kAlphaLow.blue);
443 	uint8 blueLow = lut.dir(kAlphaLow.green);
444 	uint8 redHigh = lut.dir(kAlphaHigh.red);
445 	uint8 greenHigh = lut.dir(kAlphaHigh.blue);
446 	uint8 blueHigh = lut.dir(kAlphaHigh.green);
447 
448 	for (uint32 y = 0; y < height; y++) {
449 		uint8* p = row;
450 		for (uint32 x = 0; x < width; x++) {
451 			p[3] = 255;
452 			if (x % 8 >= 4) {
453 				if (y % 8 >= 4) {
454 					p[0] = blueLow;
455 					p[1] = greenLow;
456 					p[2] = redLow;
457 				} else {
458 					p[0] = blueHigh;
459 					p[1] = greenHigh;
460 					p[2] = redHigh;
461 				}
462 			} else {
463 				if (y % 8 >= 4) {
464 					p[0] = blueHigh;
465 					p[1] = greenHigh;
466 					p[2] = redHigh;
467 				} else {
468 					p[0] = blueLow;
469 					p[1] = greenLow;
470 					p[2] = redLow;
471 				}
472 			}
473 			p += 4;
474 		}
475 		row += bpr;
476 	}
477 }
478 
479 // #pragma mark -
480 
481 // _NextZoomInLevel
482 double
483 CanvasView::_NextZoomInLevel(double zoom) const
484 {
485 	if (zoom < 1)
486 		return 1;
487 	if (zoom < 1.5)
488 		return 1.5;
489 	if (zoom < 2)
490 		return 2;
491 	if (zoom < 3)
492 		return 3;
493 	if (zoom < 4)
494 		return 4;
495 	if (zoom < 6)
496 		return 6;
497 	if (zoom < 8)
498 		return 8;
499 	if (zoom < 16)
500 		return 16;
501 	if (zoom < 32)
502 		return 32;
503 	return 64;
504 }
505 
506 // _NextZoomOutLevel
507 double
508 CanvasView::_NextZoomOutLevel(double zoom) const
509 {
510 	if (zoom > 32)
511 		return 32;
512 	if (zoom > 16)
513 		return 16;
514 	if (zoom > 8)
515 		return 8;
516 	if (zoom > 6)
517 		return 6;
518 	if (zoom > 4)
519 		return 4;
520 	if (zoom > 3)
521 		return 3;
522 	if (zoom > 2)
523 		return 2;
524 	if (zoom > 1.5)
525 		return 1.5;
526 	return 1;
527 }
528 
529 // _SetZoom
530 void
531 CanvasView::_SetZoom(double zoomLevel)
532 {
533 	if (fZoomLevel == zoomLevel)
534 		return;
535 
536 	// zoom into mouse position, or into center of view
537 	BPoint anchor = MouseInfo()->position;
538 	BRect bounds(Bounds());
539 	if (!bounds.Contains(anchor)) {
540 		bounds = _CanvasRect();
541 		anchor.x = (bounds.left + bounds.right) / 2.0;
542 		anchor.y = (bounds.top + bounds.bottom) / 2.0;
543 	}
544 printf("anchor: %.2f, %.2f\n", anchor.x, anchor.y);
545 
546 	BPoint offset;
547 	if (fZoomLevel < zoomLevel) {
548 		offset.x = anchor.x * (zoomLevel / fZoomLevel) - anchor.x;
549 		offset.y = anchor.y * (zoomLevel / fZoomLevel) - anchor.y;
550 	} else {
551 		offset.x = -anchor.x * (zoomLevel / fZoomLevel);
552 		offset.y = -anchor.y * (zoomLevel / fZoomLevel);
553 	}
554 
555 	fZoomLevel = zoomLevel;
556 	SetDataRect(_LayoutCanvas());
557 
558 printf("offset: %.2f, %.2f\n", offset.x, offset.y);
559 
560 	SetScrollOffset(ScrollOffset() + offset);
561 
562 	Invalidate();
563 }
564 
565 // _LayoutCanvas
566 BRect
567 CanvasView::_LayoutCanvas()
568 {
569 	if (!fBitmap)
570 		return BRect(0, 0, -1, -1);
571 
572 	// size of zoomed bitmap
573 	BRect r(fBitmap->Bounds());
574 	r.OffsetTo(0, 0);
575 	r.right = floorf((r.Width() + 1) * fZoomLevel + 0.5) - 1;
576 	r.bottom = floorf((r.Height() + 1) * fZoomLevel + 0.5) - 1;
577 
578 	// TODO: ask manipulators to extend size
579 
580 	// left top of canvas within empty area
581 	fCanvasOrigin.x = floorf(r.Width() * 0.25);
582 	fCanvasOrigin.y = floorf(r.Height() * 0.25);
583 
584 	// resize for empty area around bitmap
585 	r.right += r.Width() * 0.5;
586 	r.bottom += r.Height() * 0.5;
587 
588 	return r;
589 }
590 
591