Skip to content

Commit

Permalink
Split allocators into area and cell allocators
Browse files Browse the repository at this point in the history
  • Loading branch information
JD557 committed May 4, 2024
1 parent 5ac170c commit 46ae468
Show file tree
Hide file tree
Showing 13 changed files with 87 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
package eu.joaocosta.interim

import eu.joaocosta.interim.{Font, HorizontalAlignment, Rect, TextLayout, VerticalAlignment}
import eu.joaocosta.interim._

/** A layout allocator is a side-effectful function that, given a size (width or height) tries to allocate
* a new area.
/** A layout allocator is a side-effectful function that tries to allocate some space inside of an area.
*
* Note that calls to this function are side effectful, as each call reserves an area.
*/
trait LayoutAllocator:
sealed trait LayoutAllocator:
def area: Rect

def allocate(width: Int, height: Int): Rect
def allocate(text: String, font: Font, paddingW: Int = 0, paddingH: Int = 0): Rect =
val textArea =
TextLayout.computeArea(area.resize(-2 * paddingW, -2 * paddingH), text, font)
allocate(textArea.w + 2 * paddingW, textArea.h + 2 * paddingH)
object LayoutAllocator:
/** Allocator that allows one to allocate space based on a required area.
*/
trait AreaAllocator extends LayoutAllocator:
def allocate(width: Int, height: Int): Rect
def fill(): Rect = allocate(Int.MaxValue, Int.MaxValue)
def allocate(text: String, font: Font, paddingW: Int = 0, paddingH: Int = 0): Rect =
val textArea =
TextLayout.computeArea(area.resize(-2 * paddingW, -2 * paddingH), text, font)
allocate(textArea.w + 2 * paddingW, textArea.h + 2 * paddingH)

def fill(): Rect = allocate(Int.MaxValue, Int.MaxValue)
/** Allocator that allows one to request new cells.
*
* The preallocated cells can also be accessed as an `IndexedSeq`
*/
trait CellAllocator extends LayoutAllocator with IndexedSeq[Rect]:
lazy val cells: IndexedSeq[Rect]
protected lazy val cellsIterator = cells.iterator

def apply(i: Int): Rect = cells(i)
val length = cells.length

def nextCell(): Rect =
if (!cellsIterator.hasNext) area.copy(w = 0, h = 0)
else cellsIterator.next()

object LayoutAllocator:
trait RowAllocator extends LayoutAllocator:
def nextRow(height: Int): Rect
def allocate(width: Int, height: Int): Rect = nextRow(height)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ trait Components:
type Component[+T] = (inputState: InputState.Historical, uiContext: UiContext) ?=> T

trait ComponentWithValue[T]:
def allocateArea(using allocator: LayoutAllocator): Rect
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect

def render(area: Rect, value: Ref[T]): Component[Unit]

def render(value: Ref[T])(using allocator: LayoutAllocator): Component[Unit] =
def render(value: Ref[T])(using allocator: LayoutAllocator.AreaAllocator): Component[Unit] =
render(allocateArea, value)

def applyRef(area: Rect, value: Ref[T]): Component[T] =
Expand All @@ -32,23 +32,25 @@ trait Components:
case x: T => applyValue(area, x)
case x: Ref[T] => applyRef(area, x)

inline def apply(value: T | Ref[T])(using allocator: LayoutAllocator): Component[T] = apply(allocateArea, value)
inline def apply(value: T | Ref[T])(using allocator: LayoutAllocator.AreaAllocator): Component[T] =
apply(allocateArea, value)

trait ComponentWithBody[I, F[_]]:
def allocateArea(using allocator: LayoutAllocator): Rect
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect

def render[T](area: Rect, body: I => T): Component[F[T]]

def render[T](body: I => T)(using allocator: LayoutAllocator): Component[Unit] =
def render[T](body: I => T)(using allocator: LayoutAllocator.AreaAllocator): Component[Unit] =
render(allocateArea, body)

def apply[T](area: Rect)(body: I => T): Component[F[T]] = render(area, body)

def apply[T](area: Rect)(body: => T)(using ev: I =:= Unit): Component[F[T]] = render(area, _ => body)

def apply[T](body: I => T)(using allocator: LayoutAllocator): Component[F[T]] = render(allocateArea, body)
def apply[T](body: I => T)(using allocator: LayoutAllocator.AreaAllocator): Component[F[T]] =
render(allocateArea, body)

def apply[T](body: => T)(using allocator: LayoutAllocator, ev: I =:= Unit): Component[F[T]] =
def apply[T](body: => T)(using allocator: LayoutAllocator.AreaAllocator, ev: I =:= Unit): Component[F[T]] =
render(allocateArea, _ => body)

/** Button component. Returns true if it's being clicked, false otherwise.
Expand All @@ -61,7 +63,7 @@ trait Components:
skin: ButtonSkin = ButtonSkin.default()
): ComponentWithBody[Unit, Option] =
new ComponentWithBody[Unit, Option]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator, label)

def render[T](area: Rect, body: Unit => T): Component[Option[T]] =
Expand All @@ -77,7 +79,7 @@ trait Components:
skin: CheckboxSkin = CheckboxSkin.default()
): ComponentWithValue[Boolean] =
new ComponentWithValue[Boolean]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator)

def render(area: Rect, value: Ref[Boolean]): Component[Unit] =
Expand All @@ -99,7 +101,7 @@ trait Components:
skin: ButtonSkin = ButtonSkin.default()
): ComponentWithValue[T] =
new ComponentWithValue[T]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator, label)

def render(area: Rect, value: Ref[T]): Component[Unit] =
Expand All @@ -121,7 +123,7 @@ trait Components:
skin: SelectSkin = SelectSkin.default()
): ComponentWithValue[PanelState[Int]] =
new ComponentWithValue[PanelState[Int]]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator, labels)

def render(area: Rect, value: Ref[PanelState[Int]]): Component[Unit] =
Expand Down Expand Up @@ -152,7 +154,7 @@ trait Components:
skin: SliderSkin = SliderSkin.default()
): ComponentWithValue[Int] =
new ComponentWithValue[Int]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator)

def render(area: Rect, value: Ref[Int]): Component[Unit] =
Expand All @@ -175,7 +177,7 @@ trait Components:
skin: TextInputSkin = TextInputSkin.default()
): ComponentWithValue[String] =
new ComponentWithValue[String]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator)

def render(area: Rect, value: Ref[String]): Component[Unit] =
Expand All @@ -191,7 +193,7 @@ trait Components:
*/
final def moveHandle(id: ItemId, skin: HandleSkin = HandleSkin.default()): ComponentWithValue[Rect] =
new ComponentWithValue[Rect]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator)

def render(area: Rect, value: Ref[Rect]): Component[Unit] =
Expand All @@ -209,7 +211,7 @@ trait Components:
*/
final def resizeHandle(id: ItemId, skin: HandleSkin = HandleSkin.default()): ComponentWithValue[Rect] =
new ComponentWithValue[Rect]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator)

def render(area: Rect, value: Ref[Rect]): Component[Unit] =
Expand All @@ -230,7 +232,7 @@ trait Components:
skin: HandleSkin = HandleSkin.default()
): ComponentWithValue[PanelState[T]] =
new ComponentWithValue[PanelState[T]]:
def allocateArea(using allocator: LayoutAllocator): Rect =
def allocateArea(using allocator: LayoutAllocator.AreaAllocator): Rect =
skin.allocateArea(allocator)

def render(area: Rect, value: Ref[PanelState[T]]): Component[Unit] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,26 @@ trait Primitives:

/** Draws a rectangle filling the specified area with a color.
*/
final def rectangle(area: Rect, color: Color)(using uiContext: UiContext): Unit =
uiContext.pushRenderOp(RenderOp.DrawRect(area, color))
final def rectangle(area: Rect | LayoutAllocator.CellAllocator, color: Color)(using uiContext: UiContext): Unit =
val reservedArea = area match {
case rect: Rect => rect
case alloc: LayoutAllocator.CellAllocator => alloc.nextCell()
}
uiContext.pushRenderOp(RenderOp.DrawRect(reservedArea, color))

/** Draws the outline a rectangle inside the specified area with a color.
*/
final def rectangleOutline(area: Rect, color: Color, strokeSize: Int)(using uiContext: UiContext): Unit =
val top = area.copy(h = strokeSize)
val bottom = top.move(dx = 0, dy = area.h - strokeSize)
val left = area.copy(w = strokeSize)
val right = left.move(dx = area.w - strokeSize, dy = 0)
final def rectangleOutline(area: Rect | LayoutAllocator.CellAllocator, color: Color, strokeSize: Int)(using
uiContext: UiContext
): Unit =
val reservedArea = area match {
case rect: Rect => rect
case alloc: LayoutAllocator.CellAllocator => alloc.nextCell()
}
val top = reservedArea.copy(h = strokeSize)
val bottom = top.move(dx = 0, dy = reservedArea.h - strokeSize)
val left = reservedArea.copy(w = strokeSize)
val right = left.move(dx = reservedArea.w - strokeSize, dy = 0)
rectangle(top, color)
rectangle(bottom, color)
rectangle(left, color)
Expand All @@ -35,7 +45,7 @@ trait Primitives:
* @param verticalAlignment how the text should be aligned vertically
*/
final def text(
area: Rect | LayoutAllocator,
area: Rect | LayoutAllocator.AreaAllocator,
color: Color,
message: String,
font: Font = Font.default,
Expand All @@ -46,8 +56,8 @@ trait Primitives:
): Unit =
if (message.nonEmpty)
val reservedArea = area match {
case rect: Rect => rect
case alloc: LayoutAllocator => alloc.allocate(message, font)
case rect: Rect => rect
case alloc: LayoutAllocator.AreaAllocator => alloc.allocate(message, font)
}
uiContext.pushRenderOp(
RenderOp.DrawText(reservedArea, color, message, font, reservedArea, horizontalAlignment, verticalAlignment)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ final class DynamicColumnAllocator(
padding: Int,
alignment: HorizontalAlignment.Left.type | HorizontalAlignment.Right.type
) extends LayoutAllocator.ColumnAllocator
with LayoutAllocator.AreaAllocator
with (Int => Rect):
private var currentX = area.x
private var currentW = area.w
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ final class DynamicRowAllocator(
padding: Int,
alignment: VerticalAlignment.Top.type | VerticalAlignment.Bottom.type
) extends LayoutAllocator.RowAllocator
with LayoutAllocator.AreaAllocator
with (Int => Rect):
private var currentY = area.y
private var currentH = area.h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ final class StaticColumnAllocator(
numColumns: Int,
alignment: HorizontalAlignment.Left.type | HorizontalAlignment.Right.type
) extends LayoutAllocator.ColumnAllocator
with IndexedSeq[Rect]:
val cells: IndexedSeq[Rect] =
with LayoutAllocator.AreaAllocator
with LayoutAllocator.CellAllocator:
lazy val cells: IndexedSeq[Rect] =
if (numColumns == 0) Vector.empty
else
val columnSize = (area.w - (numColumns - 1) * padding) / numColumns.toDouble
Expand All @@ -21,14 +22,7 @@ final class StaticColumnAllocator(
if (alignment == HorizontalAlignment.Left) baseCells
else baseCells.reverse

def apply(i: Int): Rect = cells(i)
val length = cells.length

private val cellsIterator = cells.iterator

def nextColumn(): Rect =
if (!cellsIterator.hasNext) area.copy(w = 0, h = 0)
else cellsIterator.next()
def nextColumn(): Rect = nextCell()

def nextColumn(width: Int): Rect =
if (!cellsIterator.hasNext) area.copy(w = 0, h = 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ final class StaticRowAllocator(
numRows: Int,
alignment: VerticalAlignment.Top.type | VerticalAlignment.Bottom.type
) extends LayoutAllocator.RowAllocator
with IndexedSeq[Rect]:
val cells: IndexedSeq[Rect] =
with LayoutAllocator.AreaAllocator
with LayoutAllocator.CellAllocator:
lazy val cells: IndexedSeq[Rect] =
if (numRows == 0) Vector.empty
else
val rowSize = (area.h - (numRows - 1) * padding) / numRows.toDouble
Expand All @@ -21,14 +22,7 @@ final class StaticRowAllocator(
if (alignment == VerticalAlignment.Top) baseCells
else baseCells.reverse

def apply(i: Int): Rect = cells(i)
val length = cells.length

private val cellsIterator = cells.iterator

def nextRow(): Rect =
if (!cellsIterator.hasNext) area.copy(w = 0, h = 0)
else cellsIterator.next()
def nextRow(): Rect = nextCell()

def nextRow(height: Int): Rect =
if (!cellsIterator.hasNext) area.copy(w = 0, h = 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import eu.joaocosta.interim._
import eu.joaocosta.interim.api.Primitives._

trait ButtonSkin:
def allocateArea(allocator: LayoutAllocator, label: String): Rect
def allocateArea(allocator: LayoutAllocator.AreaAllocator, label: String): Rect
def buttonArea(area: Rect): Rect
def renderButton(area: Rect, label: String, itemStatus: UiContext.ItemStatus)(using
uiContext: UiContext
Expand All @@ -19,7 +19,7 @@ object ButtonSkin extends DefaultSkin:
colorScheme: ColorScheme
) extends ButtonSkin:

def allocateArea(allocator: LayoutAllocator, label: String): Rect =
def allocateArea(allocator: LayoutAllocator.AreaAllocator, label: String): Rect =
allocator.allocate(label, font, paddingH = buttonHeight / 2)

def buttonArea(area: Rect): Rect =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import eu.joaocosta.interim._
import eu.joaocosta.interim.api.Primitives._

trait CheckboxSkin:
def allocateArea(allocator: LayoutAllocator): Rect
def allocateArea(allocator: LayoutAllocator.AreaAllocator): Rect
def checkboxArea(area: Rect): Rect
def renderCheckbox(area: Rect, value: Boolean, itemStatus: UiContext.ItemStatus)(using uiContext: UiContext): Unit

Expand All @@ -15,7 +15,7 @@ object CheckboxSkin extends DefaultSkin:
colorScheme: ColorScheme
) extends CheckboxSkin:

def allocateArea(allocator: LayoutAllocator): Rect =
def allocateArea(allocator: LayoutAllocator.AreaAllocator): Rect =
allocator.allocate(Font.default.fontSize, Font.default.fontSize)

def checkboxArea(area: Rect): Rect =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import eu.joaocosta.interim._
import eu.joaocosta.interim.api.Primitives._

trait HandleSkin:
def allocateArea(allocator: LayoutAllocator): Rect
def allocateArea(allocator: LayoutAllocator.AreaAllocator): Rect

def moveHandleArea(area: Rect): Rect
def closeHandleArea(area: Rect): Rect
Expand All @@ -18,7 +18,7 @@ object HandleSkin extends DefaultSkin:

final case class Default(colorScheme: ColorScheme) extends HandleSkin:

def allocateArea(allocator: LayoutAllocator): Rect =
def allocateArea(allocator: LayoutAllocator.AreaAllocator): Rect =
allocator.allocate(Font.default.fontSize, Font.default.fontSize)

def moveHandleArea(area: Rect): Rect =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import eu.joaocosta.interim._
import eu.joaocosta.interim.api.Primitives._

trait SelectSkin:
def allocateArea(allocator: LayoutAllocator, labels: Vector[String]): Rect
def allocateArea(allocator: LayoutAllocator.AreaAllocator, labels: Vector[String]): Rect

def selectBoxArea(area: Rect): Rect
def renderSelectBox(area: Rect, value: Int, labels: Vector[String], itemStatus: UiContext.ItemStatus)(using
Expand All @@ -24,7 +24,7 @@ object SelectSkin extends DefaultSkin:
colorScheme: ColorScheme
) extends SelectSkin:

def allocateArea(allocator: LayoutAllocator, labels: Vector[String]): Rect =
def allocateArea(allocator: LayoutAllocator.AreaAllocator, labels: Vector[String]): Rect =
val largestLabel = labels.maxByOption(_.size).getOrElse("")
allocator.allocate(largestLabel, font, paddingW = padding, paddingH = padding)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import eu.joaocosta.interim._
import eu.joaocosta.interim.api.Primitives._

trait SliderSkin:
def allocateArea(allocator: LayoutAllocator): Rect
def allocateArea(allocator: LayoutAllocator.AreaAllocator): Rect
def sliderArea(area: Rect): Rect
def renderSlider(area: Rect, min: Int, value: Int, max: Int, itemStatus: UiContext.ItemStatus)(using
uiContext: UiContext
Expand All @@ -18,7 +18,7 @@ object SliderSkin extends DefaultSkin:
colorScheme: ColorScheme
) extends SliderSkin:

def allocateArea(allocator: LayoutAllocator): Rect =
def allocateArea(allocator: LayoutAllocator.AreaAllocator): Rect =
allocator.allocate(Font.default.fontSize + 2 * padding, Font.default.fontSize + 2 * padding)

def sliderArea(area: Rect): Rect = area.shrink(padding)
Expand Down
Loading

0 comments on commit 46ae468

Please sign in to comment.