diff --git a/src/data/action/symbol.cpp b/src/data/action/symbol.cpp index 6b14cd30..b4b48324 100644 --- a/src/data/action/symbol.cpp +++ b/src/data/action/symbol.cpp @@ -129,8 +129,8 @@ void SymbolPartRotateAction::perform(bool to_undo) { angle = -angle; } -void SymbolPartRotateAction::rotateTo(double newAngle) { - double oldAngle = angle; +void SymbolPartRotateAction::rotateTo(Radians newAngle) { + Radians oldAngle = angle; angle = newAngle; // constrain? if (constrain) { @@ -141,7 +141,7 @@ void SymbolPartRotateAction::rotateTo(double newAngle) { if (oldAngle != angle) rotateBy(angle - oldAngle); } -void SymbolPartRotateAction::rotateBy(double deltaAngle) { +void SymbolPartRotateAction::rotateBy(Radians deltaAngle) { // Rotation 'matrix' transform( Matrix2D(cos(deltaAngle), -sin(deltaAngle) diff --git a/src/data/action/symbol.hpp b/src/data/action/symbol.hpp index f3bc984c..4e79654c 100644 --- a/src/data/action/symbol.hpp +++ b/src/data/action/symbol.hpp @@ -85,13 +85,13 @@ class SymbolPartRotateAction : public SymbolPartMatrixAction { virtual void perform(bool to_undo); /// Update this action to rotate to a different angle - void rotateTo(double newAngle); + void rotateTo(Radians newAngle); /// Update this action to rotate by a deltaAngle - void rotateBy(double deltaAngle); + void rotateBy(Radians deltaAngle); private: - double angle; ///< How much to rotate? + Radians angle; ///< How much to rotate? public: bool constrain; ///< Constrain movement? }; diff --git a/src/data/action/symbol_part.cpp b/src/data/action/symbol_part.cpp index 52847d9a..9a08c03f 100644 --- a/src/data/action/symbol_part.cpp +++ b/src/data/action/symbol_part.cpp @@ -459,8 +459,8 @@ void SymmetryMoveAction::move(const Vector2D& deltaDelta) { symmetry.handle = snap_vector(symmetry.center + original + delta, snap) - symmetry.center; if (constrain) { // constrain to multiples of 2pi/24 i.e. 24 stops - double angle = atan2(symmetry.handle.y, symmetry.handle.x); - double mult = (2 * M_PI) / 24; + Radians angle = atan2(symmetry.handle.y, symmetry.handle.x); + Radians mult = (2 * M_PI) / 24; angle = floor(angle / mult + 0.5) * mult; symmetry.handle = Vector2D(cos(angle), sin(angle)) * symmetry.handle.length(); } diff --git a/src/data/field.cpp b/src/data/field.cpp index e456397b..f2082bf7 100644 --- a/src/data/field.cpp +++ b/src/data/field.cpp @@ -170,7 +170,7 @@ int Style::update(Context& ctx) { else {int tb = int(top + bottom); top = (tb - height) / 2; bottom = (tb + height) / 2; } // adjust rotation point if (angle != 0 && (automatic_side & (AUTO_LEFT | AUTO_TOP))) { - double s = sin(angle * M_PI / 180), c = cos(angle * M_PI / 180); + double s = sin(deg_to_rad(angle)), c = cos(deg_to_rad(angle)); if (automatic_side & AUTO_LEFT) { // attach right corner instead of left left = left + width * (1 - c); top = top + width * s; diff --git a/src/data/field.hpp b/src/data/field.hpp index 9512c806..639d78cf 100644 --- a/src/data/field.hpp +++ b/src/data/field.hpp @@ -101,7 +101,7 @@ class Style : public IntrusivePtrVirtualBase { Scriptable left, top; ///< Position of this field Scriptable width, height; ///< Position of this field Scriptable right, bottom; ///< Position of this field - Scriptable angle; ///< Rotation of the box + Scriptable angle; ///< Rotation of the box Scriptable visible; ///< Is this field visible? CachedScriptableMask mask; ///< Mask image diff --git a/src/data/settings.hpp b/src/data/settings.hpp index a807444c..4b9849ff 100644 --- a/src/data/settings.hpp +++ b/src/data/settings.hpp @@ -12,6 +12,7 @@ #include #include #include +#include class Game; class StyleSheet; @@ -98,7 +99,7 @@ class StyleSheetSettings : public IntrusivePtrBase { // Rendering/display settings Defaultable card_zoom; - Defaultable card_angle; + Defaultable card_angle; Defaultable card_anti_alias; Defaultable card_borders; Defaultable card_draw_editing; diff --git a/src/data/symbol.cpp b/src/data/symbol.cpp index 5af5be33..c6e0ddc1 100644 --- a/src/data/symbol.cpp +++ b/src/data/symbol.cpp @@ -255,7 +255,7 @@ String SymbolSymmetry::expectedName() const { Bounds SymbolSymmetry::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) { Bounds bounds; // See SymbolViewer::draw - double b = 2 * handle.angle(); + Radians b = 2 * handle.angle(); int copies = kind == SYMMETRY_REFLECTION ? this->copies & ~1 : this->copies; FOR_EACH_CONST(p, parts) { for (int i = 0 ; i < copies ; ++i) { diff --git a/src/gfx/generated_image.hpp b/src/gfx/generated_image.hpp index 7833cad3..fbe84c6a 100644 --- a/src/gfx/generated_image.hpp +++ b/src/gfx/generated_image.hpp @@ -36,7 +36,7 @@ class GeneratedImage : public ScriptValue { mutable int width, height; ///< Width to force the image to, or 0 to keep the width of the input ///< In that case, width and height will be later set to the actual size double zoom; ///< Zoom factor to use, when width=height=0 - int angle; ///< Angle to rotate image by afterwards + Radians angle; ///< Angle to rotate image by afterwards PreserveAspect preserve_aspect; bool saturate; Package* package; ///< Package to load images from @@ -264,13 +264,13 @@ class FlipImageVertical : public SimpleFilterImage { /// Rotate an image class RotateImage : public SimpleFilterImage { public: - inline RotateImage(const GeneratedImageP& image, double angle) + inline RotateImage(const GeneratedImageP& image, Radians angle) : SimpleFilterImage(image), angle(angle) {} virtual Image generate(const Options& opt) const; virtual bool operator == (const GeneratedImage& that) const; private: - double angle; + Radians angle; }; // ----------------------------------------------------------------------------- : EnlargeImage diff --git a/src/gfx/gfx.hpp b/src/gfx/gfx.hpp index 7ca61043..caae9f89 100644 --- a/src/gfx/gfx.hpp +++ b/src/gfx/gfx.hpp @@ -16,6 +16,7 @@ #include #include +#include #include // ----------------------------------------------------------------------------- : Resampling @@ -52,29 +53,15 @@ void sharp_resample_and_clip(const Image& img_in, Image& img_out, wxRect rect, i * rect = rectangle to draw in (a rectangle somewhere around pos) * stretch = amount to stretch in the direction of the text after drawing */ -void draw_resampled_text(DC& dc, const RealPoint& pos, const RealRect& rect, double stretch, int angle, AColor color, const String& text, int blur_radius = 0, int repeat = 1); +void draw_resampled_text(DC& dc, const RealPoint& pos, const RealRect& rect, double stretch, Radians angle, AColor color, const String& text, int blur_radius = 0, int repeat = 1); // scaling factor to use when drawing resampled text extern const int text_scaling; // ----------------------------------------------------------------------------- : Image rotation -/// Is an angle a {0,90,180,270}? -inline bool straight(int angle) { return angle % 90 == 0; } - -/// Is an angle sideways (90 or 270 degrees)? -inline bool sideways(int angle) { - int a = (angle + 3600) % 180; - return (a > 45 && a < 135); -} - -/// Convert radians to degrees -inline double rad_to_deg(double rad) { return rad * (180.0 / M_PI); } -/// Convert degrees to radians -inline double deg_to_rad(double deg) { return deg * (M_PI / 180.0); } - /// Rotates an image counter clockwise -Image rotate_image(const Image& image, double angle); +Image rotate_image(const Image& image, Radians angle); /// Flip an image horizontally Image flip_image_horizontal(const Image& image); diff --git a/src/gfx/resample_text.cpp b/src/gfx/resample_text.cpp index fa25b9b9..bb454595 100644 --- a/src/gfx/resample_text.cpp +++ b/src/gfx/resample_text.cpp @@ -165,7 +165,7 @@ void blur_image_alpha(Image& img) { // Draw text by first drawing it using a larger font and then downsampling it // optionally rotated by an angle -void draw_resampled_text(DC& dc, const RealPoint& pos, const RealRect& rect, double stretch, int angle, AColor color, const String& text, int blur_radius, int repeat) { +void draw_resampled_text(DC& dc, const RealPoint& pos, const RealRect& rect, double stretch, Radians angle, AColor color, const String& text, int blur_radius, int repeat) { // transparent text can be ignored if (color.alpha == 0) return; // enlarge slightly; some fonts are larger then the GetTextExtent tells us (especially italic fonts) @@ -183,11 +183,11 @@ void draw_resampled_text(DC& dc, const RealPoint& pos, const RealRect& rect, dou // now draw the text mdc.SetFont(dc.GetFont()); mdc.SetTextForeground(*wxWHITE); - mdc.DrawRotatedText(text, xsub, ysub, angle); + mdc.DrawRotatedText(text, xsub, ysub, rad_to_deg(angle)); // get image mdc.SelectObject(wxNullBitmap); // step 2. sample down - double ca = fabs(cos(deg_to_rad(angle))), sa = fabs(sin(deg_to_rad(angle))); + double ca = fabs(cos(angle)), sa = fabs(sin(angle)); w += int(w * (stretch - 1) * ca); // GCC makes annoying conversion warnings if *= is used here. h += int(h * (stretch - 1) * sa); Image img_small(w, h, false); diff --git a/src/gfx/rotate_image.cpp b/src/gfx/rotate_image.cpp index 76bfd590..7d6d82f1 100644 --- a/src/gfx/rotate_image.cpp +++ b/src/gfx/rotate_image.cpp @@ -46,7 +46,7 @@ Image rotate_image_impl(Image img) { // ----------------------------------------------------------------------------- : Rotations // Function object to handle rotation -struct Rotate90 { +struct Rotate90deg { /// Init a rotated image, where the source is w * h pixels inline static void init(Image& img, UInt w, UInt h) { img.Create(h, w, false); @@ -59,7 +59,7 @@ struct Rotate90 { } }; -struct Rotate180 { +struct Rotate180deg { inline static void init(Image& img, UInt w, UInt h) { img.Create(w, h, false); } @@ -70,7 +70,7 @@ struct Rotate180 { } }; -struct Rotate270 { +struct Rotate270deg { inline static void init(Image& img, UInt w, UInt h) { img.Create(h, w, false); } @@ -83,19 +83,15 @@ struct Rotate270 { // ----------------------------------------------------------------------------- : Interface -double almost_equal(double x, double y) { - return fabs(x-y) < 1e-6; -} - -Image rotate_image(const Image& image, double angle) { - double a = fmod(angle, 360); - if (almost_equal(a, 0)) return image; - if (almost_equal(a, 90)) return rotate_image_impl (image); - if (almost_equal(a,180)) return rotate_image_impl(image); - if (almost_equal(a,270)) return rotate_image_impl(image); +Image rotate_image(const Image& image, Radians angle) { + double a = constrain_radians(angle); + if (is_rad0(a)) return image; + if (is_rad90(a)) return rotate_image_impl (image); + if (is_rad180(a)) return rotate_image_impl(image); + if (is_rad270(a)) return rotate_image_impl(image); else { if (!image.HasAlpha()) const_cast(image).InitAlpha(); - return image.Rotate(angle * M_PI / 180, wxPoint(0,0)); + return image.Rotate(angle, wxPoint(0,0)); } } diff --git a/src/gui/control/card_viewer.cpp b/src/gui/control/card_viewer.cpp index c2acdf2b..aa1ab77c 100644 --- a/src/gui/control/card_viewer.cpp +++ b/src/gui/control/card_viewer.cpp @@ -30,7 +30,7 @@ wxSize CardViewer::DoGetBestSize() const { if (!stylesheet) stylesheet = set->stylesheet; StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet); wxSize size(int(stylesheet->card_width * ss.card_zoom()), int(stylesheet->card_height * ss.card_zoom())); - if (sideways(ss.card_angle())) swap(size.x, size.y); + if (is_sideways(deg_to_rad(ss.card_angle()))) swap(size.x, size.y); return size + ws - cs; } return cs; @@ -143,7 +143,7 @@ Rotation CardViewer::getRotation() const { if (!stylesheet) stylesheet = set->stylesheet; StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet); int dx = GetScrollPos(wxHORIZONTAL), dy = GetScrollPos(wxVERTICAL); - return Rotation(ss.card_angle(), stylesheet->getCardRect().move(-dx,-dy,0,0), ss.card_zoom(), 1.0, ROTATION_ATTACH_TOP_LEFT); + return Rotation(deg_to_rad(ss.card_angle()), stylesheet->getCardRect().move(-dx,-dy,0,0), ss.card_zoom(), 1.0, ROTATION_ATTACH_TOP_LEFT); } // ----------------------------------------------------------------------------- : Event table diff --git a/src/gui/control/graph.cpp b/src/gui/control/graph.cpp index beb5627b..839e3eff 100644 --- a/src/gui/control/graph.cpp +++ b/src/gui/control/graph.cpp @@ -493,7 +493,7 @@ void PieGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const { Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); dc.SetPen(fg); // draw pies - double angle = M_PI/2; + Radians angle = M_PI/2; int i = 0; FOR_EACH_CONST(g, axis.groups) { // draw pie @@ -501,7 +501,7 @@ void PieGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const { if (g.size > 0) { bool active = i == current; dc.SetPen(active ? fg : lerp(fg,g.color,0.5)); - double end_angle = angle - 2 * M_PI * (double)g.size / axis.total; + Radians end_angle = angle - 2 * M_PI * (double)g.size / axis.total; dc.DrawEllipticArc(pie_pos, active ? pie_size_large : pie_size, end_angle, angle); angle = end_angle; } @@ -510,7 +510,7 @@ void PieGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const { // draw spokes if (axis.groups.size() > 1) { int i = 0; - double angle = M_PI/2; + Radians angle = M_PI/2; FOR_EACH_CONST(g, axis.groups) { if (true) { int i2 = (i - 1 + (int)axis.groups.size()) % (int)axis.groups.size(); @@ -539,7 +539,7 @@ int PieGraph::findItem(const RealPoint& pos, const RealRect& screen_rect, bool t double pos_angle = atan2(-delta.y, delta.x) - M_PI/2; // in range [-pi..pi] if (pos_angle < 0) pos_angle += 2 * M_PI; // find angle - double angle = 2 * M_PI; + Radians angle = 2 * M_PI; int i = 0; FOR_EACH_CONST(g, axis.groups) { angle -= 2 * M_PI * (double)g.size / axis.total; @@ -705,14 +705,14 @@ void ScatterPieGraph::draw(RotatedDC& dc, const vector& current, DrawLayer RealSize radius_s(radius,radius); RealPoint center(screen_rect.left() + (x+0.5) * size.width + 0.5, screen_rect.bottom() - (y+0.5) * size.height + 0.5); // draw pie slices - double angle = 0; + Radians angle = 0; size_t j = 0; FOR_EACH(g, axis3.groups) { UInt val = values3D[i * axis3.groups.size() + j++]; if (val > 0) { dc.SetBrush(g.color); dc.SetPen(active ? fg : lerp(fg,g.color,0.5)); - double end_angle = angle + 2 * M_PI * (double)val / value; + Radians end_angle = angle + 2 * M_PI * (double)val / value; dc.DrawEllipticArc(center, radius_s, angle, end_angle); angle = end_angle; } diff --git a/src/gui/control/native_look_editor.cpp b/src/gui/control/native_look_editor.cpp index ddd68174..772513fa 100644 --- a/src/gui/control/native_look_editor.cpp +++ b/src/gui/control/native_look_editor.cpp @@ -38,7 +38,7 @@ void NativeLookEditor::drawViewer(RotatedDC& dc, ValueViewer& v) { if (!e || e->drawLabel()) { // draw control border and box Style& s = *v.getStyle(); - draw_control_box(this, dc.getDC(), dc.trRectStraight(s.getInternalRect().grow(1)), current_editor == e, e != nullptr); + draw_control_box(this, dc.getDC(), dc.trRectToBB(s.getInternalRect().grow(1)), current_editor == e, e != nullptr); // draw label dc.SetFont(*wxNORMAL_FONT); dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT)); diff --git a/src/gui/print_window.cpp b/src/gui/print_window.cpp index d5b50d8f..33a6e855 100644 --- a/src/gui/print_window.cpp +++ b/src/gui/print_window.cpp @@ -39,7 +39,7 @@ class TextBufferDC : public wxMemoryDC { TextBufferDC(int width, int height, bool buffer_text); virtual void DoDrawText(const String& str, int x, int y); - virtual void DoDrawRotatedText(const String& str, int x, int y, double angle); + virtual void DoDrawRotatedText(const String& str, int x, int y, Radians angle); /// Copy the contents of the DC to a target device, this DC becomes invalid void drawToDevice(DC& dc, int x = 0, int y = 0); @@ -51,10 +51,10 @@ class TextBufferDC : public wxMemoryDC { Color color; int x, y; String text; - double angle; + Radians angle; double user_scale_x, user_scale_y; - TextDraw(wxFont font, Color color, double user_scale_x, double user_scale_y, int x, int y, String text, double angle = 0) + TextDraw(wxFont font, Color color, double user_scale_x, double user_scale_y, int x, int y, String text, Radians angle = 0) : font(font), color(color), x(x), y(y), text(text), angle(angle), user_scale_x(user_scale_x), user_scale_y(user_scale_y) {} }; @@ -83,13 +83,13 @@ void TextBufferDC::DoDrawText(const String& str, int x, int y) { wxMemoryDC::DoDrawText(str,x,y); } } -void TextBufferDC::DoDrawRotatedText(const String& str, int x, int y, double angle) { +void TextBufferDC::DoDrawRotatedText(const String& str, int x, int y, Radians angle) { if (buffer_text) { double usx,usy; GetUserScale(&usx, &usy); text.push_back( intrusive(new TextDraw(GetFont(), GetTextForeground(), usx, usy, x, y, str, angle)) ); } else { - wxMemoryDC::DoDrawRotatedText(str,x,y,angle); + wxMemoryDC::DoDrawRotatedText(str,x,y,rad_to_deg(angle)); } } @@ -104,8 +104,8 @@ void TextBufferDC::drawToDevice(DC& dc, int x, int y) { dc.SetUserScale(usx * t->user_scale_x, usx * t->user_scale_y); dc.SetFont (t->font); dc.SetTextForeground(t->color); - if (t->angle) { - dc.DrawRotatedText(t->text, t->x + x, t->y + y, t->angle); + if (!is_rad0(t->angle)) { + dc.DrawRotatedText(t->text, t->x + x, t->y + y, rad_to_deg(t->angle)); } else { dc.DrawText(t->text, t->x + x, t->y + y); } diff --git a/src/gui/set/cards_panel.cpp b/src/gui/set/cards_panel.cpp index fb4a316d..a3527772 100644 --- a/src/gui/set/cards_panel.cpp +++ b/src/gui/set/cards_panel.cpp @@ -14,6 +14,7 @@ #include // for HoverButton #include #include +#include #include #include #include @@ -34,6 +35,25 @@ DECLARE_TYPEOF_COLLECTION(AddCardsScriptP); #define HAVE_TOOLBAR_DROPDOWN_MENU 1 #endif +// ----------------------------------------------------------------------------- : DropDownMRUList + +/// A drop down list of recent choices, for autocomplete +class DropDownMRUList : public DropDownList { + public: + DropDownMRUList(Window* parent, vector const& choices) + : DropDownList(parent) + , choices(choices) + {} + + vector choices; + + protected: + virtual size_t selection() const { return NO_SELECTION; } + virtual size_t itemCount() const { return choices.size(); } + virtual String itemText(size_t item) const { return choices.at(item); } + virtual void select(size_t item); +}; + // ----------------------------------------------------------------------------- : FilterControl /// Text control that forwards focus events to the parent @@ -467,7 +487,7 @@ void CardsPanel::onCommand(int id) { case ID_CARD_ROTATE_0: case ID_CARD_ROTATE_90: case ID_CARD_ROTATE_180: case ID_CARD_ROTATE_270: { StyleSheetSettings& ss = settings.stylesheetSettingsFor(set->stylesheetFor(card_list->getCard())); ss.card_angle.assign( - id == ID_CARD_ROTATE ? (ss.card_angle() + 90) % 360 + id == ID_CARD_ROTATE ? sane_fmod(ss.card_angle() + 90, 360) : id == ID_CARD_ROTATE_0 ? 0 : id == ID_CARD_ROTATE_90 ? 90 : id == ID_CARD_ROTATE_180 ? 180 diff --git a/src/gui/set/stats_panel.cpp b/src/gui/set/stats_panel.cpp index 481e5b3b..e115a037 100644 --- a/src/gui/set/stats_panel.cpp +++ b/src/gui/set/stats_panel.cpp @@ -258,16 +258,18 @@ void StatDimensionList::drawItem(DC& dc, int x, int y, size_t item) { RealPoint pos = align_in_rect(ALIGN_MIDDLE_LEFT, size, rect); dc.DrawText(str, (int)pos.x, (int)pos.y); // draw selection icon - for (size_t j = 1 ; j <= prefered_dimension_count ; ++j) { + for (size_t j = 1 ; j <= dimensions.size() ; ++j) { + bool prefered = j <= prefered_dimension_count; if (isSelected(item,j)) { // TODO: different icons for different dimensions /* - int cx = x + columns[j].offset.x + columns[j].size.x/2; - int cy = y + columns[j].offset.y + columns[j].size.y/2; - dc.SetPen(*wxTRANSPARENT_PEN); - dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); - dc.DrawCircle(cx,cy,6); */ + int cx = x + subcolumns[j].offset.x + subcolumns[j].size.x/2; + int cy = y + subcolumns[j].offset.y + subcolumns[j].size.y/2; + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(prefered ? wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) + : lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW),0.5)); + dc.DrawCircle(cx,cy,6); } } } diff --git a/src/gui/symbol/select_editor.cpp b/src/gui/symbol/select_editor.cpp index 34019b73..d7f11a5d 100644 --- a/src/gui/symbol/select_editor.cpp +++ b/src/gui/symbol/select_editor.cpp @@ -31,12 +31,12 @@ SymbolSelectEditor::SymbolSelectEditor(SymbolControl* control, bool rotate) // Load resource images Image rot = load_resource_image(_("handle_rotate")); handleRotateTL = wxBitmap(rot); - handleRotateTR = wxBitmap(rotate_image(rot,90)); - handleRotateBR = wxBitmap(rotate_image(rot,180)); - handleRotateBL = wxBitmap(rotate_image(rot,270)); + handleRotateTR = wxBitmap(rotate_image(rot,rad90)); + handleRotateBR = wxBitmap(rotate_image(rot,rad180)); + handleRotateBL = wxBitmap(rotate_image(rot,rad270)); Image shear = load_resource_image(_("handle_shear_x")); handleShearX = wxBitmap(shear); - handleShearY = wxBitmap(rotate_image(shear,90)); + handleShearY = wxBitmap(rotate_image(shear,rad90)); handleCenter = wxBitmap(load_resource_image(_("handle_center"))); // Make sure all parts have updated bounds getSymbol()->updateBounds(); @@ -353,7 +353,7 @@ void SymbolSelectEditor::onMouseDrag (const Vector2D& from, const Vector2D& to, scaleAction->move(dMin, dMax); } else if (rotateAction) { // rotate the selected parts - double angle = angleTo(to); + Radians angle = angleTo(to); rotateAction->constrain = ev.ControlDown(); rotateAction->rotateTo(startAngle - angle); } else if (shearAction) { diff --git a/src/gui/symbol/select_editor.hpp b/src/gui/symbol/select_editor.hpp index 6a67cf99..cf98b134 100644 --- a/src/gui/symbol/select_editor.hpp +++ b/src/gui/symbol/select_editor.hpp @@ -84,7 +84,7 @@ class SymbolSelectEditor : public SymbolEditorBase { CLICK_TOGGLE, // same selection, not moved -> switch to rotate mode } click_mode; // At what angle is the handle we started draging for rotation - double startAngle; + Radians startAngle; // what side are we dragging/rotating on? int scaleX, scaleY; // have we dragged? @@ -110,7 +110,7 @@ class SymbolSelectEditor : public SymbolEditorBase { bool onAnyHandle(const Vector2D& mpos, int* dxOut, int* dyOut); /// Angle between center and pos - double angleTo(const Vector2D& pos); + Radians angleTo(const Vector2D& pos); /// Update minV and maxV to be the bounding box of the selected_parts /// Updates center to be the rotation center of the parts diff --git a/src/gui/value/choice.cpp b/src/gui/value/choice.cpp index 2995e2fc..7c472be3 100644 --- a/src/gui/value/choice.cpp +++ b/src/gui/value/choice.cpp @@ -297,7 +297,7 @@ void ChoiceValueEditor::onLoseFocus() { void ChoiceValueEditor::draw(RotatedDC& dc) { ChoiceValueViewer::draw(dc); if (nativeLook()) { - draw_drop_down_arrow(&editor(), dc.getDC(), dc.trRectStraight(style().getInternalRect().grow(1)), drop_down->IsShown()); + draw_drop_down_arrow(&editor(), dc.getDC(), dc.trRectToBB(style().getInternalRect().grow(1)), drop_down->IsShown()); } } void ChoiceValueEditor::determineSize(bool) { diff --git a/src/gui/value/choice.hpp b/src/gui/value/choice.hpp index bdd26563..cad68c30 100644 --- a/src/gui/value/choice.hpp +++ b/src/gui/value/choice.hpp @@ -88,7 +88,7 @@ class DropDownChoiceListBase : public DropDownList { // ----------------------------------------------------------------------------- : DropDownChoiceList -/// A drop down list of color choices +/// A drop down list of choices class DropDownChoiceList : public DropDownChoiceListBase { public: DropDownChoiceList(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group); diff --git a/src/gui/value/color.cpp b/src/gui/value/color.cpp index f9a4aefe..fb31b3a2 100644 --- a/src/gui/value/color.cpp +++ b/src/gui/value/color.cpp @@ -142,7 +142,7 @@ void ColorValueEditor::onLoseFocus() { void ColorValueEditor::draw(RotatedDC& dc) { ColorValueViewer::draw(dc); if (nativeLook()) { - draw_drop_down_arrow(&editor(), dc.getDC(), dc.trRectStraight(dc.getInternalRect().grow(1)), drop_down->IsShown()); + draw_drop_down_arrow(&editor(), dc.getDC(), dc.trRectToBB(dc.getInternalRect().grow(1)), drop_down->IsShown()); } } void ColorValueEditor::determineSize(bool) { diff --git a/src/gui/value/package_choice.cpp b/src/gui/value/package_choice.cpp index b8a3cf6e..4693a063 100644 --- a/src/gui/value/package_choice.cpp +++ b/src/gui/value/package_choice.cpp @@ -103,7 +103,7 @@ void PackageChoiceValueEditor::onLoseFocus() { void PackageChoiceValueEditor::draw(RotatedDC& dc) { PackageChoiceValueViewer::draw(dc); if (nativeLook()) { - draw_drop_down_arrow(&editor(), dc.getDC(), dc.trRectStraight(style().getInternalRect().grow(1)), drop_down && drop_down->IsShown()); + draw_drop_down_arrow(&editor(), dc.getDC(), dc.trRectToBB(style().getInternalRect().grow(1)), drop_down && drop_down->IsShown()); } } void PackageChoiceValueEditor::determineSize(bool) { diff --git a/src/gui/value/text.cpp b/src/gui/value/text.cpp index 794bc6d5..fd6d7a74 100644 --- a/src/gui/value/text.cpp +++ b/src/gui/value/text.cpp @@ -717,8 +717,8 @@ wxCursor TextValueEditor::cursor(const RealPoint& pos) const { hovered_words = p.get(); const_cast(this)->redrawWordListIndicators(); } - int angle = viewer.getRotation().getAngle() + style().angle; - if (sideways(angle)) { // 90 or 270 degrees + Radians angle = viewer.getRotation().getAngle() + deg_to_rad(style().angle); + if (is_sideways(angle)) { // 90 or 270 degrees if (!rotated_ibeam.Ok()) { rotated_ibeam = wxCursor(load_resource_cursor(_("rot_text"))); } @@ -1248,7 +1248,7 @@ bool TextValueEditor::search(FindInfo& find, bool from_start) { void TextValueEditor::determineSize(bool force_fit) { if (!nativeLook()) return; - style().angle = 0; // no rotation in nativeLook + style().angle = 0; // force no rotation in nativeLook if (scrollbar) { // muliline, determine scrollbar size Rotation rot = viewer.getRotation(); diff --git a/src/main.cpp b/src/main.cpp index babbfc52..7ff9b274 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -292,9 +292,10 @@ void MSE::HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEvent& even // ----------------------------------------------------------------------------- : Events int MSE::FilterEvent(wxEvent& ev) { - if (ev.GetEventType() == wxEVT_MOUSE_CAPTURE_LOST) { + /*if (ev.GetEventType() == wxEVT_MOUSE_CAPTURE_LOST) { return 1; } else { return -1; - } + }*/ + return -1; } diff --git a/src/mse.vcproj b/src/mse.vcproj index 4bb02f04..c067bb77 100644 --- a/src/mse.vcproj +++ b/src/mse.vcproj @@ -2551,6 +2551,10 @@ + + diff --git a/src/render/card/viewer.cpp b/src/render/card/viewer.cpp index c6ba5435..f83db623 100644 --- a/src/render/card/viewer.cpp +++ b/src/render/card/viewer.cpp @@ -116,7 +116,7 @@ Context& DataViewer::getContext() const { Rotation DataViewer::getRotation() const { if (!stylesheet) stylesheet = set->stylesheet; StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet); - return Rotation(ss.card_angle(), stylesheet->getCardRect(), ss.card_zoom(), 1.0, ROTATION_ATTACH_TOP_LEFT); + return Rotation(deg_to_rad(ss.card_angle()), stylesheet->getCardRect(), ss.card_zoom(), 1.0, ROTATION_ATTACH_TOP_LEFT); } Package& DataViewer::getStylePackage() const { diff --git a/src/render/symbol/viewer.cpp b/src/render/symbol/viewer.cpp index 971bf545..62a5a2bb 100644 --- a/src/render/symbol/viewer.cpp +++ b/src/render/symbol/viewer.cpp @@ -151,7 +151,7 @@ void SymbolViewer::combineSymbolPart(DC& dc, const SymbolPart& part, bool& paint } } else if (const SymbolSymmetry* s = part.isSymbolSymmetry()) { // Draw all parts, in reverse order (bottom to top), also draw rotated copies - double b = 2 * s->handle.angle(); + Radians b = 2 * s->handle.angle(); Matrix2D old_m = multiply; Vector2D old_o = origin; int copies = s->kind == SYMMETRY_REFLECTION ? s->copies / 2 * 2 : s->copies; @@ -345,11 +345,11 @@ void SymbolViewer::highlightPart(DC& dc, const SymbolSymmetry& sym, HighlightSty // center RealPoint center = rotation.tr(sym.center); // draw 'spokes' - double angle = atan2(sym.handle.y, sym.handle.x); + Radians angle = atan2(sym.handle.y, sym.handle.x); dc.SetPen(wxPen(color, sym.kind == SYMMETRY_ROTATION ? 1 : 3)); int copies = sym.kind == SYMMETRY_REFLECTION ? sym.copies / 2 * 2 : sym.copies; for (int i = 0; i < copies ; ++i) { - double a = angle + (i + 0.5) * 2 * M_PI / copies; + Radians a = angle + (i + 0.5) * 2 * M_PI / copies; Vector2D dir(cos(a), sin(a)); Vector2D dir2 = rotation.tr(sym.center + 2 * dir); dc.DrawLine(int(center.x), int(center.y), int(dir2.x), int(dir2.y)); diff --git a/src/render/value/image.cpp b/src/render/value/image.cpp index 19b90bf5..75cb2427 100644 --- a/src/render/value/image.cpp +++ b/src/render/value/image.cpp @@ -19,7 +19,7 @@ void ImageValueViewer::draw(RotatedDC& dc) { DrawWhat what = viewer.drawWhat(this); // reset? int w = max(0,(int)dc.trX(style().width)), h = max(0,(int)dc.trY(style().height)); - int a = dc.trAngle(0); //% TODO : Add getAngle()? + Radians a = dc.getAngle(); const AlphaMask& alpha_mask = getMask(w,h); if (bitmap.Ok() && (a != angle || size.width != w || size.height != h)) { bitmap = Bitmap(); @@ -46,7 +46,7 @@ void ImageValueViewer::draw(RotatedDC& dc) { is_default = true; if (what & DRAW_EDITING) { bitmap = imagePlaceholder(dc, w, h, image, what & DRAW_EDITING); - if (alpha_mask.isLoaded() || a) { + if (alpha_mask.isLoaded() || !is_rad0(a)) { image = bitmap.ConvertToImage(); // we need to convert back to an image } else { image = Image(); @@ -57,7 +57,7 @@ void ImageValueViewer::draw(RotatedDC& dc) { if (!image.Ok() && !bitmap.Ok() && style().width > 40) { // placeholder bitmap bitmap = imagePlaceholder(dc, w, h, wxNullImage, what & DRAW_EDITING); - if (alpha_mask.isLoaded() || a) { + if (alpha_mask.isLoaded() || !is_rad0(a)) { // we need to convert back to an image image = bitmap.ConvertToImage(); } diff --git a/src/render/value/image.hpp b/src/render/value/image.hpp index 16ee96d1..bf9566b3 100644 --- a/src/render/value/image.hpp +++ b/src/render/value/image.hpp @@ -29,8 +29,8 @@ class ImageValueViewer : public ValueViewer { private: Bitmap bitmap; ///< Cached bitmap RealSize size; ///< Size of cached bitmap - int angle; ///< Angle of cached bitmap - int is_default; ///< Is the default placeholder image used? + Radians angle; ///< Angle of cached bitmap + bool is_default; ///< Is the default placeholder image used? /// Generate a placeholder image static Bitmap imagePlaceholder(const Rotation& rot, UInt w, UInt h, const Image& background, bool editing); diff --git a/src/render/value/viewer.cpp b/src/render/value/viewer.cpp index f2f8169e..863e7d46 100644 --- a/src/render/value/viewer.cpp +++ b/src/render/value/viewer.cpp @@ -36,7 +36,7 @@ RealRect ValueViewer::boundingBox() const { } Rotation ValueViewer::getRotation() const { - return Rotation(getStyle()->angle, getStyle()->getExternalRect(), 1.0, getStretch()); + return Rotation(deg_to_rad(getStyle()->angle), getStyle()->getExternalRect(), 1.0, getStretch()); } bool ValueViewer::setFieldBorderPen(RotatedDC& dc) { diff --git a/src/script/functions/image.cpp b/src/script/functions/image.cpp index 07eb843f..5a4bf63a 100644 --- a/src/script/functions/image.cpp +++ b/src/script/functions/image.cpp @@ -131,8 +131,8 @@ SCRIPT_FUNCTION(flip_vertical) { SCRIPT_FUNCTION(rotate) { SCRIPT_PARAM_C(GeneratedImageP, input); - SCRIPT_PARAM_N(double, _("angle"), angle); - return intrusive(new RotateImage(input,angle)); + SCRIPT_PARAM_N(Degrees, _("angle"), angle); + return intrusive(new RotateImage(input,deg_to_rad(angle))); } SCRIPT_FUNCTION(drop_shadow) { diff --git a/src/script/image.cpp b/src/script/image.cpp index 44fce34b..a4e820e2 100644 --- a/src/script/image.cpp +++ b/src/script/image.cpp @@ -125,11 +125,13 @@ void CachedScriptableImage::generateCached(const GeneratedImage::Options& option } } else { // image - if (cached_i.Ok() && (options.angle - cached_angle + 360) % 90 == 0) { + Radians relative_rotation = options.angle + rad360 - cached_angle; + if (cached_i.Ok() && is_straight(relative_rotation)) { + // we need only an {0,90,180,270} degree rotation compared to the cached one, this doesn't reduce image quality if ((w_ok && h_ok) || (options.preserve_aspect == ASPECT_FIT && (w_ok || h_ok))) { // only one dimension has to fit when fitting if (options.angle != cached_angle) { // rotate cached image - cached_i = rotate_image(cached_i, options.angle - cached_angle + 360); + cached_i = rotate_image(cached_i, relative_rotation); cached_angle = options.angle; } *image = cached_i; @@ -137,8 +139,8 @@ void CachedScriptableImage::generateCached(const GeneratedImage::Options& option } } } - // hack: temporarily set angle to 0, do actual rotation after applying mask - int a = options.angle; + // hack(part1): temporarily set angle to 0, do actual rotation after applying mask + Radians a = options.angle; const_cast(options).angle = 0; // generate cached_i = generate(options); @@ -153,7 +155,7 @@ void CachedScriptableImage::generateCached(const GeneratedImage::Options& option mask->get(mask_opts).setAlpha(cached_i); } if (options.angle != 0) { - // hack(pt2) do the actual rotation now + // hack(part2) do the actual rotation now cached_i = rotate_image(cached_i, options.angle); } if (*combine <= COMBINE_NORMAL) { diff --git a/src/script/image.hpp b/src/script/image.hpp index 04e91e44..8c48a646 100644 --- a/src/script/image.hpp +++ b/src/script/image.hpp @@ -105,7 +105,7 @@ class CachedScriptableImage : public ScriptableImage { Image cached_i; ///< The cached image Bitmap cached_b; ///< *or* the cached bitmap RealSize cached_size; ///< The size of the image before rotating - int cached_angle; + Radians cached_angle; }; // ----------------------------------------------------------------------------- : CachedScriptableMask diff --git a/src/util/angle.hpp b/src/util/angle.hpp new file mode 100644 index 00000000..1ca574bd --- /dev/null +++ b/src/util/angle.hpp @@ -0,0 +1,77 @@ +//+----------------------------------------------------------------------------+ +//| Description: Magic Set Editor - Program to make Magic (tm) cards | +//| Copyright: (C) 2001 - 2010 Twan van Laarhoven and Sean Hunt | +//| License: GNU General Public License 2 or later (see file COPYING) | +//+----------------------------------------------------------------------------+ + +#ifndef HEADER_UTIL_ANGLE +#define HEADER_UTIL_ANGLE + +// ----------------------------------------------------------------------------- : Includes + +#include + +// ----------------------------------------------------------------------------- : Degrees & radians + +typedef double Degrees; +typedef double Radians; + +/// Convert radians to degrees +inline Degrees rad_to_deg(Radians rad) { return rad * (180.0 / M_PI); } +/// Convert degrees to radians +inline Radians deg_to_rad(Degrees deg) { return deg * (M_PI / 180.0); } + +// ----------------------------------------------------------------------------- : Angle constants + +const Radians rad0 = 0; +const Radians rad45 = 0.25*M_PI; +const Radians rad90 = 0.5*M_PI; +const Radians rad180 = M_PI; +const Radians rad270 = 1.5*M_PI; +const Radians rad360 = 2.0*M_PI; + +/// Are two floating point numbers equal up to a small epsilon? +inline bool almost_equal(double x, double y) { + return fabs(x-y) < 1e-10; +} +inline bool is_rad0(double x) { + return almost_equal(x,0) || almost_equal(x,rad360); +} +inline bool is_rad90(double x) { + return almost_equal(x,rad90); +} +inline bool is_rad180(double x) { + return almost_equal(x,rad180); +} +inline bool is_rad270(double x) { + return almost_equal(x,rad270); +} + +// ----------------------------------------------------------------------------- : Angle functions + +// mod as it should be: answer in range [0..m) +inline double sane_fmod(double x, double m) { + double ans = fmod(x,m); + if (ans < 0) return ans + m; + else return ans; +} + +// constrain an angle to [0..2pi) +inline Radians constrain_radians(Radians angle) { + return sane_fmod(angle, 2*M_PI); +} + +/// Is an angle a multiple of 90 degrees? +inline bool is_straight(Radians angle) { + return almost_equal(sane_fmod(angle+rad45,rad90), rad45); +} + +/// Is an angle sideways (i.e. closer to 90 or 270 degrees than to 0 or 180 degrees)? +inline bool is_sideways(Radians angle) { + double a = sane_fmod(angle,M_PI); + return (a > 0.25*M_PI && a < 0.75*M_PI); +} + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/util/rotation.cpp b/src/util/rotation.cpp index 747a0a55..476c7c5d 100644 --- a/src/util/rotation.cpp +++ b/src/util/rotation.cpp @@ -13,13 +13,8 @@ // ----------------------------------------------------------------------------- : Rotation -// constrain an angle to [0..360) -int constrain_angle(int angle) { - return (angle + 3600) % 360; -} - -Rotation::Rotation(int angle, const RealRect& rect, double zoom, double stretch, RotationFlags flags) - : angle(constrain_angle(angle)) +Rotation::Rotation(Radians angle, const RealRect& rect, double zoom, double stretch, RotationFlags flags) + : angle(constrain_radians(angle)) , size(rect.size()) , origin(rect.position()) , zoomX(zoom * stretch) @@ -30,8 +25,7 @@ Rotation::Rotation(int angle, const RealRect& rect, double zoom, double stretch, } // set origin if (flags & ROTATION_ATTACH_TOP_LEFT) { - if (revX()) origin.x += zoom * (sideways() ? size.height : size.width); - if (revY()) origin.y += zoom * (sideways() ? size.width : size.height); + origin -= boundingBoxCorner(size); } } @@ -42,59 +36,58 @@ void Rotation::setStretch(double s) { RealPoint Rotation::tr(const RealPoint& p) const { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); double x = p.x * zoomX, y = p.y * zoomY; return RealPoint(c * x + s * y + origin.x, -s * x + c * y + origin.y); } RealPoint Rotation::trPixel(const RealPoint& p) const { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); double x = p.x * zoomX + 0.5, y = p.y * zoomY + 0.5; return RealPoint(c * x + s * y + origin.x - 0.5, -s * x + c * y + origin.y - 0.5); } RealPoint Rotation::trNoZoom(const RealPoint& p) const { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); double x = p.x, y = p.y; return RealPoint(c * x + s * y + origin.x, -s * x + c * y + origin.y); } RealPoint Rotation::trPixelNoZoom(const RealPoint& p) const { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); double x = p.x + 0.5, y = p.y + 0.5; return RealPoint(c * x + s * y + origin.x - 0.5, -s * x + c * y + origin.y - 0.5); } -/* -RealSize Rotation::trSize(const RealSize& size) const { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); - double x = size.width * zoomX, y = size.height * zoomY; - return RealSize(c * x + s * y, s * x + c * y); -} -*/ RealSize Rotation::trSizeToBB(const RealSize& size) const { - if (straight()) { - if (sideways()) { + if (is_straight(angle)) { + if (is_sideways(angle)) { return RealSize(size.height * zoomY, size.width * zoomX); } else { return RealSize(size.width * zoomX, size.height * zoomY); } } else { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); double x = size.width * zoomX, y = size.height * zoomY; return RealSize(fabs(c * x) + fabs(s * y), fabs(s * x) + fabs(c * y)); } } RealRect Rotation::trRectToBB(const RealRect& r) const { - if (straight()) { - RealSize s = trSizeToBB(r.size()); - return RealRect(tr(r.position()) - RealSize(revX()?s.width:0, revY()?s.height:0), s); + double x = r.x * zoomX, y = r.y * zoomY; + double w = r.width * zoomX, h = r.height * zoomY; + const bool special_case_optimization = false; + if (special_case_optimization && is_rad0(angle)) { + return RealRect(origin.x + x, origin.y + y, w, h); + } else if (special_case_optimization && is_rad180(angle)) { + return RealRect(origin.x - x - w, origin.y - y - h, w, h); + } else if (special_case_optimization && is_rad90(angle)) { + return RealRect(origin.x + y, origin.y - x - w, h, w); + } else if (special_case_optimization && is_rad270(angle)) { + return RealRect(origin.x - y - h, origin.y + x, h, w); } else { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); - double x = r.x * zoomX, y = r.y * zoomY; - double w = r.width * zoomX, h = r.height * zoomY; + double s = sin(angle), c = cos(angle); RealRect result(c * x + s * y + origin.x, -s * x + c * y + origin.y, 0,0); @@ -120,13 +113,8 @@ RealRect Rotation::trRectToBB(const RealRect& r) const { } } - -RealRect Rotation::trRectStraight(const RealRect& r) const { - assert(angle == 0); - return RealRect(r.position() + origin, r.size()); -} wxRegion Rotation::trRectToRegion(const RealRect& r) const { - if (straight()) { + if (is_straight(angle)) { return trRectToBB(r).toRect(); } else { wxPoint points[4] = {trPixel(RealPoint(r.left(), r.top() )) @@ -138,13 +126,13 @@ wxRegion Rotation::trRectToRegion(const RealRect& r) const { } RealPoint Rotation::trInv(const RealPoint& p) const { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); double x = p.x - origin.x, y = p.y - origin.y; return RealPoint((c * x - s * y) / zoomX, (s * x + c * y) / zoomY); } RealSize Rotation::trInv(const RealSize& x) const { - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); return RealSize((c * x.width - s * x.height) / zoomX, (s * x.width + c * x.height) / zoomY); } @@ -153,26 +141,13 @@ RealPoint Rotation::boundingBoxCorner(const RealSize& size) const { // This function is a bit tricky, // I derived it by drawing the four cases. // Two succeeding cases must agree where they overlap (0,90,180,270 degrees) - double a = deg_to_rad(angle), s = sin(a), c = cos(a); + double s = sin(angle), c = cos(angle); double w = size.width * zoomX, h = size.height * zoomY; - if (angle <= 90) return RealPoint(0, -w * s); - if (angle <= 180) return RealPoint(w * c, h * c - w * s); - if (angle <= 270) return RealPoint(w * c + h * s, h * c); - else return RealPoint(h * s, 0); + if (angle <= rad90) return RealPoint(0, -w * s); + if (angle <= rad180) return RealPoint(w * c, h * c - w * s); + if (angle <= rad270) return RealPoint(w * c + h * s, h * c); + else return RealPoint(h * s, 0); } -/* -RealPoint Rotation::boundingBoxCorner(const RealSize& size) const { - //if(true)return RealPoint(0,0); - // This function is a bit tricky, - // I derived it by drawing the four cases. - // Two succeeding cases must agree where they overlap (0,90,180,270 degrees) - double a = deg_to_rad(angle), s = sin(a), c = cos(a); - if (angle <= 90) return RealPoint( + size.width * s * s, - size.width * s * c); - if (angle <= 180) return RealPoint(size.width - size.height * s * c, size.height * c * c); - if (angle <= 270) return RealPoint(size.width - size.width * s * s, size.height + size.width * s * c); - else return RealPoint( size.height * s * c, size.height - size.height * c * c); -} -*/ // ----------------------------------------------------------------------------- : Rotater @@ -183,7 +158,7 @@ Rotater::Rotater(Rotation& rot, const Rotation& by) // apply rotation rot.origin = rot.tr(by.origin); rot.size = by.size; - rot.angle = constrain_angle(rot.angle + by.angle); + rot.angle = constrain_radians(rot.angle + by.angle); // zooming is not really correct if rot.zoomX != rot.zoomY rot.zoomX *= by.zoomX; rot.zoomY *= by.zoomY; @@ -195,7 +170,7 @@ Rotater::~Rotater() { // ----------------------------------------------------------------------------- : RotatedDC -RotatedDC::RotatedDC(DC& dc, int angle, const RealRect& rect, double zoom, RenderQuality quality, RotationFlags flags) +RotatedDC::RotatedDC(DC& dc, Radians angle, const RealRect& rect, double zoom, RenderQuality quality, RotationFlags flags) : Rotation(angle, rect, zoom, 1.0, flags) , dc(dc), quality(quality) {} @@ -234,12 +209,12 @@ void RotatedDC::DrawText (const String& text, const RealPoint& pos, AColor colo dc.GetUserScale(&usx, &usy); dc.SetUserScale(usx/text_scaling, usy/text_scaling); dc.SetTextForeground(color); - dc.DrawRotatedText(text, (int) p_ext.x, (int) p_ext.y, angle); + dc.DrawRotatedText(text, (int) p_ext.x, (int) p_ext.y, rad_to_deg(angle)); dc.SetUserScale(usx, usy); } else { RealPoint p_ext = tr(pos); dc.SetTextForeground(color); - dc.DrawRotatedText(text, (int) p_ext.x, (int) p_ext.y, angle); + dc.DrawRotatedText(text, (int) p_ext.x, (int) p_ext.y, rad_to_deg(angle)); } } @@ -249,7 +224,7 @@ void RotatedDC::DrawTextWithShadow(const String& text, const Font& font, const R } void RotatedDC::DrawBitmap(const Bitmap& bitmap, const RealPoint& pos) { - if (angle == 0) { + if (is_rad0(angle)) { RealPoint p_ext = tr(pos); dc.DrawBitmap(bitmap, to_int(p_ext.x), to_int(p_ext.y), true); } else { @@ -275,7 +250,7 @@ void RotatedDC::DrawLine (const RealPoint& p1, const RealPoint& p2) { } void RotatedDC::DrawRectangle(const RealRect& r) { - if (straight()) { + if (is_straight(angle)) { wxRect r_ext = trRectToBB(r); dc.DrawRectangle(r_ext.x, r_ext.y, r_ext.width, r_ext.height); } else { @@ -288,7 +263,7 @@ void RotatedDC::DrawRectangle(const RealRect& r) { } void RotatedDC::DrawRoundedRectangle(const RealRect& r, double radius) { - if (straight()) { + if (is_straight(angle)) { wxRect r_ext = trRectToBB(r); dc.DrawRoundedRectangle(r_ext.x, r_ext.y, r_ext.width, r_ext.height, trS(radius)); } else { @@ -307,16 +282,16 @@ void RotatedDC::DrawEllipse(const RealPoint& center, const RealSize& size) { wxSize s_ext = trSizeToBB(size); dc.DrawEllipse(c_ext.x, c_ext.y, s_ext.x, s_ext.y); } -void RotatedDC::DrawEllipticArc(const RealPoint& center, const RealSize& size, double start, double end) { +void RotatedDC::DrawEllipticArc(const RealPoint& center, const RealSize& size, Radians start, Radians end) { wxPoint c_ext = tr(center - size/2); wxSize s_ext = trSizeToBB(size); - dc.DrawEllipticArc(c_ext.x, c_ext.y, s_ext.x, s_ext.y, rad_to_deg(start) + angle, rad_to_deg(end) + angle); + dc.DrawEllipticArc(c_ext.x, c_ext.y, s_ext.x, s_ext.y, rad_to_deg(start + angle), rad_to_deg(end + angle)); } -void RotatedDC::DrawEllipticSpoke(const RealPoint& center, const RealSize& size, double angle) { +void RotatedDC::DrawEllipticSpoke(const RealPoint& center, const RealSize& size, Radians angle) { wxPoint c_ext = tr(center - size/2); wxSize s_ext = trSizeToBB(size); - double rot_angle = angle + deg_to_rad(this->angle); - double sin_angle = sin(rot_angle), cos_angle = cos(rot_angle); + Radians rot_angle = angle + this->angle; + Radians sin_angle = sin(rot_angle), cos_angle = cos(rot_angle); // position of center and of point on the boundary can vary because of rounding errors, // this code matches DrawEllipticArc (at least on windows xp). dc.DrawLine( diff --git a/src/util/rotation.hpp b/src/util/rotation.hpp index 47e2c5ef..e9c52502 100644 --- a/src/util/rotation.hpp +++ b/src/util/rotation.hpp @@ -33,7 +33,7 @@ class Rotation { /** with the given rectangle of external coordinates and a given rotation angle and zoom factor. * if is_internal then the rect gives the internal coordinates, its origin should be (0,0) */ - Rotation(int angle, const RealRect& rect = RealRect(0,0,0,0), double zoom = 1.0, double strectch = 1.0, RotationFlags flags = ROTATION_NORMAL); + Rotation(Radians angle = 0, const RealRect& rect = RealRect(0,0,0,0), double zoom = 1.0, double strectch = 1.0, RotationFlags flags = ROTATION_NORMAL); /// Change the zoom factor inline void setZoom(double z) { zoomX = zoomY = z; } @@ -44,7 +44,7 @@ class Rotation { /// Stretch factor inline double getStretch() const { return zoomX / zoomY; } /// Get the angle - inline int getAngle() const { return angle; } + inline Radians getAngle() const { return angle; } /// Change the origin inline void setOrigin(const RealPoint& o) { origin = o; } @@ -67,7 +67,7 @@ class Rotation { inline RealSize trS(const RealSize& s) const { return RealSize(s.width * zoomX, s.height * zoomY); } /// Translate an angle - inline int trAngle(int a) { return (angle + a) % 360; } + inline Radians trAngle(Radians a) { return constrain_radians(angle + a); } /// Translate a single point RealPoint tr(const RealPoint& p) const; @@ -81,12 +81,9 @@ class Rotation { RealSize trSize(const RealSize& s) const; /// Translate a single size, returns the bounding box size (non-negative) RealSize trSizeToBB(const RealSize& s) const; - /// Translate a rectangle, returns the bounding box - /* //%%the size of the result may be negative*/ + /// Translate a rectangle, returns the bounding box, the size will be non-negative RealRect trRectToBB(const RealRect& r) const; - /// Translate a rectangle, can only be used when not rotating - RealRect trRectStraight(const RealRect& r) const; - /// Translate a rectangle into a region (supports rotation + /// Translate a rectangle into a region (supports rotation) wxRegion trRectToRegion(const RealRect& rect) const; /// Translate a size or length back to internal 'coordinates' @@ -102,7 +99,7 @@ class Rotation { RealSize trInv(const RealSize& p) const; protected: - int angle; ///< The angle of rotation in degrees (counterclockwise) + Radians angle; ///< The angle of rotation in radians (counterclockwise) RealSize size; ///< Size of the rectangle, in internal coordinates RealPoint origin; ///< tr(0,0) double zoomX; ///< Zoom factor, zoom = 2.0 means that 1 internal = 2 external @@ -110,16 +107,6 @@ class Rotation { friend class Rotater; - /// Is the x axis 'reversed' (after turning sideways)? - inline bool revX() const { return angle >= 180; } - /// Is the y axis 'reversed' (after turning sideways)? - inline bool revY() const { return angle == 90 || angle == 180; } - /// Is the rotation 'simple', i.e. a multiple of 90 degrees? - inline bool straight() const { return ::straight(angle); } - /// Is the rotation sideways (90 or 270 degrees)? - // Note: angle & 2 == 0 for angle in {0, 180} and != 0 for angle in {90, 270) - inline bool sideways() const { return (angle & 2) != 0; } - /// Determine the top-left corner of the bounding box around the rotated box s (in external coordinates) RealPoint boundingBoxCorner(const RealSize& s) const; }; @@ -165,7 +152,7 @@ enum RenderQuality { */ class RotatedDC : public Rotation { public: - RotatedDC(DC& dc, int angle, const RealRect& rect, double zoom, RenderQuality quality, RotationFlags flags = ROTATION_NORMAL); + RotatedDC(DC& dc, Radians angle, const RealRect& rect, double zoom, RenderQuality quality, RotationFlags flags = ROTATION_NORMAL); RotatedDC(DC& dc, const Rotation& rotation, RenderQuality quality); // --------------------------------------------------- : Drawing @@ -190,9 +177,9 @@ class RotatedDC : public Rotation { void DrawCircle(const RealPoint& center, double radius); void DrawEllipse(const RealPoint& center, const RealSize& size); /// Draw an arc of an ellipse, angles are in radians - void DrawEllipticArc(const RealPoint& center, const RealSize& size, double start, double end); + void DrawEllipticArc(const RealPoint& center, const RealSize& size, Radians start, Radians end); /// Draw spokes of an ellipse - void DrawEllipticSpoke(const RealPoint& center, const RealSize& size, double start); + void DrawEllipticSpoke(const RealPoint& center, const RealSize& size, Radians start); // Fill the dc with the color of the current brush void Fill(); diff --git a/src/util/vector2d.hpp b/src/util/vector2d.hpp index 73c4a734..b9251db9 100644 --- a/src/util/vector2d.hpp +++ b/src/util/vector2d.hpp @@ -10,6 +10,7 @@ // ----------------------------------------------------------------------------- : Includes #include +#include #include // ----------------------------------------------------------------------------- : Rounding @@ -100,7 +101,7 @@ class Vector2D { return *this / length(); } /// Angle between this vector and the x axis - inline double angle() const { + inline Radians angle() const { return atan2(y,x); }