• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Martomate / TriPaint / 9275511596

28 May 2024 07:51PM UTC coverage: 29.682% (+1.8%) from 27.877%
9275511596

push

github

Martomate
Updated to Scala 3.4 and MUnit 1.0

2 of 11 new or added lines in 9 files covered. (18.18%)

430 existing lines in 38 files now uncovered.

401 of 1351 relevant lines covered (29.68%)

0.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/scala/tripaint/view/image/ImageGridPane.scala
1
package tripaint.view.image
2

3
import tripaint.model.{Color, FloodFillSearcher, ImageGrid, ImageGridChange, ImageGridColorLookup}
4
import tripaint.model.coords.{GridCoords, PixelCoords, TriangleCoords}
5
import tripaint.model.image.{GridCell, ImageChange}
6
import tripaint.view.EditMode
7

8
import javafx.scene.input.{MouseButton, MouseEvent}
9
import javafx.scene.shape.Rectangle
10
import scalafx.beans.property.ObjectProperty
11
import scalafx.scene.layout.Pane
12
import scalafx.scene.paint.Color as FXColor
13

14
import scala.collection.mutable
15

UNCOV
16
class ImageGridPane(imageGrid: ImageGrid) extends Pane {
×
17
  private var xScroll: Double = 0
18
  private var yScroll: Double = 0
19
  private var zoom: Double = 1d
20

21
  private object drag {
22
    var x: Double = -1
23
    var y: Double = -1
24
  }
25

26
  object colors {
27
    val primaryColor: ObjectProperty[Color] = ObjectProperty(Color.Black)
×
UNCOV
28
    val secondaryColor: ObjectProperty[Color] = ObjectProperty(Color.White)
×
29

30
    def setPrimaryColor(col: Color): Unit = {
×
UNCOV
31
      primaryColor.value = col
×
32
    }
33

34
    def setSecondaryColor(col: Color): Unit = {
×
UNCOV
35
      secondaryColor.value = col
×
36
    }
37

38
    def setPrimaryColor(col: FXColor): Unit = {
×
UNCOV
39
      setPrimaryColor(Color.fromFXColor(col))
×
40
    }
41

42
    def setSecondaryColor(col: FXColor): Unit = {
×
UNCOV
43
      setSecondaryColor(Color.fromFXColor(col))
×
44
    }
45
  }
46

47
  private val gridSearcher: FloodFillSearcher = new FloodFillSearcher(
×
UNCOV
48
    new ImageGridColorLookup(imageGrid)
×
49
  )
50

51
  private val canvas = new ImageGridCanvas(imageGrid)
×
52
  canvas.setScale(zoom)
×
UNCOV
53
  canvas.setDisplacement(xScroll, yScroll)
×
54

UNCOV
55
  children.add(canvas)
×
56

NEW
57
  imageGrid.trackChanges(this.onImageGridEvent(_))
×
58

UNCOV
59
  private val cumulativeImageChange = mutable.Map.empty[GridCoords, ImageChange.Builder]
×
60

61
  private def stopDrawing(cell: GridCell): ImageChange = {
×
UNCOV
62
    cumulativeImageChange(cell.coords).done(cell.storage)
×
63
  }
64

65
  private def drawAt(cell: GridCell, coords: TriangleCoords, color: Color): Unit = {
×
66
    if cell.storage.contains(coords) then {
×
67
      cumulativeImageChange(cell.coords).addChange(coords, cell.storage.getColor(coords), color)
×
UNCOV
68
      cell.storage.setColor(coords, color)
×
UNCOV
69
    }
×
70
  }
71

72
  private def imageAt(x: Double, y: Double): Option[(GridCell, PixelCoords)] = {
×
73
    val pt = canvas.sceneToLocal(x, y)
×
74
    val coords = PixelCoords(canvas.coordsAt(pt.x, pt.y), imageGrid.imageSize)
×
UNCOV
75
    imageGrid.images.find(_.coords == coords.image).map((_, coords))
×
76
  }
77

78
  onMouseDragged = e => {
×
79
    if !e.isConsumed then {
×
80
      val xDiff = e.getX - drag.x
×
UNCOV
81
      val yDiff = e.getY - drag.y
×
82

83
      drag.x = e.getX
×
UNCOV
84
      drag.y = e.getY
×
85

86
      EditMode.currentMode match {
×
87
        case EditMode.Organize => // TODO: implement scale and rotation if (x, y) is close enough to a corner
×
88
          setScroll(xScroll + xDiff, yScroll + yDiff)
×
89
        case _ =>
UNCOV
90
          val dist = Math.hypot(xDiff, yDiff) / zoom
×
91

92
          val steps = 4 * dist.toInt + 1
93
          for i <- 1 to steps do {
×
94
            val xx = e.getSceneX + xDiff / steps * (i - steps)
×
UNCOV
95
            val yy = e.getSceneY + yDiff / steps * (i - steps)
×
96

97
            for {
×
98
              (image, coords) <- imageAt(xx, yy)
×
99
              if image.editable
×
UNCOV
100
            } do mousePressedAt(coords, e.getButton, dragged = true)
×
101
          }
102
      }
UNCOV
103
    }
×
104
  }
105

106
  onMousePressed = e => {
×
107
    if !e.isConsumed then {
×
108
      drag.x = e.getX
×
UNCOV
109
      drag.y = e.getY
×
110

111
      for {
×
112
        (image, coords) <- imageAt(e.getSceneX, e.getSceneY)
×
UNCOV
113
        if image.editable
×
114
      } do {
×
UNCOV
115
        mousePressedAt(coords, e.getButton, dragged = false)
×
116
      }
UNCOV
117
    }
×
118
  }
119

120
  onMouseReleased = e => {
×
121
    if !e.isConsumed
×
122
    then {
×
123
      val changes = mutable.Map.empty[GridCoords, ImageChange]
×
UNCOV
124
      for im <- imageGrid.images.reverse
×
125
      do {
126
        val change = stopDrawing(im)
×
127
        change.undo()
×
UNCOV
128
        changes(im.coords) = change
×
129
      }
130

UNCOV
131
      imageGrid.performChange(new ImageGridChange(changes.toMap))
×
UNCOV
132
    }
×
133
  }
134

135
  onScroll = e => {
×
UNCOV
136
    val (dx, dy) = (e.getDeltaX, e.getDeltaY)
×
137

138
    if e.isControlDown then {
×
UNCOV
139
      val factor = Math.min(Math.max(Math.exp(e.getDeltaY * 0.01), 1.0 / 32 / zoom), 32 / zoom)
×
140
      zoom *= factor
141
      canvas.setScale(zoom)
×
142
      setScroll(xScroll * factor, yScroll * factor)
×
143
    } else {
×
UNCOV
144
      setScroll(xScroll + dx, yScroll + dy)
×
145
    }
146

UNCOV
147
    canvas.redraw()
×
148
  }
149

UNCOV
150
  private def setScroll(sx: Double, sy: Double): Unit = {
×
151
    xScroll = sx
152
    yScroll = sy
UNCOV
153
    canvas.setDisplacement(xScroll, yScroll)
×
154
  }
155

UNCOV
156
  private def mousePressedAt(
×
157
      coords: PixelCoords,
158
      button: MouseButton,
159
      dragged: Boolean
160
  ): Unit = {
161
    val colorToUse = button match {
162
      case MouseButton.PRIMARY   => Some(colors.primaryColor)
×
163
      case MouseButton.SECONDARY => Some(colors.secondaryColor)
×
UNCOV
164
      case _                     => None
×
165
    }
166

167
    for {
×
168
      image <- imageGrid(coords.image)
×
UNCOV
169
      color <- colorToUse
×
170
    } do {
171
      EditMode.currentMode match {
×
172
        case EditMode.Draw =>
×
173
          drawAt(image, coords.pix, color())
×
174
        case EditMode.Fill =>
×
175
          fill(coords, color())
×
176
        case EditMode.PickColor =>
×
177
          color() = image.storage.getColor(coords.pix)
×
UNCOV
178
        case _ =>
×
179
      }
180
    }
181
  }
182

UNCOV
183
  private inline def clamp(a: Int, lo: Int, hi: Int): Int = Math.min(Math.max(a, lo), hi)
×
184

185
  private def updateCanvasAt(x: Double, y: Double): Unit = {
×
UNCOV
186
    val (w, h) = (canvas.width().toInt, canvas.height().toInt)
×
187

188
    val startX = clamp((x - 3 * zoom).toInt, 0, w - 1)
189
    val startY = clamp((y - 3 * zoom).toInt, 0, h - 1)
190
    val endX = clamp((x + 3 * zoom + 1).toInt, 0, w - 1)
191
    val endY = clamp((y + 3 * zoom + 1).toInt, 0, h - 1)
192

193
    if endX >= startX && endY >= startY then {
×
UNCOV
194
      canvas.redraw(startX, startY, endX - startX + 1, endY - startY + 1)
×
UNCOV
195
    }
×
196
  }
197

198
  private def fill(coords: PixelCoords, color: Color): Unit = {
×
199
    for image <- imageGrid(coords.image) do {
×
UNCOV
200
      val referenceColor = image.storage.getColor(coords.pix)
×
201
      val places = gridSearcher
202
        .search(coords.toGlobal(imageGrid.imageSize), (_, col) => col == referenceColor)
×
UNCOV
203
        .map(p => PixelCoords(p, imageGrid.imageSize))
×
204

UNCOV
205
      for {
×
206
        p <- places
UNCOV
207
        im <- imageGrid(p.image)
×
208
      } do {
UNCOV
209
        drawAt(im, p.pix, color)
×
210
      }
211
    }
212

UNCOV
213
    canvas.redraw()
×
214
  }
215

216
  this.width.onChange(updateSize())
×
UNCOV
217
  this.height.onChange(updateSize())
×
218

219
  private def updateSize(): Unit = {
×
220
    this.setClip(new Rectangle(0, 0, width(), height()))
×
221
    canvas.width = width()
×
222
    canvas.height = height()
×
UNCOV
223
    canvas.redraw()
×
224
  }
225

UNCOV
226
  private def onImageGridEvent(event: ImageGrid.Event): Unit = {
×
227
    event match {
228
      case ImageGrid.Event.ImageAdded(image) =>
229
        cumulativeImageChange(image.coords) = new ImageChange.Builder
×
230
        canvas.redraw()
×
231
      case ImageGrid.Event.ImageRemoved(image) =>
232
        cumulativeImageChange -= image.coords
×
233
        canvas.redraw()
×
234
      case ImageGrid.Event.PixelChanged(coords, from, to) =>
235
        val (x, y) = canvas.locationOf(coords.toGlobal(imageGrid.imageSize))
×
236
        this.updateCanvasAt(x, y)
×
UNCOV
237
      case ImageGrid.Event.ImageChangedALot(coords) =>
×
238
        // TODO: calculate bounds of the image and only redraw that part of the canvas
UNCOV
239
        canvas.redraw()
×
240
    }
241
  }
242
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc