Skip to content

Commit

Permalink
Organised a WindowManager
Browse files Browse the repository at this point in the history
  • Loading branch information
davesmith00000 committed Oct 24, 2023
1 parent 54928dd commit 7b7ea34
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 38 deletions.
2 changes: 1 addition & 1 deletion paint/src/main/scala/roguepaint/PaintGame.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object PaintGame extends IndigoGame[Unit, Unit, Model, ViewModel]:
BootResult
.noData(
Config.config
.withMagnification(2)
.withMagnification(1)
)
.withFonts(RoguelikeTiles.Size10x10.Fonts.fontInfo)
.withAssets(Assets.assets.assetSet)
Expand Down
49 changes: 38 additions & 11 deletions paint/src/main/scala/roguepaint/PaintScene.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package roguepaint
import indigo.*
import indigo.scenes.*
import io.indigoengine.roguelike.starterkit.*
import roguepaint.components.Window
import roguepaint.components.WindowId
import roguepaint.components.WindowManager
import roguepaint.components.WindowManagerModel
import roguepaint.components.WindowManagerViewModel
import roguepaint.components.WindowModel
import roguepaint.components.WindowViewModel

object PaintScene extends Scene[Unit, Model, ViewModel]:

Expand Down Expand Up @@ -38,40 +40,65 @@ object PaintScene extends Scene[Unit, Model, ViewModel]:
model: PaintModel
): GlobalEvent => Outcome[PaintModel] =
case e =>
val updated = model.window.update(context.frameContext, e)
val updated = model.windowManager.update(context.frameContext, e)

updated.map(w => model.copy(window = w))
updated.map(w => model.copy(windowManager = w))

def updateViewModel(
context: SceneContext[Unit],
model: PaintModel,
viewModel: PaintViewModel
): GlobalEvent => Outcome[PaintViewModel] =
case e =>
val updated = viewModel.window.update(context.frameContext, model.window, e)
val updated = viewModel.windowManager.update(context.frameContext, model.windowManager, e)

updated.map(w => viewModel.copy(window = w))
updated.map(w => viewModel.copy(windowManager = w))

def present(
context: SceneContext[Unit],
model: PaintModel,
viewModel: PaintViewModel
): Outcome[SceneUpdateFragment] =
val tiles = Window.present(context.frameContext, model.window, viewModel.window)
val tiles =
WindowManager.present(context.frameContext, model.windowManager, viewModel.windowManager)

Outcome(
SceneUpdateFragment(tiles.clones)
.addCloneBlanks(tiles.blanks)
)

final case class PaintModel(window: WindowModel)
final case class PaintModel(windowManager: WindowManagerModel)
object PaintModel:
val initial: PaintModel =
PaintModel(WindowModel("test window"))
PaintModel(
WindowManagerModel.initial
.add(
WindowModel(WindowId("fixed"))
.moveTo(15, 40)
.resizeTo(15, 10)
)
.add(
WindowModel(WindowId("controls"))
.isCloseable
.isResizable
.moveTo(175, 60)
.resizeTo(20, 20)
)
.add(
WindowModel(WindowId("title bar"))
.isCloseable
.isResizable
.isDraggable
.withTitle("Controls")
.moveTo(30, 200)
.resizeTo(10, 30)
)

final case class PaintViewModel(window: WindowViewModel)
)

final case class PaintViewModel(windowManager: WindowManagerViewModel)
object PaintViewModel:
val initial: PaintViewModel =
PaintViewModel(
WindowViewModel.initial
WindowManagerViewModel.initial
)
127 changes: 101 additions & 26 deletions paint/src/main/scala/roguepaint/components/Window.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import roguepaint.Assets

object Window:

private val graphic10x10: Graphic[TerminalText] =
Graphic(10, 10, TerminalText(Assets.assets.AnikkiSquare10x10, RGBA.White, RGBA.Black))

def updateModel(
frameContext: FrameContext[Unit],
model: WindowModel
Expand All @@ -19,43 +22,109 @@ object Window:
viewModel: WindowViewModel
): GlobalEvent => Outcome[WindowViewModel] =
case FrameTick if model.bounds.size != viewModel.terminal.screenSize =>
Outcome(viewModel.resize(model))
val vm = viewModel.resize(model)
val clones =
vm.terminal.toCloneTiles(model.bounds.position, RoguelikeTiles.Size10x10.charCrops) {
case (fg, bg) =>
graphic10x10.withMaterial(TerminalText(Assets.assets.AnikkiSquare10x10, fg, bg))
}

Outcome(
vm.copy(terminalClones = clones)
)

case _ =>
Outcome(viewModel)

val graphic10x10: Graphic[TerminalText] =
Graphic(10, 10, TerminalText(Assets.assets.AnikkiSquare10x10, RGBA.White, RGBA.Black))
def present(viewModel: WindowViewModel): TerminalClones =
viewModel.terminalClones

def present(
frameContext: FrameContext[Unit],
model: WindowModel,
viewModel: WindowViewModel
): TerminalClones =
viewModel.terminal.toCloneTiles(model.bounds.position, RoguelikeTiles.Size10x10.charCrops) {
case (fg, bg) =>
graphic10x10.withMaterial(TerminalText(Assets.assets.AnikkiSquare10x10, fg, bg))
}
opaque type WindowId = String
object WindowId:
def apply(id: String): WindowId = id
extension (id: WindowId) def toString: String = id

final case class WindowModel(
id: String,
id: WindowId,
bounds: Rectangle,
title: Option[String]
title: Option[String],
draggable: Boolean,
resizable: Boolean,
closeable: Boolean
):

def withId(value: WindowId): WindowModel =
this.copy(id = value)

def withBounds(value: Rectangle): WindowModel =
this.copy(bounds = value)

def withPosition(value: Point): WindowModel =
withBounds(bounds.withPosition(value))
def moveTo(position: Point): WindowModel =
withPosition(position)
def moveTo(x: Int, y: Int): WindowModel =
moveTo(Point(x, y))
def moveBy(amount: Point): WindowModel =
withPosition(bounds.position + amount)
def moveBy(x: Int, y: Int): WindowModel =
moveBy(Point(x, y))

def withSize(value: Size): WindowModel =
withBounds(bounds.withSize(value))
def resizeTo(size: Size): WindowModel =
withSize(size)
def resizeTo(x: Int, y: Int): WindowModel =
resizeTo(Size(x, y))
def resizeBy(amount: Size): WindowModel =
withSize(bounds.size + amount)
def resizeBy(x: Int, y: Int): WindowModel =
resizeBy(Size(x, y))

def withTitle(value: String): WindowModel =
this.copy(title = Option(value))

def withDraggable(value: Boolean): WindowModel =
this.copy(draggable = value)
def isDraggable: WindowModel =
withDraggable(true)
def notDraggable: WindowModel =
withDraggable(false)

def withResizable(value: Boolean): WindowModel =
this.copy(resizable = value)
def isResizable: WindowModel =
withResizable(true)
def notResizable: WindowModel =
withResizable(false)

def withCloseable(value: Boolean): WindowModel =
this.copy(closeable = value)
def isCloseable: WindowModel =
withCloseable(true)
def notCloseable: WindowModel =
withCloseable(false)

def update(frameContext: FrameContext[Unit], event: GlobalEvent): Outcome[WindowModel] =
Window.updateModel(frameContext, this)(event)

object WindowModel:

def apply(id: String): WindowModel =
def apply(id: WindowId): WindowModel =
WindowModel(
id,
Rectangle(Point(15, 15), Size(15, 10)),
Some("Inventory")
Rectangle(Point.zero, Size.zero),
None,
false,
false,
false
)

final case class WindowViewModel(terminal: TerminalEmulator):
final case class WindowViewModel(
id: WindowId,
terminal: TerminalEmulator,
terminalClones: TerminalClones
):

def update(
frameContext: FrameContext[Unit],
Expand All @@ -69,9 +138,11 @@ final case class WindowViewModel(terminal: TerminalEmulator):

object WindowViewModel:

def initial: WindowViewModel =
def initial(id: WindowId): WindowViewModel =
WindowViewModel(
TerminalEmulator(Size.zero)
id,
TerminalEmulator(Size.zero),
TerminalClones.empty
)

def makeWindowTerminal(model: WindowModel, current: TerminalEmulator): TerminalEmulator =
Expand All @@ -80,8 +151,8 @@ object WindowViewModel:
if validSize == current.screenSize then current
else
val tiles: Batch[(Point, MapTile)] =
val grey = RGBA.White.mix(RGBA.Black, 0.5)
val title = model.title.getOrElse("").toCharArray()
val grey = RGBA.White.mix(RGBA.Black, 0.3)
val title = model.title.getOrElse("").take(model.bounds.size.width - 2).toCharArray()

(0 to validSize.height).toBatch.flatMap { _y =>
(0 to validSize.width).toBatch.map { _x =>
Expand Down Expand Up @@ -132,6 +203,10 @@ object WindowViewModel:
// top left
coords -> MapTile(Tile.`┌`, RGBA.White, RGBA.Zero)

case Point(x, 0) if model.closeable && x == maxX =>
// top right closable
coords -> MapTile(Tile.`x`, RGBA.Black, RGBA.White)

case Point(x, 0) if x == maxX =>
// top right
coords -> MapTile(Tile.`┐`, RGBA.White, RGBA.Zero)
Expand All @@ -144,6 +219,10 @@ object WindowViewModel:
// bottom left
coords -> MapTile(Tile.`└`, RGBA.White, RGBA.Zero)

case Point(x, y) if model.resizable && x == maxX && y == maxY =>
// bottom right with resize
coords -> MapTile(Tile.`▼`, RGBA.White, RGBA.Zero)

case Point(x, y) if x == maxX && y == maxY =>
// bottom right
coords -> MapTile(Tile.`┘`, RGBA.White, RGBA.Zero)
Expand All @@ -160,10 +239,6 @@ object WindowViewModel:
// Middle right
coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Zero)

case Point(x, y) if x == maxX - 1 && y == maxY - 1 =>
// Resize corner
coords -> MapTile(Tile.`/`, RGBA.White, RGBA.Zero)

case Point(x, y) =>
// Window background
coords -> MapTile(Tile.`░`, grey, RGBA.Zero)
Expand Down
90 changes: 90 additions & 0 deletions paint/src/main/scala/roguepaint/components/WindowManager.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package roguepaint.components

import indigo.*
import indigo.syntax.*
import io.indigoengine.roguelike.starterkit.*

object WindowManager:

def updateModel(
frameContext: FrameContext[Unit],
model: WindowManagerModel
): GlobalEvent => Outcome[WindowManagerModel] =
case e =>
model.windows.map(_.update(frameContext, e)).sequence.map(m => model.copy(windows = m))

def updateViewModel(
frameContext: FrameContext[Unit],
model: WindowManagerModel,
viewModel: WindowManagerViewModel
): GlobalEvent => Outcome[WindowManagerViewModel] =
case e =>
val updated =
model.windows.flatMap { m =>
viewModel.prune(model).windows.find(_.id == m.id) match
case None =>
Batch(Outcome(WindowViewModel.initial(m.id)))

case Some(vm) =>
Batch(vm.update(frameContext, m, e))
}

updated.sequence.map(vm => viewModel.copy(windows = vm))

def present(
frameContext: FrameContext[Unit],
model: WindowManagerModel,
viewModel: WindowManagerViewModel
): TerminalClones =
model.windows
.flatMap { m =>
viewModel.windows.find(_.id == m.id) match
case None =>
// Shouldn't get here.
Batch.empty

case Some(vm) =>
Batch(Window.present(vm))
}
.foldLeft(TerminalClones.empty)(_ |+| _)

extension (tc: TerminalClones)
def |+|(other: TerminalClones): TerminalClones =
TerminalClones(tc.blanks ++ other.blanks, tc.clones ++ other.clones)

final case class WindowManagerModel(windows: Batch[WindowModel]):
def add(model: WindowModel): WindowManagerModel =
this.copy(windows = windows :+ model)

def remove(id: WindowId): WindowManagerModel =
this.copy(windows = windows.filterNot(_.id == id))

def bringToTop(id: WindowId): WindowManagerModel =
windows.find(_.id == id) match
case None =>
this

case Some(w) =>
this.copy(windows = windows.filterNot(_.id == id) :+ w)

def update(frameContext: FrameContext[Unit], event: GlobalEvent): Outcome[WindowManagerModel] =
WindowManager.updateModel(frameContext, this)(event)

object WindowManagerModel:
val initial: WindowManagerModel =
WindowManagerModel(Batch.empty)

final case class WindowManagerViewModel(windows: Batch[WindowViewModel]):
def prune(model: WindowManagerModel): WindowManagerViewModel =
this.copy(windows = windows.filter(w => model.windows.exists(_.id == w.id)))

def update(
frameContext: FrameContext[Unit],
model: WindowManagerModel,
event: GlobalEvent
): Outcome[WindowManagerViewModel] =
WindowManager.updateViewModel(frameContext, model, this)(event)

object WindowManagerViewModel:
val initial: WindowManagerViewModel =
WindowManagerViewModel(Batch.empty)

0 comments on commit 7b7ea34

Please sign in to comment.