Skip to content

Commit

Permalink
Add alignment to the layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
JD557 committed May 1, 2024
1 parent 9f58db3 commit 9436e0c
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import eu.joaocosta.interim.{HorizontalAlignment, VerticalAlignment}
object Constants extends Constants

trait Constants:
final val alignLeft = HorizontalAlignment.Left
final val centerHorizontally = HorizontalAlignment.Center
final val alignRight = HorizontalAlignment.Right
final val alignLeft: HorizontalAlignment.Left.type = HorizontalAlignment.Left
final val centerHorizontally: HorizontalAlignment.Center.type = HorizontalAlignment.Center
final val alignRight: HorizontalAlignment.Right.type = HorizontalAlignment.Right

final val alignTop = VerticalAlignment.Top
final val centerVertically = VerticalAlignment.Center
final val alignBottom = VerticalAlignment.Bottom
final val alignTop: VerticalAlignment.Top.type = VerticalAlignment.Top
final val centerVertically: VerticalAlignment.Center.type = VerticalAlignment.Center
final val alignBottom: VerticalAlignment.Bottom.type = VerticalAlignment.Bottom

final val maxSize = Int.MaxValue
final val maxSize: Int.MaxValue.type = Int.MaxValue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package eu.joaocosta.interim.api

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

/** A layout allocator is a side-effectful function that, given a size (width or height) tries to allocate
* a new area.
Expand All @@ -27,9 +27,15 @@ object LayoutAllocator:
def nextColumn(width: Int): Rect
def allocate(width: Int, height: Int): Rect = nextColumn(width)

final class DynamicRowAllocator(val area: Rect, padding: Int) extends RowAllocator with (Int => Rect):
final class DynamicRowAllocator(
val area: Rect,
padding: Int,
alignment: VerticalAlignment.Top.type | VerticalAlignment.Bottom.type
) extends RowAllocator
with (Int => Rect):
private var currentY = area.y
private var currentH = area.h
private val dirMod = if (alignment == VerticalAlignment.Top) 1 else -1

def nextRow(height: Int) =
val absHeight = math.abs(height).toInt
Expand All @@ -41,7 +47,7 @@ object LayoutAllocator:
currentY = area.h
currentH = 0
area.copy(y = areaY, h = areaH)
else if (height >= 0) // Fill from the top
else if (dirMod * height >= 0) // Fill from the top
val areaY = currentY
currentY += absHeight + padding
currentH -= absHeight + padding
Expand All @@ -53,16 +59,24 @@ object LayoutAllocator:

def apply(height: Int) = nextRow(height)

final class StaticRowAllocator(val area: Rect, padding: Int, numRows: Int) extends RowAllocator with IndexedSeq[Rect]:
final class StaticRowAllocator(
val area: Rect,
padding: Int,
numRows: Int,
alignment: VerticalAlignment.Top.type | VerticalAlignment.Bottom.type
) extends RowAllocator
with IndexedSeq[Rect]:
val cells: IndexedSeq[Rect] =
if (numRows == 0) Vector.empty
else
val rowSize = (area.h - (numRows - 1) * padding) / numRows.toDouble
val intRowSize = rowSize.toInt
for
val baseCells = for
row <- (0 until numRows)
dy = (row * (rowSize + padding)).toInt
yield Rect(area.x, area.y + dy, area.w, intRowSize)
if (alignment == VerticalAlignment.Top) baseCells
else baseCells.reverse

def apply(i: Int): Rect = cells(i)
val length = cells.length
Expand All @@ -81,9 +95,15 @@ object LayoutAllocator:
acc = acc ++ cellsIterator.next()
acc

final class DynamicColumnAllocator(val area: Rect, padding: Int) extends ColumnAllocator with (Int => Rect):
final class DynamicColumnAllocator(
val area: Rect,
padding: Int,
alignment: HorizontalAlignment.Left.type | HorizontalAlignment.Right.type
) extends ColumnAllocator
with (Int => Rect):
private var currentX = area.x
private var currentW = area.w
private val dirMod = if (alignment == HorizontalAlignment.Left) 1 else -1

def nextColumn(width: Int): Rect =
val absWidth = math.abs(width).toInt
Expand All @@ -95,7 +115,7 @@ object LayoutAllocator:
currentX = area.w
currentW = 0
area.copy(x = areaX, w = areaW)
else if (width >= 0) // Fill from the left
else if (dirMod * width >= 0) // Fill from the left
val areaX = currentX
currentX += absWidth + padding
currentW -= absWidth + padding
Expand All @@ -107,18 +127,24 @@ object LayoutAllocator:

def apply(height: Int) = nextColumn(height)

final class StaticColumnAllocator(val area: Rect, padding: Int, numColumns: Int)
extends ColumnAllocator
final class StaticColumnAllocator(
val area: Rect,
padding: Int,
numColumns: Int,
alignment: HorizontalAlignment.Left.type | HorizontalAlignment.Right.type
) extends ColumnAllocator
with IndexedSeq[Rect]:
val cells: IndexedSeq[Rect] =
if (numColumns == 0) Vector.empty
else
val columnSize = (area.w - (numColumns - 1) * padding) / numColumns.toDouble
val intColumnSize = columnSize.toInt
for
val baseCells = for
column <- (0 until numColumns)
dx = (column * (columnSize + padding)).toInt
yield Rect(area.x + dx, area.y, intColumnSize, area.h)
if (alignment == HorizontalAlignment.Left) baseCells
else baseCells.reverse

def apply(i: Int): Rect = cells(i)
val length = cells.length
Expand Down
38 changes: 28 additions & 10 deletions core/shared/src/main/scala/eu/joaocosta/interim/api/Layouts.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package eu.joaocosta.interim.api

import eu.joaocosta.interim.api.LayoutAllocator._
import eu.joaocosta.interim.{InputState, Rect, UiContext}
import eu.joaocosta.interim.{HorizontalAlignment, InputState, Rect, UiContext, VerticalAlignment}

/** Objects containing all default layouts.
*
Expand Down Expand Up @@ -43,34 +43,52 @@ trait Layouts:
*
* The body receives a `row: Vector[Rect]`, where `row(y)` is the rect of the y-th row.
*/
final def rows[T](area: Rect, numRows: Int, padding: Int)(body: StaticRowAllocator ?=> T): T =
val allocator = new StaticRowAllocator(area, padding, numRows)
body(using new StaticRowAllocator(area, padding, numRows))
final def rows[T](
area: Rect,
numRows: Int,
padding: Int,
alignment: VerticalAlignment.Top.type | VerticalAlignment.Bottom.type = VerticalAlignment.Top
)(body: StaticRowAllocator ?=> T): T =
val allocator = new StaticRowAllocator(area, padding, numRows, alignment)
body(using allocator)

/** Lays out the components in a sequence of columns where all elements have the same size, separated by a padding.
*
* The body receives a `column: Vector[Rect]`, where `column(y)` is the rect of the x-th column.
*/
final def columns[T](area: Rect, numColumns: Int, padding: Int)(body: StaticColumnAllocator ?=> T): T =
val allocator = new StaticColumnAllocator(area, padding, numColumns)
final def columns[T](
area: Rect,
numColumns: Int,
padding: Int,
alignment: HorizontalAlignment.Left.type | HorizontalAlignment.Right.type = HorizontalAlignment.Left
)(body: StaticColumnAllocator ?=> T): T =
val allocator = new StaticColumnAllocator(area, padding, numColumns, alignment)
body(using allocator)

/** Lays out the components in a sequence of rows of different sizes, separated by a padding.
*
* 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: DynamicRowAllocator ?=> T): T =
val allocator = new LayoutAllocator.DynamicRowAllocator(area, padding)
final def dynamicRows[T](
area: Rect,
padding: Int,
alignment: VerticalAlignment.Top.type | VerticalAlignment.Bottom.type = VerticalAlignment.Top
)(body: DynamicRowAllocator ?=> T): T =
val allocator = new LayoutAllocator.DynamicRowAllocator(area, padding, alignment)
body(using 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: DynamicColumnAllocator ?=> T): T =
val allocator = new LayoutAllocator.DynamicColumnAllocator(area, padding)
final def dynamicColumns[T](
area: Rect,
padding: Int,
alignment: HorizontalAlignment.Left.type | HorizontalAlignment.Right.type = HorizontalAlignment.Left
)(body: DynamicColumnAllocator ?=> T): T =
val allocator = new LayoutAllocator.DynamicColumnAllocator(area, padding, alignment)
body(using allocator)

/** Handle mouse events inside a specified area.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object SliderSkin extends DefaultSkin:
) extends SliderSkin:

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

def sliderArea(area: Rect): Rect = area.shrink(padding)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object TextInputSkin extends DefaultSkin:
) extends TextInputSkin:

def allocateArea(allocator: LayoutAllocator): Rect =
val maxBorder = math.max(border, activeBorder) + 1
val maxBorder = math.max(border, activeBorder) + 2
allocator.allocate(2 * maxBorder + 8 * font.fontSize, 2 * maxBorder + font.fontSize)

def textInputArea(area: Rect): Rect = area
Expand Down
8 changes: 4 additions & 4 deletions examples/snapshot/5-colorpicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ def application(inputState: InputState, appState: AppState) =
val results = htmlColors.filter(_._1.toLowerCase.startsWith(query.get.toLowerCase))
val resultsArea = rowAlloc.fill()
val buttonSize = 32
dynamicColumns(area = resultsArea, padding = 10): newColumn ?=>
dynamicColumns(area = resultsArea, padding = 10, alignRight): newColumn ?=>
val resultsHeight = results.size * buttonSize
if (resultsHeight > resultsArea.h)
slider("result scroller", min = 0, max = resultsHeight - resultsArea.h)(newColumn(-16), resultDelta)
slider("result scroller", min = 0, max = resultsHeight - resultsArea.h)(resultDelta)
val clipArea = newColumn(maxSize)
clip(area = clipArea):
rows(area = clipArea.copy(y = clipArea.y - resultDelta.get, h = resultsHeight), numRows = results.size, padding = 10): rows ?=>
Expand All @@ -119,8 +119,8 @@ def application(inputState: InputState, appState: AppState) =

onBottom:
window(id = "settings", title = "Settings", movable = false)(area = Rect(10, 430, 250, 40)): area =>
dynamicColumns(area = area.shrink(5), padding = 10): colAlloc ?=>
if (checkbox(id = "dark mode")(colAlloc.nextColumn(-16), skins.ColorScheme.darkModeEnabled()))
dynamicColumns(area = area.shrink(5), padding = 10, alignRight): colAlloc ?=>
if (checkbox(id = "dark mode")(skins.ColorScheme.darkModeEnabled()))
skins.ColorScheme.useDarkMode()
else skins.ColorScheme.useLightMode()
text(colAlloc.fill(), textColor, "Dark Mode", Font.default, alignRight)
Expand Down

0 comments on commit 9436e0c

Please sign in to comment.