diff --git a/paint/src/main/scala/roguepaint/PaintGame.scala b/paint/src/main/scala/roguepaint/PaintGame.scala index ec08754f..b809e22e 100644 --- a/paint/src/main/scala/roguepaint/PaintGame.scala +++ b/paint/src/main/scala/roguepaint/PaintGame.scala @@ -4,57 +4,59 @@ import indigo.* import indigo.scenes.* import indigoextras.subsystems.FPSCounter import io.indigoengine.roguelike.starterkit.* +import roguepaint.shaders.BackgroundGrid import scala.scalajs.js.annotation.JSExportTopLevel @JSExportTopLevel("IndigoGame") -object PaintGame extends IndigoGame[Unit, Unit, Model, ViewModel]: +object PaintGame extends IndigoGame[Size, Size, Model, ViewModel]: - def initialScene(bootData: Unit): Option[SceneName] = + def initialScene(bootData: Size): Option[SceneName] = Option(PaintScene.name) - def scenes(bootData: Unit): NonEmptyList[Scene[Unit, Model, ViewModel]] = + def scenes(bootData: Size): NonEmptyList[Scene[Size, Model, ViewModel]] = NonEmptyList(PaintScene) val eventFilters: EventFilters = EventFilters.Permissive - def boot(flags: Map[String, String]): Outcome[BootResult[Unit]] = + def boot(flags: Map[String, String]): Outcome[BootResult[Size]] = Outcome( - BootResult - .noData( - Config.config - .withMagnification(1) - ) + BootResult( + Config.config + .withMagnification(1), + Config.config.viewport.size + ) .withFonts(RoguelikeTiles.Size10x10.Fonts.fontInfo) .withAssets(Assets.assets.assetSet) .withShaders( - TerminalText.standardShader + TerminalText.standardShader, + BackgroundGrid.shader ) // .withSubSystems(FPSCounter(Point.zero)) ) - def initialModel(startupData: Unit): Outcome[Model] = - Outcome(Model.initial) + def setup(bootData: Size, assetCollection: AssetCollection, dice: Dice): Outcome[Startup[Size]] = + Outcome(Startup.Success(bootData)) - def initialViewModel(startupData: Unit, model: Model): Outcome[ViewModel] = - Outcome(ViewModel.initial) + def initialModel(startupData: Size): Outcome[Model] = + Outcome(Model.initial) - def setup(bootData: Unit, assetCollection: AssetCollection, dice: Dice): Outcome[Startup[Unit]] = - Outcome(Startup.Success(())) + def initialViewModel(viewportSize: Size, model: Model): Outcome[ViewModel] = + Outcome(ViewModel.initial(viewportSize)) - def updateModel(context: FrameContext[Unit], model: Model): GlobalEvent => Outcome[Model] = + def updateModel(context: FrameContext[Size], model: Model): GlobalEvent => Outcome[Model] = _ => Outcome(model) def updateViewModel( - context: FrameContext[Unit], + context: FrameContext[Size], model: Model, viewModel: ViewModel ): GlobalEvent => Outcome[ViewModel] = _ => Outcome(viewModel) def present( - context: FrameContext[Unit], + context: FrameContext[Size], model: Model, viewModel: ViewModel ): Outcome[SceneUpdateFragment] = @@ -67,5 +69,5 @@ object Model: final case class ViewModel(paint: PaintViewModel) object ViewModel: - val initial: ViewModel = - ViewModel(PaintViewModel.initial) + def initial(viewportSize: Size): ViewModel = + ViewModel(PaintViewModel.initial(viewportSize)) diff --git a/paint/src/main/scala/roguepaint/PaintScene.scala b/paint/src/main/scala/roguepaint/PaintScene.scala index 02d1fc8b..d9ee190e 100644 --- a/paint/src/main/scala/roguepaint/PaintScene.scala +++ b/paint/src/main/scala/roguepaint/PaintScene.scala @@ -8,8 +8,9 @@ import roguepaint.components.WindowManager import roguepaint.components.WindowManagerModel import roguepaint.components.WindowManagerViewModel import roguepaint.components.WindowModel +import roguepaint.shaders.BackgroundGrid -object PaintScene extends Scene[Unit, Model, ViewModel]: +object PaintScene extends Scene[Size, Model, ViewModel]: type SceneModel = PaintModel type SceneViewModel = PaintViewModel @@ -36,7 +37,7 @@ object PaintScene extends Scene[Unit, Model, ViewModel]: Set() def updateModel( - context: SceneContext[Unit], + context: SceneContext[Size], model: PaintModel ): GlobalEvent => Outcome[PaintModel] = case e => @@ -45,7 +46,7 @@ object PaintScene extends Scene[Unit, Model, ViewModel]: updated.map(w => model.copy(windowManager = w)) def updateViewModel( - context: SceneContext[Unit], + context: SceneContext[Size], model: PaintModel, viewModel: PaintViewModel ): GlobalEvent => Outcome[PaintViewModel] = @@ -55,7 +56,7 @@ object PaintScene extends Scene[Unit, Model, ViewModel]: updated.map(w => viewModel.copy(windowManager = w)) def present( - context: SceneContext[Unit], + context: SceneContext[Size], model: PaintModel, viewModel: PaintViewModel ): Outcome[SceneUpdateFragment] = @@ -63,7 +64,12 @@ object PaintScene extends Scene[Unit, Model, ViewModel]: WindowManager.present(context.frameContext, model.windowManager, viewModel.windowManager) Outcome( - SceneUpdateFragment(tiles.clones) + SceneUpdateFragment( + Layer( + Batch(viewModel.bg) ++ + tiles.clones + ) + ) .addCloneBlanks(tiles.blanks) ) @@ -78,15 +84,15 @@ object PaintModel: WindowModel(WindowId("fixed")) .moveTo((screenSize.toPoint / 2) - 8) .resizeTo(16, 16) - ) + ) .add( - WindowModel(WindowId("Rogue Paint")) + WindowModel(WindowId("tools")) .withTitle("Tools") - .moveTo(1, 1) + .moveTo(0, 0) .resizeTo(20, screenSize.height - 2) ) .add( - WindowModel(WindowId("Test")) + WindowModel(WindowId("test")) .withTitle("Test") .moveTo(50, 5) .resizeTo(15, 10) @@ -94,12 +100,15 @@ object PaintModel: .isCloseable .isDraggable ) - ) -final case class PaintViewModel(windowManager: WindowManagerViewModel) +final case class PaintViewModel(windowManager: WindowManagerViewModel, bg: BlankEntity) object PaintViewModel: - val initial: PaintViewModel = + def initial(viewportSize: Size): PaintViewModel = PaintViewModel( - WindowManagerViewModel.initial + WindowManagerViewModel.initial, + BlankEntity( + viewportSize, + BackgroundGrid.shaderData + ) ) diff --git a/paint/src/main/scala/roguepaint/components/Window.scala b/paint/src/main/scala/roguepaint/components/Window.scala index b7f0dd8f..f8dc4fdd 100644 --- a/paint/src/main/scala/roguepaint/components/Window.scala +++ b/paint/src/main/scala/roguepaint/components/Window.scala @@ -17,7 +17,7 @@ object Window: ) def updateModel( - frameContext: FrameContext[Unit], + frameContext: FrameContext[Size], model: WindowModel ): GlobalEvent => Outcome[WindowModel] = case e: MouseEvent.Click => @@ -34,7 +34,7 @@ object Window: Outcome(model) def updateViewModel( - frameContext: FrameContext[Unit], + frameContext: FrameContext[Size], model: WindowModel, viewModel: WindowViewModel ): GlobalEvent => Outcome[WindowViewModel] = @@ -124,7 +124,7 @@ final case class WindowModel( def notCloseable: WindowModel = withCloseable(false) - def update(frameContext: FrameContext[Unit], event: GlobalEvent): Outcome[WindowModel] = + def update(frameContext: FrameContext[Size], event: GlobalEvent): Outcome[WindowModel] = Window.updateModel(frameContext, this)(event) object WindowModel: @@ -146,7 +146,7 @@ final case class WindowViewModel( ): def update( - frameContext: FrameContext[Unit], + frameContext: FrameContext[Size], model: WindowModel, event: GlobalEvent ): Outcome[WindowViewModel] = @@ -183,11 +183,11 @@ object WindowViewModel: // When there is a title case Point(0, 1) if model.title.isDefined => // Title bar left - coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Black) case Point(x, 1) if model.title.isDefined && x == maxX => // Title bar right - coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Black) case Point(x, 1) if model.title.isDefined => // Title text, x starts at 2 @@ -200,27 +200,27 @@ object WindowViewModel: case Some(char) => Tile(char) else Tile.SPACE - coords -> MapTile(tile, RGBA.White, RGBA.Zero) + coords -> MapTile(tile, RGBA.White, RGBA.Black) case Point(0, 2) if model.title.isDefined => // Title bar line left val tile = if maxY > 2 then Tile.`├` else Tile.`└` - coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Black) case Point(x, 2) if model.title.isDefined && x == maxX => // Title bar line right val tile = if maxY > 2 then Tile.`┤` else Tile.`┘` - coords -> MapTile(tile, RGBA.White, RGBA.Zero) + coords -> MapTile(tile, RGBA.White, RGBA.Black) case Point(x, 2) if model.title.isDefined => // Title bar line - coords -> MapTile(Tile.`─`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`─`, RGBA.White, RGBA.Black) // Normal window frame case Point(0, 0) => // top left - coords -> MapTile(Tile.`┌`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`┌`, RGBA.White, RGBA.Black) case Point(x, 0) if model.closeable && x == maxX => // top right closable @@ -228,39 +228,39 @@ object WindowViewModel: case Point(x, 0) if x == maxX => // top right - coords -> MapTile(Tile.`┐`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`┐`, RGBA.White, RGBA.Black) case Point(x, 0) => // top - coords -> MapTile(Tile.`─`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`─`, RGBA.White, RGBA.Black) case Point(0, y) if y == maxY => // bottom left - coords -> MapTile(Tile.`└`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`└`, RGBA.White, RGBA.Black) case Point(x, y) if model.resizable && x == maxX && y == maxY => // bottom right with resize - coords -> MapTile(Tile.`▼`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`▼`, RGBA.White, RGBA.Black) case Point(x, y) if x == maxX && y == maxY => // bottom right - coords -> MapTile(Tile.`┘`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`┘`, RGBA.White, RGBA.Black) case Point(x, y) if y == maxY => // bottom - coords -> MapTile(Tile.`─`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`─`, RGBA.White, RGBA.Black) case Point(0, y) => // Middle left - coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Black) case Point(x, y) if x == maxX => // Middle right - coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Zero) + coords -> MapTile(Tile.`│`, RGBA.White, RGBA.Black) case Point(x, y) => // Window background - coords -> MapTile(Tile.`░`, grey, RGBA.Zero) + coords -> MapTile(Tile.`░`, grey, RGBA.Black) } } diff --git a/paint/src/main/scala/roguepaint/components/WindowManager.scala b/paint/src/main/scala/roguepaint/components/WindowManager.scala index c29de5a4..41d9f669 100644 --- a/paint/src/main/scala/roguepaint/components/WindowManager.scala +++ b/paint/src/main/scala/roguepaint/components/WindowManager.scala @@ -7,7 +7,7 @@ import io.indigoengine.roguelike.starterkit.* object WindowManager: def updateModel( - frameContext: FrameContext[Unit], + frameContext: FrameContext[Size], model: WindowManagerModel ): GlobalEvent => Outcome[WindowManagerModel] = case WindowManagerEvent.Close(id) => @@ -17,7 +17,7 @@ object WindowManager: model.windows.map(_.update(frameContext, e)).sequence.map(m => model.copy(windows = m)) def updateViewModel( - frameContext: FrameContext[Unit], + frameContext: FrameContext[Size], model: WindowManagerModel, viewModel: WindowManagerViewModel ): GlobalEvent => Outcome[WindowManagerViewModel] = @@ -35,7 +35,7 @@ object WindowManager: updated.sequence.map(vm => viewModel.copy(windows = vm)) def present( - frameContext: FrameContext[Unit], + frameContext: FrameContext[Size], model: WindowManagerModel, viewModel: WindowManagerViewModel ): TerminalClones = @@ -70,7 +70,7 @@ final case class WindowManagerModel(windows: Batch[WindowModel]): case Some(w) => this.copy(windows = windows.filterNot(_.id == id) :+ w) - def update(frameContext: FrameContext[Unit], event: GlobalEvent): Outcome[WindowManagerModel] = + def update(frameContext: FrameContext[Size], event: GlobalEvent): Outcome[WindowManagerModel] = WindowManager.updateModel(frameContext, this)(event) object WindowManagerModel: @@ -82,7 +82,7 @@ final case class WindowManagerViewModel(windows: Batch[WindowViewModel]): this.copy(windows = windows.filter(w => model.windows.exists(_.id == w.id))) def update( - frameContext: FrameContext[Unit], + frameContext: FrameContext[Size], model: WindowManagerModel, event: GlobalEvent ): Outcome[WindowManagerViewModel] = diff --git a/paint/src/main/scala/roguepaint/shaders/BackgroundGrid.scala b/paint/src/main/scala/roguepaint/shaders/BackgroundGrid.scala new file mode 100644 index 00000000..38d3ff2e --- /dev/null +++ b/paint/src/main/scala/roguepaint/shaders/BackgroundGrid.scala @@ -0,0 +1,37 @@ +package roguepaint.shaders + +import indigo.* + +object BackgroundGrid: + + val shader: Shader = + UltravioletShader.entityFragment( + ShaderId("background grid"), + EntityShader.fragment[FragmentEnv](fragment, FragmentEnv.reference) + ) + + val shaderData: ShaderData = + ShaderData( + shader.id, + Batch.empty, + None, + None, + None, + None + ) + + import ultraviolet.syntax.* + + inline def fragment: Shader[FragmentEnv, Unit] = + Shader[FragmentEnv] { env => + + def fragment(color: vec4): vec4 = + val gridSize = 10.0f + val count = vec2(1280.0f, 720.0f) / gridSize + val size = 1.0f / count + val outline = step(0.8f, mod(env.UV, size) * count) + val lines = max(outline.x, outline.y) + val lineColour = vec3(1.0f, 1.0f, 1.0f) * max(1.0f - env.UV.y, 0.25f) * 0.15f + + vec4(lineColour * lines, lines) + }