Skip to content

Commit

Permalink
Add LayoutAllocator
Browse files Browse the repository at this point in the history
  • Loading branch information
JD557 committed Apr 23, 2024
1 parent 4b567d3 commit 7dd6fa4
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 63 deletions.
34 changes: 34 additions & 0 deletions core/shared/src/main/scala/eu/joaocosta/interim/TextLayout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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)
52 changes: 6 additions & 46 deletions core/shared/src/main/scala/eu/joaocosta/interim/api/Layouts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 7dd6fa4

Please sign in to comment.