From 432a0d309d4d57e634e1844a1353269374ed0db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Costa?= Date: Fri, 19 Apr 2024 22:16:51 +0100 Subject: [PATCH] Use official munit release --- build.sbt | 4 +- .../eu/joaocosta/interim/TextLayout.scala | 34 +++++++++ .../interim/api/LayoutAllocator.scala | 70 +++++++++++++++++++ .../eu/joaocosta/interim/api/Layouts.scala | 52 ++------------ .../eu/joaocosta/interim/api/Panels.scala | 4 +- .../joaocosta/interim/skins/ButtonSkin.scala | 6 +- .../interim/skins/CheckboxSkin.scala | 4 +- .../joaocosta/interim/skins/HandleSkin.scala | 4 +- .../joaocosta/interim/skins/SliderSkin.scala | 4 +- .../interim/skins/TextInputSkin.scala | 4 +- .../joaocosta/interim/skins/WindowSkin.scala | 8 +-- 11 files changed, 128 insertions(+), 66 deletions(-) create mode 100644 core/shared/src/main/scala/eu/joaocosta/interim/api/LayoutAllocator.scala diff --git a/build.sbt b/build.sbt index e67a2f4..64ad1c7 100644 --- a/build.sbt +++ b/build.sbt @@ -44,9 +44,7 @@ lazy val core = .in(file("core")) .settings( name := "interim", - resolvers += "jitpack" at "https://www.jitpack.io", // Bootleg Munit - libraryDependencies += "com.github.JD557.munit" %%% "munit" % "297481cecd" % Test, - //libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M11" % Test, + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M12" % Test, Compile / doc / scalacOptions ++= Seq( "-project", diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/TextLayout.scala b/core/shared/src/main/scala/eu/joaocosta/interim/TextLayout.scala index 64a6c18..ef912bd 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/TextLayout.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/TextLayout.scala @@ -90,3 +90,37 @@ object TextLayout: else layout(nextLine, dy + lineHeight, alignH(ops, textOp.textArea.w, textOp.horizontalAlignment) ++ textAcc) layout(textOp.text, 0, Nil).filter(char => (char.area & textOp.area) == char.area) + + private[interim] def computeArea( + boundingArea: Rect, + text: String, + font: Font, + lineHeight: Int + ): Rect = + @tailrec + def layout( + remaining: String, + dy: Int, + areaAcc: Rect + ): Rect = + remaining match + case "" => areaAcc + case str => + if (dy + font.fontSize > boundingArea.h) layout("", dy, areaAcc) // Can't fit this line, end here + else + val (thisLine, nextLine) = getNextLine(str, boundingArea.w, font.charWidth) + val charAreas = cumulativeSum(thisLine)(font.charWidth).map { case (char, dx) => + val width = font.charWidth(char) + val charArea = Rect( + x = boundingArea.x + dx - width, + y = boundingArea.y + dy, + w = width, + h = font.fontSize + ) + charArea + }.toList + if (charAreas.isEmpty && nextLine == remaining) + layout("", dy, areaAcc) // Can't fit a single character, end here + else + layout(nextLine, dy + lineHeight, charAreas.fold(areaAcc)(_ ++ _)) + layout(text, 0, boundingArea.copy(w = 0, h = 0)) & boundingArea diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/api/LayoutAllocator.scala b/core/shared/src/main/scala/eu/joaocosta/interim/api/LayoutAllocator.scala new file mode 100644 index 0000000..cb6ef30 --- /dev/null +++ b/core/shared/src/main/scala/eu/joaocosta/interim/api/LayoutAllocator.scala @@ -0,0 +1,70 @@ +package eu.joaocosta.interim.api + +import eu.joaocosta.interim.{Font, Rect, TextLayout} + +/** A layout allocator is a side-effectful function that, given a size (width or height) tries to allocate + * a new area. + * + * Note that calls to this function are side effectful, as each call reserves an area. + */ +trait LayoutAllocator extends (Int => Rect): + def allocate(size: Int): Rect + def apply(size: Int): Rect = allocate(size) + def allocate(text: String, font: Font): Rect + +object LayoutAllocator: + final class RowAllocator(area: Rect, padding: Int) extends LayoutAllocator: + private var currentY = area.y + private var currentH = area.h + + def allocate(height: Int) = + val absHeight = math.abs(height).toInt + if (absHeight == 0 || currentH <= 0) // Empty + area.copy(y = currentY, h = 0) + else if (absHeight >= currentH) // Fill remaining area + val areaY = currentY + val areaH = currentH + currentY = area.h + currentH = 0 + area.copy(y = areaY, h = areaH) + else if (height >= 0) // Fill from the top + val areaY = currentY + currentY += absHeight + padding + currentH -= absHeight + padding + area.copy(y = areaY, h = absHeight) + else // Fill from the bottom + val areaY = currentY + currentH - absHeight + currentH -= absHeight + padding + area.copy(y = areaY, h = absHeight) + + def allocate(text: String, font: Font): Rect = + val textArea = TextLayout.computeArea(area, text, font, (font.fontSize * 1.3).toInt) + allocate(textArea.h) + + final class ColumnAllocator(area: Rect, padding: Int) extends LayoutAllocator: + private var currentX = area.x + private var currentW = area.w + + def allocate(width: Int): Rect = + val absWidth = math.abs(width).toInt + if (absWidth == 0 || currentW <= 0) // Empty + area.copy(x = currentX, w = 0) + else if (absWidth >= currentW) // Fill remaining area + val areaX = currentX + val areaW = currentW + currentX = area.w + currentW = 0 + area.copy(x = areaX, w = areaW) + else if (width >= 0) // Fill from the left + val areaX = currentX + currentX += absWidth + padding + currentW -= absWidth + padding + area.copy(x = areaX, w = absWidth) + else // Fill from the right + val areaX = currentX + currentW - absWidth + currentW -= absWidth + padding + area.copy(x = areaX, w = absWidth) + + def allocate(text: String, font: Font): Rect = + val textArea = TextLayout.computeArea(area, text, font, (font.fontSize * 1.3).toInt) + allocate(textArea.w) diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/api/Layouts.scala b/core/shared/src/main/scala/eu/joaocosta/interim/api/Layouts.scala index daf4973..f2048ef 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/api/Layouts.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/api/Layouts.scala @@ -69,58 +69,18 @@ trait Layouts: * The body receives a `nextRow: Int => Rect`, where `nextRow(height)` is the rect of the next row, with the * specified height (if possible). If the size is negative, the row will start from the bottom. */ - final def dynamicRows[T](area: Rect, padding: Int)(body: (Int => Rect) => T): T = - var currentY = area.y - var currentH = area.h - def generateRect(height: Int): Rect = - val absHeight = math.abs(height).toInt - if (absHeight == 0 || currentH <= 0) // Empty - area.copy(y = currentY, h = 0) - else if (absHeight >= currentH) // Fill remaining area - val areaY = currentY - val areaH = currentH - currentY = area.h - currentH = 0 - area.copy(y = areaY, h = areaH) - else if (height >= 0) // Fill from the top - val areaY = currentY - currentY += absHeight + padding - currentH -= absHeight + padding - area.copy(y = areaY, h = absHeight) - else // Fill from the bottom - val areaY = currentY + currentH - absHeight - currentH -= absHeight + padding - area.copy(y = areaY, h = absHeight) - body(generateRect) + final def dynamicRows[T](area: Rect, padding: Int)(body: LayoutAllocator => T): T = + val allocator = new LayoutAllocator.RowAllocator(area, padding) + body(allocator) /** Lays out the components in a sequence of columns of different sizes, separated by a padding. * * The body receives a `nextColumn: Int => Rect`, where `nextColumn(width)` is the rect of the next column, with the * specified width (if possible). . If the size is negative, the row will start from the right. */ - final def dynamicColumns[T](area: Rect, padding: Int)(body: (Int => Rect) => T): T = - var currentX = area.x - var currentW = area.w - def generateRect(width: Int): Rect = - val absWidth = math.abs(width).toInt - if (absWidth == 0 || currentW <= 0) // Empty - area.copy(x = currentX, w = 0) - else if (absWidth >= currentW) // Fill remaining area - val areaX = currentX - val areaW = currentW - currentX = area.w - currentW = 0 - area.copy(x = areaX, w = areaW) - else if (width >= 0) // Fill from the left - val areaX = currentX - currentX += absWidth + padding - currentW -= absWidth + padding - area.copy(x = areaX, w = absWidth) - else // Fill from the right - val areaX = currentX + currentW - absWidth - currentW -= absWidth + padding - area.copy(x = areaX, w = absWidth) - body(generateRect) + final def dynamicColumns[T](area: Rect, padding: Int)(body: LayoutAllocator => T): T = + val allocator = new LayoutAllocator.ColumnAllocator(area, padding) + body(allocator) /** Handle mouse events inside a specified area. * diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/api/Panels.scala b/core/shared/src/main/scala/eu/joaocosta/interim/api/Panels.scala index 211feb8..9e50b5f 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/api/Panels.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/api/Panels.scala @@ -1,7 +1,7 @@ package eu.joaocosta.interim.api -import eu.joaocosta.interim.* -import eu.joaocosta.interim.skins.* +import eu.joaocosta.interim._ +import eu.joaocosta.interim.skins._ /** Objects containing all default panels. * diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/skins/ButtonSkin.scala b/core/shared/src/main/scala/eu/joaocosta/interim/skins/ButtonSkin.scala index 371a083..889d5fb 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/skins/ButtonSkin.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/skins/ButtonSkin.scala @@ -1,8 +1,8 @@ package eu.joaocosta.interim.skins -import eu.joaocosta.interim.TextLayout.* -import eu.joaocosta.interim.* -import eu.joaocosta.interim.api.Primitives.* +import eu.joaocosta.interim.TextLayout._ +import eu.joaocosta.interim._ +import eu.joaocosta.interim.api.Primitives._ trait ButtonSkin: def buttonArea(area: Rect): Rect diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/skins/CheckboxSkin.scala b/core/shared/src/main/scala/eu/joaocosta/interim/skins/CheckboxSkin.scala index f882a6d..5b56c30 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/skins/CheckboxSkin.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/skins/CheckboxSkin.scala @@ -1,7 +1,7 @@ package eu.joaocosta.interim.skins -import eu.joaocosta.interim.* -import eu.joaocosta.interim.api.Primitives.* +import eu.joaocosta.interim._ +import eu.joaocosta.interim.api.Primitives._ trait CheckboxSkin: def checkboxArea(area: Rect): Rect diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/skins/HandleSkin.scala b/core/shared/src/main/scala/eu/joaocosta/interim/skins/HandleSkin.scala index 052cca0..81c57fb 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/skins/HandleSkin.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/skins/HandleSkin.scala @@ -1,7 +1,7 @@ package eu.joaocosta.interim.skins -import eu.joaocosta.interim.* -import eu.joaocosta.interim.api.Primitives.* +import eu.joaocosta.interim._ +import eu.joaocosta.interim.api.Primitives._ trait HandleSkin: def moveHandleArea(area: Rect): Rect diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/skins/SliderSkin.scala b/core/shared/src/main/scala/eu/joaocosta/interim/skins/SliderSkin.scala index 3c23ab8..3ae3fb5 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/skins/SliderSkin.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/skins/SliderSkin.scala @@ -1,7 +1,7 @@ package eu.joaocosta.interim.skins -import eu.joaocosta.interim.* -import eu.joaocosta.interim.api.Primitives.* +import eu.joaocosta.interim._ +import eu.joaocosta.interim.api.Primitives._ trait SliderSkin: def sliderArea(area: Rect): Rect diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/skins/TextInputSkin.scala b/core/shared/src/main/scala/eu/joaocosta/interim/skins/TextInputSkin.scala index ccaa80a..f49e06d 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/skins/TextInputSkin.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/skins/TextInputSkin.scala @@ -1,7 +1,7 @@ package eu.joaocosta.interim.skins -import eu.joaocosta.interim.* -import eu.joaocosta.interim.api.Primitives.* +import eu.joaocosta.interim._ +import eu.joaocosta.interim.api.Primitives._ trait TextInputSkin: def textInputArea(area: Rect): Rect diff --git a/core/shared/src/main/scala/eu/joaocosta/interim/skins/WindowSkin.scala b/core/shared/src/main/scala/eu/joaocosta/interim/skins/WindowSkin.scala index 172bc2e..28cca55 100644 --- a/core/shared/src/main/scala/eu/joaocosta/interim/skins/WindowSkin.scala +++ b/core/shared/src/main/scala/eu/joaocosta/interim/skins/WindowSkin.scala @@ -1,9 +1,9 @@ package eu.joaocosta.interim.skins -import eu.joaocosta.interim.TextLayout.* -import eu.joaocosta.interim.* -import eu.joaocosta.interim.api.Components.* -import eu.joaocosta.interim.api.Primitives.* +import eu.joaocosta.interim.TextLayout._ +import eu.joaocosta.interim._ +import eu.joaocosta.interim.api.Components._ +import eu.joaocosta.interim.api.Primitives._ trait WindowSkin: def titleArea(area: Rect): Rect