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