diff --git a/engine/cairoShimCairo.cc b/engine/cairoShimCairo.cc index 267e9ad49..05ff404aa 100644 --- a/engine/cairoShimCairo.cc +++ b/engine/cairoShimCairo.cc @@ -1,91 +1,136 @@ -#include "cairoShim.h" +#include "cairoShimCairo.h" #define CAIRO_WIN32_STATIC_BUILD #include #undef CAIRO_WIN32_STATIC_BUILD +#include using namespace std; -namespace ravel +namespace minsky { - void CairoShimCairo::moveTo(double x, double y) + CairoShimCairo::CairoShimCairo(cairo_t* c) : cairo(c) {} + + CairoShimCairo::~CairoShimCairo() = default; + + // Drawing operations + void CairoShimCairo::moveTo(double x, double y) const {cairo_move_to(cairo,x,y);} - void CairoShimCairo::lineTo(double x, double y) + void CairoShimCairo::lineTo(double x, double y) const {cairo_line_to(cairo,x,y);} - void CairoShimCairo::relMoveTo(double x, double y) + void CairoShimCairo::relMoveTo(double x, double y) const {cairo_rel_move_to(cairo,x,y);} - void CairoShimCairo::relLineTo(double x, double y) + void CairoShimCairo::relLineTo(double x, double y) const {cairo_rel_line_to(cairo,x,y);} - void CairoShimCairo::arc - (double x, double y, double radius, double start, double end) + void CairoShimCairo::arc(double x, double y, double radius, double start, double end) const {cairo_arc(cairo,x,y,radius,start,end);} - // paths - void CairoShimCairo::newPath() + void CairoShimCairo::curveTo(double x1, double y1, double x2, double y2, double x3, double y3) const + {cairo_curve_to(cairo,x1,y1,x2,y2,x3,y3);} + + void CairoShimCairo::rectangle(double x, double y, double width, double height) const + {cairo_rectangle(cairo,x,y,width,height);} + + // Path operations + void CairoShimCairo::newPath() const {cairo_new_path(cairo);} - void CairoShimCairo::closePath() + void CairoShimCairo::newSubPath() const + {cairo_new_sub_path(cairo);} + + void CairoShimCairo::closePath() const {cairo_close_path(cairo);} - void CairoShimCairo::fill() + void CairoShimCairo::getCurrentPoint(double& x, double& y) const + {cairo_get_current_point(cairo, &x, &y);} + + // Fill and stroke operations + void CairoShimCairo::fill() const {cairo_fill(cairo);} + + void CairoShimCairo::fillPreserve() const + {cairo_fill_preserve(cairo);} - void CairoShimCairo::clip() + void CairoShimCairo::clip() const {cairo_clip(cairo);} - void CairoShimCairo::stroke() + void CairoShimCairo::resetClip() const + {cairo_reset_clip(cairo);} + + void CairoShimCairo::stroke() const {cairo_stroke(cairo);} - void CairoShimCairo::strokePreserve() + void CairoShimCairo::strokePreserve() const {cairo_stroke_preserve(cairo);} - void CairoShimCairo::setLineWidth(double w) + void CairoShimCairo::paint() const + {cairo_paint(cairo);} + + // Line properties + void CairoShimCairo::setLineWidth(double w) const {cairo_set_line_width(cairo, w);} - // sources - void CairoShimCairo::setSourceRGB - (double r, double g, double b) + double CairoShimCairo::getLineWidth() const + {return cairo_get_line_width(cairo);} + + void CairoShimCairo::setDash(const double* dashes, int num_dashes, double offset) const + {cairo_set_dash(cairo, dashes, num_dashes, offset);} + + void CairoShimCairo::setFillRule(cairo_fill_rule_t fill_rule) const + {cairo_set_fill_rule(cairo, fill_rule);} + + // Color operations + void CairoShimCairo::setSourceRGB(double r, double g, double b) const {cairo_set_source_rgb(cairo,r,g,b);} - void CairoShimCairo::setSourceRGBA - (double r, double g, double b, double a) + void CairoShimCairo::setSourceRGBA(double r, double g, double b, double a) const {cairo_set_source_rgba(cairo,r,g,b,a);} - // text. Argument is in UTF8 encoding - void CairoShimCairo::showText(const std::string& text) + // Text operations + void CairoShimCairo::showText(const std::string& text) const {cairo_show_text(cairo,text.c_str());} - void CairoShimCairo::setTextExtents(const std::string& text) - {cairo_text_extents(cairo,text.c_str(),&extents);} - - double CairoShimCairo::textWidth() const - {return extents.width;} + void CairoShimCairo::setFontSize(double size) const + {cairo_set_font_size(cairo, size);} - double CairoShimCairo::textHeight() const - {return extents.height;} + void CairoShimCairo::textExtents(const std::string& text, cairo_text_extents_t& extents) const + {cairo_text_extents(cairo,text.c_str(),&extents);} - // matrix transformation - void CairoShimCairo::identityMatrix() + // Transformation operations + void CairoShimCairo::identityMatrix() const {cairo_identity_matrix(cairo);} - void CairoShimCairo::translate(double x, double y) + void CairoShimCairo::translate(double x, double y) const {cairo_translate(cairo,x,y);} - void CairoShimCairo::scale(double sx, double sy) + void CairoShimCairo::scale(double sx, double sy) const {cairo_scale(cairo,sx,sy);} - void CairoShimCairo::rotate(double angle) + void CairoShimCairo::rotate(double angle) const {cairo_rotate(cairo,angle);} - // context manipulation - void CairoShimCairo::save() + void CairoShimCairo::userToDevice(double& x, double& y) const + {cairo_user_to_device(cairo, &x, &y);} + + // Context state operations + void CairoShimCairo::save() const {cairo_save(cairo);} - void CairoShimCairo::restore() + void CairoShimCairo::restore() const {cairo_restore(cairo);} - + // Tolerance + void CairoShimCairo::setTolerance(double tolerance) const + {cairo_set_tolerance(cairo, tolerance);} + + // Pango support + ecolab::Pango& CairoShimCairo::pango() const + { + if (!m_pango) + m_pango.reset(new ecolab::Pango(cairo)); + return *m_pango; + } } diff --git a/engine/cairoShimCairo.h b/engine/cairoShimCairo.h index 72aeefd6c..9eab97293 100644 --- a/engine/cairoShimCairo.h +++ b/engine/cairoShimCairo.h @@ -1,56 +1,83 @@ #ifndef CAIROSHIMCAIRO_H #define CAIROSHIMCAIRO_H -#include "cairoShim.h" +#include "../model/ICairoShim.h" #include +#include +#include + +namespace ecolab { class Pango; } namespace minsky { + /// Concrete implementation of ICairoShim using actual Cairo library class CairoShimCairo: public ICairoShim { - cairo_t* cairo; + cairo_t* cairo; + mutable std::unique_ptr m_pango; CairoShimCairo(const CairoShimCairo&)=delete; void operator=(const CairoShimCairo&)=delete; public: - // template parameter G = cairo_t* or HDC - CairoShim(cairo_t*); - ~CairoShim(); - - void moveTo(double x, double y); - void lineTo(double x, double y); - void relMoveTo(double x, double y); - void relLineTo(double x, double y); - void arc(double x, double y, double radius, double start, double end); - - void setLineWidth(double); - - // paths - void newPath(); - void closePath(); - void fill(); - void clip(); - void stroke(); - void strokePreserve(); - - // sources - void setSourceRGB(double r, double g, double b); - void setSourceRGBA(double r, double g, double b, double a); - - // text. Argument is in UTF8 encoding - void showText(const std::string&); - void setTextExtents(const std::string&); - double textWidth() const; - double textHeight() const; - - // matrix transformation - void identityMatrix(); - void translate(double x, double y); - void scale(double sx, double sy); - void rotate(double angle); ///< angle in radians - - // context manipulation - void save(); - void restore(); + CairoShimCairo(cairo_t* c); + ~CairoShimCairo() override; + + // Drawing operations + void moveTo(double x, double y) const override; + void lineTo(double x, double y) const override; + void relMoveTo(double x, double y) const override; + void relLineTo(double x, double y) const override; + void arc(double x, double y, double radius, double start, double end) const override; + void curveTo(double x1, double y1, double x2, double y2, double x3, double y3) const override; + void rectangle(double x, double y, double width, double height) const override; + + // Path operations + void newPath() const override; + void newSubPath() const override; + void closePath() const override; + void getCurrentPoint(double& x, double& y) const override; + + // Fill and stroke operations + void fill() const override; + void fillPreserve() const override; + void stroke() const override; + void strokePreserve() const override; + void clip() const override; + void resetClip() const override; + void paint() const override; + + // Line properties + void setLineWidth(double width) const override; + double getLineWidth() const override; + void setDash(const double* dashes, int num_dashes, double offset) const override; + void setFillRule(cairo_fill_rule_t fill_rule) const override; + + // Color operations + void setSourceRGB(double r, double g, double b) const override; + void setSourceRGBA(double r, double g, double b, double a) const override; + + // Text operations + void showText(const std::string& text) const override; + void setFontSize(double size) const override; + void textExtents(const std::string& text, cairo_text_extents_t& extents) const override; + + // Transformation operations + void identityMatrix() const override; + void translate(double x, double y) const override; + void scale(double sx, double sy) const override; + void rotate(double angle) const override; + void userToDevice(double& x, double& y) const override; + + // Context state operations + void save() const override; + void restore() const override; + + // Tolerance + void setTolerance(double tolerance) const override; + + // Pango support + ecolab::Pango& pango() const override; + // TEMPORARY: Internal accessor for migration - to be removed once all implementations are updated + cairo_t* _internalGetCairoContext() const { return cairo; } }; } diff --git a/model/ICairoShim.h b/model/ICairoShim.h new file mode 100644 index 000000000..3f1f042c0 --- /dev/null +++ b/model/ICairoShim.h @@ -0,0 +1,95 @@ +/* + Abstract interface for Cairo operations to enable testing and mocking + @copyright Steve Keen 2012 + @author Russell Standish + This file is part of Minsky. + + Minsky is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Minsky is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Minsky. If not, see . +*/ +#ifndef ICAIROSHIM_H +#define ICAIROSHIM_H + +#include +#include + +namespace minsky +{ + // Forward declarations for Pango + namespace ecolab { class Pango; } + + /// Abstract interface for Cairo drawing operations + class ICairoShim + { + public: + virtual ~ICairoShim() = default; + + // Drawing operations + virtual void moveTo(double x, double y) const = 0; + virtual void lineTo(double x, double y) const = 0; + virtual void relMoveTo(double x, double y) const = 0; + virtual void relLineTo(double x, double y) const = 0; + virtual void arc(double x, double y, double radius, double start, double end) const = 0; + virtual void curveTo(double x1, double y1, double x2, double y2, double x3, double y3) const = 0; + virtual void rectangle(double x, double y, double width, double height) const = 0; + + // Path operations + virtual void newPath() const = 0; + virtual void newSubPath() const = 0; + virtual void closePath() const = 0; + virtual void getCurrentPoint(double& x, double& y) const = 0; + + // Fill and stroke operations + virtual void fill() const = 0; + virtual void fillPreserve() const = 0; + virtual void stroke() const = 0; + virtual void strokePreserve() const = 0; + virtual void clip() const = 0; + virtual void resetClip() const = 0; + virtual void paint() const = 0; + + // Line properties + virtual void setLineWidth(double width) const = 0; + virtual double getLineWidth() const = 0; + virtual void setDash(const double* dashes, int num_dashes, double offset) const = 0; + virtual void setFillRule(cairo_fill_rule_t fill_rule) const = 0; + + // Color operations + virtual void setSourceRGB(double r, double g, double b) const = 0; + virtual void setSourceRGBA(double r, double g, double b, double a) const = 0; + + // Text operations + virtual void showText(const std::string& text) const = 0; + virtual void setFontSize(double size) const = 0; + virtual void textExtents(const std::string& text, cairo_text_extents_t& extents) const = 0; + + // Transformation operations + virtual void identityMatrix() const = 0; + virtual void translate(double x, double y) const = 0; + virtual void scale(double sx, double sy) const = 0; + virtual void rotate(double angle) const = 0; + virtual void userToDevice(double& x, double& y) const = 0; + + // Context state operations + virtual void save() const = 0; + virtual void restore() const = 0; + + // Tolerance + virtual void setTolerance(double tolerance) const = 0; + + // Pango support for text rendering + virtual ecolab::Pango& pango() const = 0; + }; +} + +#endif // ICAIROSHIM_H diff --git a/model/cairoItems.cc b/model/cairoItems.cc index 9dd0c6cb5..eba7f8190 100644 --- a/model/cairoItems.cc +++ b/model/cairoItems.cc @@ -120,3 +120,18 @@ void minsky::drawTriangle cairo_fill(cairo); } +void minsky::drawTriangle +(const ICairoShim& cairoShim, double x, double y, const cairo::Colour& col, double angle) +{ + cairoShim.save(); + cairoShim.newPath(); + cairoShim.setSourceRGBA(col.r,col.g,col.b,col.a); + cairoShim.translate(x,y); + cairoShim.rotate(angle); + cairoShim.moveTo(10,0); + cairoShim.lineTo(0,-3); + cairoShim.lineTo(0,3); + cairoShim.fill(); + cairoShim.restore(); +} + diff --git a/model/cairoItems.h b/model/cairoItems.h index 08881a20f..a4137445a 100644 --- a/model/cairoItems.h +++ b/model/cairoItems.h @@ -54,5 +54,6 @@ namespace minsky }; void drawTriangle(cairo_t* cairo, double x, double y, const ecolab::cairo::Colour& col, double angle=0); + void drawTriangle(const ICairoShim& cairoShim, double x, double y, const ecolab::cairo::Colour& col, double angle=0); } #endif diff --git a/model/dataOp.h b/model/dataOp.h index 3163273b9..d787b44cc 100644 --- a/model/dataOp.h +++ b/model/dataOp.h @@ -37,6 +37,12 @@ namespace minsky else drawUserFunction(cairo); } + void draw(const ICairoShim& cairoShim) const override { + if (description().empty()) + OperationBase::draw(cairoShim); + else + drawUserFunction(cairoShim); + } public: ~DataOp() {} diff --git a/model/godleyIcon.cc b/model/godleyIcon.cc index 20df647c3..28d0dc5c6 100644 --- a/model/godleyIcon.cc +++ b/model/godleyIcon.cc @@ -27,6 +27,7 @@ #include #include #include +#include "../engine/cairoShimCairo.h" #include "godleyIcon.rcd" #include "itemT.rcd" #include "godleyTableWindow.xcd" @@ -519,6 +520,13 @@ namespace minsky } } + void GodleyIcon::draw(const ICairoShim& cairoShim) const + { + // TODO: Implement properly without cairo_t* delegation + auto& shimImpl = dynamic_cast(cairoShim); + draw(shimImpl._internalGetCairoContext()); + } + string GodleyIcon::rowSum(int row) const { if (row==0) // A-L-E sum values across stockvars diff --git a/model/godleyIcon.h b/model/godleyIcon.h index 933efd03c..1dab29e73 100644 --- a/model/godleyIcon.h +++ b/model/godleyIcon.h @@ -133,6 +133,7 @@ namespace minsky /// draw icon to \a context void draw(cairo_t* cairo) const override; + void draw(const ICairoShim& cairoShim) const override; /// return the A-L-E row sum for \a row std::string rowSum(int row) const; diff --git a/model/group.cc b/model/group.cc index 5dbf77cda..95186c099 100644 --- a/model/group.cc +++ b/model/group.cc @@ -25,6 +25,7 @@ #include "autoLayout.h" #include "equations.h" #include +#include "../engine/cairoShimCairo.h" #include "group.rcd" #include "itemT.rcd" #include "bookmark.rcd" @@ -1040,6 +1041,13 @@ namespace minsky } + void Group::draw(const ICairoShim& cairoShim) const + { + // TODO: Implement properly without cairo_t* delegation + auto& shimImpl = dynamic_cast(cairoShim); + draw(shimImpl._internalGetCairoContext()); + } + void Group::draw1edge(const vector& vars, cairo_t* cairo, float x) const { diff --git a/model/group.h b/model/group.h index d339efe3b..26bb8c6e1 100644 --- a/model/group.h +++ b/model/group.h @@ -269,6 +269,7 @@ namespace minsky void makeSubroutine(); void draw(cairo_t*) const override; + void draw(ICairoShim&) const override; /// draw representations of edge variables around group icon void drawEdgeVariables(cairo_t*) const; diff --git a/model/intOp.cc b/model/intOp.cc index 522b8b73b..68a65dc10 100644 --- a/model/intOp.cc +++ b/model/intOp.cc @@ -22,6 +22,7 @@ #include "intOp.h" #include "intOp.rcd" #include "itemT.rcd" +#include "../engine/cairoShimCairo.h" #include "minsky_epilogue.h" namespace minsky @@ -177,6 +178,155 @@ namespace minsky cairo_clip(cairo); if (selected) drawSelected(cairo); } + + void IntOp::draw(const ICairoShim& cairoShim) const + { + // TODO: Refactor to use cairoShim methods instead of raw cairo_t* + auto& shimImpl = dynamic_cast(cairoShim); + cairo_t* cairo = shimImpl._internalGetCairoContext(); + // if rotation is in 1st or 3rd quadrant, rotate as + // normal, otherwise flip the text so it reads L->R + auto [angle,textFlipped]=rotationAsRadians(); + double coupledIntTranslation=0; + const float z=zoomFactor(); + + float l=OperationBase::l*z, r=OperationBase::r*z, + h=OperationBase::h*z; + + if (fabs(l)iWidth()) intVarWidth=0.5*intVar->iWidth()*z; + // set the port location... + const Rotate rot(rotation(), x(), y()); + auto ivp=rot(x()+r+ivo+intVarWidth, y()); + intVar->moveTo(ivp.x(), ivp.y()); + + cairoShim.save(); + cairoShim.translate(r+ivo+intVarWidth,0); + // to get text to render correctly, we need to set + // the var's rotation, then antirotate it + intVar->rotation(rotation()); + cairoShim.rotate(-M_PI*rotation()/180.0); + rv.draw(); + cairoShim.restore(); + + // build clip path the hard way grr... + cairoShim.moveTo(l,h); + cairoShim.lineTo(l,-h); + cairoShim.lineTo(r,0); + cairoShim.lineTo(r+ivo,0); + float rvw=rv.width()*z, rvh=rv.height()*z; + if (rv.width()iWidth()) rvw=intVar->iWidth()*z; + if (rv.height()iHeight()) rvh=intVar->iHeight()*z; + cairoShim.lineTo(r+ivo,-rvh); + cairoShim.lineTo(r+ivo+2*rvw,-rvh); + cairoShim.lineTo(r+ivo+2*rvw+2*z,0); + cairoShim.lineTo(r+ivo+2*rvw,rvh); + cairoShim.lineTo(r+ivo,rvh); + cairoShim.lineTo(r+ivo,0); + cairoShim.lineTo(r,0); + cairoShim.closePath(); + } + + cairo::Path clipPath(cairo); + + double x0=r, y0=0, x1=l, y1=numPorts() > 2? -h+3: 0, + x2=l, y2=numPorts() > 2? h-3: 0; + + if (textFlipped) swap(y1,y2); + + // adjust for integration variable + if (coupled()) + x0+=intVarOffset+2*intVarWidth+2; + + cairoShim.save(); + cairo_identity_matrix(cairo); + cairo_translate(cairo, x(), y()); + cairo_rotate(cairo, angle); + cairo_user_to_device(cairo, &x0, &y0); + cairo_user_to_device(cairo, &x1, &y1); + cairo_user_to_device(cairo, &x2, &y2); + cairoShim.restore(); + + if (numPorts()>0) + m_ports[0]->moveTo(x0, y0); + if (numPorts()>1) + m_ports[1]->moveTo(x1, y1); + if (numPorts()>2) + m_ports[2]->moveTo(x2, y2); + + cairoShim.translate(-coupledIntTranslation,0); + cairoShim.restore(); // undo rotation + if (mouseFocus) + { + drawPorts(cairoShim); + displayTooltip(cairoShim,tooltip()); + } + if (onResizeHandles) drawResizeHandles(cairoShim); + + cairoShim.newPath(); + clipPath.appendToCurrent(cairo); + cairoShim.clip(); + if (selected) drawSelected(cairoShim); + } void IntOp::resize(const LassoBox& b) { diff --git a/model/intOp.h b/model/intOp.h index cba540eb8..62e8d949f 100644 --- a/model/intOp.h +++ b/model/intOp.h @@ -64,6 +64,7 @@ namespace minsky {return intVar->valueId();} void draw(cairo_t*) const override; + void draw(ICairoShim&) const override; void resize(const LassoBox& b) override; /// return reference to integration variable diff --git a/model/item.cc b/model/item.cc index fa8869504..beaa19c0e 100644 --- a/model/item.cc +++ b/model/item.cc @@ -305,6 +305,21 @@ namespace minsky cairo_stroke(cairo); } + void Item::drawPorts(const ICairoShim& cairoShim) const + { + cairoShim.save(); + cairoShim.newPath(); + for (auto& p: m_ports) + { + cairoShim.newSubPath(); + cairoShim.arc(p->x()-x(), p->y()-y(), portRadius*zoomFactor(), 0, 2*M_PI); + } + cairoShim.setSourceRGB(0,0,0); + cairoShim.setLineWidth(1); + cairoShim.stroke(); + cairoShim.restore(); + } + void Item::drawSelected(cairo_t* cairo) { // implemented by filling the clip region with a transparent grey @@ -313,6 +328,15 @@ namespace minsky cairo_paint(cairo); } + void Item::drawSelected(const ICairoShim& cairoShim) + { + // implemented by filling the clip region with a transparent grey + cairoShim.save(); + cairoShim.setSourceRGBA(0.5,0.5,0.5,0.4); + cairoShim.paint(); + cairoShim.restore(); + } + void Item::drawResizeHandle(cairo_t* cairo, double x, double y, double sf, double angle) { const cairo::CairoSave cs(cairo); @@ -328,6 +352,23 @@ namespace minsky cairo_move_to(cairo,.2,1); cairo_line_to(cairo,1,1); } + + void Item::drawResizeHandle(const ICairoShim& cairoShim, double x, double y, double sf, double angle) + { + cairoShim.save(); + cairoShim.translate(x,y); + cairoShim.rotate(angle); + cairoShim.scale(sf,sf); + cairoShim.moveTo(-1,-.2); + cairoShim.lineTo(-1,-1); + cairoShim.lineTo(1,1); + cairoShim.lineTo(1,0.2); + cairoShim.moveTo(-1,-1); + cairoShim.lineTo(-.2,-1); + cairoShim.moveTo(.2,1); + cairoShim.lineTo(1,1); + cairoShim.restore(); + } // Refactor resize() code for all canvas items here. For feature 25 and 94 void Item::resize(const LassoBox& b) @@ -352,12 +393,31 @@ namespace minsky cairo_stroke(cairo); } + void Item::drawResizeHandles(const ICairoShim& cairoShim) const + { + auto sf=resizeHandleSize(); + double angle=0.5*M_PI; + for (auto& p: corners()) + { + angle+=0.5*M_PI; + drawResizeHandle(cairoShim,p.x()-x(),p.y()-y(),sf,angle); + } + cairoShim.stroke(); + } + void BottomRightResizerItem::drawResizeHandles(cairo_t* cairo) const { const Point p=resizeHandleCoords(); drawResizeHandle(cairo,p.x()-x(),p.y()-y(),resizeHandleSize(),0); cairo_stroke(cairo); } + + void BottomRightResizerItem::drawResizeHandles(const ICairoShim& cairoShim) const + { + const Point p=resizeHandleCoords(); + drawResizeHandle(cairoShim,p.x()-x(),p.y()-y(),resizeHandleSize(),0); + cairoShim.stroke(); + } // default is just to display the detailed text (ie a "note") void Item::draw(cairo_t* cairo) const @@ -389,6 +449,35 @@ namespace minsky if (selected) drawSelected(cairo); } + void Item::draw(const ICairoShim& cairoShim) const + { + auto [angle,flipped]=rotationAsRadians(); + const Rotate r(rotation()+(flipped? 180:0),0,0); + auto& pango = cairoShim.pango(); + const float z=zoomFactor(); + pango.angle=angle+(flipped? M_PI: 0); + pango.setFontSize(12.0*scaleFactor()*z); + pango.setMarkup(latexToPango(detailedText())); + // parameters of icon in userspace (unscaled) coordinates + const float w=0.5*pango.width()+2*z; + const float h=0.5*pango.height()+4*z; + + cairoShim.moveTo(r.x(-w+1,-h+2), r.y(-w+1,-h+2)); + pango.show(); + + if (mouseFocus) { + displayTooltip(cairoShim,tooltip()); + } + if (onResizeHandles) drawResizeHandles(cairoShim); + cairoShim.moveTo(r.x(-w,-h), r.y(-w,-h)); + cairoShim.lineTo(r.x(w,-h), r.y(w,-h)); + cairoShim.lineTo(r.x(w,h), r.y(w,h)); + cairoShim.lineTo(r.x(-w,h), r.y(-w,h)); + cairoShim.closePath(); + cairoShim.clip(); + if (selected) drawSelected(cairoShim); + } + void Item::dummyDraw() const { const ecolab::cairo::Surface s(cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA,NULL)); @@ -418,6 +507,30 @@ namespace minsky } } + void Item::displayTooltip(const ICairoShim& cairoShim, const std::string& tooltip) const + { + const string unitstr=units().latexStr(); + if (!tooltip.empty() || !unitstr.empty()) + { + cairoShim.save(); + auto& pango = cairoShim.pango(); + string toolTipText=latexToPango(tooltip); + if (!unitstr.empty()) + toolTipText+=" Units:"+latexToPango(unitstr); + pango.setMarkup(toolTipText); + const float z=zoomFactor(); + cairoShim.translate(z*(0.5*bb.width())+10, + z*(-0.5*bb.height())-20); + cairoShim.rectangle(0,0,pango.width(),pango.height()); + cairoShim.setSourceRGB(1,1,1); + cairoShim.fillPreserve(); + cairoShim.setSourceRGB(0,0,0); + pango.show(); + cairoShim.stroke(); + cairoShim.restore(); + } + } + shared_ptr Item::closestOutPort(float x, float y) const { if (auto v=select(x,y)) diff --git a/model/item.h b/model/item.h index 7d8961cfe..f08e18f5e 100644 --- a/model/item.h +++ b/model/item.h @@ -26,6 +26,7 @@ #include "geometry.h" #include "str.h" #include "polyRESTProcessBase.h" +#include "ICairoShim.h" #include @@ -163,6 +164,7 @@ namespace minsky } memoisedRotator; static void drawResizeHandle(cairo_t* cairo, double x, double y, double sf, double angle); + static void drawResizeHandle(const ICairoShim& cairoShim, double x, double y, double sf, double angle); public: @@ -289,6 +291,7 @@ namespace minsky /// draw this item into a cairo context virtual void draw(cairo_t* cairo) const; + virtual void draw(const ICairoShim& cairoShim) const; /// resize this item on the canvas virtual void resize(const LassoBox& b); /// factor by which item has been resized @@ -301,6 +304,7 @@ namespace minsky /// display tooltip text, eg on mouseover virtual void displayTooltip(cairo_t*, const std::string&) const; + virtual void displayTooltip(ICairoShim&, const std::string&) const; /// update display after a step() virtual void updateIcon(double t) {} @@ -310,8 +314,11 @@ namespace minsky virtual ~Item() {} void drawPorts(cairo_t* cairo) const; + void drawPorts(ICairoShim& cairoShim) const; static void drawSelected(cairo_t* cairo); + static void drawSelected(const ICairoShim& cairoShim); virtual void drawResizeHandles(cairo_t* cairo) const; + virtual void drawResizeHandles(const ICairoShim& cairoShim) const; /// returns the clicktype given a mouse click at \a x, \a y. virtual ClickType::Type clickType(float x, float y) const; @@ -363,6 +370,7 @@ namespace minsky { bool onResizeHandle(float x, float y) const override; void drawResizeHandles(cairo_t* cairo) const override; + void drawResizeHandles(const ICairoShim& cairoShim) const override; /// returns coordinates of the resizer handle virtual Point resizeHandleCoords() const; }; diff --git a/model/lock.cc b/model/lock.cc index 18001d737..d74baf112 100644 --- a/model/lock.cc +++ b/model/lock.cc @@ -103,6 +103,34 @@ namespace minsky if (selected) drawSelected(cairo); } + void Lock::draw(const ICairoShim& cairoShim) const + { + const float z=zoomFactor()*scaleFactor(); + const float w=iWidth()*z, h=iHeight()*z; + + { + cairoShim.save(); + cairoShim.translate(-0.5*w,-0.5*h); + SVGRenderer* icon=locked()? &lockedIcon: &unlockedIcon; + // TODO: Add SVGRenderer support to ICairoShim + auto& shimImpl = dynamic_cast(cairoShim); + icon->render(shimImpl._internalGetCairoContext(),w,h); + cairoShim.restore(); + } + + if (mouseFocus) + { + drawPorts(cairoShim); + displayTooltip(cairoShim,tooltip()); + if (onResizeHandles) drawResizeHandles(cairoShim); + } + + // add 8 pt margin to allow for ports + cairoShim.rectangle(-0.5*w-8,-0.5*h-8,w+16,h+8); + cairoShim.clip(); + if (selected) drawSelected(cairoShim); + } + Units Lock::units(bool check) const { if (locked()) diff --git a/model/lock.h b/model/lock.h index c09b90bcf..50e4afe69 100644 --- a/model/lock.h +++ b/model/lock.h @@ -42,6 +42,7 @@ namespace minsky static SVGRenderer lockedIcon; static SVGRenderer unlockedIcon; void draw(cairo_t* cairo) const override; + void draw(const ICairoShim& cairoShim) const override; Units units(bool) const override; /// Ravel this is connected to. nullptr if not connected to a Ravel Ravel* ravelInput() const; diff --git a/model/operation.cc b/model/operation.cc index eea1d7da3..2f7f18603 100644 --- a/model/operation.cc +++ b/model/operation.cc @@ -27,6 +27,7 @@ #include #include +#include "../engine/cairoShimCairo.h" #include "minsky_epilogue.h" #include @@ -218,6 +219,97 @@ namespace minsky if (selected) drawSelected(cairo); } + void OperationBase::drawUserFunction(const ICairoShim& cairoShim) const + { + // if rotation is in 1st or 3rd quadrant, rotate as + // normal, otherwise flip the text so it reads L->R + const double angle=rotation() * M_PI / 180.0; + const bool textFlipped=flipped(rotation()); + const float z=zoomFactor(); + + auto& c=dynamic_cast(*this); + + auto& pango = cairoShim.pango(); + pango.setFontSize(10.0*scaleFactor()*z); + pango.setMarkup(latexToPango(c.description())); + pango.angle=angle + (textFlipped? M_PI: 0); + const Rotate r(rotation()+ (textFlipped? 180: 0),0,0); + + // parameters of icon in userspace (unscaled) coordinates + float w, h, hoffs; + w=0.5*pango.width()+2*z; + h=0.5*pango.height()+4*z; + hoffs=pango.top()/z; + + { + cairoShim.save(); + cairoShim.moveTo(r.x(-w+1,-h-hoffs+2*z), r.y(-w+1,-h-hoffs+2*z)); + pango.show(); + cairoShim.restore(); + } + + cairoShim.rotate(angle); + + cairoShim.setSourceRGB(0,0,1); + cairoShim.moveTo(-w,-h); + cairoShim.lineTo(-w,h); + cairoShim.lineTo(w,h); + + cairoShim.lineTo(w+2*z,0); + cairoShim.lineTo(w,-h); + cairoShim.closePath(); + cairoShim.save(); // Save the clip path shape + cairoShim.stroke(); + cairoShim.restore(); + + cairoShim.rotate(-angle); // undo rotation + + // set the output ports coordinates + // compute port coordinates relative to the icon's + // point of reference + const Rotate rr(rotation(),0,0); + + m_ports[0]->moveTo(x()+rr.x(w+2,0), y()+rr.y(w+2,0)); + switch (numPorts()) + { + case 1: break; + case 2: + m_ports[1]->moveTo(x()+rr.x(-w,0), y()+rr.y(-w,0)); + break; + case 3: default: + m_ports[1]->moveTo(x()+rr.x(-w,0), y()+rr.y(-w,textFlipped? h-3: -h+3)); + m_ports[2]->moveTo(x()+rr.x(-w,0), y()+rr.y(-w,textFlipped? -h+3: h-3)); + break; + } + if (type()==OperationType::userFunction) + { + cairoShim.setSourceRGB(0,0,0); + // TODO: DrawBinOp needs complete ICairoShim refactoring + // For now using temporary workaround + auto& shimImpl = dynamic_cast(cairoShim); + DrawBinOp drawBinOp(shimImpl._internalGetCairoContext(), zoomFactor()); + drawBinOp.drawPort([&](){drawBinOp.drawSymbol("x");},-1.1*w,-1.1*h,rotation()); + drawBinOp.drawPort([&](){drawBinOp.drawSymbol("y");},-1.1*w,1.1*h,rotation()); + } + if (mouseFocus) + { + drawPorts(cairoShim); + displayTooltip(cairoShim,tooltip()); + if (onResizeHandles) drawResizeHandles(cairoShim); + } + // Re-create the clip path + cairoShim.rotate(angle); + cairoShim.moveTo(-w,-h); + cairoShim.lineTo(-w,h); + cairoShim.lineTo(w,h); + cairoShim.lineTo(w+2*z,0); + cairoShim.lineTo(w,-h); + cairoShim.closePath(); + cairoShim.clip(); + cairoShim.rotate(-angle); + if (selected) drawSelected(cairoShim); + } + void OperationBase::setCachedText(cairo_t* cairo, const std::string& text, double size) const { if (cachedPango && cairo==cachedPango->cairoContext()) return; @@ -316,6 +408,102 @@ namespace minsky clipPath.appendToCurrent(cairo); cairo_clip(cairo); if (selected) drawSelected(cairo); + } + + void OperationBase::draw(const ICairoShim& cairoShim) const + { + // TODO: Refactor iconDraw to use ICairoShim + auto& shimImpl = dynamic_cast(cairoShim); + cairo_t* cairo = shimImpl._internalGetCairoContext(); + // if rotation is in 1st or 3rd quadrant, rotate as + // normal, otherwise flip the text so it reads L->R + const double angle=rotation() * M_PI / 180.0; + const bool textFlipped=flipped(rotation()); + const float z=zoomFactor(); + + { + cairoShim.save(); + cairoShim.scale(z,z); + iconDraw(cairo); + cairoShim.restore(); + } + + + cairoShim.save(); + cairoShim.rotate(angle); + + float l=OperationBase::l*z, r=OperationBase::r*z, + h=OperationBase::h*z; + + if (fabs(l)<0.5*iWidth()*z) l=-0.5*iWidth()*z; + if (r<0.5*iWidth()*z) r=0.5*iWidth()*z; + if (h<0.5*iHeight()*z) h=0.5*iHeight()*z; + + cairoShim.moveTo(-r,-h); + cairoShim.lineTo(-r,h); + cairoShim.lineTo(r,h); + cairoShim.lineTo(r+2*z,0); + cairoShim.lineTo(r,-h); + + cairoShim.closePath(); + + cairoShim.setSourceRGB(0,0,1); + cairoShim.strokePreserve(); + + cairo::Path clipPath(cairo); + + // compute port coordinates relative to the icon's + // point of reference. Move outport 2 pixels right for ticket For ticket 362. + double x0=r, y0=0, x1=l, y1=numPorts() > 2? -h+3: 0, + x2=l, y2=numPorts() > 2? h-3: 0; + + if (textFlipped) swap(y1,y2); + + { + cairoShim.save(); + cairoShim.identityMatrix(); + cairoShim.translate(x(), y()); + cairoShim.rotate(angle); + cairoShim.userToDevice(x0, y0); + cairoShim.userToDevice(x1, y1); + cairoShim.userToDevice(x2, y2); + cairoShim.restore(); + } + + if (numPorts()>0) + m_ports[0]->moveTo(x0, y0); + if (numPorts()>1) + { +#ifdef DISPLAY_POW_UPSIDE_DOWN + if (type()==OperationType::pow) + ports[1]->moveTo(x2, y2); + else +#endif + m_ports[1]->moveTo(x1, y1); + } + + if (numPorts()>2) + { +#ifdef DISPLAY_POW_UPSIDE_DOWN + if (type()==OperationType::pow) + ports[2]->moveTo(x1, y1); + else +#endif + m_ports[2]->moveTo(x2, y2); + } + + cairoShim.restore(); // undo rotation + if (mouseFocus) + { + drawPorts(cairoShim); + displayTooltip(cairoShim,tooltip()); + if (onResizeHandles) drawResizeHandles(cairoShim); + } + + cairoShim.newPath(); + clipPath.appendToCurrent(cairo); + cairoShim.clip(); + if (selected) drawSelected(cairoShim); } void OperationBase::resize(const LassoBox& b) diff --git a/model/operationBase.h b/model/operationBase.h index ef92909d6..42e7a24c9 100644 --- a/model/operationBase.h +++ b/model/operationBase.h @@ -74,8 +74,10 @@ namespace minsky virtual void addPorts(); void drawUserFunction(cairo_t* cairo) const; + void drawUserFunction(const ICairoShim& cairoShim) const; void draw(cairo_t*) const override; + void draw(const ICairoShim&) const override; void resize(const LassoBox& b) override; float scaleFactor() const override; diff --git a/model/phillipsDiagram.cc b/model/phillipsDiagram.cc index 15496bc17..98cef9031 100644 --- a/model/phillipsDiagram.cc +++ b/model/phillipsDiagram.cc @@ -22,6 +22,7 @@ #include "phillipsDiagram.rcd" #include "phillipsDiagram.xcd" #include "minsky.h" +#include "../engine/cairoShimCairo.h" #include "minsky_epilogue.h" using ecolab::cairo::CairoSave; @@ -75,6 +76,13 @@ namespace minsky } } + void PhillipsStock::draw(const ICairoShim& cairoShim) const + { + // TODO: Implement properly without cairo_t* delegation + auto& shimImpl = dynamic_cast(cairoShim); + draw(shimImpl._internalGetCairoContext()); + } + bool PhillipsDiagram::redraw(int, int, int width, int height) { diff --git a/model/phillipsDiagram.h b/model/phillipsDiagram.h index 2aa1a0b9e..7168d54ee 100644 --- a/model/phillipsDiagram.h +++ b/model/phillipsDiagram.h @@ -70,6 +70,7 @@ namespace minsky static std::map maxStock; std::size_t numPorts() const override {return 2;} void draw(cairo_t* cairo) const override; + void draw(const ICairoShim& cairoShim) const override; }; class PhillipsDiagram: public RenderNativeWindow diff --git a/model/plotWidget.cc b/model/plotWidget.cc index 0fe74c815..6e7002f74 100644 --- a/model/plotWidget.cc +++ b/model/plotWidget.cc @@ -26,6 +26,7 @@ #include #include #include +#include "../engine/cairoShimCairo.h" #include "CSVTools.xcd" #include "itemT.rcd" @@ -212,6 +213,13 @@ namespace minsky if (selected) drawSelected(cairo); } + + void PlotWidget::draw(const ICairoShim& cairoShim) const + { + // TODO: Implement properly without cairo_t* delegation + auto& shimImpl = dynamic_cast(cairoShim); + draw(shimImpl._internalGetCairoContext()); + } void PlotWidget::scalePlot() { diff --git a/model/plotWidget.h b/model/plotWidget.h index daa758d59..d2183a3ae 100644 --- a/model/plotWidget.h +++ b/model/plotWidget.h @@ -167,6 +167,7 @@ namespace minsky void disconnectAllVars(); using ecolab::Plot::draw; void draw(cairo_t* cairo) const override; + void draw(const ICairoShim& cairoShim) const override; void requestRedraw(); ///< redraw plot using current data to all open windows void redrawWithBounds() override {redraw(0,0,500,500);} diff --git a/model/ravelWrap.cc b/model/ravelWrap.cc index aba2303d8..70fe07fba 100644 --- a/model/ravelWrap.cc +++ b/model/ravelWrap.cc @@ -24,6 +24,7 @@ #include "dimension.h" #include "minskyTensorOps.h" #include "pango.h" +#include "../engine/cairoShimCairo.h" #include "capiRenderer.xcd" #include "CSVTools.xcd" @@ -141,6 +142,71 @@ namespace minsky if (selected) drawSelected(cairo); } + void Ravel::draw(const ICairoShim& cairoShim) const + { + const double z=zoomFactor(), r=m_editorMode? 1.1*z*wrappedRavel.radius(): 30*z; + if (flipped) + { + m_ports[0]->moveTo(x()-1.1*r, y()); + m_ports[1]->moveTo(x()+1.1*r, y()); + drawTriangle(cairoShim,m_ports[1]->x()-x(),m_ports[1]->y()-y(),{0,0,0,1},M_PI); + } + else + { + m_ports[0]->moveTo(x()+1.1*r, y()); + m_ports[1]->moveTo(x()-1.1*r, y()); + drawTriangle(cairoShim,m_ports[1]->x()-x(),m_ports[1]->y()-y(),{0,0,0,1},0); + } + if (mouseFocus) + { + drawPorts(cairoShim); + displayTooltip(cairoShim,tooltip().empty()? explanation: tooltip()); + // Resize handles always visible on mousefocus. For ticket 92. + if (m_editorMode) drawResizeHandles(cairoShim); + } + cairoShim.rectangle(-r,-r,2*r,2*r); + cairoShim.rectangle(-1.1*r,-1.1*r,2.2*r,2.2*r); + cairoShim.strokePreserve(); + if (onBorder || lockGroup) + { // shadow the border when mouse is over it + cairoShim.save(); + cairo::Colour c{1,1,1,0}; + if (lockGroup) + c=palette[ lockGroup->colour() % paletteSz ]; + c.r*=0.5; c.g*=0.5; c.b*=0.5; + c.a=onBorder? 0.5:0.3; + cairoShim.setSourceRGBA(c.r,c.g,c.b,c.a); + cairoShim.setFillRule(CAIRO_FILL_RULE_EVEN_ODD); + cairoShim.fillPreserve(); + cairoShim.restore(); + } + + cairoShim.clip(); + + { + cairoShim.save(); + cairoShim.rectangle(-r,-r,2*r,2*r); + cairoShim.clip(); + if (m_editorMode) + { + cairoShim.scale(z,z); + // TODO: CairoRenderer needs ICairoShim support + auto& shimImpl = dynamic_cast(cairoShim); + CairoRenderer cr(shimImpl._internalGetCairoContext()); + wrappedRavel.render(cr); + } + else + { + cairoShim.translate(-r,-r); + // TODO: SVGRenderer needs ICairoShim support + auto& shimImpl = dynamic_cast(cairoShim); + svgRenderer.render(shimImpl._internalGetCairoContext(),2*r,2*r); + } + cairoShim.restore(); + } + if (selected) drawSelected(cairoShim); + } + void Ravel::resize(const LassoBox& b) { wrappedRavel.rescale(0.5*std::max(fabs(b.x0-b.x1),fabs(b.y0-b.y1))/(1.21*zoomFactor())); diff --git a/model/ravelWrap.h b/model/ravelWrap.h index 7f23e55a9..064949a35 100644 --- a/model/ravelWrap.h +++ b/model/ravelWrap.h @@ -110,6 +110,7 @@ namespace minsky void broadcastStateToLockGroup() const; void draw(cairo_t* cairo) const override; + void draw(const ICairoShim& cairoShim) const override; void resize(const LassoBox&) override; bool inItem(float x, float y) const override; void onMouseDown(float x, float y) override; diff --git a/model/sheet.cc b/model/sheet.cc index dbf61f743..33d9f0d6b 100644 --- a/model/sheet.cc +++ b/model/sheet.cc @@ -24,6 +24,7 @@ #include "plotWidget.h" #include #include +#include "../engine/cairoShimCairo.h" #include "itemT.rcd" #include "sheet.rcd" @@ -520,6 +521,13 @@ void Sheet::draw(cairo_t* cairo) const cairo_clip(cairo); } +void Sheet::draw(const ICairoShim& cairoShim) const +{ + // TODO: Implement properly without cairo_t* delegation + auto& shimImpl = dynamic_cast(cairoShim); + draw(shimImpl._internalGetCairoContext()); +} + void Sheet::exportAsCSV(const string& filename, bool tabular) const { if (!value) diff --git a/model/sheet.h b/model/sheet.h index b1955789b..acbeb7e1b 100644 --- a/model/sheet.h +++ b/model/sheet.h @@ -71,6 +71,7 @@ namespace minsky const std::string& setSliceIndicator(); void draw(cairo_t* cairo) const override; + void draw(const ICairoShim& cairoShim) const override; /// calculates the input value void computeValue(); diff --git a/model/switchIcon.cc b/model/switchIcon.cc index b03f90b0b..1d69c3555 100644 --- a/model/switchIcon.cc +++ b/model/switchIcon.cc @@ -21,6 +21,7 @@ #include "itemT.rcd" #include "switchIcon.h" #include "switchIcon.rcd" +#include "../engine/cairoShimCairo.h" #include "minsky_epilogue.h" using namespace ecolab::cairo; using namespace ecolab; @@ -123,6 +124,56 @@ namespace minsky cairo_clip(cairo); if (selected) drawSelected(cairo); } + + void SwitchIcon::draw(const ICairoShim& cairoShim) const + { + auto z=zoomFactor(); + const float width=m_width*z, height=m_height*z; + cairoShim.setLineWidth(1); + cairoShim.rectangle(-0.5*width,-0.5*height,width,height); + cairoShim.stroke(); + + const float w=flipped? -width: width; + const float o=flipped? -8: 8; + // output port + drawTriangle(cairoShim, 0.5*w, 0, palette[0], flipped? M_PI: 0); + m_ports[0]->moveTo(x()+0.5*w, y()); + // control port + drawTriangle(cairoShim, 0, -0.5*height-8, palette[0], M_PI/2); + m_ports[1]->moveTo(x(), y()-0.5*height-8); + const float dy=height/numCases(); + float y1=-0.5*height+0.5*dy; + // case ports + for (size_t i=2; imoveTo(x()+-0.5*w-o, y()+y1); + } + // draw indicating arrow + cairoShim.moveTo(0.5*w, 0); + try + { + y1=-0.5*width+0.5*dy+switchValue()*dy; + } + catch (const std::exception&) + { + y1=-0.5*width+0.5*dy; + } + cairoShim.lineTo(-0.45*w,0.9*y1); + cairoShim.stroke(); + + if (mouseFocus) + { + drawPorts(cairoShim); + displayTooltip(cairoShim,tooltip()); + if (onResizeHandles) drawResizeHandles(cairoShim); + } + + // add 8 pt margin to allow for ports + cairoShim.rectangle(-0.5*width-8,-0.5*height-8,width+16,height+8); + cairoShim.clip(); + if (selected) drawSelected(cairoShim); + } } CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::SwitchIcon); diff --git a/model/switchIcon.h b/model/switchIcon.h index a18f26d8a..cb453790b 100644 --- a/model/switchIcon.h +++ b/model/switchIcon.h @@ -65,6 +65,7 @@ namespace minsky /// draw icon to \a context void draw(cairo_t* context) const override; + void draw(const ICairoShim& cairoShim) const override; /// whether icon is oriented so input ports are on the rhs, and output on the lhs bool flipped=false; diff --git a/model/userFunction.h b/model/userFunction.h index c4cf841dc..2115752af 100644 --- a/model/userFunction.h +++ b/model/userFunction.h @@ -47,6 +47,8 @@ namespace minsky Units units(bool check=false) const override; void displayTooltip(cairo_t* cr, const std::string& tt) const override {Item::displayTooltip(cr,tt.empty()? expression: tt+" "+expression);} + void displayTooltip(const ICairoShim& cr, const std::string& tt) const override + {Item::displayTooltip(cr,tt.empty()? expression: tt+" "+expression);} using NamedOp::description; std::string description(const std::string&) override; @@ -58,6 +60,7 @@ namespace minsky {return (t==OperationType::userFunction)? new UserFunction: nullptr;} void draw(cairo_t* cairo) const override {drawUserFunction(cairo);} + void draw(const ICairoShim& cairoShim) const override {drawUserFunction(cairoShim);} }; diff --git a/model/variable.cc b/model/variable.cc index 09511ca97..113fc2a43 100644 --- a/model/variable.cc +++ b/model/variable.cc @@ -38,6 +38,7 @@ #include "variable.rcd" #include +#include "../engine/cairoShimCairo.h" #include "minsky_epilogue.h" #include @@ -877,6 +878,186 @@ void VariableBase::draw(cairo_t *cairo) const if (selected) drawSelected(cairo); } +void VariableBase::draw(const ICairoShim& cairoShim) const +{ + // TODO: Refactor RenderVariable to use ICairoShim + auto& shimImpl = dynamic_cast(cairoShim); + cairo_t* cairo = shimImpl._internalGetCairoContext(); + auto [angle,flipped]=rotationAsRadians(); + const float z=zoomFactor(); + + // grab a thread local copy of the renderer caches, as MacOSX does + // rendering on a different thread, and this avoids a race condition + // when the cache is invalidated + auto l_cachedNameRender=cachedNameRender; + if (!l_cachedNameRender || cairo!=cachedNameRender->cairoContext()) + { + l_cachedNameRender=cachedNameRender=std::make_shared(*this,cairo); + l_cachedNameRender->setFontSize(12.0); + } + + // if rotation is in 1st or 3rd quadrant, rotate as + // normal, otherwise flip the text so it reads L->R + const Rotate r(rotation() + (flipped? 180:0),0,0); + l_cachedNameRender->angle=angle+(flipped? M_PI:0); + + // parameters of icon in userspace (unscaled) coordinates + const double w=std::max(l_cachedNameRender->width(), 0.5f*iWidth()); + const double h=std::max(l_cachedNameRender->height(), 0.5f*iHeight()); + const double hoffs=l_cachedNameRender->top(); + + unique_ptr clipPath; + { + cairoShim.save(); + cairoShim.scale(z,z); + cairoShim.moveTo(r.x(-w+1,-h-hoffs+2), r.y(-w+1,-h-hoffs+2)); + { + cairoShim.save(); + if (local()) + cairoShim.setSourceRGB(0,0,1); + l_cachedNameRender->show(); + cairoShim.restore(); + } + + auto vv=vValue(); + if (miniPlot && vv && vv->size()==1) + try + { + if (cachedTime!=cminsky().t) + { + cachedTime=cminsky().t; + miniPlot->addPt(0,cachedTime,vv->value()); + miniPlot->setMinMax(); + } + cairoShim.save(); + cairoShim.translate(-w,-h); + miniPlot->draw(cairo,2*w,2*h); + cairoShim.restore(); + } + catch (...) {} // ignore errors in obtaining values + + // For feature 47 + try + { + if (type()!=constant && !ioVar() && vv && vv->size()==1 && vv->idxInRange()) + { + auto l_cachedMantissa=cachedMantissa; + auto l_cachedExponent=cachedExponent; + if (!l_cachedMantissa || l_cachedMantissa->cairoContext()!=cairo) + { + l_cachedMantissa=cachedMantissa=make_shared(cairo); + l_cachedMantissa->setFontSize(6.0); + l_cachedExponent=cachedExponent=make_shared(cairo); + l_cachedExponent->setFontSize(6.0); + cachedValue=nan(""); + } + + auto val=engExp(); + if (value()!=cachedValue) + { + cachedValue=value(); + if (!isnan(value())) { + if (sliderVisible()) + l_cachedMantissa->setMarkup + (mantissa(val, + int(1+ + (vv->sliderStepRel? + -log10(vv->maxSliderSteps()): + log10(vv->value()/vv->maxSliderSteps()) + )))); + else + l_cachedMantissa->setMarkup(mantissa(val)); + } + else if (isinf(value())) { // Display non-zero divide by zero as infinity. For ticket 1155 + if (signbit(value())) l_cachedMantissa->setMarkup("-∞"); + else l_cachedMantissa->setMarkup("∞"); + } + else // Display all other NaN cases as ???. For ticket 1155 + l_cachedMantissa->setMarkup("???"); + l_cachedExponent->setMarkup(expMultiplier(val.engExp)); + } + l_cachedMantissa->angle=angle+(flipped? M_PI:0); + + cairoShim.moveTo(r.x(w-l_cachedMantissa->width()-2,-h-hoffs+2), + r.y(w-l_cachedMantissa->width()-2,-h-hoffs+2)); + l_cachedMantissa->show(); + + if (val.engExp!=0 && !isnan(value())) // Avoid large exponential number in variable value display. For ticket 1155 + { + cairoShim.moveTo(r.x(w-l_cachedExponent->width()-2,0),r.y(w-l_cachedExponent->width()-2,0)); + l_cachedExponent->show(); + } + } + } + catch (...) {} // ignore errors in obtaining values + + { + cairoShim.save(); + cairoShim.rotate(angle); + // constants and parameters should be rendered in blue, all others in red + switch (type()) + { + case constant: case parameter: + cairoShim.setSourceRGB(0,0,1); + break; + default: + cairoShim.setSourceRGB(1,0,0); + break; + } + cairoShim.moveTo(-w,-h); + if (lhs()) + cairoShim.lineTo(-w+2,0); + cairoShim.lineTo(-w,h); + cairoShim.lineTo(w,h); + cairoShim.lineTo(w+2,0); + cairoShim.lineTo(w,-h); + cairoShim.closePath(); + clipPath.reset(new cairo::Path(cairo)); + cairoShim.stroke(); + if (sliderVisible()) + { + // draw slider + cairoShim.save(); + cairoShim.setSourceRGB(0,0,0); + try + { + cairoShim.arc((flipped?-1.0:1.0)*l_cachedNameRender->handlePos(), (flipped? h: -h), sliderHandleRadius, 0, 2*M_PI); + } + catch (const error&) {} // handlePos() may throw. + cairoShim.fill(); + cairoShim.restore(); + } + cairoShim.restore(); + }// undo rotation + + const double x0=z*w, y0=0, x1=-z*w+2, y1=0; + const double sa=sin(angle), ca=cos(angle); + if (!m_ports.empty()) + m_ports[0]->moveTo(x()+(x0*ca-y0*sa), + y()+(y0*ca+x0*sa)); + if (m_ports.size()>1) + m_ports[1]->moveTo(x()+(x1*ca-y1*sa), + y()+(y1*ca+x1*sa)); + cairoShim.restore(); + } + + auto g=group.lock(); + if (mouseFocus || (ioVar() && g && g->mouseFocus)) + { + cairoShim.save(); + drawPorts(cairoShim); + displayTooltip(cairoShim,tooltip()); + if (onResizeHandles) drawResizeHandles(cairoShim); + cairoShim.restore(); + } + + cairoShim.newPath(); + clipPath->appendToCurrent(cairo); + // Rescale size of variable attached to intop. For ticket 94 + cairoShim.clip(); + if (selected) drawSelected(cairoShim); +} + void VariableBase::resize(const LassoBox& b) { const float invZ=1/zoomFactor(); diff --git a/model/variable.h b/model/variable.h index 4da63e80f..f0037f9e6 100644 --- a/model/variable.h +++ b/model/variable.h @@ -198,7 +198,8 @@ namespace minsky /** draws the icon onto the given cairo context @return cairo path of icon outline */ - void draw(cairo_t*) const override; + void draw(cairo_t*) const override; + void draw(const ICairoShim&) const override; void resize(const LassoBox& b) override; ClickType::Type clickType(float x, float y) const override; diff --git a/model/wire.cc b/model/wire.cc index 49653c912..c54f7c0fe 100644 --- a/model/wire.cc +++ b/model/wire.cc @@ -28,6 +28,7 @@ #include "pango.h" #include "plotWidget.h" #include "SVGItem.h" +#include "../engine/cairoShimCairo.h" #include "wire.rcd" #include "minsky_epilogue.h" #include @@ -506,6 +507,14 @@ namespace } } + void Wire::draw(const ICairoShim& cairoShim, bool reverseArrow) const + { + // Wire drawing is complex and uses storeCairoCoords which requires cairo_t* + // TODO: This will be properly refactored in a separate PR (per owner comment) + auto& shimImpl = dynamic_cast(cairoShim); + draw(shimImpl._internalGetCairoContext(), reverseArrow); + } + void Wire::split() { // add I/O variables if this wire crosses a group boundary diff --git a/model/wire.h b/model/wire.h index 5d0b1072f..6c7bfb385 100644 --- a/model/wire.h +++ b/model/wire.h @@ -21,6 +21,7 @@ #include "noteBase.h" #include "intrusiveMap.h" +#include "ICairoShim.h" #include #include @@ -66,6 +67,7 @@ namespace minsky /// draw this item into a cairo context void draw(cairo_t* cairo, bool reverseArrow=false) const; + void draw(const ICairoShim& cairoShim, bool reverseArrow=false) const; /// display coordinates std::vector coords() const; diff --git a/package-lock.json b/package-lock.json index 283967596..a3ea4db8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,6 +92,7 @@ "integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.8.0" } @@ -227,6 +228,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver"