diff --git a/art/glooga.svg b/art/glooga.svg
index ba701b1..1922efa 100644
--- a/art/glooga.svg
+++ b/art/glooga.svg
@@ -8,13 +8,14 @@
version="1.1"
id="svg5"
xml:space="preserve"
- inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="glooga.svg"
inkscape:export-filename="glooga.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
+
+
+
diff --git a/swingio/src/main/scala/Main.scala b/swingio/src/main/scala/Main.scala
new file mode 100644
index 0000000..72d9803
--- /dev/null
+++ b/swingio/src/main/scala/Main.scala
@@ -0,0 +1,249 @@
+import net.bulbyvr.splooge.core.*
+import util.{Image, JVMImage, AffineTransform}
+import cats.syntax.all.*
+import cats.effect.syntax.all.*
+import net.bulbyvr.swing.io.all.{*, given}
+import net.bulbyvr.swing.io.wrapper.*
+import net.bulbyvr.swing.io.wrapper.Image as WImage
+import net.bulbyvr.swing.io.wrapper.event.*
+import net.bulbyvr.swing.io.{IOSwingApp, AwtEventDispatchEC}
+import cats.effect.*
+import java.awt.image.BufferedImage
+import fs2.concurrent.*
+import javax.imageio.ImageIO
+import javax.swing.UIManager
+def imageSelector(image: SignallingRef[IO, Option[BufferedImage]]): Resource[IO, Component[IO]] = {
+ flow(
+ button(
+ text := "Select Image...",
+ onBtnClick --> {
+ _.evalMap(_ => FileChooser[IO].open).evalMap(maybeFile => maybeFile.traverse(file => IO.blocking { ImageIO.read(file) } )).foreach(image.set)
+ }
+ ),
+ button(
+ text := "Clear Image",
+ onBtnClick --> {
+ _.foreach(_ => image.set(None))
+ }
+ )
+ )
+}
+def GameDropdown(game: SignallingRef[IO, GameStyle], games: Signal[IO, Seq[GameStyle]], image: SignallingRef[IO, Option[BufferedImage]]): Resource[IO, Component[IO]] = {
+ flow(
+ comboBox[GameStyle].withSelf { self => (
+ items <-- games,
+ onSelectionChange --> {
+ _.evalMap(_ => self.item.get).foreach(game.set(_) *> image.set(None))
+ }
+ )}
+ )
+}
+
+def OtherWeaponsDropdown(daLabel: String, weapon: SignallingRef[IO, OtherWeapon], game: SignallingRef[IO, GameStyle],
+ selectedFile: SignallingRef[IO, Option[BufferedImage]], name: SignallingRef[IO, Option[String]], weapons: Seq[OtherWeapon]): Resource[IO, Component[IO]] = {
+ val curGames = weapon.map(_.games)
+ curGames.discrete.debug().compile.drain.background.void *>
+ box(
+ flow(
+ label(text := daLabel + " Name: ",
+ icon <-- (weapon.asInstanceOf[Signal[IO, OtherWeapon]], game, selectedFile).tupled.discrete.evalMap( (w, g, f) =>
+ (f match {
+ case Some(value) => JVMImage(value).pure[IO]
+ case None => Image.loadFromResource(otherPath(w, g)).map(_.asInstanceOf[JVMImage])
+ }).flatMap(scaleImage).map(it => WImage[IO](it.inner))
+ ).hold1Resource),
+ textField.withSelf { self =>
+ (
+ columns := 20,
+ onValueChange --> {
+ _.evalMap(_ => self.text.get).map(it => if (it == "") None else Some(it)).foreach(name.set)
+ }
+ )
+ }
+ ),
+ flow(
+ comboBox[OtherWeapon].withSelf { self => (
+ items := weapons,
+ onSelectionChange --> {
+ _.evalMap(_ => self.item.get).foreach(it => weapon.set(it) *> game.set(it.games.head) *> selectedFile.set(None))
+ },
+ renderer := { (it: OtherWeapon) =>
+ it.name
+ }
+ )},
+ GameDropdown(game, curGames, selectedFile)
+ ),
+ imageSelector(selectedFile),
+ )
+}
+
+def BuildOtherWeaponsDropdown(label: String, weapons: Seq[OtherWeapon]): IO[(SignallingRef[IO, OtherWeapon], SignallingRef[IO, GameStyle], SignallingRef[IO, Option[BufferedImage]],
+ SignallingRef[IO, Option[String]], Resource[IO, Component[IO]]
+ )] = {
+ for {
+ weapon <- SignallingRef[IO].of(weapons.head)
+ game <- SignallingRef[IO].of(weapons.head.games.head)
+ image <- SignallingRef[IO].of[Option[BufferedImage]](None)
+ name <- SignallingRef[IO].of[Option[String]](None)
+ dropdown <- IO(OtherWeaponsDropdown(label, weapon, game, image, name, weapons))
+ } yield (weapon, game, image, name, dropdown)
+}
+
+def scaleImage(image: JVMImage): IO[JVMImage] = {
+ val canvas = Image(64, 64).getCanvas()
+ val transform = AffineTransform.scaling(64.0 / image.width, 64.0 / image.width)
+ for {
+ _ <- canvas.drawImage(image, transform)
+ r <- canvas.complete()
+ } yield r.asInstanceOf[JVMImage]
+}
+def MainWeaponDropdown(weapon: SignallingRef[IO, Weapon], style: SignallingRef[IO, WeaponStyle], do2D: SignallingRef[IO, Boolean],
+ selectedFile: SignallingRef[IO, Option[BufferedImage]],
+ name: SignallingRef[IO, Option[String]],
+ weapons: Seq[Weapon]) = {
+ val styles = weapon.map(_.styles)
+ box(
+ flow(
+ label(text := "Main name: ",
+ icon <-- (weapon.asInstanceOf[Signal[IO, Weapon]], style, do2D, selectedFile).tupled.discrete.evalMap( (w, s, d2, f) =>
+ (f match {
+ case Some(value) => JVMImage(value).pure[IO]
+ case None => weaponPath(w, s, d2).flatMap(Image.loadFromResource(_).map(_.asInstanceOf[JVMImage]))
+ }).flatMap(scaleImage).map(it => WImage[IO](it.inner))
+
+ ).hold1Resource
+ ),
+ textField.withSelf { self => (
+ columns := 20,
+ onValueChange --> {
+ _.evalMap(_ => self.text.get)
+ .map(it => if (it == "") None else Some(it))
+ .foreach(name.set(_) *> selectedFile.set(None))
+ }
+ )},
+ comboBox[Weapon].withSelf { self =>
+ ( items := weapons,
+ onSelectionChange --> {
+ _.evalMap(_ => self.item.get)
+ .foreach(it => weapon.set(it) *> style.set(it.styles.head) *> selectedFile.set(None))
+ },
+ renderer := { (it: Weapon) =>
+ it.name.pure[IO]
+ }
+ )
+ },
+ comboBox[WeaponStyle].withSelf { self =>
+ ( items <-- styles,
+ onSelectionChange --> {
+ _.evalMap(_ => self.item.get)
+ .foreach(style.set(_) *> selectedFile.set(None))
+ },
+ renderer := { (it: WeaponStyle) =>
+ it.pretty.pure[IO]
+ }
+ )
+ },
+ flow(
+ checkbox.withSelf { self => (
+ onBtnClick --> {
+ _.evalMap(_ => self.selected.get)
+ .foreach(do2D.set(_) *> selectedFile.set(None))
+ },
+ text := "Flat Icons"
+ )}
+ )
+ ),
+ imageSelector(selectedFile)
+ )
+}
+
+def BuildMainWeaponDropdown(weapons: Seq[Weapon]): IO[(SignallingRef[IO, Weapon], SignallingRef[IO, WeaponStyle], SignallingRef[IO, Boolean],
+ SignallingRef[IO, Option[BufferedImage]], SignallingRef[IO, Option[String]], Resource[IO, Component[IO]])] = {
+ for {
+ weapon <- SignallingRef[IO].of(weapons.head)
+ style <- SignallingRef[IO].of(weapons.head.styles.head)
+ do2D <- SignallingRef[IO].of(false)
+ selectedFile <- SignallingRef[IO].of[Option[BufferedImage]](None)
+ name <- SignallingRef[IO].of[Option[String]](None)
+ dropdown <- IO(MainWeaponDropdown(weapon, style, do2D, selectedFile, name, weapons))
+ } yield (weapon, style, do2D, selectedFile, name, dropdown)
+}
+def pathExists(path: String): IO[Boolean] = {
+ IO.delay { this.getClass.getResource(path) }.map(_ != null)
+}
+
+def otherPath(weapon: OtherWeapon, game: GameStyle): String = {
+ weapon.root + "/" + game.name + "_" + weapon.name.replace(' ', '_').toLowerCase() + ".png"
+}
+def weaponPath(weapon: Weapon, style : WeaponStyle, twodim : Boolean) : IO[String] = {
+
+ val (noDimPath, goodPath) = weapon.basePath(style, twodim)
+
+ for {
+ path <-
+ if (twodim)
+ for {
+ exists <- pathExists(goodPath)
+ } yield {
+ if (exists) goodPath else noDimPath
+ }
+ else
+ for {
+ exists <- pathExists(noDimPath)
+ } yield {
+ if (exists) noDimPath else goodPath
+ }
+ } yield path
+}
+def renderKit(mainWeapon: Weapon, mainStyle: WeaponStyle, sub: OtherWeapon, subGame: GameStyle,
+ special: OtherWeapon, spGame: GameStyle, twodim: Boolean, mainName: Option[String], subName: Option[String],
+ spName: Option[String], mainImage: Option[BufferedImage], subImage: Option[BufferedImage], spImage: Option[BufferedImage]): IO[JVMImage] = {
+ val renderer = Splooge3KitGen.renderKit
+ val daMainName = mainName.getOrElse(mainWeapon.name)
+ val daSubName = subName.getOrElse(sub.name)
+ val daSpName = spName.getOrElse(special.name)
+
+ for {
+ mainImg <- mainImage.map(it => JVMImage(it).pure[IO]).getOrElse(weaponPath(mainWeapon, mainStyle, twodim) >>= Image.loadFromResource)
+ subImg <- subImage.map(it => JVMImage(it).pure[IO]).getOrElse(Image.loadFromResource(otherPath(sub, subGame)))
+ spImg <- spImage.map(it => JVMImage(it).pure[IO]).getOrElse(Image.loadFromResource(otherPath(special, spGame)))
+ res <- renderer(daMainName, mainImg, daSubName, subImg, daSpName, spImg, None, None)
+ } yield res.asInstanceOf[JVMImage]
+}
+import javax.swing.JFileChooser
+import java.io.File
+
+object App extends IOSwingApp {
+ def render = for {
+ (mainWeapon, mainStyle, do2D, mainImage, mainName, mainDropdown) <- BuildMainWeaponDropdown(Mains.weapons).toResource
+ (sub, subGame, subImage, subName, subDropdown) <- BuildOtherWeaponsDropdown("Sub", Subs.weapons).toResource
+ (special, spGame, spImage, spName, spDropdown) <- BuildOtherWeaponsDropdown("Special", Specials.weapons).toResource
+ win <- window(
+ title := "Splatoon 3 Kit Generator",
+ lookAndFeel := UIManager.getInstalledLookAndFeels().find(_.getName() == "GTK+").map(_.getClassName()).getOrElse(UIManager.getSystemLookAndFeelClassName()),
+ box(
+ mainDropdown,
+ subDropdown,
+ spDropdown,
+ button(
+ text := "Generate!",
+ onBtnClick --> {
+ _.evalMap { _ =>
+ (mainWeapon.get, mainStyle.get, sub.get, subGame.get, special.get,
+ spGame.get, do2D.get, mainName.get, subName.get, spName.get, mainImage.get, subImage.get,
+ spImage.get).tupled
+ }.evalMap { (main, mainStyle, sub, subGame, special, spGame, do2D, mainName, subName, spName, mainImage, subImage, spImage) =>
+ for {
+ _ <- IO.println(main.name)
+ img <- renderKit(main, mainStyle, sub, subGame, special, spGame, do2D, mainName, subName, spName, mainImage, subImage, spImage)
+ } yield img
+ }.foreach(img => FileChooser[IO].save.flatMap(file => file.traverse(it => IO.blocking { ImageIO.write(img.inner, "png", it) }).void ) )
+ }
+ )
+ ),
+
+ )
+
+ } yield win
+
+}