// Dialect supporting graphics operations. See the documentation for further // information on how to use this library // Written by Kim Bruce // Last revised 4/13/2014 import "StandardPrelude" as StandardPrelude inherits StandardPrelude.methods import "gtk" as gtk import "gdk" as gdk import "cairo" as cairo import "sys" as sys import "mgcollections" as collections import "math" as math import "io" as io method sqrt(n:Number) -> Number {n^(0.5)} method sqr(n:Number) -> Number {n*n} def Pi: Number is public = 3.14159 type GColor = { red -> Number green -> Number blue -> Number asString -> String } type DrawingBlock = { apply(ctx:Context) -> Done } type Widget = { add_events(_:Number) -> Done set_size_request(width:Number,height:Number) -> Done on(action:String)do(DrawingBlock) -> Done } // Location on the screen type Point = StandardPrelude.Point // coordinates of location // x->Number // y->Number // shift location by dx to right and dy down // translate(dx:Number,dy:Number)->Location // distance from origin // length -> Number // return distance from this location to other // distanceTo(other:Point)->Number // return sum of two points (gives translation) // +(other:Point) -> Number // return difference of two points // -(other: Point) -> Number // return if other at same place as self // ==(other:Point)->Boolean // return string representation of location // asString->String //} // Objects that can be drawn on screen and moved around type Graphic = { // location of object on screen x -> Number y -> Number location -> Point // Add this object to canvas c addToCanvas(c:Canvas)->Done // Remove this object from its canvas removeFromCanvas->Done // Is this object visible on the screen? isVisible->Boolean // Determine if object is shown on screen visible:=(_:Boolean)->Done // move this object to newLocn moveTo(newLocn:Point)->Done // move this object dx to the right and dy down moveBy(dx:Number,dy:Number)->Done // Does this object contain locn contains(locn:Point)->Boolean // Does other overlap with this object overlaps(other:Graphic2D)->Boolean // set the color of this object to c color:=(c:GColor)->Done // return the color of this object color->GColor // Send this object up one layer on the screen sendForward->Done // send this object down one layer on the screen sendBackward -> Done // send this object to the top layer on the screen sendToFront -> Done // send this object to the bottom layer on the screen sendToBack -> Done // Return a string representation of the object asString -> String } // Context for drawing. Not visible to user type Context = { set_source_rgb(r:Number,g:Number,b:Number) -> Done rectangle(x:Number,y:Number,w:Number,h:Number) -> Done move_to(x:Number,y:Number) -> Done line_to(x:Number,y:Number)->Done fill -> Done stroke -> Done save -> Done restore -> Done translate(dx:Number,dy:Number) -> Done scale(w:Number,h:Number) -> Done arc(xc:Number, yc:Number, radius:Number, angle1:Number, angle2:Number) -> Done font_size:=(size:Number) -> Done show_text(contents:String)->Done } // Canvas holding graphic objects type Canvas = { // add d to canvas add(d:Graphic)->Done // remove d from window remove(d:Graphic)->Done // indicate that screen must be redrawn setStateChanged->Done // clear the canvas clear -> Done // initialize canvas to use in graphics app doSetUp(graphicApp:GraphicApplication) -> Done // Send d to top layer of graphics sendToFront(d:Graphic)->Done // send d to bottom layer of graphics sendToBack(d:Graphic)->Done // send d up one layer in graphics sendForward(d:Graphic)->Done // send d down one layer in graphics sendBackward(d:Graphic)->Done drawingArea -> Widget // return the current dimensions of the canvas width -> Number height -> Number } // Type of object that run a graphical application that draws // objects on a screen and responds to mouse actions type GraphicApplication = { // canvas holds graphic objects on screen canvas -> Canvas // set the title in the window // only works with some browsers windowTitle:= (newTitle:String) -> Done // response to mouse click at mousePoint onMouseClick(mousePoint:Point)->Done // response to mouse click at mousePoint onMousePress(mousePoint:Point)->Done // response to mouse release at mousePoint onMouseRelease(mousePoint:Point)->Done // response to mouse move at mousePoint onMouseMove(mousePoint:Point)->Done // response to mouse click at mousePoint onMouseDrag(mousePoint:Point)->Done // response to mouse entry to window at mousePoint // currently not available for use onMouseEnter(mousePoint:Point)->Done // response to mouse exit of window at mousePoint // currently not available for use onMouseExit(mousePoint:Point)->Done // must be invoked to create window and its contents // as well as prepare the window to handle mouse events startGraphics -> Done } def colorRangeError is public = RuntimeError.refine "color component out of bounds" // Simple color class class aColor.r(r')g(g')b(b') -> GColor{ if((r' < 0) || (r' > 255)) then {colorRangeError.raise "red index {r'} out of bounds 0..255"} if((g' < 0) || (g' > 255)) then {colorRangeError.raise "green index {g'} out of bounds 0..255"} if((b' < 0) || (b' > 255)) then {colorRangeError.raise "blue index {b'} out of bounds 0..255"} def red:Number is public = r' / 255 def green:Number is public = g' / 255 def blue:Number is public = b' / 255 method asString { "rgb({red*255}, {green*255}, {blue*255})" } } // return a randomly chosen color method randomColor -> GColor { aColor.r(math.random*255)g(math.random*255)b(math.random*255) } // return a random number from m to n inclusive method randomIntFrom(m)to(n) -> Number { (((n-m+1)*math.random).truncate%(n-m+1))+m } // class generating immutable Locations on screen //class aLocation.at(x':Number,y':Number)->Point { // def x:Number is public = x' // def y:Number is public = y' // method ==(other:Location)->Boolean { // (other.x == x) && (other.y == y) // } // method translate(dx:Number,dy:Number)->Location { // aLocation.at(x + dx,y + dy) // } // method distanceTo(other:Location)->Number { // sqrt(sqr(x-other.x)+sqr(y-other.y)) // } // method asString->String { // "({x},{y})" // } //} // class representing objects that manage graphics on screen class aCanvas.size(width':Number,height':Number) -> Canvas { // size of canvas var width:Number is readable := width' var height:Number is readable := height' // list of all objects on canvas (hidden or not) var objects := collections.list.new // actual drawing areas where objects are displayed def drawingArea:Widget is readable = gtk.drawing_area // defaults size of drawing area & canvas drawingArea.set_size_request(width',height') // enable response to mouse events drawingArea.add_events(gdk.GDK_BUTTON_PRESS_MASK) drawingArea.add_events(gdk.GDK_BUTTON_RELEASE_MASK) drawingArea.add_events(gdk.GDK_POINTER_MOTION_MASK) drawingArea.add_events(gdk.GDK_ENTER_NOTIFY_MASK) drawingArea.add_events(gdk.GDK_LEAVE_NOTIFY_MASK) // draw all visible objects on "draw" event drawingArea.on "draw" do { ctx-> ctx.set_source_rgb(255,255,255) ctx.rectangle(0,0,width,height) // used to need to write self.x, fixed? ctx.fill for (objects) do {obj-> if(obj.isVisible) then {obj.draw(ctx)} } } // remove all items from canvas method clear->Done { objects := collections.list.new setStateChanged } // Add new item d to canvas method add(d:Graphic)->Done { objects.push(d) setStateChanged } // where object m is in list method indexOf(m:Graphic,lst:List) -> Number is confidential { for(lst.indices)do{ i->if(lst[i] == m) then {return i} } -1 } // remove m from items on canvas method remove(m:Graphic) -> Done{ if (objects.last == m) then { def last = objects.pop } else { def last = objects.pop remove(m) objects.push(last) } } // send item d to front/top layer of canvas method sendToFront(d)->Done { remove(d) objects.push(d) setStateChanged } // send item d to back/bottom layer of canvas method sendToBack(d:Graphic)->Done { var index:Number := indexOf(d,objects) if (index > 1) then { while {index > 1} do { objects[index] := objects[index-1] index := index - 1 } objects[1] := d setStateChanged } } // send item d one position higher on canvas method sendForward(d:Graphic)->Done { def index:Number = indexOf(d,objects) if (index < objects.size) then { objects[index] := objects[index+1] objects[index+1] := d setStateChanged } } // send item d one position lower on canvas method sendBackward(d:Graphic)->Done { def index:Number = indexOf(d,objects) if (index > 1) then { objects[index] := objects[index-1] objects[index-1] := d setStateChanged } } // method is place holder in case complications arise from concurrency // just calls paintAll method setStateChanged->Done{ paintAll } // redraw all items on canvas. Used when anything changes method paintAll->Done { drawingArea.queue_draw } // debug method to print all objects on canvas method printObjects -> Done { for(objects)do {obj -> print (obj) print " " } } // string representation of canvas method asString -> String { "canvas: with {objects.size} objects" } // Keep track of mouse actions so know whether clicking, dragging, etc. var buttonPressed:Boolean := false var buttonPressTime:Number := 0 // BUG: Why do I have to initialize this?? var clickOK:Boolean := false def maxClickTime: Number = 0.2 // Associate events on canvas with methods in graphics applications method doSetUp(graphicApp:GraphicApplication,window) -> Done { drawingArea.on "motion-notify-event" do {evt -> if(buttonPressed) then { graphicApp.onMouseDrag((evt.x)@(evt.y)) } else { graphicApp.onMouseMove((evt.x)@(evt.y)) } clickOK := false } drawingArea.on "button-press-event" do {evt -> buttonPressTime := sys.elapsed graphicApp.onMousePress((evt.x)@(evt.y)) buttonPressed := true clickOK := true } // hooks up onMouseRelease -- crashes occasionally if don't // initialize buttonPressTime drawingArea.on "button-release-event" do {evt -> graphicApp.onMouseRelease((evt.x)@(evt.y)) if (((sys.elapsed - buttonPressTime) < maxClickTime) && clickOK) then { graphicApp.onMouseClick((evt.x)@(evt.y)) } buttonPressed := false clickOK := false } drawingArea.on "enter-notify-event" do {evt -> graphicApp.onMouseEnter((evt.x)@(evt.y)) clickOK := false } drawingArea.on "leave-notify-event" do {evt -> graphicApp.onMouseExit((evt.x)@(evt.y)) clickOK := false } } } // abstract class to be extended to create graphic application using mouse actions class aGraphicApplication.size(width':Number,height':Number) ->GraphicApplication{ def window is readable = gtk.window(gtk.GTK_WINDOW_TOPLEVEL) window.title := "Simple graphics" window.on "destroy" do { gtk.main_quit } def border:Number = 16 // width of scrollbars on window that encroach on canvas // not consistent between browsers, alas! var windowWidth:Number := width'+border var windowHeight:Number := height'+border // print "windowWidth: {windowWidth}, windowHeight: {windowHeight}" window.set_default_size(windowWidth,windowHeight) method windowTitle:= (newTitle:String) -> Done { window.title := newTitle } def canvas:Canvas is readable = aCanvas.size(width',height') // window.add(canvas.drawingArea) canvas.doSetUp(self,window) // create layout for window def borderLayout is readable = gtk.grid window.add(borderLayout) // boxes to hold GUI components def southBox is readable = gtk.box(gtk.GTK_ORIENTATION_HORIZONTAL, 6) def northBox is readable = gtk.box(gtk.GTK_ORIENTATION_HORIZONTAL, 6) def westBox is readable = gtk.box(gtk.GTK_ORIENTATION_VERTICAL, 6) def eastBox is readable = gtk.box(gtk.GTK_ORIENTATION_VERTICAL, 6) // Arrange like gridlayout with drawingarea in center. borderLayout.attach(northBox,0,0,3,1) borderLayout.attach(westBox,0,1,1,1) borderLayout.attach(canvas.drawingArea,1,1,1,1) borderLayout.attach(eastBox,2,1,1,1) borderLayout.attach(southBox,0,3,3,1) // Grab gtk component corresponding to each objectdraw component // If add new components then add new cases. method getGTKComponent(component) is confidential { var realComponent match(component) case{tf:TextField -> realComponent := tf.theTextField} case{but:Button -> realComponent := but.theButton} realComponent } // need to fix to get accurate measurements of how much to add! method addToSouth(component)->Done { southBox.pack_start(getGTKComponent(component),true,true,5) windowHeight := windowHeight + border // print "change height" window.set_default_size(windowWidth,windowHeight) } method addToNorth(component)->Done { northBox.pack_start(getGTKComponent(component),true,true,5) windowHeight := windowHeight + border // print "change height" window.set_default_size(windowWidth,windowHeight) } // Need to fix to see how much to add method addToWest(component)->Done { westBox.pack_start(getGTKComponent(component),true,true,5) } method addToEast(component)->Done { eastBox.pack_start(getGTKComponent(component),true,true,5) } // canvas.doSetUp(self,window) method onMouseClick(mousePoint:Point)->Done{ // print "user clicked at ({mousePoint.x},{mousePoint.y})" } method onMousePress(mousePoint:Point)->Done{ // print "button pressed at ({mousePoint.x},{mousePoint.y})" } method onMouseRelease(mousePoint:Point)->Done{ // print "button released at ({mousePoint.x},{mousePoint.y})" } method onMouseMove(mousePoint:Point)->Done{ // print "button moved at ({mousePoint.x},{mousePoint.y})" } method onMouseDrag(mousePoint:Point)->Done{ // print "button dragged at ({mousePoint.x},{mousePoint.y})" } method onMouseEnter(mousePoint:Point)->Done{ // print "mouse entered window" } method onMouseExit(mousePoint:Point)->Done{ // print "mouse exited window" } method startGraphics -> Done { window.show_all gtk.main } } // Two-dimensional objects that can also be resized type Graphic2D = Graphic & type { // dimensions of object width->Number height->Number // Change dimensions of object setSize(width:Number,height:Number)->Done width:=(width:Number)->Done height:=(height:Number)->Done } // One-dimensional objects type Line = Graphic & type { // start and end of line start -> Point end -> Point // set start and end of line start:=(start':Point) -> Done end:=(end':Point) -> Done setEndPoints(start':Point,end':Point) -> Done } // predefined colors in objectdraw def white:GColor is public = aColor.r(255)g(255)b(255) def black:GColor is public = aColor.r(0)g(0)b(0) def green:GColor is public = aColor.r(0)g(255)b(0) def red:GColor is public = aColor.r(255)g(0)b(0) def gray:GColor is public = aColor.r(60)g(60)b(60) def blue:GColor is public = aColor.r(0)g(0)b(255) def cyan:GColor is public = aColor.r(0)g(255)b(255) def magenta:GColor is public = aColor.r(255)g(0)b(255) def yellow:GColor is public = aColor.r(255)g(255)b(0) def neutral:GColor is public = aColor.r(220)g(220)b(220) // abstract superclass for drawable objects class aDrawable.at(location':Point) on (canvas':Canvas) -> Graphic { // location of object on screen var location is readable := location' // x coordinate of object method x { location.x } // y coordinate of object method y->Number { location.y } // The canvas this object is part of var canvas:Canvas := canvas' // the color of this object var theColor:GColor := black method color -> GColor {theColor} method color:=(newColor:GColor) -> Done { theColor := newColor setStateChanged } var theFrameColor:GColor := black method frameColor -> GColor { theFrameColor } method frameColor:=(newfColor:GColor) -> Done { theFrameColor := newfColor setStateChanged } // Determine if object is shown on screen var isVisible:Boolean is readable := true method visible:=(b:Boolean) -> Done { isVisible := b setStateChanged } // Add this object to canvas c method addToCanvas(c:Canvas)->Done { canvas := c c.add(self) } // Remove this object from its canvas method removeFromCanvas->Done { canvas.remove(self) setStateChanged } // move this object to newLocn method moveTo(newLocn:Point)->Done{ location := newLocn setStateChanged } // move this object dx to the right and dy down method moveBy(dx:Number,dy:Number)->Done{ location := location+(dx@dy) setStateChanged } method contains(locn:Point) -> Boolean { print "not implemented for this object yet" false } method overlaps(otheObject:Graphic2D) -> Boolean { print "not yet implemented for this object" false } // Send this object up one layer on the screen method sendForward->Done{ canvas.sendForward(self) } // send this object down one layer on the screen method sendBackward->Done{ canvas.sendBackward(self) } // send this object to the top layer on the screen method sendToFront->Done{ canvas.sendToFront(self) } // send this object to the bottom layer on the screen method sendToBack->Done{ canvas.sendToBack(self) } // Tell the system that the screen needs to be repainted method setStateChanged->Done is confidential { canvas.setStateChanged } // Draw this object on the applet !! confidential method draw(ctx)->Done {} // Return a string representation of the object method asString->String{"Graphic object"} } // abstract class for two-dimensional objects class aDrawable2D.at(location':Point)size(width':Number,height':Number)on(canvas':Canvas) -> Graphic2D{ inherits aDrawable.at(location')on(canvas') var theWidth:Number := width' method width -> Number {theWidth} var theHeight:Number := height' method height -> Number {theHeight} method contains(locn:Point)->Boolean{ (x <= locn.x) && (locn.x <= (x + width)) &&(y <= locn.y) && (locn.y <= (y + height)) } method overlaps(other:Graphic2DFS)->Boolean{ def itemleft = other.x def itemright = other.x + other.width def itemtop = other.y def itembottom = other.y+other.height def disjoint:Boolean = ((x+width) < itemleft) || (itemright < x) || ((y+height) < itemtop) || (itembottom < y) return !disjoint } method asString->String{ "aDrawable2D object at ({x},{y}) "++ "with height {height}, width {width}, and color {color}" } } // abstract class to be extended for 2 dimensional objects that can be // resized. class aResizable2D.at(location':Point)size(width':Number,height':Number) on(canvas':Canvas) -> Graphic2D{ inherits aDrawable2D.at(location')size(width',height')on(canvas') method width:=(w:Number){ theWidth := w setStateChanged } method height:=(h:Number){ theHeight := h setStateChanged } method setSize(w:Number,h:Number){ width := w height := h } method asString->String { "Resizable2D object at ({x},{y}) "++ "with height {height}, width {width}, and color {color}" } } // class to generate framed rectangle at (x',y') with size width' x height' // created on canvas' class aFramedRect.at(location':Point)size(width':Number,height':Number) on(canvas':Canvas) -> Graphic2D{ inherits aResizable2D.at(location')size(width',height')on(canvas') addToCanvas(canvas') method draw(ctx)->Done { ctx.set_source_rgb(color.red, color.green, color.blue) ctx.rectangle(x,y,width,height) ctx.stroke } method asString->String { "FramedRect at ({x},{y}) "++ "with height {height}, width {width}, and color {color}" } // DEBUG: Temporary to work around bug method ==(other) { asString == other.asString } } // class to generate filled rectangle at (x',y') with size width' x height' // created on canvas' class aFilledRect.at(location':Point)size(width':Number,height':Number) on(canvas':Canvas) -> Graphic2D{ inherits aResizable2D.at(location')size(width',height')on(canvas') addToCanvas(canvas') method draw(ctx)->Done { ctx.set_source_rgb(color.red, color.green, color.blue) ctx.rectangle(x,y,width,height) // used to need to write self.x, fixed? ctx.fill } method asString->String { "FilledRect at ({x},{y}) "++ "with height {height}, width {width}, and color {color}" } // DEBUG: Temporary to work around bug method ==(other) { asString == other.asString } } // class to generate framed oval at (x',y') with size width' x height' // created on canvas' class aFramedOval.at(location':Point)size(width':Number,height':Number) on(canvas':Canvas) -> Graphic2D{ inherits aResizable2D.at(location')size(width',height')on(canvas') addToCanvas(canvas') method draw(ctx)->Done { ctx.set_source_rgb(color.red, color.green, color.blue) ctx.save ctx.translate(x+width/2,y+height/2) ctx.scale(width/2,height/2) ctx.arc(0,0,1,0,2*Pi) ctx.restore ctx.stroke } method asString->String { "FramedOval at ({x},{y}) "++ "with height {height}, width {width}, and color {color}" } // DEBUG: Temporary to work around bug method ==(other) { asString == other.asString } } // class to generate filled oval at (x',y') with size width' x height' // created on canvas' class aFilledOval.at(location':Point)size(width':Number,height':Number) on(canvas':Canvas) -> Graphic2D{ inherits aResizable2D.at(location')size(width',height')on(canvas') addToCanvas(canvas') method draw(ctx)->Done { ctx.set_source_rgb(color.red, color.green, color.blue) ctx.save ctx.translate(x+width/2,y+height/2) ctx.scale(width/2,height/2) ctx.arc(0,0,1,0,2*Pi) ctx.restore ctx.fill } method asString->String { "FilledOval at ({x},{y}) "++ "with height {height}, width {width}, and color {color}" } // DEBUG: Temporary to work around bug method ==(other) { asString == other.asString } } // class to generate framed arc at (x',y') with size width' x height' // from startAngle radians to endAngle radians created on canvas' // Note that angle 0 is in direction of positive x axis and increases in // angles go clockwise. class aFramedArc.at(location':Point)size(width':Number,height':Number) from(startAngle:Number)to(endAngle:Number)on(canvas':Canvas) -> Graphic2D{ inherits aResizable2D.at(location')size(width',height')on(canvas') addToCanvas(canvas') method draw(ctx)->Done { ctx.set_source_rgb(color.red, color.green, color.blue) ctx.save ctx.translate(x+width/2,y+height/2) ctx.scale(width/2,height/2) if (startAngle <= endAngle) then { ctx.arc(0,0,1,(startAngle*Pi)/180,(endAngle*Pi)/180) } else { ctx.arc(0,0,1,(endAngle*Pi)/180,(startAngle*Pi)/180) } ctx.restore ctx.stroke } method asString->String { "FramedArc at ({x},{y}) "++ "with height {height}, width {width}, and color {color} going "++ "from {startAngle} degrees to {endAngle} degrees" } // DEBUG: Temporary to work around bug method ==(other) { asString == other.asString } } // class to generate filled arc at (x',y') with size width' x height' // from startAngle degrees to endAngle degrees created on canvas' // Note that angle 0 is in direction of positive x axis and increases in // angles go clockwise. class aFilledArc.at(location':Point)size(width':Number,height':Number) from(startAngle:Number)to(endAngle:Number)on(canvas':Canvas) -> Graphic2D{ inherits aResizable2D.at(location')size(width',height')on(canvas') addToCanvas(canvas') method draw(ctx)->Done { ctx.set_source_rgb(color.red, color.green, color.blue) ctx.save ctx.translate(x+width/2,y+height/2) ctx.scale(width/2,height/2) if (startAngle <= endAngle) then { ctx.arc(0,0,1,(startAngle*Pi)/180,(endAngle*Pi)/180) } else { ctx.arc(0,0,1,(endAngle*Pi)/180,(startAngle*Pi)/180) } ctx.restore ctx.fill } method asString->String { "FilledArc at ({x},{y}) "++ "with height {height}, width {width}, and color {color} going "++ "from {startAngle} degrees to {endAngle} degrees" } // DEBUG: Temporary to work around bug method ==(other) { asString == other.asString } } // class to generate an image on canvas' at (x',y') with size width' x height' // The image is taken from the file fileName and must be in "png" format. // currently doesn't work, perhaps because can't find file. class aDrawableImage.at(location':Point)size(width':Number,height':Number) file(fileName:String)on(canvas':Canvas) -> Graphic2D{ inherits aResizable2D.at(location')size(width',height')on(canvas') addToCanvas(canvas') if (!io.exists(fileName)) then { io.error.write("Error: no such file '{fileName}' ") sys.exit(1) } def surface = cairo.image_surface_create_from_png(fileName) def imWidth = surface.width def imHeight = surface.height method draw(ctx)->Done { ctx.save ctx.translate(x,y) ctx.scale(width/imWidth,height/imHeight) ctx.set_source_surface(surface,0,0) ctx.paint ctx.restore } method asString->String { "DrawableImage at ({x},{y}) "++ "with height {height}, width {width}, "++ "from file {fileName}" } // DEBUG: Temporary to work around bug method ==(other) { asString == other.asString } } type LineFactory = { from(start':Point)to(end':Point)on(canvas':Canvas) -> Line from (pt: Point) length (len: Number) direction (radians: Number) on (canvas:Canvas) -> Line } def aLine: LineFactory = object { // Create a line from start' to end' on canvas' method from(start':Point)to(end':Point)on(canvas':Canvas) -> Line { object { inherits aDrawable.at(start')on(canvas') var theEnd: Point := end' // position of start of line (same as location) method start -> Point { location } // position of end of line method end -> Point {theEnd} addToCanvas(canvas') // set start and end of line method start:=(newStart:Point) -> Done { location := newStart setStateChanged } method end:=(newEnd:Point) -> Done { theEnd := newEnd setStateChanged } method setEndPoints(newStart:Point,newEnd:Point) -> Done { start := newStart end := newEnd } method draw(ctx)->Done { ctx.set_source_rgb(color.red, color.green, color.blue) ctx.move_to(location.x,location.y) ctx.line_to(theEnd.x, theEnd.y) ctx.stroke } method moveBy(dx:Number,dy:Number) ->Done { location := location + (dx@dy) //.translate(dx,dy) theEnd := theEnd + (dx@dy) //.translate(dx,dy) setStateChanged } method dist2(v:Point, w:Point) -> Number is confidential{ sqr(v.x - w.x) + sqr(v.y - w.y) } method distToSegmentSquared(p:Point, v:Point, w:Point) -> Number is confidential { var l2:Number := dist2(v, w) if (l2 == 0) then { return dist2(p, v)} var t:Number := ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2 if (t < 0) then {return dist2(p, v)} if (t > 1) then {return dist2(p, w)} dist2(p, ( (v.x + t * (w.x - v.x))@ (v.y + t * (w.y - v.y) ))) } method distToSegment(p:Point, v:Point, w:Point) -> Number { sqrt(distToSegmentSquared(p, v, w)) } method contains(pt:Point) -> Boolean { def tolerance: Number = 2 // say true if pt within tolerance of line distToSegment(pt,start,end) < tolerance } method asString->String { "Line from {start} to {end} with color {color}" } } } method from (pt: Point) length (len: Number) direction (radians: Number) on (canvas:Canvas) -> Line { def endpt: Point = pt+((len*math.cos(radians)) @ (-len*math.sin(radians))) aLine.from(pt) to (endpt) on (canvas) } } // type for text items type Text = Graphic & type{ // return the contents displayed in the item contents -> String // reset the contents displayed to be s contents:=(s:String) -> Done // return width of text item (currently inaccurate) width -> Number // return size of the font used to display the contents fontSize -> Number // Set the size of the font used to display the contents fontSize:=(size:Number) -> Done } // class to generate text at location' on canvas' initially showing // contents' class aText.at(location':Point)with(contents':String)on(canvas':Canvas) -> Text { inherits aDrawable.at(location')on(canvas') addToCanvas(canvas') var theContents:String := contents' var fsize:Number is readable := 12 method width -> Number { theContents.size*fsize/2 } method draw(ctx) -> Done { ctx.font_size := fontSize ctx.set_source_rgb(color.red, color.green, color.blue) ctx.move_to(x,y) ctx.show_text(contents) ctx.fill } method contents -> String { theContents } method contents:=(newContents:String) -> Done { theContents := newContents setStateChanged } method fontSize:=(size':Number) -> Done { fsize := size' setStateChanged } method fontSize -> Done { fsize } method asString -> String { "Text at ({x},{y}) with value {contents} and color {color}" } } type Block0 = {apply -> Done} type GTKTextField = { text -> String text:=(newText:String) -> Done connect(action:String,block:Block0) -> Done } type TextField = { text -> String text:=(newLabel:String) -> Done addTextFieldListener(listener: TextFieldListener) -> Done theTextField -> GTKTextField } type TextFieldListener = { textFieldActivated(evt:TextFieldEvent) -> Done } type TextFieldEvent = { source -> TextField text -> String } class aTextFieldEvent.text(text':String) from (source':TextField) { def text:String is readable = text' def source:TextField is readable = source' } class aTextField.withText(text':String) { def theTextField:GTKTextField is readable = gtk.entry theTextField.text := text' method text -> String {theTextField.text} method text:=(newText:String) -> Done { theTextField.text := newText } def listeners = collections.list.new method addTextFieldListener(listener:TextFieldListener) -> Done { listeners.push(listener) } theTextField.connect("activate", { def evt: TextFieldEvent = aTextFieldEvent.text(theTextField.text) from (self) for(listeners) do { listener:TextFieldListener -> listener.textFieldActivated(evt) } }) } type GTKButton = { label:=(newLabel:String) -> Done on(action:String)do(block: Block0) -> Done } type Button = { label -> String label:=(newLabel:String) -> Done addButtonListener(listener: ButtonListener) -> Done } type ButtonListener = { buttonPressed(evt:ButtonEvent) -> Done } type ButtonEvent = { source -> Button label -> String } class aButtonEvent.label(label':String) from (source':Button) { def label:String is readable = label' def source:Button is readable = source' } class aButton.withLabel(label':String) { def theButton is readable = gtk.button theButton.label := label' var theLabel:String := label' method label -> String {theLabel} method label:=(newLabel:String) -> Done { theLabel:= newLabel theButton.label := newLabel } def listeners = collections.list.new method addButtonListener(listener:ButtonListener) -> Done { listeners.push(listener) } theButton.on "clicked" do { def evt = aButtonEvent.label(label)from(self) for(listeners) do { listener:ButtonListener -> listener.buttonPressed(evt) } } }