/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx.p5 import cc.drx._ import processing.core._ import processing.core.PConstants._ // import processing.core.PApplet._ // import P5._ //TODO implement the core interface to draw the included draw implicits that have been used for a long time class DrawContextP5(val g:PGraphics) extends DrawContext{ import Style._ override def !(p:Default.type):Unit = { //g.background(White) super.!(p) //--canvas/context g.rectMode(CORNERS) //draw rect from NW to SE g.ellipseMode(CENTER) //specify an ellipse by x, y radius ?? g.noStroke g.noFill } //--query def size:Vec = Vec(g.width, g.height) def stroke:Option[Color] = ??? def fill:Option[Color] = ??? def weight:Double = ??? //--required properties `p` // note this argument based dispatching is applied at compile time and does not need runtime pattern matching //---coloring def !(p:Background):Unit = g.background(p.c) def !(p:Fill):Unit = g.fill(p.c) def !(p:Stroke):Unit = g.stroke(p.c) def !(p:FillNone.type):Unit = g.noFill def !(p:StrokeNone.type):Unit = g.noStroke def !(p:Weight):Unit = g.strokeWeight(p.value) def !(p:Font):Unit = g.textFont(pFont(p), p.size) //--cache to auto load and keep resources, this removes the initialization requirement and implementation details to the surface drawn context def textSize(text:Text,font:Font):Vec = Vec(textWidth(font,text.value), font.size) //FIXME properly set the y size /**compute the width of represented string*/ def textWidth(f:Font,str:String):Double = { val p = pFont(f) //store to prevent re-calling the def (0d /: str){case (a,c) => a + p.width(c)}*f.size } def pFont(font:Font) = fontCache(font) override def save(file:File):Try[File] = Try{g save file.path; file} //TODO thow an error if the file could not be saved... private val fontCache:Cache[Style.Font,PFont] = Cache{font:Style.Font => import java.awt.{Font => JFont} def mkFont(in: => Input) = Try{JFont.createFont(JFont.TRUETYPE_FONT, in.is).deriveFont(JFont.PLAIN,font.size)} val jFont = mkFont(File(font.name).in) orElse mkFont(Input.resource(File(font.name)).get) getOrElse //get is wrapped in the try for the failure to get new JFont(font.name, JFont.PLAIN, font.size.round.toInt) new PFont(jFont, true) //use smoothing by default } private val svgCache = Cache{xml:String => new PShapeSVG(processing.data.XML.parse(xml)) } private val imgCache = Cache{f:File => val bytes = if(f.isFile) f.in.toByteArray else Input.resource(f).map{_.toByteArray}.getOrElse(Array.empty[Byte]) val pImg = new PImage(new javax.swing.ImageIcon(bytes).getImage) pImg.format = ARGB pImg } def emptyCache() = { //TODO or use the word clear fontCache.empty svgCache.empty imgCache.empty } //--Alignment def !(p:Align):Unit = g.textAlign( p.horz match { case Left => LEFT case Center => CENTER case Right => RIGHT }, p.vert match { case Top => TOP case Midline => CENTER case Bottom => BOTTOM } ) //--transform (use affine matrix instead?) def !(p:Translate):Unit = g.translate(p.t.x,p.t.y) def !(p:ScaleProperty):Unit = g.scale(p.t.x,p.t.y) def !(p:Rotate):Unit = g.rotate(p.r.rad) //-- shapes `s` def !(p:Path):Unit = { val head:Vec = p.vertices.head.last val rest:Iterable[Vertex] = p.vertices.tail g.beginShape() rest.foreach{ //TODO it would be nice if these didn't require pattern match but to make something working (run with it for now and optimize/generalize later) case Vec(x,y,_) => g.vertex(x,y) case BezierVertex(ca,cb,b) => g.bezierVertex(ca.x,ca.y, cb.x,cb.y, b.x,b.y) } if(p.isClosed) g.endShape(CLOSE) else g.endShape } def !(s:Circ):Unit = g.ellipse(s.c.x , s.c.y , s.r*2 , s.r*2) def !(s:Line):Unit = g.line(s.a.x,s.a.y, s.b.x,s.b.y) def !(s:Rect):Unit = g.rect(s.a.x , s.a.y , s.b.x , s.b.y) //assuming rectMode(CORNERS) def !(s:Text):Unit = g.text(s.value , s.pos.x , s.pos.y) private def shape(f: => Unit):Unit = {g.beginShape(); f; g.endShape(CLOSE)} def !(s:Poly):Unit = shape{s.vertices.foreach{v => g.vertex(v.x, v.y)}} def !(tri:Tri):Unit = shape{ g.vertex(tri.a.x, tri.a.y) g.vertex(tri.b.x, tri.b.y) g.vertex(tri.c.x, tri.c.y) } def style(f: =>Unit):Unit = {g.pushStyle; f; g.popStyle} def matrix(f: =>Unit):Unit = {g.pushMatrix; f; g.popMatrix} def !(a:Arc):Unit = style{ g.noFill g.strokeWeight(a.w) g.strokeCap(SQUARE) val w = a.r*2 //g.ellipseMode(is set to CENTER as default which means RADIUS ???) g.arc(a.c.x,a.c.y, w,w, a.angle.min.rad, a.angle.max.rad, OPEN) } def !(e:Ellipse):Unit = { if(e.rotation == Angle(0)) g.ellipse(e.c.x,e.c.y, e.r.x*2,e.r.y*2) else matrix{ g.translate(e.c.x, e.c.y) g.rotate(e.rotation.rad) g.ellipse(0,0, e.r.x*2,e.r.y*2) } } private def rawImageOf(img:Img):PImage = img match { case ImgFile(file, _) => imgCache(file) case _ => ??? //TODO Img wrapped Pimage } def !(img:Img):Unit = { val p:PImage = rawImageOf(img) val box = Rect(p.width,p.height) fitIn img.box //fit the picture size including aspect ratio into the drawing box //TODO check if rotations can also work here... g.image(p, box.a.x, box.a.y, box.width, box.height) } def !(svg:Svg):Unit = g.shape(svgCache(svg.xml)) def !(video:Video):Unit = ??? def !(html:Html):Unit = ??? def remote(video:Video):MediaRemote = ??? // def !(arrow:Arrow):Unit = ??? def !(star:Star):Unit = shape{for(v <- star.vertices) g.vertex(v.x, v.y)} override def !(z:Bezier):Unit = g.bezier(z.a.x, z.a.y, z.ca.x, z.ca.y, z.cb.x, z.cb.y, z.b.x,z.b.y) //general bezier def close():Unit = {} //TODO close some elements }