Skip to content

Commit

Permalink
#226: creates a mouseover effect in the box view
Browse files Browse the repository at this point in the history
  • Loading branch information
rladstaetter committed May 9, 2024
1 parent 0e9d28c commit a8df687
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import app.logorrr.TestFiles
import app.logorrr.conf.LogoRRRGlobals
import app.logorrr.io.FileId
import app.logorrr.usecases.SingleFileApplicationTest
import app.logorrr.views.block.BlockConstants
import app.logorrr.views.ops.{DecreaseBlockSizeButton, IncreaseBlockSizeButton}
import app.logorrr.views.search.OpsToolBar
import org.junit.jupiter.api.Test

/**
Expand All @@ -21,7 +21,7 @@ class SimpleBlockSizeTest extends SingleFileApplicationTest(TestFiles.simpleLog0
val count = 10

for (_ <- 1 to count) increaseBlockSize(fileId)
assert(size + (OpsToolBar.blockSizeStep * count) == LogoRRRGlobals.getLogFileSettings(fileId).getBlockSize)
assert(size + (BlockConstants.BlockSizeStep * count) == LogoRRRGlobals.getLogFileSettings(fileId).getBlockSize)

// decrease again
for (_ <- 1 to 10) decreaseBlockSize(fileId)
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/scala/app/logorrr/views/block/BlockConstants.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package app.logorrr.views.block

object BlockConstants {

/**
* minimum block size
*/
val MinBlockSize = 1

/** increment/decrement block size */
val BlockSizeStep: Int = 2

/** max reachable block size */
val MaxBlockSize: Int = 70 * BlockSizeStep


}
51 changes: 20 additions & 31 deletions app/src/main/scala/app/logorrr/views/block/BlockImage.scala
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
package app.logorrr.views.block

import app.logorrr.model.LogEntry
import app.logorrr.util.{CanLog, MathUtil}
import app.logorrr.util.MathUtil
import app.logorrr.views.search.Filter
import javafx.beans.property.{ReadOnlyDoubleProperty, SimpleIntegerProperty}
import javafx.collections.ObservableList
import javafx.scene.image.WritableImage

object BlockConstants {

/**
* minimum block size
*/
val MinBlockSize = 1

/** increment/decrement block size */
val BlockSizeStep: Int = 2

/** max reachable block size */
val MaxBlockSize: Int = 70 * BlockSizeStep


}

object BlockImage {

Expand Down Expand Up @@ -69,23 +55,26 @@ object BlockImage {
}
}

def apply(blockNumber: Int
, entries: java.util.List[LogEntry]
, selectedLineNumberProperty: SimpleIntegerProperty
, filtersProperty: ObservableList[Filter]
, blockSizeProperty: SimpleIntegerProperty
, widthProperty: ReadOnlyDoubleProperty
, heightProperty: SimpleIntegerProperty
): BlockImage = {
val pixelBuffer = LPixelBuffer(blockNumber
, Range(entries.get(0).lineNumber, entries.get(entries.size - 1).lineNumber)
, RectangularShape(if (widthProperty.get().toInt - BlockImage.ScrollBarWidth > 0) widthProperty.get().toInt - BlockImage.ScrollBarWidth else widthProperty.get().toInt, heightProperty.get())
, blockSizeProperty
, entries
, filtersProperty
, Array.fill(widthProperty.get().toInt * heightProperty.get())(LPixelBuffer.defaultBackgroundColor)
, selectedLineNumberProperty)
new BlockImage(pixelBuffer)
}

}


class BlockImage(blockNumber: Int
, entries: java.util.List[LogEntry]
, selectedLineNumberProperty: SimpleIntegerProperty
, filtersProperty: ObservableList[Filter]
, blockSizeProperty: SimpleIntegerProperty
, widthProperty: ReadOnlyDoubleProperty
, heightProperty: SimpleIntegerProperty
)
extends WritableImage(LPixelBuffer(blockNumber
, Range(entries.get(0).lineNumber, entries.get(entries.size - 1).lineNumber)
, RectangularShape(if (widthProperty.get().toInt - BlockImage.ScrollBarWidth > 0) widthProperty.get().toInt - BlockImage.ScrollBarWidth else widthProperty.get().toInt, heightProperty.get())
, blockSizeProperty
, entries
, filtersProperty
, Array.fill(widthProperty.get().toInt * heightProperty.get())(LPixelBuffer.defaultBackgroundColor)
, selectedLineNumberProperty)) with CanLog
class BlockImage(val pixelBuffer: LPixelBuffer) extends WritableImage(pixelBuffer)
61 changes: 38 additions & 23 deletions app/src/main/scala/app/logorrr/views/block/ChunkListCell.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package app.logorrr.views.block
import app.logorrr.model.LogEntry
import app.logorrr.util.{CanLog, JfxUtils}
import app.logorrr.views.search.Filter
import javafx.animation.{KeyFrame, Timeline}
import javafx.beans.property.{ReadOnlyDoubleProperty, SimpleIntegerProperty}
import javafx.collections.ObservableList
import javafx.event.EventHandler
import javafx.event.{ActionEvent, EventHandler}
import javafx.scene.control.ListCell
import javafx.scene.image.ImageView
import javafx.scene.input.{MouseButton, MouseEvent}
import javafx.util.Duration

import scala.util.Try

Expand All @@ -25,7 +27,7 @@ class ChunkListCell(selectedLineNumberProperty: SimpleIntegerProperty

// if user selects an entry in the ChunkListView set selectedLineNumberProperty. This property is observed
// via an listener and a yellow square will be painted.
private val mouseEventHandler = new EventHandler[MouseEvent]() {
private val mouseClickedHandler = new EventHandler[MouseEvent]() {
override def handle(me: MouseEvent): Unit = {
// on left mouse button
if (me.getButton.equals(MouseButton.PRIMARY)) {
Expand All @@ -35,39 +37,52 @@ class ChunkListCell(selectedLineNumberProperty: SimpleIntegerProperty
selectedLineNumberProperty.set(value.lineNumber)
// we have to select also the entry in the LogTextView, couldn't get it to work with bindings / listeners
scrollTo(value)
case None => logTrace("no element found")
case None =>
}
}
}
}
private val mouseMovedHandler: EventHandler[MouseEvent] = (me: MouseEvent) => {
val index = BlockImage.indexOf(me.getX.toInt, me.getY.toInt, blockSizeProperty.get, widthProperty.get.toInt - BlockImage.ScrollBarWidth)
getEntryAt(getItem, index) match {
case Some(logEntry) =>
Option(getGraphic).map(_.asInstanceOf[ImageView].getImage.asInstanceOf[BlockImage].pixelBuffer) match {
case Some(pb) =>
val col = Filter.calcColor(logEntry.value, pb.filters)
pb.paintBlockAtIndexWithColor(index, logEntry.lineNumber, col.darker())
// schedule repaint with original color again some time in the future
val task: Runnable = () => pb.paintBlockAtIndexWithColor(index, logEntry.lineNumber, col)

setOnMouseClicked(mouseEventHandler)
// Create a Timeline that fires once after 500 milliseconds
val timeline = new Timeline(new KeyFrame(Duration.millis(500), (_: ActionEvent) => task.run()))
timeline.setCycleCount(1) // Ensure it runs only once
timeline.play()
case None =>
}
case None =>
}

}

setOnMouseMoved(mouseMovedHandler)
setOnMouseClicked(mouseClickedHandler)


override def updateItem(t: Chunk, empty: Boolean): Unit = JfxUtils.execOnUiThread {
super.updateItem(t, empty)

if (empty || Option(t).isEmpty) {
if (empty || Option(t).isEmpty || blockSizeProperty.get() <= 0 || widthProperty.get() <= 0) {
setGraphic(null)
} else {
if (widthProperty.get() > 0) {
if (blockSizeProperty.get() > 0) {
val bv = new BlockImage(t.number
, entries = t.entries
, selectedLineNumberProperty
, filtersProperty
, blockSizeProperty
, widthProperty
, heightProperty = new SimpleIntegerProperty(t.height))
setGraphic(new ImageView(bv))
} else {
logTrace(s"Blocksize was ${blockSizeProperty.get()}, setting graphic to null")
setGraphic(null)
}
} else {
logTrace(s"Width was ${widthProperty.get()}, setting graphic to null")
setGraphic(null)
}
val bv = BlockImage(t.number
, entries = t.entries
, selectedLineNumberProperty
, filtersProperty
, blockSizeProperty
, widthProperty
, heightProperty = new SimpleIntegerProperty(t.height))
val view = new ImageView(bv)
setGraphic(view)
}
}

Expand Down
17 changes: 8 additions & 9 deletions app/src/main/scala/app/logorrr/views/block/ChunkListView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,21 @@ class ChunkListView(val logEntries: ObservableList[LogEntry]
private val logEntriesInvalidationListener = JfxUtils.mkInvalidationListener(_ => recalculateAndUpdateItems("invalidation"))

/** if user selects a new active log entry, recalculate and implicitly repaint */
val selectedRp = mkRecalculateAndUpdateItemListener("selected")
private val selectedRp = mkRecalculateAndUpdateItemListener("selected")

/** if blocksize changes, recalculate */
val blockSizeRp = mkRecalculateAndUpdateItemListener("blockSize")
private val blockSizeRp = mkRecalculateAndUpdateItemListener("blockSize")

/** if width changes, recalculate */
val widthRp = mkRecalculateAndUpdateItemListener("width")
private val widthRp = mkRecalculateAndUpdateItemListener("width")

/** if height changes, recalculate */
val heightRp = mkRecalculateAndUpdateItemListener("height")
private val heightRp = mkRecalculateAndUpdateItemListener("height")

def mkRecalculateAndUpdateItemListener(ctx: String): ChangeListener[Number] = new ChangeListener[Number] {
override def changed(observable: ObservableValue[_ <: Number], oldValue: Number, newValue: Number): Unit = {
if (oldValue != newValue && newValue.doubleValue() != 0.0) {
recalculateAndUpdateItems(ctx)
}
// context variable just here for debugging
private def mkRecalculateAndUpdateItemListener(ctx: String): ChangeListener[Number] = (_: ObservableValue[_ <: Number], oldValue: Number, newValue: Number) => {
if (oldValue != newValue && newValue.doubleValue() != 0.0) {
recalculateAndUpdateItems(ctx)
}
}

Expand Down
67 changes: 42 additions & 25 deletions app/src/main/scala/app/logorrr/views/block/LPixelBuffer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ object LPixelBuffer extends CanLog {

val defaultBackgroundColor: Int = ColorUtil.toARGB(Color.WHITE)

def calcColors(color: Color): (Int, Int, Int) = {
(ColorUtil.toARGB(color), ColorUtil.toARGB(color.brighter()), ColorUtil.toARGB(color.darker()))
}

def drawRect(rawInts: Array[Int]
, i: Int
, width: Int
Expand Down Expand Up @@ -61,10 +65,10 @@ object LPixelBuffer extends CanLog {
// Update rawInts directly without array copying
for (ly <- y until maxHeight - 1) {
val startPos = ly * canvasWidth + x
// val endPos = startPos + length
// val endPos = startPos + length
// if (startPos >= 0 && endPos < rawInts.length) {
// Fill the portion of rawInts with lineArray
for (i <- 0 to squarewidth) rawInts(startPos + i) = col
// Fill the portion of rawInts with lineArray
for (i <- 0 to squarewidth) rawInts(startPos + i) = col
//}
}

Expand Down Expand Up @@ -106,13 +110,14 @@ case class LPixelBuffer(blockNumber: Int
private val name = s"${range.start}_${range.end}"

lazy val background: Array[Int] = Array.fill(shape.area)(LPixelBuffer.defaultBackgroundColor)

/* hardcoded highlight color */
private val highlightedColor = Color.YELLOW
private lazy val yellow = ColorUtil.toARGB(highlightedColor)
private lazy val yellowBright = ColorUtil.toARGB(highlightedColor.brighter())
private lazy val yellowDark = ColorUtil.toARGB(highlightedColor.darker)
private lazy val (yellow, yellowBright, yellowDark) = LPixelBuffer.calcColors(highlightedColor)

init()


def init(): Unit = {
assert(shape.width != 0, s"For $name, width was ${shape.width}.")
assert(shape.height != 0, s"For $name, height was ${shape.height}.")
Expand All @@ -122,15 +127,16 @@ case class LPixelBuffer(blockNumber: Int

private def cleanBackground(): Unit = System.arraycopy(background, 0, rawInts, 0, background.length)

def blockSize: Int = blockSizeProperty.get()
def getBlockSize: Int = blockSizeProperty.get()

def filters: Seq[Filter] = Option(filtersProperty).map(_.asScala.toSeq).getOrElse(Seq())

/** function is performance relevant */
private def paint(): Unit = {
if (blockSize != 0 && shape.width > blockSize) {
if (blockSize == 1) {
if (getBlockSize != 0 && shape.width > getBlockSize) {
if (getBlockSize == 1) {
paintPixels()
} else if (blockSize > 1) {
} else if (getBlockSize > 1) {
paintRects()
}
}
Expand All @@ -156,29 +162,40 @@ case class LPixelBuffer(blockNumber: Int
})
}

def paintBlockAtIndexWithColor(i: Int, lineNumber: Int, color: Color): Unit = {
updateBuffer((_: PixelBuffer[IntBuffer]) => {
paintBlock(i, lineNumber, color)
shape
})
}

private def paintRects(): Unit = {
updateBuffer((_: PixelBuffer[IntBuffer]) => {
cleanBackground()
var i = 0
entries.forEach(e => {
if (e.lineNumber == selectedLineNumberProperty.getValue) {
LPixelBuffer.drawRect(rawInts, i, shape.width, blockSize, yellow, yellowBright, yellowDark)
} else {
val color = Filter.calcColor(e.value, filters)
val colorDark = color.darker()
val colorBright = color.brighter()
LPixelBuffer.drawRect(rawInts
, i
, shape.width
, blockSize
, ColorUtil.toARGB(color)
, ColorUtil.toARGB(colorDark)
, ColorUtil.toARGB(colorBright)
)
}
val color = Filter.calcColor(e.value, filters)
paintBlock(i, e.lineNumber, color)
i = i + 1
})
shape
})
}

private def paintBlock(i: Int, lineNumber: Int, color: Color): Unit = {
if (lineNumber == selectedLineNumberProperty.getValue) {
LPixelBuffer.drawRect(rawInts, i, shape.width, getBlockSize, yellow, yellowBright, yellowDark)
} else {
val colorDark = color.darker()
val colorBright = color.brighter()
LPixelBuffer.drawRect(rawInts
, i
, shape.width
, getBlockSize
, ColorUtil.toARGB(color)
, ColorUtil.toARGB(colorDark)
, ColorUtil.toARGB(colorBright)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,19 @@ class LogFileTabContent(mutLogFileSettings: MutLogFileSettings
private val chunkListView = ChunkListView(filteredList, mutLogFileSettings, logTextView.scrollToItem)


val blockSizeSlider = new BlockSizeSlider
blockSizeSlider.valueProperty().bindBidirectional(mutLogFileSettings.blockSizeProperty)
val vbox = {
val vBox = new BorderPane(chunkListView, blockSizeSlider, null, null, null)
private val blockSizeSlider = {
val bs = new BlockSizeSlider
bs.valueProperty().bindBidirectional(mutLogFileSettings.blockSizeProperty)
bs
}


private val blockPane = {
val bBp = new BorderPane(chunkListView, blockSizeSlider, null, null, null)
// vBox.setStyle("-fx-background-color: #b6ff7a;")
VBox.setVgrow(vBox, javafx.scene.layout.Priority.ALWAYS)
vBox.setMaxHeight(java.lang.Double.MAX_VALUE)
vBox
VBox.setVgrow(bBp, javafx.scene.layout.Priority.ALWAYS)
bBp.setMaxHeight(java.lang.Double.MAX_VALUE)
bBp
}

// start listener declarations
Expand All @@ -63,7 +68,7 @@ class LogFileTabContent(mutLogFileSettings: MutLogFileSettings
private val opsRegion: OpsRegion = new OpsRegion(opsToolBar, filtersToolBar)

private val pane = {
val s = new SplitPane(vbox, logTextView)
val s = new SplitPane(blockPane, logTextView)
//s.setStyle("-fx-background-color: #ffa07a;")
s
}
Expand Down

0 comments on commit a8df687

Please sign in to comment.