Skip to content

Commit

Permalink
Fixed #85: TextArea component
Browse files Browse the repository at this point in the history
  • Loading branch information
davesmith00000 committed May 26, 2024
1 parent ac73010 commit 55da713
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
4 changes: 4 additions & 0 deletions demo/src/main/scala/demo/ComponentsWindow2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package demo
import indigo.*
import roguelikestarterkit.*
import roguelikestarterkit.ui.components.Input
import roguelikestarterkit.ui.components.TextArea

object ComponentsWindow2:

Expand Down Expand Up @@ -51,6 +52,9 @@ object ComponentsWindow2:
.add(
Input(20, Input.Theme(charSheet))
)
.add(
TextArea("abc.\nde,f\n0123456!", TextArea.Theme(charSheet))
)
)
.withTitle("More component examples")
.moveTo(2, 2)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package roguelikestarterkit.ui.components

import indigo.*
import indigo.syntax.*
import roguelikestarterkit.shaders.material
import roguelikestarterkit.terminal.RogueTerminalEmulator
import roguelikestarterkit.terminal.TerminalMaterial
import roguelikestarterkit.tiles.RoguelikeTiles10x10
import roguelikestarterkit.tiles.RoguelikeTiles5x6
import roguelikestarterkit.ui.component.ComponentFragment
import roguelikestarterkit.ui.component.StatelessComponent
import roguelikestarterkit.ui.datatypes.Bounds
import roguelikestarterkit.ui.datatypes.CharSheet
import roguelikestarterkit.ui.datatypes.Coords
import roguelikestarterkit.ui.datatypes.Dimensions
import roguelikestarterkit.ui.datatypes.UiContext

import scala.annotation.targetName

/** TextAreas are a simple `Component` that render text.
*/
final case class TextArea[ReferenceData](
text: ReferenceData => List[String],
bounds: Bounds,
render: (Coords, List[String]) => Outcome[ComponentFragment]
):
def withText(value: String): TextArea[ReferenceData] =
this.copy(text = _ => value.split("\n").toList)
def withText(f: ReferenceData => String): TextArea[ReferenceData] =
this.copy(text = (r: ReferenceData) => f(r).split("\n").toList)

def withBounds(value: Bounds): TextArea[ReferenceData] =
this.copy(bounds = value)

// Delegates, for convenience.

def update[StartupData, ContextData](
context: UiContext[ReferenceData]
): GlobalEvent => Outcome[TextArea[ReferenceData]] =
summon[StatelessComponent[TextArea[ReferenceData], ReferenceData]].updateModel(context, this)

def present[StartupData, ContextData](
context: UiContext[ReferenceData]
): Outcome[ComponentFragment] =
summon[StatelessComponent[TextArea[ReferenceData], ReferenceData]].present(context, this)

def reflow: TextArea[ReferenceData] =
summon[StatelessComponent[TextArea[ReferenceData], ReferenceData]].reflow(this)

def cascade(parentBounds: Bounds): TextArea[ReferenceData] =
summon[StatelessComponent[TextArea[ReferenceData], ReferenceData]].cascade(this, parentBounds)

object TextArea:

private def findBounds(text: List[String]): Bounds =
val maxLength =
text.foldLeft(0) { (acc, line) =>
if line.length > acc then line.length else acc
}
Bounds(0, 0, maxLength, text.length)

/** Minimal label constructor with custom rendering function
*/
def apply[ReferenceData](text: String)(
present: (Coords, List[String]) => Outcome[ComponentFragment]
): TextArea[ReferenceData] =
val t = text.split("\n").toList
TextArea(_ => t, findBounds(t), present)

@targetName("TextArea_apply_curried")
def apply[ReferenceData](text: ReferenceData => String)(
present: (Coords, List[String]) => Outcome[ComponentFragment]
): TextArea[ReferenceData] =
TextArea((r: ReferenceData) => text(r).split("\n").toList, Bounds(0, 0, 1, 1), present)

private val graphic = Graphic(0, 0, TerminalMaterial(AssetName(""), RGBA.White, RGBA.Black))

private def presentTextArea(
charSheet: CharSheet,
fgColor: RGBA,
bgColor: RGBA,
dimensions: Dimensions
): (Coords, List[String]) => Outcome[ComponentFragment] = { case (offset, label) =>
val size = dimensions.unsafeToSize

val terminal =
RogueTerminalEmulator(size)
.putLines(Point.zero, Batch.fromList(label), fgColor, bgColor)
.toCloneTiles(
CloneId(s"label_${charSheet.assetName.toString}"),
offset.toScreenSpace(charSheet.size),
charSheet.charCrops
) { case (fg, bg) =>
graphic.withMaterial(TerminalMaterial(charSheet.assetName, fg, bg))
}

Outcome(ComponentFragment(terminal))
}

/** Creates a TextArea rendered using the RogueTerminalEmulator based on a `TextArea.Theme`, with
* bounds based on the text length
*/
def apply[ReferenceData](text: String, theme: Theme): TextArea[ReferenceData] =
val t = text.split("\n").toList
val d = findBounds(t)
TextArea(
_ => t,
d,
presentTextArea(
theme.charSheet,
theme.colors.foreground,
theme.colors.background,
d.dimensions
)
)

/** Creates a TextArea rendered using the RogueTerminalEmulator based on a `TextArea.Theme`, with
* custom bounds
*/
def apply[ReferenceData](text: ReferenceData => String, theme: Theme): TextArea[ReferenceData] =
val d = Bounds(0, 0, 1, 1)
TextArea(
(r: ReferenceData) => text(r).split("\n").toList,
d,
presentTextArea(
theme.charSheet,
theme.colors.foreground,
theme.colors.background,
d.dimensions
)
)

given [ReferenceData]: StatelessComponent[TextArea[ReferenceData], ReferenceData] with
def bounds(model: TextArea[ReferenceData]): Bounds =
model.bounds

def updateModel(
context: UiContext[ReferenceData],
model: TextArea[ReferenceData]
): GlobalEvent => Outcome[TextArea[ReferenceData]] =
case FrameTick =>
Outcome(model.withBounds(findBounds(model.text(context.reference))))

case _ =>
Outcome(model)

def present(
context: UiContext[ReferenceData],
model: TextArea[ReferenceData]
): Outcome[ComponentFragment] =
model.render(context.bounds.coords, model.text(context.reference))

final case class Theme(
charSheet: CharSheet,
colors: TerminalTileColors
):
def withCharSheet(value: CharSheet): Theme =
this.copy(charSheet = value)

def withColors(foreground: RGBA, background: RGBA): Theme =
this.copy(colors = TerminalTileColors(foreground, background))

object Theme:
def apply(charSheet: CharSheet, foreground: RGBA, background: RGBA): Theme =
Theme(
charSheet,
TerminalTileColors(foreground, background)
)

def apply(charSheet: CharSheet): Theme =
Theme(charSheet, RGBA.White, RGBA.Black)

0 comments on commit 55da713

Please sign in to comment.