From 0e684ccfed8462d5e8647473765eafcf970899f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Tue, 11 Jul 2006 17:25:37 +0000 Subject: [PATCH] * added a framework for affine transformations and manipulating them - Transformable: the base class with a nice interface to agg::trans_affine - ChannelTransform: inheriting from Transformable, keeping the affine parameters separate - TransformBox: inheriting from ChannelTransform and Manipulator - TransformShapesBox: transfering the TransformBox transformation onto multiple selected Shape objects * Shape inherits from Transformable * solved an important TODO in IconRenderer: a Gradient is now transformed along with a Shape TODO: Undo/Redo for manipulating the transformation git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@18099 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/icon-o-matic/Jamfile | 8 + src/apps/icon-o-matic/MainWindow.cpp | 20 + .../icon-o-matic/document/IconRenderer.cpp | 87 ++- src/apps/icon-o-matic/shape/Shape.cpp | 16 +- src/apps/icon-o-matic/shape/Shape.h | 5 + .../transformable/ChannelTransform.cpp | 227 +++++++ .../transformable/ChannelTransform.h | 67 ++ .../transformable/TransformBox.cpp | 594 +++++++++++++++++ .../icon-o-matic/transformable/TransformBox.h | 124 ++++ .../transformable/TransformBoxStates.cpp | 615 ++++++++++++++++++ .../transformable/TransformBoxStates.h | 163 +++++ .../transformable/TransformCommand.cpp | 154 +++++ .../transformable/TransformCommand.h | 78 +++ .../transformable/TransformShapesBox.cpp | 181 ++++++ .../transformable/TransformShapesBox.h | 60 ++ .../transformable/Transformable.cpp | 312 +++++++++ .../transformable/Transformable.h | 68 ++ 17 files changed, 2748 insertions(+), 31 deletions(-) create mode 100644 src/apps/icon-o-matic/transformable/ChannelTransform.cpp create mode 100644 src/apps/icon-o-matic/transformable/ChannelTransform.h create mode 100644 src/apps/icon-o-matic/transformable/TransformBox.cpp create mode 100644 src/apps/icon-o-matic/transformable/TransformBox.h create mode 100644 src/apps/icon-o-matic/transformable/TransformBoxStates.cpp create mode 100644 src/apps/icon-o-matic/transformable/TransformBoxStates.h create mode 100644 src/apps/icon-o-matic/transformable/TransformCommand.cpp create mode 100644 src/apps/icon-o-matic/transformable/TransformCommand.h create mode 100644 src/apps/icon-o-matic/transformable/TransformShapesBox.cpp create mode 100644 src/apps/icon-o-matic/transformable/TransformShapesBox.h create mode 100644 src/apps/icon-o-matic/transformable/Transformable.cpp create mode 100644 src/apps/icon-o-matic/transformable/Transformable.h diff --git a/src/apps/icon-o-matic/Jamfile b/src/apps/icon-o-matic/Jamfile index b6e32cd733..8513ca5dd8 100644 --- a/src/apps/icon-o-matic/Jamfile +++ b/src/apps/icon-o-matic/Jamfile @@ -26,6 +26,7 @@ local sourceDirs = shape shape/commands style + transformable transformer ; @@ -138,6 +139,13 @@ Application Icon-O-Matic : SetGradientCommand.cpp Style.cpp StyleManager.cpp + # transformable + ChannelTransform.cpp + Transformable.cpp + TransformBox.cpp + TransformBoxStates.cpp + TransformCommand.cpp + TransformShapesBox.cpp # transformer AffineTransformer.cpp ContourTransformer.cpp diff --git a/src/apps/icon-o-matic/MainWindow.cpp b/src/apps/icon-o-matic/MainWindow.cpp index 377482d2ad..6c19e18cc7 100644 --- a/src/apps/icon-o-matic/MainWindow.cpp +++ b/src/apps/icon-o-matic/MainWindow.cpp @@ -31,6 +31,7 @@ #include "SwatchGroup.h" #include "TransformerFactory.h" #include "TransformerListView.h" +#include "TransformShapesBox.h" // TODO: just for testing #include "AffineTransformer.h" @@ -144,6 +145,25 @@ case MSG_SHAPE_SELECTED: { fPathListView->SetCurrentShape(shape); fStyleListView->SetCurrentShape(shape); fTransformerListView->SetShape(shape); + + BList selectedShapes; + ShapeContainer* shapes = fDocument->Icon()->Shapes(); + int32 count = shapes->CountShapes(); + for (int32 i = 0; i < count; i++) { + shape = shapes->ShapeAtFast(i); + if (shape->IsSelected()) { + selectedShapes.AddItem((void*)shape); + } + } + + if (selectedShapes.CountItems() > 0) { + TransformShapesBox* transformBox = new TransformShapesBox( + fCanvasView, + (const Shape**)selectedShapes.Items(), + selectedShapes.CountItems()); + fState->DeleteManipulators(); + fState->AddManipulator(transformBox); + } break; } diff --git a/src/apps/icon-o-matic/document/IconRenderer.cpp b/src/apps/icon-o-matic/document/IconRenderer.cpp index fda009238e..7950078722 100644 --- a/src/apps/icon-o-matic/document/IconRenderer.cpp +++ b/src/apps/icon-o-matic/document/IconRenderer.cpp @@ -27,6 +27,11 @@ using std::nothrow; class StyleHandler { + struct StyleItem { + Style* style; + Transformation transformation; + }; + public: StyleHandler(GammaTable& gammaTable) : fStyles(20), @@ -35,13 +40,20 @@ class StyleHandler { fColor(0, 0, 0, 0) {} + ~StyleHandler() + { + int32 count = fStyles.CountItems(); + for (int32 i = 0; i < count; i++) + delete (StyleItem*)fStyles.ItemAtFast(i); + } + bool is_solid(unsigned styleIndex) const { - Style* style = (Style*)fStyles.ItemAt(styleIndex); - if (!style) + StyleItem* styleItem = (StyleItem*)fStyles.ItemAt(styleIndex); + if (!styleItem) return true; - return style->Gradient() == NULL; + return styleItem->style->Gradient() == NULL; } const agg::rgba8& color(unsigned styleIndex); @@ -49,9 +61,21 @@ class StyleHandler { void generate_span(agg::rgba8* span, int x, int y, unsigned len, unsigned styleIndex); - bool AddStyle(Style* style) + bool AddStyle(Style* style, const Transformation& transformation) { - return fStyles.AddItem((void*)style); + if (!style) + return false; + StyleItem* item = new (nothrow) StyleItem; + if (!item) + return false; + item->style = style; + item->transformation = transformation; + // TODO: if the style uses a gradient and the gradient + // should be transformed along with the shape, then + // we should multiply the item->transformation with the + // gradient transformation + item->transformation.invert(); + return fStyles.AddItem((void*)item); } private: @@ -59,7 +83,7 @@ private: void _GenerateGradient(agg::rgba8* span, int x, int y, unsigned len, GradientFunction function, const agg::rgba8* gradientColors, - agg::trans_affine& gradientTransform); + Transformation& gradientTransform); BList fStyles; GammaTable& fGammaTable; @@ -71,13 +95,13 @@ private: const agg::rgba8& StyleHandler::color(unsigned styleIndex) { - Style* style = (Style*)fStyles.ItemAt(styleIndex); - if (!style) { + StyleItem* styleItem = (StyleItem*)fStyles.ItemAt(styleIndex); + if (!styleItem) { printf("no style at index: %d!\n", styleIndex); return fTransparent; } - const rgb_color& c = style->Color(); + const rgb_color& c = styleItem->style->Color(); fColor = agg::rgba8(fGammaTable.dir(c.red), fGammaTable.dir(c.green), fGammaTable.dir(c.blue), @@ -91,55 +115,52 @@ void StyleHandler::generate_span(agg::rgba8* span, int x, int y, unsigned len, unsigned styleIndex) { - Style* style = (Style*)fStyles.ItemAt(styleIndex); - if (!style || !style->Gradient()) { + StyleItem* styleItem = (StyleItem*)fStyles.ItemAt(styleIndex); + if (!styleItem || !styleItem->style->Gradient()) { printf("no style/gradient at index: %d!\n", styleIndex); // TODO: memset() span? return; } + Style* style = styleItem->style; Gradient* gradient = style->Gradient(); const agg::rgba8* colors = style->Colors(); - agg::trans_affine transformation; - // TODO: construct the gradient transformation here - // remember to invert() it! - switch (gradient->Type()) { case GRADIENT_LINEAR: { agg::gradient_x function; _GenerateGradient(span, x, y, len, function, colors, - transformation); + styleItem->transformation); break; } case GRADIENT_CIRCULAR: { agg::gradient_radial function; _GenerateGradient(span, x, y, len, function, colors, - transformation); + styleItem->transformation); break; } case GRADIENT_DIAMONT: { agg::gradient_diamond function; _GenerateGradient(span, x, y, len, function, colors, - transformation); + styleItem->transformation); break; } case GRADIENT_CONIC: { agg::gradient_conic function; _GenerateGradient(span, x, y, len, function, colors, - transformation); + styleItem->transformation); break; } case GRADIENT_XY: { agg::gradient_xy function; _GenerateGradient(span, x, y, len, function, colors, - transformation); + styleItem->transformation); break; } case GRADIENT_SQRT_XY: { agg::gradient_sqrt_xy function; _GenerateGradient(span, x, y, len, function, colors, - transformation); + styleItem->transformation); break; } } @@ -151,7 +172,7 @@ void StyleHandler::_GenerateGradient(agg::rgba8* span, int x, int y, unsigned len, GradientFunction function, const agg::rgba8* gradientColors, - agg::trans_affine& gradientTransform) + Transformation& gradientTransform) { typedef agg::pod_auto_array ColorArray; @@ -262,6 +283,7 @@ IconRenderer::_Render(const BRect& r) fBaseRendererPre.clear(agg::rgba8(0, 0, 0, 0)); +//bigtime_t start = system_time(); StyleHandler styleHandler(fGammaTable); fRasterizer.reset(); @@ -272,19 +294,21 @@ IconRenderer::_Render(const BRect& r) for (int32 i = 0; i < shapeCount; i++) { Shape* shape = fIcon->Shapes()->ShapeAtFast(i); - if (!styleHandler.AddStyle(shape->Style())) { + Transformation transform(*shape); + transform.multiply(fGlobalTransform); + // NOTE: this works only because "agg::trans_affine", + // "Transformable" and "Transformation" are all the + // same thing + + if (!styleHandler.AddStyle(shape->Style(), transform)) { printf("IconRenderer::_Render() - out of memory\n"); break; } fRasterizer.styles(i, -1); - if (fGlobalTransform.is_identity()) { - fRasterizer.add_path(shape->VertexSource()); - } else { - agg::conv_transform - scaledPath(shape->VertexSource(), fGlobalTransform); - fRasterizer.add_path(scaledPath); - } + agg::conv_transform + scaledPath(shape->VertexSource(), transform); + fRasterizer.add_path(scaledPath); } agg::render_scanlines_compound(fRasterizer, @@ -296,5 +320,8 @@ IconRenderer::_Render(const BRect& r) if (fGammaTable.gamma() != 1.0) fPixelFormat.apply_gamma_inv(fGammaTable); + +//if (fRenderingBuffer.width() == 64) +//printf("rendering 64x64: %lld\n", system_time() - start); } diff --git a/src/apps/icon-o-matic/shape/Shape.cpp b/src/apps/icon-o-matic/shape/Shape.cpp index 4cd3e853b2..ad0fae5306 100644 --- a/src/apps/icon-o-matic/shape/Shape.cpp +++ b/src/apps/icon-o-matic/shape/Shape.cpp @@ -25,6 +25,7 @@ ShapeListener::~ShapeListener() // constructor Shape::Shape(::Style* style) : IconObject(""), + Transformable(), Observer(), PathContainerListener(), @@ -46,6 +47,7 @@ Shape::Shape(::Style* style) // constructor Shape::Shape(const Shape& other) : IconObject(other), + Transformable(other), Observer(), PathContainerListener(), @@ -94,6 +96,16 @@ Shape::~Shape() // #pragma mark - +// TransformationChanged +void +Shape::TransformationChanged() +{ + // TODO: notify appearance change + _NotifyRerender(); +} + +// #pragma mark - + // ObjectChanged void Shape::ObjectChanged(const Observable* object) @@ -219,7 +231,9 @@ Shape::Bounds(bool updateLast) const double left, top, right, bottom; ::VertexSource& source = const_cast(this)->VertexSource(); - agg::bounding_rect(source, pathID, 0, 1, + agg::conv_transform< ::VertexSource, Transformable> + transformedSource(source, *this); + agg::bounding_rect(transformedSource, pathID, 0, 1, &left, &top, &right, &bottom); BRect bounds(left, top, right, bottom); diff --git a/src/apps/icon-o-matic/shape/Shape.h b/src/apps/icon-o-matic/shape/Shape.h index bbf0dd39e0..52218579db 100644 --- a/src/apps/icon-o-matic/shape/Shape.h +++ b/src/apps/icon-o-matic/shape/Shape.h @@ -8,6 +8,7 @@ #include "Observer.h" #include "PathContainer.h" #include "PathSource.h" +#include "Transformable.h" #include "VectorPath.h" class Style; @@ -28,6 +29,7 @@ class ShapeListener { }; class Shape : public IconObject, + public Transformable, public Observer, // observing all the paths and the style public PathContainerListener, public PathListener { @@ -36,6 +38,9 @@ class Shape : public IconObject, Shape(const Shape& other); virtual ~Shape(); + // Transformable interface + virtual void TransformationChanged(); + // Observer interface virtual void ObjectChanged(const Observable* object); diff --git a/src/apps/icon-o-matic/transformable/ChannelTransform.cpp b/src/apps/icon-o-matic/transformable/ChannelTransform.cpp new file mode 100644 index 0000000000..d03480c513 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/ChannelTransform.cpp @@ -0,0 +1,227 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "ChannelTransform.h" + +#include + +// constructor +ChannelTransform::ChannelTransform() + : Transformable(), + fPivot(0.0, 0.0), + fTranslation(0.0, 0.0), + fRotation(0.0), + fXScale(1.0), + fYScale(1.0) +{ +} + +// copy constructor +ChannelTransform::ChannelTransform(const ChannelTransform& other) + : Transformable(other), + fPivot(other.fPivot), + fTranslation(other.fTranslation), + fRotation(other.fRotation), + fXScale(other.fXScale), + fYScale(other.fYScale) +{ +} + +// destructor +ChannelTransform::~ChannelTransform() +{ +} + +// SetTransformation +void +ChannelTransform::SetTransformation(BPoint pivot, + BPoint translation, + double rotation, + double xScale, + double yScale) +{ + if (fTranslation != translation || + fPivot != pivot || + fRotation != rotation || + fXScale != xScale || + fYScale != yScale) { + + fPivot = pivot; + fTranslation = translation; + fRotation = rotation; + fXScale = xScale; + fYScale = yScale; + + _UpdateMatrix(); + } +} + +// SetPivot +void +ChannelTransform::SetPivot(BPoint pivot) +{ + if (pivot == fPivot) + return; + + fPivot = pivot; + + _UpdateMatrix(); +} + +// TranslateBy +void +ChannelTransform::TranslateBy(BPoint offset) +{ + if (offset.x == 0.0 && offset.y == 0.0) + return; + + fTranslation += offset; + + _UpdateMatrix(); +} + +// RotateBy +// +// converts a rotation in world coordinates into +// a combined local rotation and a translation +void +ChannelTransform::RotateBy(BPoint origin, double degrees) +{ + if (degrees == 0.0) + return; + + origin -= fPivot; + + fRotation += degrees; + + // rotate fTranslation + double xOffset = fTranslation.x - origin.x; + double yOffset = fTranslation.y - origin.y; + + agg::trans_affine_rotation m(degrees * PI / 180.0); + m.transform(&xOffset, &yOffset); + + fTranslation.x = origin.x + xOffset; + fTranslation.y = origin.y + yOffset; + + _UpdateMatrix(); +} + + +// RotateBy +void +ChannelTransform::RotateBy(double degrees) +{ + if (degrees == 0.0) + return; + + fRotation += degrees; + + _UpdateMatrix(); +} + +//// ScaleBy +//// +//// converts a scalation in world coordinates into +//// a combined local scalation and a translation +//void +//ChannelTransform::ScaleBy(BPoint origin, double xScale, double yScale) +//{ +// if (xScale == 1.0 && yScale == 1.0) +// return; +// +// fXScale *= xScale; +// fYScale *= yScale; +// +// // scale fTranslation +// double xOffset = fTranslation.x - origin.x; +// double yOffset = fTranslation.y - origin.y; +// +// fTranslation.x = origin.x + (xOffset * xScale); +// fTranslation.y = origin.y + (yOffset * yScale); +// +// _UpdateMatrix(); +//} + +// ScaleBy +void +ChannelTransform::ScaleBy(double xScale, double yScale) +{ + if (xScale == 1.0 && yScale == 1.0) + return; + + fXScale *= xScale; + fYScale *= yScale; + + _UpdateMatrix(); +} + +// SetTranslationAndScale +void +ChannelTransform::SetTranslationAndScale(BPoint offset, + double xScale, double yScale) +{ + if (fTranslation == offset && fXScale == xScale && fYScale == yScale) + return; + + fTranslation = offset; + + fXScale = xScale; + fYScale = yScale; + + _UpdateMatrix(); +} + +// Reset +void +ChannelTransform::Reset() +{ + SetTransformation(B_ORIGIN, B_ORIGIN, 0.0, 1.0, 1.0); +} + +// = +ChannelTransform& +ChannelTransform::operator=(const ChannelTransform& other) +{ + fTranslation = other.fTranslation; + fRotation = other.fRotation; + fXScale = other.fXScale; + fYScale = other.fYScale; + + Transformable::operator=(other); + + return *this; +} + +// _UpdateMatrix +void +ChannelTransform::_UpdateMatrix() +{ + // fix up scales in case any is zero + double xScale = fXScale; + if (xScale == 0.0) + xScale = 0.000001; + double yScale = fYScale; + if (yScale == 0.0) + yScale = 0.000001; + + // start clean + reset(); + // the "pivot" is like the offset from world to local + // coordinate system and is the center for rotation and scale + multiply(agg::trans_affine_translation(-fPivot.x, -fPivot.y)); + multiply(agg::trans_affine_scaling(xScale, yScale)); + multiply(agg::trans_affine_rotation(fRotation * PI / 180.0)); + + multiply(agg::trans_affine_translation(fPivot.x + fTranslation.x, + fPivot.y + fTranslation.y)); + + // call hook function + Update(); +} + diff --git a/src/apps/icon-o-matic/transformable/ChannelTransform.h b/src/apps/icon-o-matic/transformable/ChannelTransform.h new file mode 100644 index 0000000000..d48b051c6a --- /dev/null +++ b/src/apps/icon-o-matic/transformable/ChannelTransform.h @@ -0,0 +1,67 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#ifndef CHANNEL_TRANSFORM_H +#define CHANNEL_TRANSFORM_H + +#include "Transformable.h" + +class ChannelTransform : public Transformable { + public: + ChannelTransform(); + ChannelTransform(const ChannelTransform& other); + virtual ~ChannelTransform(); + + // ChannelTransform + virtual void Update(bool deep = true) {} + + void SetTransformation(BPoint pivot, + BPoint translation, + double rotation, + double xScale, + double yScale); + + void SetPivot(BPoint pivot); + + virtual void TranslateBy(BPoint offset); + virtual void RotateBy(BPoint origin, double degrees); + + void ScaleBy(double xScale, double yScale); + void RotateBy(double degrees); + + void SetTranslationAndScale(BPoint offset, + double xScale, + double yScale); + + virtual void Reset(); + + inline BPoint Pivot() const + { return fPivot; } + inline BPoint Translation() const + { return fTranslation; } + inline double LocalRotation() const + { return fRotation; } + inline double LocalXScale() const + { return fXScale; } + inline double LocalYScale() const + { return fYScale; } + + ChannelTransform& operator=(const ChannelTransform& other); + + private: + void _UpdateMatrix(); + + BPoint fPivot; + BPoint fTranslation; + double fRotation; + double fXScale; + double fYScale; +}; + +#endif // CHANNEL_TRANSFORM_H + diff --git a/src/apps/icon-o-matic/transformable/TransformBox.cpp b/src/apps/icon-o-matic/transformable/TransformBox.cpp new file mode 100644 index 0000000000..4f05edb5f5 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformBox.cpp @@ -0,0 +1,594 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "TransformBox.h" + +#include + +#include +#include + +#include + +#include "support.h" + +#include "TransformBoxStates.h" +#include "StateView.h" +#include "TransformCommand.h" + +#define INSET 8.0 + +// constructor +TransformBox::TransformBox(StateView* view, BRect box) + : ChannelTransform(), + Manipulator(NULL), + fOriginalBox(box), + + fLeftTop(box.LeftTop()), + fRightTop(box.RightTop()), + fLeftBottom(box.LeftBottom()), + fRightBottom(box.RightBottom()), + + fPivot((fLeftTop.x + fRightBottom.x) / 2.0, + (fLeftTop.y + fRightBottom.y) / 2.0), + fPivotOffset(B_ORIGIN), + + fCurrentCommand(NULL), + fCurrentState(NULL), + + fDragging(false), + fMousePos(-10000.0, -10000.0), + fModifiers(0), + + fNudging(false), + + fView(view), + + fDragLTState(new DragCornerState(this, DragCornerState::LEFT_TOP_CORNER)), + fDragRTState(new DragCornerState(this, DragCornerState::RIGHT_TOP_CORNER)), + fDragLBState(new DragCornerState(this, DragCornerState::LEFT_BOTTOM_CORNER)), + fDragRBState(new DragCornerState(this, DragCornerState::RIGHT_BOTTOM_CORNER)), + + fDragLState(new DragSideState(this, DragSideState::LEFT_SIDE)), + fDragRState(new DragSideState(this, DragSideState::RIGHT_SIDE)), + fDragTState(new DragSideState(this, DragSideState::TOP_SIDE)), + fDragBState(new DragSideState(this, DragSideState::BOTTOM_SIDE)), + + fRotateState(new RotateBoxState(this)), + fTranslateState(new DragBoxState(this)), + fOffsetCenterState(new OffsetCenterState(this)) +{ +} + +// destructor +TransformBox::~TransformBox() +{ + delete fCurrentCommand; + + delete fDragLTState; + delete fDragRTState; + delete fDragLBState; + delete fDragRBState; + + delete fDragLState; + delete fDragRState; + delete fDragTState; + delete fDragBState; + + delete fRotateState; + delete fTranslateState; + delete fOffsetCenterState; +} + +// Draw +void +TransformBox::Draw(BView* into, BRect updateRect) +{ + // convert to canvas view coordinates + BPoint lt = fLeftTop; + BPoint rt = fRightTop; + BPoint lb = fLeftBottom; + BPoint rb = fRightBottom; + BPoint c = fPivot; + + TransformFromCanvas(lt); + TransformFromCanvas(rt); + TransformFromCanvas(lb); + TransformFromCanvas(rb); + TransformFromCanvas(c); + + into->SetDrawingMode(B_OP_COPY); + into->SetHighColor(255, 255, 255, 255); + into->SetLowColor(0, 0, 0, 255); + _StrokeBWLine(into, lt, rt); + _StrokeBWLine(into, rt, rb); + _StrokeBWLine(into, rb, lb); + _StrokeBWLine(into, lb, lt); + + double rotation = ViewSpaceRotation(); + _StrokeBWPoint(into, lt, rotation); + _StrokeBWPoint(into, rt, rotation + 90.0); + _StrokeBWPoint(into, rb, rotation + 180.0); + _StrokeBWPoint(into, lb, rotation + 270.0); + + BRect cr(c, c); + cr.InsetBy(-3.0, -3.0); + into->StrokeEllipse(cr, B_SOLID_HIGH); + cr.InsetBy(1.0, 1.0); + into->StrokeEllipse(cr, B_SOLID_LOW); + into->SetDrawingMode(B_OP_COPY); +} + +// #pragma mark - + +// MouseDown +bool +TransformBox::MouseDown(BPoint where) +{ + TransformToCanvas(where); + + fDragging = true; + if (fCurrentState) { + fCurrentState->SetOrigin(where); + + delete fCurrentCommand; + fCurrentCommand = MakeAction(fCurrentState->ActionName(), + fCurrentState->ActionNameIndex()); + } + + return true; +} + +// MouseMoved +void +TransformBox::MouseMoved(BPoint where) +{ + TransformToCanvas(where); + + if (fMousePos != where) { + fMousePos = where; + if (fCurrentState) { + fCurrentState->DragTo(fMousePos, fModifiers); + fCurrentState->UpdateViewCursor(fView, fMousePos); + } + } +} + +// MouseUp +Command* +TransformBox::MouseUp() +{ + fDragging = false; + return FinishTransaction(); +} + +// MouseOver +bool +TransformBox::MouseOver(BPoint where) +{ + TransformToCanvas(where); + + _SetState(_DragStateFor(where, 1.0 /*zoom*/)); + fMousePos = where; + if (fCurrentState) { + fCurrentState->UpdateViewCursor(fView, fMousePos); + return true; + } + return false; +} + +// DoubleClicked +bool +TransformBox::DoubleClicked(BPoint where) +{ + return false; +} + +// #pragma mark - + +// Bounds +BRect +TransformBox::Bounds() +{ + // convert from canvas view coordinates + BPoint lt = fLeftTop; + BPoint rt = fRightTop; + BPoint lb = fLeftBottom; + BPoint rb = fRightBottom; + BPoint c = Pivot(); + + TransformFromCanvas(lt); + TransformFromCanvas(rt); + TransformFromCanvas(lb); + TransformFromCanvas(rb); + TransformFromCanvas(c); + + BRect r; + r.left = min5(lt.x, rt.x, lb.x, rb.x, c.x); + r.top = min5(lt.y, rt.y, lb.y, rb.y, c.y); + r.right = max5(lt.x, rt.x, lb.x, rb.x, c.x); + r.bottom = max5(lt.y, rt.y, lb.y, rb.y, c.y); + return r; +} + +// TrackingBounds +BRect +TransformBox::TrackingBounds(BView* withinView) +{ + return withinView->Bounds(); +} + +// #pragma mark - + +// ModifiersChanged +void +TransformBox::ModifiersChanged(uint32 modifiers) +{ + fModifiers = modifiers; + if (fDragging && fCurrentState) { + fCurrentState->DragTo(fMousePos, fModifiers); + } +} + +// HandleKeyDown +bool +TransformBox::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command) +{ + // TODO: nudging + return false; +} + +// HandleKeyUp +bool +TransformBox::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command) +{ + // TODO: nudging + return false; +} + +// #pragma mark - + +// UpdateCursor +void +TransformBox::UpdateCursor() +{ + if (fCurrentState) + fCurrentState->UpdateViewCursor(fView, fMousePos); +} + +// AttachedToView +void +TransformBox::AttachedToView(BView* view) +{ + view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); +} + +// DetachedFromView +void +TransformBox::DetachedFromView(BView* view) +{ + view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); +} + +// pragma mark - + +// Update +void +TransformBox::Update(bool deep) +{ + // recalculate the points from the original box + fLeftTop = fOriginalBox.LeftTop(); + fRightTop = fOriginalBox.RightTop(); + fLeftBottom = fOriginalBox.LeftBottom(); + fRightBottom = fOriginalBox.RightBottom(); + + fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0; + fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0; + + fPivot += fPivotOffset; + + // transform the points for display + Transform(&fLeftTop); + Transform(&fRightTop); + Transform(&fLeftBottom); + Transform(&fRightBottom); + + Transform(&fPivot); +} + +// OffsetPivot +void +TransformBox::OffsetPivot(BPoint offset) +{ + if (offset != BPoint(0.0, 0.0)) { + fPivotOffset += offset; + Update(false); + } +} + +// SetBox +void +TransformBox::SetBox(BRect box) +{ + if (fOriginalBox != box) { + fOriginalBox = box; + Update(false); + } +} + +// FinishTransaction +Command* +TransformBox::FinishTransaction() +{ + Command* command = fCurrentCommand; + if (fCurrentCommand) { + fCurrentCommand->SetNewTransformation(Pivot(), + Translation(), + LocalRotation(), + LocalXScale(), + LocalYScale()); + fCurrentCommand = NULL; + } + return command; +} + +// NudgeBy +void +TransformBox::NudgeBy(BPoint offset) +{ + if (!fNudging && !fCurrentCommand) { + fCurrentCommand = MakeAction("Move", 0/*MOVE*/); + fNudging = true; + } + if (fNudging) { + TranslateBy(offset); + } +} + +// FinishNudging +Command* +TransformBox::FinishNudging() +{ + fNudging = false; + return FinishTransaction(); +} + +// TransformFromCanvas +void +TransformBox::TransformFromCanvas(BPoint& point) const +{ +} + +// TransformToCanvas +void +TransformBox::TransformToCanvas(BPoint& point) const +{ +} + +// ViewSpaceRotation +double +TransformBox::ViewSpaceRotation() const +{ + // assume no inherited transformation + return LocalRotation(); +} + +// TODO: why another version? +// point_line_dist +float +point_line_dist(BPoint start, BPoint end, BPoint p, float radius) +{ + BRect r(min_c(start.x, end.x), + min_c(start.y, end.y), + max_c(start.x, end.x), + max_c(start.y, end.y)); + r.InsetBy(-radius, -radius); + if (r.Contains(p)) { + return fabs(agg::calc_line_point_distance(start.x, start.y, + end.x, end.y, + p.x, p.y)); + } + return min_c(point_point_distance(start, p), + point_point_distance(end, p)); +} + +// _DragStateFor +// +// where is expected in canvas view coordinates +DragState* +TransformBox::_DragStateFor(BPoint where, float canvasZoom) +{ + DragState* state = NULL; + // convert to canvas zoom level + // + // the conversion is necessary, because the "hot regions" + // around a point should be the same size no matter what + // zoom level the canvas is displayed at + + float inset = INSET / canvasZoom; + + // priorities: + // transformation center point has highest priority ?!? + if (point_point_distance(where, Pivot()) < inset) + state = fOffsetCenterState; + + if (!state) { + // next, the inner area of the box + + // for the following calculations + // we can apply the inverse transformation to all points + // this way we have to consider BRects only, not transformed polygons + BPoint lt = fLeftTop; + BPoint rb = fRightBottom; + BPoint w = where; + + InverseTransform(&w); + InverseTransform(<); + InverseTransform(&rb); + + // next priority has the inside of the box + BRect iR(lt, rb); + float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0)); + float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0)); + + iR.InsetBy(hInset, vInset); + if (iR.Contains(w)) + state = fTranslateState; + } + + if (!state) { + // next priority have the corners + float dLT = point_point_distance(fLeftTop, where); + float dRT = point_point_distance(fRightTop, where); + float dLB = point_point_distance(fLeftBottom, where); + float dRB = point_point_distance(fRightBottom, where); + float d = min4(dLT, dRT, dLB, dRB); + if (d < inset) { + if (d == dLT) + state = fDragLTState; + else if (d == dRT) + state = fDragRTState; + else if (d == dLB) + state = fDragLBState; + else if (d == dRB) + state = fDragRBState; + } + } + + if (!state) { + // next priority have the sides + float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset); + float dR = point_line_dist(fRightTop, fRightBottom, where, inset); + float dT = point_line_dist(fLeftTop, fRightTop, where, inset); + float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset); + float d = min4(dL, dR, dT, dB); + if (d < inset) { + if (d == dL) + state = fDragLState; + else if (d == dR) + state = fDragRState; + else if (d == dT) + state = fDragTState; + else if (d == dB) + state = fDragBState; + } + } + + if (!state) { + BPoint lt = fLeftTop; + BPoint rb = fRightBottom; + BPoint w = where; + + InverseTransform(&w); + InverseTransform(<); + InverseTransform(&rb); + + // check inside of the box again + BRect iR(lt, rb); + if (iR.Contains(w)) { + state = fTranslateState; + } else { + // last priority has the rotate state + state = fRotateState; + } + } + + return state; +} + +// _StrokeBWLine +void +TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const +{ + // find out how to offset the second line optimally + BPoint offset(0.0, 0.0); + // first, do we have a more horizontal line or a more vertical line? + float xDiff = to.x - from.x; + float yDiff = to.y - from.y; + if (fabs(xDiff) > fabs(yDiff)) { + // horizontal + if (xDiff > 0.0) { + offset.y = -1.0; + } else { + offset.y = 1.0; + } + } else { + // vertical + if (yDiff < 0.0) { + offset.x = -1.0; + } else { + offset.x = 1.0; + } + } + // stroke two lines in high and low color of the view + into->StrokeLine(from, to, B_SOLID_LOW); + from += offset; + to += offset; + into->StrokeLine(from, to, B_SOLID_HIGH); +} + +// _StrokeBWPoint +void +TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const +{ + double x = point.x; + double y = point.y; + + double x1 = x; + double y1 = y - 5.0; + + double x2 = x - 5.0; + double y2 = y - 5.0; + + double x3 = x - 5.0; + double y3 = y; + + agg::trans_affine m; + + double xOffset = -x; + double yOffset = -y; + + agg::trans_affine_rotation r(angle * PI / 180.0); + + r.transform(&xOffset, &yOffset); + xOffset = x + xOffset; + yOffset = y + yOffset; + + m.multiply(r); + m.multiply(agg::trans_affine_translation(xOffset, yOffset)); + + m.transform(&x, &y); + m.transform(&x1, &y1); + m.transform(&x2, &y2); + m.transform(&x3, &y3); + + BPoint p[4]; + p[0] = BPoint(x, y); + p[1] = BPoint(x1, y1); + p[2] = BPoint(x2, y2); + p[3] = BPoint(x3, y3); + + into->FillPolygon(p, 4, B_SOLID_HIGH); + + into->StrokeLine(p[0], p[1], B_SOLID_LOW); + into->StrokeLine(p[1], p[2], B_SOLID_LOW); + into->StrokeLine(p[2], p[3], B_SOLID_LOW); + into->StrokeLine(p[3], p[0], B_SOLID_LOW); +} + +// _SetState +void +TransformBox::_SetState(DragState* state) +{ + if (state != fCurrentState) { + fCurrentState = state; + fCurrentState->UpdateViewCursor(fView, fMousePos); + } +} + + diff --git a/src/apps/icon-o-matic/transformable/TransformBox.h b/src/apps/icon-o-matic/transformable/TransformBox.h new file mode 100644 index 0000000000..0bdc85dfe3 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformBox.h @@ -0,0 +1,124 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#ifndef TRANSFORM_BOX_H +#define TRANSFORM_BOX_H + +#include "ChannelTransform.h" +#include "Manipulator.h" + +class Command; +class StateView; +class DragState; +class TransformCommand; + +class TransformBox : public ChannelTransform, + public Manipulator { + public: + TransformBox(StateView* view, + BRect box); + virtual ~TransformBox(); + + // Manipulator interface + virtual void Draw(BView* into, BRect updateRect); + + virtual bool MouseDown(BPoint where); + virtual void MouseMoved(BPoint where); + virtual Command* MouseUp(); + virtual bool MouseOver(BPoint where); + virtual bool DoubleClicked(BPoint where); + + virtual BRect Bounds(); + virtual BRect TrackingBounds(BView* withinView); + + virtual void ModifiersChanged(uint32 modifiers); + virtual bool HandleKeyDown(uint32 key, uint32 modifiers, + Command** _command); + virtual bool HandleKeyUp(uint32 key, uint32 modifiers, + Command** _command); + + virtual void UpdateCursor(); + + virtual void AttachedToView(BView* view); + virtual void DetachedFromView(BView* view); + + // TransformBox + virtual void Update(bool deep = true); + + void OffsetPivot(BPoint offset); + void SetBox(BRect box); + BRect Box() const + { return fOriginalBox; } + + Command* FinishTransaction(); + + void NudgeBy(BPoint offset); + bool IsNudging() const + { return fNudging; } + Command* FinishNudging(); + + virtual void TransformFromCanvas(BPoint& point) const; + virtual void TransformToCanvas(BPoint& point) const; + + + virtual TransformCommand* MakeAction(const char* actionName, + uint32 nameIndex) const = 0; + + bool IsRotating() const + { return fCurrentState == fRotateState; } + virtual double ViewSpaceRotation() const; + + private: + DragState* _DragStateFor(BPoint canvasWhere, + float canvasZoom); + void _StrokeBWLine(BView* into, + BPoint from, BPoint to) const; + void _StrokeBWPoint(BView* into, + BPoint point, double angle) const; + + BRect fOriginalBox; + + BPoint fLeftTop; + BPoint fRightTop; + BPoint fLeftBottom; + BPoint fRightBottom; + + BPoint fPivot; + BPoint fPivotOffset; + + TransformCommand* fCurrentCommand; + DragState* fCurrentState; + + bool fDragging; + BPoint fMousePos; + uint32 fModifiers; + + bool fNudging; + + protected: + // "static" state objects + void _SetState(DragState* state); + + StateView* fView; + + DragState* fDragLTState; + DragState* fDragRTState; + DragState* fDragLBState; + DragState* fDragRBState; + + DragState* fDragLState; + DragState* fDragRState; + DragState* fDragTState; + DragState* fDragBState; + + DragState* fRotateState; + DragState* fTranslateState; + DragState* fOffsetCenterState; +}; + +#endif // TRANSFORM_BOX_H diff --git a/src/apps/icon-o-matic/transformable/TransformBoxStates.cpp b/src/apps/icon-o-matic/transformable/TransformBoxStates.cpp new file mode 100644 index 0000000000..24e32c74e6 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformBoxStates.cpp @@ -0,0 +1,615 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "TransformBoxStates.h" + +#include +#include + +#include +#include +#include + +#include "cursors.h" +#include "support.h" + +#include "TransformBox.h" +//#include "Strings.h" + +// constructor +DragState::DragState(TransformBox* parent) + : fOrigin(0.0, 0.0), + fParent(parent) +{ +} + +// SetOrigin +void +DragState::SetOrigin(BPoint origin) +{ + fOrigin = origin; +} + +// ActionName +const char* +DragState::ActionName() const +{ + return "Transformation"; +} + +// ActionNameIndex +uint32 +DragState::ActionNameIndex() const +{ + return TRANSFORMATION; +} + +// _SetViewCursor +void +DragState::_SetViewCursor(BView* view, const uchar* cursorData) const +{ + BCursor cursor(cursorData); + view->SetViewCursor(&cursor); +} + +// #pragma mark - DragCornerState + +// constructor +DragCornerState::DragCornerState(TransformBox* parent, uint32 corner) + : DragState(parent), + fCorner(corner) +{ +} + +// SetOrigin +void +DragCornerState::SetOrigin(BPoint origin) +{ + fOldXScale = fParent->LocalXScale(); + fOldYScale = fParent->LocalYScale(); + + fOldOffset = fParent->Translation(); + + // copy the matrix at the start of the drag procedure + fMatrix.reset(); + fMatrix.multiply(agg::trans_affine_scaling(fOldXScale, fOldYScale)); + fMatrix.multiply(agg::trans_affine_rotation(fParent->LocalRotation() * PI / 180.0)); + fMatrix.multiply(agg::trans_affine_translation(fParent->Translation().x, + fParent->Translation().y)); + + double x = origin.x; + double y = origin.y; + fMatrix.inverse_transform(&x, &y); + origin.x = x; + origin.y = y; + + BRect box = fParent->Box(); + switch (fCorner) { + case LEFT_TOP_CORNER: + fXOffsetFromCorner = origin.x - box.left; + fYOffsetFromCorner = origin.y - box.top; + fOldWidth = box.left - box.right; + fOldHeight = box.top - box.bottom; + origin.x = box.right; + origin.y = box.bottom; + break; + case RIGHT_TOP_CORNER: + fXOffsetFromCorner = origin.x - box.right; + fYOffsetFromCorner = origin.y - box.top; + fOldWidth = box.right - box.left; + fOldHeight = box.top - box.bottom; + origin.x = box.left; + origin.y = box.bottom; + break; + case LEFT_BOTTOM_CORNER: + fXOffsetFromCorner = origin.x - box.left; + fYOffsetFromCorner = origin.y - box.bottom; + fOldWidth = box.left - box.right; + fOldHeight = box.bottom - box.top; + origin.x = box.right; + origin.y = box.top; + break; + case RIGHT_BOTTOM_CORNER: + fXOffsetFromCorner = origin.x - box.right; + fYOffsetFromCorner = origin.y - box.bottom; + fOldWidth = box.right - box.left; + fOldHeight = box.bottom - box.top; + origin.x = box.left; + origin.y = box.top; + break; + } + DragState::SetOrigin(origin); +} +// DragTo +void +DragCornerState::DragTo(BPoint current, uint32 modifiers) +{ + double x = current.x; + double y = current.y; + fMatrix.inverse_transform(&x, &y); + + double xScale = 1.0; + double yScale = 1.0; + BPoint translation(0.0, 0.0); + switch (fCorner) { + case LEFT_TOP_CORNER: + case RIGHT_TOP_CORNER: + case LEFT_BOTTOM_CORNER: + case RIGHT_BOTTOM_CORNER: + x -= fOrigin.x; + y -= fOrigin.y; + if (fOldWidth != 0.0) + xScale = (x - fXOffsetFromCorner) / (fOldWidth); + if (fOldHeight != 0.0) + yScale = (y - fYOffsetFromCorner) / (fOldHeight); + // constrain aspect ratio if shift is pressed + if (modifiers & B_SHIFT_KEY) { + if (fabs(xScale) > fabs(yScale)) + yScale = yScale > 0.0 ? fabs(xScale) : -fabs(xScale); + else + xScale = xScale > 0.0 ? fabs(yScale) : -fabs(yScale); + } + translation.x = fOrigin.x - fOrigin.x * xScale; + translation.y = fOrigin.y - fOrigin.y * yScale; + break; + } + x = translation.x; + y = translation.y; + fMatrix.transform(&x, &y); + translation.x = x; + translation.y = y; + + fParent->SetTranslationAndScale(translation, + xScale * fOldXScale, + yScale * fOldYScale); +} + +// UpdateViewCursor +void +DragCornerState::UpdateViewCursor(BView* view, BPoint current) const +{ + float rotation = fmod(360.0 - fParent->ViewSpaceRotation() + 22.5, 180.0); + bool flipX = fParent->LocalXScale() < 0.0; + bool flipY = fParent->LocalYScale() < 0.0; + if (rotation < 45.0) { + switch (fCorner) { + case LEFT_TOP_CORNER: + case RIGHT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kLeftTopRightBottomCursor + : kLeftBottomRightTopCursor); + else + _SetViewCursor(view, flipY ? kLeftBottomRightTopCursor + : kLeftTopRightBottomCursor); + break; + case RIGHT_TOP_CORNER: + case LEFT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kLeftBottomRightTopCursor + : kLeftTopRightBottomCursor); + else + _SetViewCursor(view, flipY ? kLeftTopRightBottomCursor + : kLeftBottomRightTopCursor); + break; + } + } else if (rotation < 90.0) { + switch (fCorner) { + case LEFT_TOP_CORNER: + case RIGHT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kLeftRightCursor + : kUpDownCursor); + else + _SetViewCursor(view, flipY ? kUpDownCursor + : kLeftRightCursor); + break; + case RIGHT_TOP_CORNER: + case LEFT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kUpDownCursor + : kLeftRightCursor); + else + _SetViewCursor(view, flipY ? kLeftRightCursor + : kUpDownCursor); + break; + } + } else if (rotation < 135.0) { + switch (fCorner) { + case LEFT_TOP_CORNER: + case RIGHT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kLeftBottomRightTopCursor + : kLeftTopRightBottomCursor); + else + _SetViewCursor(view, flipY ? kLeftTopRightBottomCursor + : kLeftBottomRightTopCursor); + break; + break; + case RIGHT_TOP_CORNER: + case LEFT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kLeftTopRightBottomCursor + : kLeftBottomRightTopCursor); + else + _SetViewCursor(view, flipY ? kLeftBottomRightTopCursor + : kLeftTopRightBottomCursor); + break; + break; + } + } else { + switch (fCorner) { + case LEFT_TOP_CORNER: + case RIGHT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kUpDownCursor + : kLeftRightCursor); + else + _SetViewCursor(view, flipY ? kLeftRightCursor + : kUpDownCursor); + break; + case RIGHT_TOP_CORNER: + case LEFT_BOTTOM_CORNER: + if (flipX) + _SetViewCursor(view, flipY ? kLeftRightCursor + : kUpDownCursor); + else + _SetViewCursor(view, flipY ? kUpDownCursor + : kLeftRightCursor); + break; + } + } +} + +// ActionName +const char* +DragCornerState::ActionName() const +{ + return "Scale"; +} + +// ActionNameIndex +uint32 +DragCornerState::ActionNameIndex() const +{ + return SCALE; +} + + +// #pragma mark - DragSideState + +DragSideState::DragSideState(TransformBox* parent, uint32 side) + : DragState(parent), + fSide(side) +{ +} + +// SetOrigin +void +DragSideState::SetOrigin(BPoint origin) +{ + fOldXScale = fParent->LocalXScale(); + fOldYScale = fParent->LocalYScale(); + + fOldOffset = fParent->Translation(); + + // copy the matrix at the start of the drag procedure + fMatrix.reset(); + fMatrix.multiply(agg::trans_affine_scaling(fOldXScale, fOldYScale)); + fMatrix.multiply(agg::trans_affine_rotation(fParent->LocalRotation() * PI / 180.0)); + fMatrix.multiply(agg::trans_affine_translation(fParent->Translation().x, + fParent->Translation().y)); + + double x = origin.x; + double y = origin.y; + fMatrix.inverse_transform(&x, &y); + origin.x = x; + origin.y = y; + + BRect box = fParent->Box(); + switch (fSide) { + case LEFT_SIDE: + fOffsetFromSide = origin.x - box.left; + fOldSideDist = box.left - box.right; + origin.x = box.right; + break; + case RIGHT_SIDE: + fOffsetFromSide = origin.x - box.right; + fOldSideDist = box.right - box.left; + origin.x = box.left; + break; + case TOP_SIDE: + fOffsetFromSide = origin.y - box.top; + fOldSideDist = box.top - box.bottom; + origin.y = box.bottom; + break; + case BOTTOM_SIDE: + fOffsetFromSide = origin.y - box.bottom; + fOldSideDist = box.bottom - box.top; + origin.y = box.top; + break; + } + DragState::SetOrigin(origin); +} + +// DragTo +void +DragSideState::DragTo(BPoint current, uint32 modifiers) +{ + double x = current.x; + double y = current.y; + fMatrix.inverse_transform(&x, &y); + + double xScale = 1.0; + double yScale = 1.0; + BPoint translation(0.0, 0.0); + switch (fSide) { + case LEFT_SIDE: + case RIGHT_SIDE: + x -= fOrigin.x; + if (fOldSideDist != 0.0) + xScale = (x - fOffsetFromSide) / (fOldSideDist); + translation.x = fOrigin.x - fOrigin.x * xScale; + break; + case TOP_SIDE: + case BOTTOM_SIDE: + y -= fOrigin.y; + if (fOldSideDist != 0.0) + yScale = (y - fOffsetFromSide) / (fOldSideDist); + translation.y = fOrigin.y - fOrigin.y * yScale; + break; + } + x = translation.x; + y = translation.y; + fMatrix.transform(&x, &y); + translation.x = x; + translation.y = y; + + fParent->SetTranslationAndScale(translation, + xScale * fOldXScale, + yScale * fOldYScale); +} + +// UpdateViewCursor +void +DragSideState::UpdateViewCursor(BView* view, BPoint current) const +{ + float rotation = fmod(360.0 - fParent->ViewSpaceRotation() + 22.5, 180.0); + if (rotation < 45.0) { + switch (fSide) { + case LEFT_SIDE: + case RIGHT_SIDE: + _SetViewCursor(view, kLeftRightCursor); + break; + case TOP_SIDE: + case BOTTOM_SIDE: + _SetViewCursor(view, kUpDownCursor); + break; + } + } else if (rotation < 90.0) { + switch (fSide) { + case LEFT_SIDE: + case RIGHT_SIDE: + _SetViewCursor(view, kLeftBottomRightTopCursor); + break; + case TOP_SIDE: + case BOTTOM_SIDE: + _SetViewCursor(view, kLeftTopRightBottomCursor); + break; + } + } else if (rotation < 135.0) { + switch (fSide) { + case LEFT_SIDE: + case RIGHT_SIDE: + _SetViewCursor(view, kUpDownCursor); + break; + case TOP_SIDE: + case BOTTOM_SIDE: + _SetViewCursor(view, kLeftRightCursor); + break; + } + } else { + switch (fSide) { + case LEFT_SIDE: + case RIGHT_SIDE: + _SetViewCursor(view, kLeftTopRightBottomCursor); + break; + case TOP_SIDE: + case BOTTOM_SIDE: + _SetViewCursor(view, kLeftBottomRightTopCursor); + break; + } + } +} + +// ActionName +const char* +DragSideState::ActionName() const +{ + return "Scale"; +} + +// ActionNameIndex +uint32 +DragSideState::ActionNameIndex() const +{ + return SCALE; +} + + +// #pragma mark - DragBoxState + +// SetOrigin +void +DragBoxState::SetOrigin(BPoint origin) +{ + fOldTranslation = fParent->Translation(); + DragState::SetOrigin(origin); +} + +// DragTo +void +DragBoxState::DragTo(BPoint current, uint32 modifiers) +{ + BPoint offset = current - fOrigin; + BPoint newTranslation = fOldTranslation + offset; + if (modifiers & B_SHIFT_KEY) { + if (fabs(offset.x) > fabs(offset.y)) + newTranslation.y = fOldTranslation.y; + else + newTranslation.x = fOldTranslation.x; + } + fParent->TranslateBy(newTranslation - fParent->Translation()); +} + +// UpdateViewCursor +void +DragBoxState::UpdateViewCursor(BView* view, BPoint current) const +{ + _SetViewCursor(view, kMoveCursor); +} + +// ActionName +const char* +DragBoxState::ActionName() const +{ + return "Move"; +} + +// ActionNameIndex +uint32 +DragBoxState::ActionNameIndex() const +{ + return MOVE; +} + + +// #pragma mark - RotateBoxState + +// constructor +RotateBoxState::RotateBoxState(TransformBox* parent) + : DragState(parent), + fOldAngle(0.0) +{ +} + +// SetOrigin +void +RotateBoxState::SetOrigin(BPoint origin) +{ + DragState::SetOrigin(origin); + fOldAngle = fParent->LocalRotation(); +} + +// DragTo +void +RotateBoxState::DragTo(BPoint current, uint32 modifiers) +{ + double angle = calc_angle(fParent->Pivot(), fOrigin, current); + + if (modifiers & B_SHIFT_KEY) { + if (angle < 0.0) + angle -= 22.5; + else + angle += 22.5; + angle = 45.0 * ((int32)angle / 45); + } + + double newAngle = fOldAngle + angle; + + fParent->RotateBy(newAngle - fParent->LocalRotation()); +} + +// UpdateViewCursor +void +RotateBoxState::UpdateViewCursor(BView* view, BPoint current) const +{ + BPoint origin(fParent->Pivot()); + fParent->TransformToCanvas(origin); + fParent->TransformToCanvas(current); + BPoint from = origin + BPoint(sinf(22.5 * 180.0 / PI) * 50.0, + -cosf(22.5 * 180.0 / PI) * 50.0); + + float rotation = calc_angle(origin, from, current) + 180.0; + + if (rotation < 45.0) { + _SetViewCursor(view, kRotateLCursor); + } else if (rotation < 90.0) { + _SetViewCursor(view, kRotateLTCursor); + } else if (rotation < 135.0) { + _SetViewCursor(view, kRotateTCursor); + } else if (rotation < 180.0) { + _SetViewCursor(view, kRotateRTCursor); + } else if (rotation < 225.0) { + _SetViewCursor(view, kRotateRCursor); + } else if (rotation < 270.0) { + _SetViewCursor(view, kRotateRBCursor); + } else if (rotation < 315.0) { + _SetViewCursor(view, kRotateBCursor); + } else { + _SetViewCursor(view, kRotateLBCursor); + } +} + +// ActionName +const char* +RotateBoxState::ActionName() const +{ + return "Rotate"; +} + +// ActionNameIndex +uint32 +RotateBoxState::ActionNameIndex() const +{ + return ROTATE; +} + + + +// #pragma mark - OffsetCenterState + +// SetOrigin +void +OffsetCenterState::SetOrigin(BPoint origin) +{ + fParent->InverseTransform(&origin); + DragState::SetOrigin(origin); +} + + +// DragTo +void +OffsetCenterState::DragTo(BPoint current, uint32 modifiers) +{ + fParent->InverseTransform(¤t); + fParent->OffsetPivot(current - fOrigin); + fOrigin = current; +} + +// UpdateViewCursor +void +OffsetCenterState::UpdateViewCursor(BView* view, BPoint current) const +{ + _SetViewCursor(view, kPathMoveCursor); +} + +// ActionName +const char* +OffsetCenterState::ActionName() const +{ + return "Move Pivot"; +} + +// ActionNameIndex +uint32 +OffsetCenterState::ActionNameIndex() const +{ + return MOVE_PIVOT; +} + + diff --git a/src/apps/icon-o-matic/transformable/TransformBoxStates.h b/src/apps/icon-o-matic/transformable/TransformBoxStates.h new file mode 100644 index 0000000000..bf26d4b5aa --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformBoxStates.h @@ -0,0 +1,163 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#ifndef TRANSFORM_BOX_STATES_H +#define TRANSFORM_BOX_STATES_H + +#include + +#include + +class BView; +class TransformBox; + +// TODO: remove and replace with translation string indices once multiple +// languages are supported +#define TRANSFORMATION 0 +#define ROTATE 0 +#define MOVE 0 +#define SCALE 0 +#define MOVE_PIVOT 0 +// + +// base class +class DragState { + public: + DragState(TransformBox* parent); + virtual ~DragState() {} + + virtual void SetOrigin(BPoint origin); + virtual void DragTo(BPoint current, uint32 modifiers) = 0; + virtual void UpdateViewCursor(BView* view, BPoint current) const = 0; + + virtual const char* ActionName() const; + virtual uint32 ActionNameIndex() const; + + protected: + void _SetViewCursor(BView* view, + const uchar* cursorData) const; + + BPoint fOrigin; + TransformBox* fParent; +}; + +// scaling states +class DragCornerState : public DragState { + public: + enum { + LEFT_TOP_CORNER, + RIGHT_TOP_CORNER, + LEFT_BOTTOM_CORNER, + RIGHT_BOTTOM_CORNER, + }; + DragCornerState(TransformBox* parent, + uint32 corner); + virtual ~DragCornerState() {} + + virtual void SetOrigin(BPoint origin); + virtual void DragTo(BPoint current, uint32 modifiers); + virtual void UpdateViewCursor(BView* view, BPoint current) const; + + virtual const char* ActionName() const; + virtual uint32 ActionNameIndex() const; + + private: + uint32 fCorner; + + float fXOffsetFromCorner; + float fYOffsetFromCorner; + double fOldXScale; + double fOldYScale; + double fOldWidth; + double fOldHeight; + agg::trans_affine fMatrix; + BPoint fOldOffset; +}; + +class DragSideState : public DragState { + public: + enum { + LEFT_SIDE, + TOP_SIDE, + RIGHT_SIDE, + BOTTOM_SIDE, + }; + DragSideState(TransformBox* parent, + uint32 side); + virtual ~DragSideState() {} + + virtual void SetOrigin(BPoint origin); + virtual void DragTo(BPoint current, uint32 modifiers); + virtual void UpdateViewCursor(BView* view, BPoint current) const; + + virtual const char* ActionName() const; + virtual uint32 ActionNameIndex() const; + + private: + uint32 fSide; + + float fOffsetFromSide; + double fOldXScale; + double fOldYScale; + double fOldSideDist; + agg::trans_affine fMatrix; + BPoint fOldOffset; +}; + +// translate state +class DragBoxState : public DragState { + public: + DragBoxState(TransformBox* parent) + : DragState(parent) {} + virtual ~DragBoxState() {} + + virtual void SetOrigin(BPoint origin); + virtual void DragTo(BPoint current, uint32 modifiers); + virtual void UpdateViewCursor(BView* view, BPoint current) const; + + virtual const char* ActionName() const; + virtual uint32 ActionNameIndex() const; + + private: + BPoint fOldTranslation; +}; + +// rotate state +class RotateBoxState : public DragState { + public: + RotateBoxState(TransformBox* parent); + virtual ~RotateBoxState() {} + + virtual void SetOrigin(BPoint origin); + virtual void DragTo(BPoint current, uint32 modifiers); + virtual void UpdateViewCursor(BView* view, BPoint current) const; + + virtual const char* ActionName() const; + virtual uint32 ActionNameIndex() const; + + private: + double fOldAngle; +}; + +// offset center state +class OffsetCenterState : public DragState { + public: + OffsetCenterState(TransformBox* parent) + : DragState(parent) {} + virtual ~OffsetCenterState() {} + + virtual void SetOrigin(BPoint origin); + virtual void DragTo(BPoint current, uint32 modifiers); + virtual void UpdateViewCursor(BView* view, BPoint current) const; + + virtual const char* ActionName() const; + virtual uint32 ActionNameIndex() const; + +}; + +#endif // TRANSFORM_BOX_STATES_H diff --git a/src/apps/icon-o-matic/transformable/TransformCommand.cpp b/src/apps/icon-o-matic/transformable/TransformCommand.cpp new file mode 100644 index 0000000000..0348c76ff9 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformCommand.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "TransformCommand.h" + +#include + +// constructor +TransformCommand::TransformCommand(BPoint pivot, + BPoint translation, + double rotation, + double xScale, + double yScale, + const char* actionName, + uint32 nameIndex) + : Command(), + fOldPivot(pivot), + fOldTranslation(translation), + fOldRotation(rotation), + fOldXScale(xScale), + fOldYScale(yScale), + + fNewPivot(pivot), + fNewTranslation(translation), + fNewRotation(rotation), + fNewXScale(xScale), + fNewYScale(yScale), + + fName(actionName), + fNameIndex(nameIndex) +{ +} + +// constructor +TransformCommand::TransformCommand(const char* actionName, + uint32 nameIndex) + : Command(), + fOldPivot(B_ORIGIN), + fOldTranslation(B_ORIGIN), + fOldRotation(0.0), + fOldXScale(1.0), + fOldYScale(1.0), + + fNewPivot(B_ORIGIN), + fNewTranslation(B_ORIGIN), + fNewRotation(0.0), + fNewXScale(1.0), + fNewYScale(1.0), + + fName(actionName), + fNameIndex(nameIndex) +{ +} + +// destructor +TransformCommand::~TransformCommand() +{ +} + +// InitCheck +status_t +TransformCommand::InitCheck() +{ + if ((fNewPivot != fOldPivot + || fNewTranslation != fOldTranslation + || fNewRotation != fOldRotation + || fNewXScale != fOldXScale + || fNewYScale != fOldYScale)) + return B_OK; + else + return B_NO_INIT; +} + +// Perform +status_t +TransformCommand::Perform() +{ + // objects are already transformed + return B_OK; +} + +// Undo +status_t +TransformCommand::Undo() +{ + status_t status = InitCheck(); + if (status >= B_OK) { + _SetTransformation(fOldPivot - fNewPivot, + fOldTranslation - fNewTranslation, + fOldRotation - fNewRotation, + fOldXScale - fNewXScale, + fOldYScale - fNewYScale); + } + return status; +} + +// Redo +status_t +TransformCommand::Redo() +{ + status_t status = InitCheck(); + if (status >= B_OK) { + _SetTransformation(fNewPivot - fOldPivot, + fNewTranslation - fOldTranslation, + fNewRotation - fOldRotation, + fNewXScale - fOldXScale, + fNewYScale - fOldYScale); + } + return status; +} + +// GetName +void +TransformCommand::GetName(BString& name) +{ + name << _GetString(fNameIndex, fName.String()); +} + +// SetNewTransformation +void +TransformCommand::SetNewTransformation(BPoint pivot, + BPoint translation, + double rotation, + double xScale, + double yScale) +{ + fNewPivot = pivot; + fNewTranslation = translation; + fNewRotation = rotation; + fNewXScale = xScale; + fNewYScale = yScale; +} + +// SetNewTranslation +void +TransformCommand::SetNewTranslation(BPoint translation) +{ + // NOTE: convinience method for nudging + fNewTranslation = translation; +} + +// SetName +void +TransformCommand::SetName(const char* actionName, uint32 nameIndex) +{ + fName.SetTo(actionName); + fNameIndex = nameIndex; +} + diff --git a/src/apps/icon-o-matic/transformable/TransformCommand.h b/src/apps/icon-o-matic/transformable/TransformCommand.h new file mode 100644 index 0000000000..db1ec84105 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformCommand.h @@ -0,0 +1,78 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#ifndef TRANSFORM_COMMAND_H +#define TRANSFORM_COMMAND_H + +#include +#include + +#include "Command.h" + +class TransformCommand : public Command { + public: + TransformCommand(BPoint pivot, + BPoint translation, + double rotation, + double xScale, + double yScale, + + const char* actionName, + uint32 nameIndex); + + TransformCommand(const char* actionName, + uint32 nameIndex); + + virtual ~TransformCommand(); + + // Command interface + virtual status_t InitCheck(); + + virtual status_t Perform(); + virtual status_t Undo(); + virtual status_t Redo(); + + virtual void GetName(BString& name); + + // TransformCommand + void SetNewTransformation(BPoint pivot, + BPoint translation, + double rotation, + double xScale, + double yScale); + + void SetNewTranslation(BPoint translation); + // convinience for "nudging" + + void SetName(const char* actionName, + uint32 nameIndex); + + protected: + virtual status_t _SetTransformation(BPoint pivotDiff, + BPoint translationDiff, + double rotationDiff, + double xScaleDiff, + double yScaleDiff) const = 0; + + BPoint fOldPivot; + BPoint fOldTranslation; + double fOldRotation; + double fOldXScale; + double fOldYScale; + + BPoint fNewPivot; + BPoint fNewTranslation; + double fNewRotation; + double fNewXScale; + double fNewYScale; + + BString fName; + uint32 fNameIndex; +}; + +#endif // TRANSFORM_COMMAND_H diff --git a/src/apps/icon-o-matic/transformable/TransformShapesBox.cpp b/src/apps/icon-o-matic/transformable/TransformShapesBox.cpp new file mode 100644 index 0000000000..2a3bffd628 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformShapesBox.cpp @@ -0,0 +1,181 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "TransformShapesBox.h" + +#include +#include +#include + +#include "CanvasView.h" +#include "Shape.h" +#include "StateView.h" +//#include "TransformShapesCommand.h" + +using std::nothrow; + +// constructor +TransformShapesBox::TransformShapesBox(CanvasView* view, + const Shape** shapes, + int32 count) + : TransformBox(view, BRect(0.0, 0.0, 1.0, 1.0)), + + fCanvasView(view), + + fShapes(shapes && count > 0 ? new Shape*[count] : NULL), + fCount(count), + + fOriginals(NULL), + + fParentTransform() +{ + if (fShapes) { + // allocate storage for the current transformations + // of each object + fOriginals = new double[fCount * 6]; + + memcpy(fShapes, shapes, fCount * sizeof(Shape*)); + + for (int32 i = 0; i < fCount; i++) { + if (fShapes[i]) + fShapes[i]->AddObserver(this); + } + + // trigger init + ObjectChanged(fShapes[0]); + } else { + SetBox(BRect(0, 0, -1, -1)); + } +} + +// destructor +TransformShapesBox::~TransformShapesBox() +{ + if (fShapes) { + for (int32 i = 0; i < fCount; i++) { + if (fShapes[i]) + fShapes[i]->RemoveObserver(this); + } + delete[] fShapes; + } + + delete[] fOriginals; +} + +// Update +void +TransformShapesBox::Update(bool deep) +{ + BRect r = Bounds(); + + TransformBox::Update(deep); + + BRect dirty(r | Bounds()); + dirty.InsetBy(-8, -8); + fView->Invalidate(dirty); + + if (!deep || !fShapes) + return; + + for (int32 i = 0; i < fCount; i++) { + if (!fShapes[i]) + continue; + + fShapes[i]->RemoveObserver(this); + fShapes[i]->SuspendNotifications(true); + + // reset the objects transformation to the saved state + fShapes[i]->LoadFrom(&fOriginals[i * 6]); + // combined with the current transformation + fShapes[i]->Multiply(*this); + + fShapes[i]->SuspendNotifications(false); + fShapes[i]->AddObserver(this); + } +} + +// ObjectChanged +void +TransformShapesBox::ObjectChanged(const Observable* object) +{ + if (!fView->LockLooper()) + return; + + fParentTransform.Reset(); + // figure out bounds and store initial transformations + BRect box(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN); + for (int32 i = 0; i < fCount; i++) { + if (!fShapes[i]) + continue; + + box = box | fShapes[i]->Bounds(); + fShapes[i]->StoreTo(&fOriginals[i * 6]); + } + Reset(); + SetBox(box); + + fView->UnlockLooper(); +} + +// Perform +Command* +TransformShapesBox::Perform() +{ + return NULL; +} + +// Cancel +Command* +TransformShapesBox::Cancel() +{ + SetTransformation(B_ORIGIN, B_ORIGIN, 0.0, 1.0, 1.0); + + return NULL; +} + +// TransformFromCanvas +void +TransformShapesBox::TransformFromCanvas(BPoint& point) const +{ + fParentTransform.InverseTransform(&point); + fCanvasView->ConvertFromCanvas(&point); +} + +// TransformToCanvas +void +TransformShapesBox::TransformToCanvas(BPoint& point) const +{ + fCanvasView->ConvertToCanvas(&point); + fParentTransform.Transform(&point); +} + +// ViewSpaceRotation +double +TransformShapesBox::ViewSpaceRotation() const +{ + Transformable t(*this); + t.Multiply(fParentTransform); + return t.rotation() * 180.0 / PI; +} + +// MakeAction +TransformCommand* +TransformShapesBox::MakeAction(const char* actionName, uint32 nameIndex) const +{ +// return new TransformShapesCommand(fShapes, fCount, +// +// Pivot(), +// Translation(), +// LocalRotation(), +// LocalXScale(), +// LocalYScale(), +// +// actionName, +// nameIndex); + return NULL; +} diff --git a/src/apps/icon-o-matic/transformable/TransformShapesBox.h b/src/apps/icon-o-matic/transformable/TransformShapesBox.h new file mode 100644 index 0000000000..d1775563f9 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/TransformShapesBox.h @@ -0,0 +1,60 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#ifndef TRANSFORM_SHAPES_BOX_H +#define TRANSFORM_SHAPES_BOX_H + +#include "TransformBox.h" + +class CanvasView; +class Shape; + +class TransformShapesBox : public TransformBox { + public: + TransformShapesBox(CanvasView* view, + const Shape** objects, + int32 count); + virtual ~TransformShapesBox(); + + // Observer interface (Manipulator is an Observer) + virtual void ObjectChanged(const Observable* object); + + // TransformBox interface + virtual void Update(bool deep = true); + + virtual void TransformFromCanvas(BPoint& point) const; + virtual void TransformToCanvas(BPoint& point) const; + virtual double ViewSpaceRotation() const; + + virtual TransformCommand* MakeAction(const char* actionName, + uint32 nameIndex) const; + + // TransformShapesBox + Command* Perform(); + Command* Cancel(); + + Shape** Shapes() const + { return fShapes; } + int32 CountShapes() const + { return fCount; } + + private: + CanvasView* fCanvasView; + + Shape** fShapes; + int32 fCount; + + // saves the transformable objects transformation states + // prior to this transformation + double* fOriginals; + + Transformable fParentTransform; +}; + +#endif // TRANSFORM_SHAPES_BOX_H + diff --git a/src/apps/icon-o-matic/transformable/Transformable.cpp b/src/apps/icon-o-matic/transformable/Transformable.cpp new file mode 100644 index 0000000000..e8e5d55833 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/Transformable.cpp @@ -0,0 +1,312 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#include "Transformable.h" + +#include +#include + +#include "support.h" + +// constructor +Transformable::Transformable() + : agg::trans_affine() +{ +} + +// copy constructor +Transformable::Transformable(const Transformable& other) + : agg::trans_affine(other) +{ +} + +// destructor +Transformable::~Transformable() +{ +} + +// StoreTo +void +Transformable::StoreTo(double matrix[6]) const +{ + store_to(matrix); +} + +// LoadFrom +void +Transformable::LoadFrom(double matrix[6]) +{ + // before calling the potentially heavy TransformationChanged() + // hook function, make sure that the transformation + // really changed + Transformable t; + t.load_from(matrix); + if (*this != t) { + load_from(matrix); + TransformationChanged(); + } +} + +// SetTransform +void +Transformable::SetTransform(const Transformable& other) +{ + if (*this != other) { + *this = other; + TransformationChanged(); + } +} + +// operator= +Transformable& +Transformable::operator=(const Transformable& other) +{ + if (other != *this) { + reset(); + multiply(other); + TransformationChanged(); + } + return *this; +} + +// Multiply +Transformable& +Transformable::Multiply(const Transformable& other) +{ + if (!other.IsIdentity()) { + multiply(other); + TransformationChanged(); + } + return *this; +} + +// Reset +void +Transformable::Reset() +{ + reset(); +} + +// Invert +void +Transformable::Invert() +{ + invert(); +} + +// IsIdentity +bool +Transformable::IsIdentity() const +{ + double m[6]; + store_to(m); + if (m[0] == 1.0 && + m[1] == 0.0 && + m[2] == 0.0 && + m[3] == 1.0 && + m[4] == 0.0 && + m[5] == 0.0) + return true; + return false; +} + +// IsTranslationOnly +bool +Transformable::IsTranslationOnly() const +{ + double m[6]; + store_to(m); + if (m[0] == 1.0 && + m[1] == 0.0 && + m[2] == 0.0 && + m[3] == 1.0) + return true; + return false; +} + +// IsNotDistorted +bool +Transformable::IsNotDistorted() const +{ + double m[6]; + store_to(m); + return (m[0] == m[3]); +} + +// IsValid +bool +Transformable::IsValid() const +{ + double m[6]; + store_to(m); + return ((m[0] * m[3] - m[1] * m[2]) != 0.0); +} + +// operator== +bool +Transformable::operator==(const Transformable& other) const +{ + double m1[6]; + other.store_to(m1); + double m2[6]; + store_to(m2); + if (m1[0] == m2[0] && + m1[1] == m2[1] && + m1[2] == m2[2] && + m1[3] == m2[3] && + m1[4] == m2[4] && + m1[5] == m2[5]) + return true; + return false; +} + +// operator!= +bool +Transformable::operator!=(const Transformable& other) const +{ + return !(*this == other); +} + +// Transform +void +Transformable::Transform(double* x, double* y) const +{ + transform(x, y); +} + +// Transform +void +Transformable::Transform(BPoint* point) const +{ + if (point) { + double x = point->x; + double y = point->y; + + transform(&x, &y); + + point->x = x; + point->y = y; + } +} + +// Transform +BPoint +Transformable::Transform(const BPoint& point) const +{ + BPoint p(point); + Transform(&p); + return p; +} + +// InverseTransform +void +Transformable::InverseTransform(double* x, double* y) const +{ + inverse_transform(x, y); +} + +// InverseTransform +void +Transformable::InverseTransform(BPoint* point) const +{ + if (point) { + double x = point->x; + double y = point->y; + + inverse_transform(&x, &y); + + point->x = x; + point->y = y; + } +} + +// InverseTransform +BPoint +Transformable::InverseTransform(const BPoint& point) const +{ + BPoint p(point); + InverseTransform(&p); + return p; +} + +// TransformBounds +BRect +Transformable::TransformBounds(BRect bounds) const +{ + if (bounds.IsValid()) { + BPoint lt(bounds.left, bounds.top); + BPoint rt(bounds.right, bounds.top); + BPoint lb(bounds.left, bounds.bottom); + BPoint rb(bounds.right, bounds.bottom); + + Transform(<); + Transform(&rt); + Transform(&lb); + Transform(&rb); + + return BRect(floorf(min4(lt.x, rt.x, lb.x, rb.x)), + floorf(min4(lt.y, rt.y, lb.y, rb.y)), + ceilf(max4(lt.x, rt.x, lb.x, rb.x)), + ceilf(max4(lt.y, rt.y, lb.y, rb.y))); + } + return bounds; +} + +// TranslateBy +void +Transformable::TranslateBy(BPoint offset) +{ + if (offset.x != 0.0 || offset.y != 0.0) { + multiply(agg::trans_affine_translation(offset.x, offset.y)); + TransformationChanged(); + } +} + +// RotateBy +void +Transformable::RotateBy(BPoint origin, double degrees) +{ + if (degrees != 0.0) { + multiply(agg::trans_affine_translation(-origin.x, -origin.y)); + multiply(agg::trans_affine_rotation(degrees * (PI / 180.0))); + multiply(agg::trans_affine_translation(origin.x, origin.y)); + TransformationChanged(); + } +} + +// ScaleBy +void +Transformable::ScaleBy(BPoint origin, double xScale, double yScale) +{ + if (xScale != 1.0 || yScale != 1.0) { + multiply(agg::trans_affine_translation(-origin.x, -origin.y)); + multiply(agg::trans_affine_scaling(xScale, yScale)); + multiply(agg::trans_affine_translation(origin.x, origin.y)); + TransformationChanged(); + } +} + +// ShearBy +void +Transformable::ShearBy(BPoint origin, double xShear, double yShear) +{ + if (xShear != 0.0 || yShear != 0.0) { + multiply(agg::trans_affine_translation(-origin.x, -origin.y)); + multiply(agg::trans_affine_skewing(xShear, yShear)); + multiply(agg::trans_affine_translation(origin.x, origin.y)); + TransformationChanged(); + } +} + +// TransformationChanged +void +Transformable::TransformationChanged() +{ + // default implementation doesn't care +} + diff --git a/src/apps/icon-o-matic/transformable/Transformable.h b/src/apps/icon-o-matic/transformable/Transformable.h new file mode 100644 index 0000000000..2924e689f2 --- /dev/null +++ b/src/apps/icon-o-matic/transformable/Transformable.h @@ -0,0 +1,68 @@ +/* + * Copyright 2006, Haiku. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ + +#ifndef TRANSFORMABLE_H +#define TRANSFORMABLE_H + +#include + +#include + +#include "Observable.h" + +class Transformable : public agg::trans_affine { + public: + Transformable(); + Transformable(const Transformable& other); + virtual ~Transformable(); + + void StoreTo(double matrix[6]) const; + void LoadFrom(double matrix[6]); + + // set to or combine with other matrix + void SetTransform(const Transformable& other); + Transformable& operator=(const Transformable& other); + Transformable& Multiply(const Transformable& other); + virtual void Reset(); + + void Invert(); + + bool IsIdentity() const; + bool IsTranslationOnly() const; + bool IsNotDistorted() const; + bool IsValid() const; + + bool operator==(const Transformable& other) const; + bool operator!=(const Transformable& other) const; + + // transforms coordiantes + void Transform(double* x, double* y) const; + void Transform(BPoint* point) const; + BPoint Transform(const BPoint& point) const; + + void InverseTransform(double* x, double* y) const; + void InverseTransform(BPoint* point) const; + BPoint InverseTransform(const BPoint& point) const; + + // transforms the rectangle "bounds" and + // returns the *bounding box* of that + BRect TransformBounds(BRect bounds) const; + + // some convenience functions + virtual void TranslateBy(BPoint offset); + virtual void RotateBy(BPoint origin, double degrees); + virtual void ScaleBy(BPoint origin, double xScale, double yScale); + virtual void ShearBy(BPoint origin, double xShear, double yShear); + + virtual void TransformationChanged(); + // hook function that is called when the transformation + // is changed for some reason +}; + +#endif // TRANSFORMABLE_H +