/* * Copyright 2006-2007, 2011, Stephan Aßmus * All rights reserved. Distributed under the terms of the MIT License. */ #include "CanvasView.h" #include #include #include #include #include #include #include "cursors.h" #include "ui_defines.h" #include "CommandStack.h" #include "IconRenderer.h" using std::nothrow; CanvasView::CanvasView(BRect frame) : StateView(frame, "canvas view", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS), fBitmap(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)), fBackground(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)), fIcon(NULL), fRenderer(new IconRenderer(fBitmap)), fDirtyIconArea(fBitmap->Bounds()), fCanvasOrigin(0.0, 0.0), fZoomLevel(8.0), fSpaceHeldDown(false), fInScrollTo(false), fScrollTracking(false), fScrollTrackingStart(0.0, 0.0), fMouseFilterMode(SNAPPING_OFF) { _MakeBackground(); fRenderer->SetBackground(fBackground); } CanvasView::~CanvasView() { SetIcon(NULL); delete fRenderer; delete fBitmap; delete fBackground; } // #pragma mark - void CanvasView::AttachedToWindow() { StateView::AttachedToWindow(); SetViewColor(B_TRANSPARENT_COLOR); SetLowColor(kStripesHigh); SetHighColor(kStripesLow); // init data rect for scrolling and center bitmap in the view BRect dataRect = _LayoutCanvas(); SetDataRect(dataRect); BRect bounds(Bounds()); BPoint dataRectCenter((dataRect.left + dataRect.right) / 2, (dataRect.top + dataRect.bottom) / 2); BPoint boundsCenter((bounds.left + bounds.right) / 2, (bounds.top + bounds.bottom) / 2); BPoint offset = ScrollOffset(); offset.x = roundf(offset.x + dataRectCenter.x - boundsCenter.x); offset.y = roundf(offset.y + dataRectCenter.y - boundsCenter.y); SetScrollOffset(offset); } void CanvasView::FrameResized(float width, float height) { // keep canvas centered BPoint oldCanvasOrigin = fCanvasOrigin; SetDataRect(_LayoutCanvas()); if (oldCanvasOrigin != fCanvasOrigin) Invalidate(); } void CanvasView::Draw(BRect updateRect) { _DrawInto(this, updateRect); } // #pragma mark - void CanvasView::MouseDown(BPoint where) { if (!IsFocus()) MakeFocus(true); int32 buttons; if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) < B_OK) buttons = 0; // handle clicks of the third mouse button ourselves (panning), // otherwise have StateView handle it (normal clicks) if (fSpaceHeldDown || (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) { // switch into scrolling mode and update cursor fScrollTracking = true; where.x = roundf(where.x); where.y = roundf(where.y); fScrollOffsetStart = ScrollOffset(); fScrollTrackingStart = where - fScrollOffsetStart; _UpdateToolCursor(); SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | B_SUSPEND_VIEW_FOCUS); } else { StateView::MouseDown(where); } } void CanvasView::MouseUp(BPoint where) { if (fScrollTracking) { // stop scroll tracking and update cursor fScrollTracking = false; _UpdateToolCursor(); // update StateView mouse position uint32 transit = Bounds().Contains(where) ? B_INSIDE_VIEW : B_OUTSIDE_VIEW; StateView::MouseMoved(where, transit, NULL); } else { StateView::MouseUp(where); } } void CanvasView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) { if (fScrollTracking) { uint32 buttons; GetMouse(&where, &buttons, false); if (!buttons) { MouseUp(where); return; } where.x = roundf(where.x); where.y = roundf(where.y); where -= ScrollOffset(); BPoint offset = where - fScrollTrackingStart; SetScrollOffset(fScrollOffsetStart - offset); } else { // normal mouse movement handled by StateView if (!fSpaceHeldDown) StateView::MouseMoved(where, transit, dragMessage); } } void CanvasView::FilterMouse(BPoint* where) const { switch (fMouseFilterMode) { case SNAPPING_64: ConvertToCanvas(where); where->x = floorf(where->x + 0.5); where->y = floorf(where->y + 0.5); ConvertFromCanvas(where); break; case SNAPPING_32: ConvertToCanvas(where); where->x /= 2.0; where->y /= 2.0; where->x = floorf(where->x + 0.5); where->y = floorf(where->y + 0.5); where->x *= 2.0; where->y *= 2.0; ConvertFromCanvas(where); break; case SNAPPING_16: ConvertToCanvas(where); where->x /= 4.0; where->y /= 4.0; where->x = floorf(where->x + 0.5); where->y = floorf(where->y + 0.5); where->x *= 4.0; where->y *= 4.0; ConvertFromCanvas(where); break; case SNAPPING_OFF: default: break; } } bool CanvasView::MouseWheelChanged(BPoint where, float x, float y) { if (!Bounds().Contains(where)) return false; if (y > 0.0) { _SetZoom(_NextZoomOutLevel(fZoomLevel), true); return true; } else if (y < 0.0) { _SetZoom(_NextZoomInLevel(fZoomLevel), true); return true; } return false; } // #pragma mark - void CanvasView::SetScrollOffset(BPoint newOffset) { if (fInScrollTo) return; fInScrollTo = true; newOffset = ValidScrollOffsetFor(newOffset); if (!fScrollTracking) { BPoint mouseOffset = newOffset - ScrollOffset(); MouseMoved(fMouseInfo.position + mouseOffset, fMouseInfo.transit, NULL); } Scrollable::SetScrollOffset(newOffset); fInScrollTo = false; } void CanvasView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset) { BPoint offset = newOffset - oldOffset; if (offset == B_ORIGIN) { // prevent circular code (MouseMoved might call ScrollBy...) return; } ScrollBy(offset.x, offset.y); } void CanvasView::VisibleSizeChanged(float oldWidth, float oldHeight, float newWidth, float newHeight) { BRect dataRect(_LayoutCanvas()); SetDataRect(dataRect); } // #pragma mark - void CanvasView::AreaInvalidated(const BRect& area) { if (fDirtyIconArea.Contains(area)) return; fDirtyIconArea = fDirtyIconArea | area; BRect viewArea(area); ConvertFromCanvas(&viewArea); Invalidate(viewArea); } // #pragma mark - void CanvasView::SetIcon(Icon* icon) { if (fIcon == icon) return; if (fIcon) fIcon->RemoveListener(this); fIcon = icon; fRenderer->SetIcon(icon); if (fIcon) fIcon->AddListener(this); } void CanvasView::SetMouseFilterMode(uint32 mode) { if (fMouseFilterMode == mode) return; fMouseFilterMode = mode; Invalidate(_CanvasRect()); } void CanvasView::ConvertFromCanvas(BPoint* point) const { point->x = point->x * fZoomLevel + fCanvasOrigin.x; point->y = point->y * fZoomLevel + fCanvasOrigin.y; } void CanvasView::ConvertToCanvas(BPoint* point) const { point->x = (point->x - fCanvasOrigin.x) / fZoomLevel; point->y = (point->y - fCanvasOrigin.y) / fZoomLevel; } void CanvasView::ConvertFromCanvas(BRect* r) const { r->left = r->left * fZoomLevel + fCanvasOrigin.x; r->top = r->top * fZoomLevel + fCanvasOrigin.y; r->right++; r->bottom++; r->right = r->right * fZoomLevel + fCanvasOrigin.x; r->bottom = r->bottom * fZoomLevel + fCanvasOrigin.y; r->right--; r->bottom--; } void CanvasView::ConvertToCanvas(BRect* r) const { r->left = (r->left - fCanvasOrigin.x) / fZoomLevel; r->top = (r->top - fCanvasOrigin.y) / fZoomLevel; r->right = (r->right - fCanvasOrigin.x) / fZoomLevel; r->bottom = (r->bottom - fCanvasOrigin.y) / fZoomLevel; } // #pragma mark - bool CanvasView::_HandleKeyDown(uint32 key, uint32 modifiers) { switch (key) { case 'z': case 'y': if (modifiers & B_SHIFT_KEY) CommandStack()->Redo(); else CommandStack()->Undo(); break; case '+': _SetZoom(_NextZoomInLevel(fZoomLevel)); break; case '-': _SetZoom(_NextZoomOutLevel(fZoomLevel)); break; case B_SPACE: fSpaceHeldDown = true; _UpdateToolCursor(); break; default: return StateView::_HandleKeyDown(key, modifiers); } return true; } bool CanvasView::_HandleKeyUp(uint32 key, uint32 modifiers) { switch (key) { case B_SPACE: fSpaceHeldDown = false; _UpdateToolCursor(); break; default: return StateView::_HandleKeyUp(key, modifiers); } return true; } BRect CanvasView::_CanvasRect() const { BRect r; if (fBitmap == NULL) return r; r = fBitmap->Bounds(); ConvertFromCanvas(&r); return r; } void CanvasView::_DrawInto(BView* view, BRect updateRect) { if (fDirtyIconArea.IsValid()) { fRenderer->Render(fDirtyIconArea, true); fDirtyIconArea.Set(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN); } // icon BRect canvas(_CanvasRect()); view->DrawBitmap(fBitmap, fBitmap->Bounds(), canvas); // grid int32 gridLines = 0; int32 scale = 1; switch (fMouseFilterMode) { case SNAPPING_64: gridLines = 63; break; case SNAPPING_32: gridLines = 31; scale = 2; break; case SNAPPING_16: gridLines = 15; scale = 4; break; case SNAPPING_OFF: default: break; } view->SetDrawingMode(B_OP_BLEND); for (int32 i = 1; i <= gridLines; i++) { BPoint cross(i * scale, i * scale); ConvertFromCanvas(&cross); view->StrokeLine(BPoint(canvas.left, cross.y), BPoint(canvas.right, cross.y)); view->StrokeLine(BPoint(cross.x, canvas.top), BPoint(cross.x, canvas.bottom)); } view->SetDrawingMode(B_OP_COPY); // outside icon BRegion outside(Bounds() & updateRect); outside.Exclude(canvas); view->FillRegion(&outside, kStripes); StateView::Draw(view, updateRect); } void CanvasView::_MakeBackground() { uint8* row = (uint8*)fBackground->Bits(); uint32 bpr = fBackground->BytesPerRow(); uint32 width = fBackground->Bounds().IntegerWidth() + 1; uint32 height = fBackground->Bounds().IntegerHeight() + 1; const GammaTable& lut = fRenderer->GammaTable(); uint8 redLow = lut.dir(kAlphaLow.red); uint8 greenLow = lut.dir(kAlphaLow.blue); uint8 blueLow = lut.dir(kAlphaLow.green); uint8 redHigh = lut.dir(kAlphaHigh.red); uint8 greenHigh = lut.dir(kAlphaHigh.blue); uint8 blueHigh = lut.dir(kAlphaHigh.green); for (uint32 y = 0; y < height; y++) { uint8* p = row; for (uint32 x = 0; x < width; x++) { p[3] = 255; if (x % 8 >= 4) { if (y % 8 >= 4) { p[0] = blueLow; p[1] = greenLow; p[2] = redLow; } else { p[0] = blueHigh; p[1] = greenHigh; p[2] = redHigh; } } else { if (y % 8 >= 4) { p[0] = blueHigh; p[1] = greenHigh; p[2] = redHigh; } else { p[0] = blueLow; p[1] = greenLow; p[2] = redLow; } } p += 4; } row += bpr; } } void CanvasView::_UpdateToolCursor() { if (fIcon) { if (fScrollTracking || fSpaceHeldDown) { // indicate scrolling mode const uchar* cursorData = fScrollTracking ? kGrabCursor : kHandCursor; BCursor cursor(cursorData); SetViewCursor(&cursor, true); } else { // pass on to current state of StateView UpdateStateCursor(); } } else { BCursor cursor(kStopCursor); SetViewCursor(&cursor, true); } } // #pragma mark - double CanvasView::_NextZoomInLevel(double zoom) const { if (zoom < 1) return 1; if (zoom < 1.5) return 1.5; if (zoom < 2) return 2; if (zoom < 3) return 3; if (zoom < 4) return 4; if (zoom < 6) return 6; if (zoom < 8) return 8; if (zoom < 16) return 16; if (zoom < 32) return 32; return 64; } double CanvasView::_NextZoomOutLevel(double zoom) const { if (zoom > 32) return 32; if (zoom > 16) return 16; if (zoom > 8) return 8; if (zoom > 6) return 6; if (zoom > 4) return 4; if (zoom > 3) return 3; if (zoom > 2) return 2; if (zoom > 1.5) return 1.5; return 1; } void CanvasView::_SetZoom(double zoomLevel, bool mouseIsAnchor) { if (fZoomLevel == zoomLevel) return; BPoint anchor; if (mouseIsAnchor) { // zoom into mouse position anchor = MouseInfo()->position; } else { // zoom into center of view BRect bounds(Bounds()); anchor.x = (bounds.left + bounds.right + 1) / 2.0; anchor.y = (bounds.top + bounds.bottom + 1) / 2.0; } BPoint canvasAnchor = anchor; ConvertToCanvas(&canvasAnchor); fZoomLevel = zoomLevel; BRect dataRect = _LayoutCanvas(); ConvertFromCanvas(&canvasAnchor); BPoint offset = ScrollOffset(); offset.x = roundf(offset.x + canvasAnchor.x - anchor.x); offset.y = roundf(offset.y + canvasAnchor.y - anchor.y); Invalidate(); SetDataRectAndScrollOffset(dataRect, offset); } BRect CanvasView::_LayoutCanvas() { // size of zoomed bitmap BRect r(_CanvasRect()); r.OffsetTo(B_ORIGIN); // ask current view state to extend size // TODO: Ask StateViewState to extend bounds... BRect stateBounds = r; //ViewStateBounds(); // resize for empty area around bitmap // (the size we want, but might still be much smaller than view) r.InsetBy(-50, -50); // center data rect in bounds BRect bounds(Bounds()); if (bounds.Width() > r.Width()) r.InsetBy(-ceilf((bounds.Width() - r.Width()) / 2), 0); if (bounds.Height() > r.Height()) r.InsetBy(0, -ceilf((bounds.Height() - r.Height()) / 2)); if (stateBounds.IsValid()) { stateBounds.InsetBy(-20, -20); r = r | stateBounds; } return r; }