First commit

This commit is contained in:
Rohan Sircar 2020-08-27 19:58:18 +05:30
parent 56dac9a58a
commit e3abe03456
19 changed files with 759 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
*.class
*.log
# sbt specific
.cache/
.history/
.lib/
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/
metals.sbt
.metals
.bloop
# Scala-IDE specific
.scala_dependencies
.worksheet
.idea/
.vscode
/project/project

1
.scalafmt.conf Normal file
View File

@ -0,0 +1 @@
version = "2.6.4"

24
UNLICENSE Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

61
build.sbt Normal file
View File

@ -0,0 +1,61 @@
// Name of the project
name := "ScalaFX Hello World"
// Project version
version := "14-R19"
// Version of Scala used by the project
scalaVersion := "2.13.3"
// Add dependency on ScalaFX library
libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19"
resolvers += Resolver.sonatypeRepo("snapshots")
enablePlugins(JavaFxPlugin)
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.1.4",
"io.monix" %% "monix" % "3.2.2",
"io.monix" %% "monix-bio" % "1.0.0",
"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0",
"com.softwaremill.sttp.client" %% "core" % "2.2.5",
"com.softwaremill.sttp.client" %% "monix" % "2.2.5",
"com.softwaremill.sttp.client" %% "circe" % "2.2.5",
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
"com.github.valskalla" %% "odin-monix" % "0.8.1",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.8",
"com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-slf4j" % "0.8.1"
)
scalacOptions ++= Seq(
"-unchecked",
"-deprecation",
"-Xcheckinit",
"-encoding",
"utf8",
"-feature",
"-Ywarn-unused:imports"
)
// Fork a new JVM for 'run' and 'test:run', to avoid JavaFX double initialization problems
fork := true
// Determine OS version of JavaFX binaries
lazy val osName = System.getProperty("os.name") match {
case n if n.startsWith("Linux") => "linux"
case n if n.startsWith("Mac") => "mac"
case n if n.startsWith("Windows") => "win"
case _ => throw new Exception("Unknown platform!")
}
// Add JavaFX dependencies
lazy val javaFXModules =
Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
libraryDependencies ++= javaFXModules.map(m =>
"org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName
)

2
project/build.properties Normal file
View File

@ -0,0 +1,2 @@
sbt.version=1.3.10

6
project/plugin.sbt Normal file
View File

@ -0,0 +1,6 @@
scalacOptions ++= Seq("-unchecked", "-deprecation")
// [https://github.com/sbt/sbteclipse]
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
// addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
addSbtPlugin("com.quadstingray" % "sbt-javafx" % "1.5.2")

View File

@ -0,0 +1,5 @@
javafx-dispatcher {
type = "Dispatcher"
executor = "akka.dispatch.gui.JavaFXEventThreadExecutorServiceConfigurator"
throughput = 1
}

View File

@ -0,0 +1,219 @@
package nova.monadic_sfx
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import monix.eval.Task
import monix.execution.Scheduler
// import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend
// import sttp.client._
// import sttp.client.circe._
// import io.circe.generic.auto._
import scala.util.Failure
import scala.util.Success
import akka.{actor => classic}
import nova.monadic_sfx.executors._
import cats.effect.Resource
import nova.monadic_sfx.models._
import nova.monadic_sfx.ui.DefaultUI
import nova.monadic_sfx.http.Backend
import nova.monadic_sfx.modules.MainModule
import scalafx.stage.Stage
import scalafx.scene.layout.FlowPane
import nova.monadic_sfx.pages.LoginPage
import scala.concurrent.duration._
import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend
import scalafx.Includes._
import scala.concurrent.Await
import akka.actor.typed.scaladsl.adapter._
import scalafx.application.Platform
import io.odin.syntax._
// import io.odin._
import io.odin.monix._
import nova.monadic_sfx.pages.HomePage
object ScalaFXHelloWorld extends JFXApp with MainModule {
val logger = consoleLogger().withAsyncUnsafe()
lazy val schedulers: Schedulers = new Schedulers()
implicit lazy val defaultScheduler: Scheduler = schedulers.fx
lazy val backendTask = AsyncHttpClientMonixBackend()
lazy val actorSystemTask = Task {
classic.ActorSystem(
name = "FXActorSystem"
)
}
lazy val application = for {
_ <- logger.info("Starting application..")
backend <- backendTask
actorSystem <- actorSystemTask
// to spawn child actors
// _ <- Task { actorSystem.spawn() }
appStage <- Task { makePrimaryStage(backend, actorSystem) }
// splash screen
_ <- Task {
// this stage refers to implicit jfx stage
// makes this impure, but I can't think of a better way right now
stage = appStage
}
// wait 2 seconds before showing home screen
d <- deps
fib1 <- d.send().start
_ <- Task.sleep(2.seconds)
_ <- Task {
// appStage.maximized = true
appStage.height = 800
appStage.width = 800
appStage
.scene()
.setRoot(
LoginPage(appStage, backend, actorSystem)
)
}
// _ <- fib1.join
} yield ()
application.runToFuture
.onComplete(res =>
res match {
case Failure(exception) => {
println("Application start failed. Reason -")
exception.printStackTrace()
}
case Success(value) => println("Application started Successfully")
}
)
// Task
// .suspend {
// val program = Task {
// stage = new PrimaryStage {
// // initStyle(StageStyle.Unified)
// title = "ScalaFX Hello World"
// scene = defaultUI.scene
// }
// }
// val backendResource = AsyncHttpClientMonixBackend
// .resource()
// .use { implicit backend =>
// Task
// .suspend(
// (for {
// req <-
// basicRequest
// .get(uri"https://httpbin.org/get")
// .response(asJson[HttpBinResponse])
// .send()
// } yield println(req)) >>
// Task(println(Thread.currentThread().getName()))
// )
// // .delayExecution(1.second)
// }
// .executeOn(Scheduler.global)
// val akkaResource = Resource
// .make(Task {
// classic.ActorSystem(
// name = "FXActorSystem"
// )
// })(sys => Task(println("Shutting down actor system")) >> Task(sys.terminate()))
// .use { implicit system =>
// // system.spa
// // system.typed
// // val javaFxActor = system.actorOf(
// // Props[JavaFxActor]().withDispatcher("javafx-dispatcher"),
// // "javaFxActor"
// // )
// // val swingActor = system.actorOf(
// // Props[SwingActor]().withDispatcher("swing-dispatcher"),
// // "swingActor"
// // )
// Task.unit
// }
// .delayExecution(1.second)
// backendResource.start >> akkaResource.start >>
// program.to[Task].asyncBoundary >>
// Task(println(Thread.currentThread().getName()))
// .executeOn(Scheduler.global)
// Task.parZip3(
// program.to[Task].executeOn(defaultScheduler),
// // backendResource,
// // dummyRequester.send(),
// // akkaResource,
// Task(println(Thread.currentThread().getName()))
// .executeOn(schedulers.cpu)
// )
// }
// .runToFuture
// .onComplete(res =>
// res match {
// case Failure(exception) => {
// println("Application start failed. Reason -")
// exception.printStackTrace()
// }
// case Success(value) => println("Application started Successfully")
// }
// )
// new TaskApp {
// override protected def scheduler: Scheduler =
// JFXExecutionContexts.javaFxScheduler
// override def run(args: List[String]): Task[ExitCode] =
// Task.suspend {
// Task {
// AsyncHttpClientMonixBackend().flatMap(implicit backend => {
// val req = RequestPayload("").asJson
// basicRequest.get(uri"").body(req).send()
// })
// } >>
// program.to[Task].executeOn(JFXExecutionContexts.javaFxScheduler) >>
// Task(println(Thread.currentThread().getName()))
// .executeOn(Scheduler.global) >>
// // Task.unit.asyncBoundary >>
// Task.pure(ExitCode.Success)
// }
// }
// }
// Task.sleep(3.seconds).flatMap { _ =>
//
// (for {
// req <-
// basicRequest
// .get(uri"https://httpbin.org/get")
// .response(asJson[HttpBinResponse])
// .send()
// } yield println(req)) >> Task(
// println(Thread.currentThread().getName())
// ) >>
// backend.close()
// }
//
def test(stage: Stage) = {
stage.scene().setRoot(new FlowPane())
}
def makePrimaryStage(
backend: AppTypes.HttpBackend,
actorSystem: classic.ActorSystem
) = {
new PrimaryStage {
scene = new DefaultUI().scene
onCloseRequest = () => {
val f2 = actorSystem.terminate()
val f1 = backend.close().runToFuture
println("Closing backend")
Await.result(f1, 3.seconds)
println("Closing actor system")
println(Thread.currentThread().getName())
Await.result(f2, 3.seconds)
println("Actor system closed")
Platform.exit()
System.exit(0)
}
}
}
}

View File

@ -0,0 +1,13 @@
package nova.monadic_sfx
import monix.eval.Task
import sttp.client.SttpBackend
import monix.reactive.Observable
import sttp.client.asynchttpclient.WebSocketHandler
import java.nio.ByteBuffer
trait AppTypes {}
object AppTypes {
type HttpBackend =
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler]
}

View File

@ -0,0 +1,84 @@
package nova.monadic_sfx.executors
import akka.dispatch.{
DispatcherPrerequisites,
ExecutorServiceFactory,
ExecutorServiceConfigurator
}
import com.typesafe.config.Config
import java.util.concurrent.{
ExecutorService,
AbstractExecutorService,
ThreadFactory,
TimeUnit
}
import java.util.Collections
import javax.swing.SwingUtilities
import javafx.application.Platform
import monix.execution.Scheduler
import scala.concurrent.ExecutionContext
import java.util.concurrent.Executor
// First we wrap invokeLater/runLater as an ExecutorService
abstract class GUIExecutorService extends AbstractExecutorService {
def execute(command: Runnable): Unit
def shutdown(): Unit = ()
def shutdownNow() = Collections.emptyList[Runnable]
def isShutdown = false
def isTerminated = false
def awaitTermination(l: Long, timeUnit: TimeUnit) = true
}
object JavaFXExecutorService extends GUIExecutorService {
override def execute(command: Runnable) = Platform.runLater(command)
}
object SwingExecutorService extends GUIExecutorService {
override def execute(command: Runnable) = SwingUtilities.invokeLater(command)
}
class JavaFXEventThreadExecutorServiceConfigurator(
config: Config,
prerequisites: DispatcherPrerequisites
) extends ExecutorServiceConfigurator(config, prerequisites) {
private val f = new ExecutorServiceFactory {
def createExecutorService: ExecutorService = JavaFXExecutorService
}
def createExecutorServiceFactory(
id: String,
threadFactory: ThreadFactory
): ExecutorServiceFactory = f
}
// Then we create an ExecutorServiceConfigurator so that Akka can use our SwingExecutorService for the dispatchers
class SwingEventThreadExecutorServiceConfigurator(
config: Config,
prerequisites: DispatcherPrerequisites
) extends ExecutorServiceConfigurator(config, prerequisites) {
private val f = new ExecutorServiceFactory {
def createExecutorService: ExecutorService = SwingExecutorService
}
def createExecutorServiceFactory(
id: String,
threadFactory: ThreadFactory
): ExecutorServiceFactory = f
}
object JFXExecutionContexts {
val javaFxExecutionContext: ExecutionContext =
ExecutionContext.fromExecutor(new Executor {
def execute(command: Runnable): Unit = {
Platform.runLater(command)
}
})
val fxScheduler =
Scheduler(javaFxExecutionContext)
}

View File

@ -0,0 +1,9 @@
package nova.monadic_sfx.executors
import monix.execution.Scheduler
class Schedulers(
val blockingIO: Scheduler = Scheduler.io(),
val cpu: Scheduler = Scheduler.global,
val fx: Scheduler = JFXExecutionContexts.fxScheduler
)

View File

@ -0,0 +1,10 @@
package nova.monadic_sfx.http
import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend
import nova.monadic_sfx.AppTypes
object Backend {
val backend = AsyncHttpClientMonixBackend
.resource()
def apply() = { backend }
}

View File

@ -0,0 +1,27 @@
package nova.monadic_sfx.http.requests
import nova.monadic_sfx.AppTypes
import nova.monadic_sfx.AppTypes.HttpBackend
import monix.eval.Task
import sttp.client._
import sttp.client.circe._
import io.circe.generic.auto._
import nova.monadic_sfx.models._
class DummyRequest(backend: HttpBackend) extends AppTypes {
private implicit val _backend = backend
def send() = {
Task
.suspend(
(for {
req <-
basicRequest
.get(uri"https://httpbin.org/get")
.response(asJson[HttpBinResponse])
.send()
} yield println(req)) >>
Task(println(Thread.currentThread().getName()))
)
}
}

View File

@ -0,0 +1,8 @@
package nova.monadic_sfx.models
case class RequestPayload(data: String)
final case class HttpBinResponse(
url: String,
origin: String,
headers: Map[String, String]
)

View File

@ -0,0 +1,79 @@
package nova.monadic_sfx.modules
import nova.monadic_sfx.executors.Schedulers
import monix.execution.Scheduler
import nova.monadic_sfx.http.requests.DummyRequest
import monix.eval.Task
import akka.{actor => classic}
import cats.effect.Resource
import nova.monadic_sfx.AppTypes
trait MainModule {
import com.softwaremill.macwire._
// val schedulers: Schedulers = new Schedulers()
// implicit val defaultScheduler: Scheduler = schedulers.fx
// val program =
// for {
// backend <- Backend()
// fxActorSystem <-
// Resource
// .make(Task {
// classic.ActorSystem(
// name = "FXActorSystem"
// )
// })(sys => Task(sys.terminate()))
// } yield {
// val dummyRequester = wire[DummyRequest]
// dummyRequester.send()
// }
// val program = Backend().use(backend =>
// Resource
// .make(Task {
// classic.ActorSystem(
// name = "FXActorSystem"
// )
// })(sys => Task(sys.terminate()))
// .use { implicit system =>
// // system.spa
// // system.typed
// // val javaFxActor = system.actorOf(
// // Props[JavaFxActor]().withDispatcher("javafx-dispatcher"),
// // "javaFxActor"
// // )
// // val swingActor = system.actorOf(
// // Props[SwingActor]().withDispatcher("swing-dispatcher"),
// // "swingActor"
// // )
// Task(new DummyRequest(backend)) >>
// Task.unit
// }
// )
def schedulers: Schedulers
def defaultScheduler: Scheduler
def backendTask: Task[AppTypes.HttpBackend]
def actorSystemTask: Task[classic.ActorSystem]
def deps =
for {
backend <- backendTask
actorSystem <- actorSystemTask
dummyRequesterTask <- Task {
wireDeps(backend, actorSystem)
}
} yield dummyRequesterTask
def wireDeps(
backend: AppTypes.HttpBackend,
system: classic.ActorSystem
): DummyRequest = {
wire[DummyRequest]
// new DummyRequest(backend)
}
}

View File

@ -0,0 +1,39 @@
package nova.monadic_sfx.pages
import nova.monadic_sfx.AppTypes
import scalafx.scene.control.TextField
import scalafx.scene.control._
import scalafx.scene.layout.VBox
import scalafx.scene.Node
import scalafx.Includes._
import scalafx.scene.layout.HBox
import scalafx.scene.text.Text
import scalafx.scene.Parent
import scalafx.application.JFXApp.PrimaryStage
class HomePage(
backend: AppTypes.HttpBackend,
system: akka.actor.ActorSystem,
onLogout: () => Unit
) {
private lazy val root = new HBox {
children = List(
new Text {
text = "hello"
},
new Button {
text = "logout"
onAction = () => onLogout()
}
)
}
def render = root
}
object HomePage {
def apply(
backend: AppTypes.HttpBackend,
system: akka.actor.ActorSystem,
onLogout: () => Unit
): Parent =
new HomePage(backend, system, onLogout).render
}

View File

@ -0,0 +1,57 @@
package nova.monadic_sfx.pages
import nova.monadic_sfx.AppTypes
import scalafx.scene.control.TextField
import scalafx.scene.control._
import scalafx.scene.layout.VBox
import scalafx.scene.Node
import scalafx.Includes._
import scalafx.scene.Parent
import scalafx.application.JFXApp.PrimaryStage
// import io.odin.syntax._
// import _root_.monix.eval.Task
// import io.odin.monix._
// import javafx.beans.property.ObjectProperty
// import javafx.event.{ActionEvent, EventHandler}
class LoginPage(
appStage: PrimaryStage,
backend: AppTypes.HttpBackend,
system: akka.actor.ActorSystem
) {
//pure function callbacks, but with side effects still
private def onLogout(stage: PrimaryStage) = {
println("logging out")
stage.scene().setRoot(render)
}
private def onLogin(stage: PrimaryStage) = {
println("logging in")
stage
.scene()
.setRoot(HomePage(backend, system, () => onLogout(appStage)))
}
private lazy val root = new VBox {
children = Seq(
new TextField {
text = "username"
editable = true
},
new TextField {
text = "password"
},
new Button {
text = "Login"
onAction = () => onLogin(appStage)
}
)
}
def render: Parent = root
}
object LoginPage {
def apply(
appStage: PrimaryStage,
backend: AppTypes.HttpBackend,
system: akka.actor.ActorSystem
) = new LoginPage(appStage, backend, system).render
}

View File

@ -0,0 +1,55 @@
package nova.monadic_sfx.ui
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.effect.DropShadow
import scalafx.scene.layout.HBox
import scalafx.scene.paint.Color._
import scalafx.scene.paint._
import scalafx.scene.text.Text
import monix.eval.Coeval
class DefaultUI {
val scene =
new Scene {
fill = Color.rgb(38, 38, 38)
content = new HBox {
padding = Insets(50, 80, 50, 80)
children = Seq(
new Text {
text = "Scala"
style = "-fx-font: normal bold 100pt sans-serif"
fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed))
},
new Text {
text = "FX"
style = "-fx-font: italic bold 100pt sans-serif"
fill = new LinearGradient(
endX = 0,
stops = Stops(White, DarkGray)
)
effect = new DropShadow {
color = DarkGray
radius = 15
spread = 0.25
}
}
)
}
}
// val program = Coeval
// .suspend {
// Coeval(println("hello")) >>
// Coeval(println(Thread.currentThread().getName())) >>
// Coeval {
// stage = new PrimaryStage {
// // initStyle(StageStyle.Unified)
// title = "ScalaFX Hello World"
// scene = scn
// }
// }
// }
}

View File

@ -0,0 +1,36 @@
package org.slf4j.impl
import cats.effect.{ContextShift, Clock, Effect, IO, Timer}
import io.odin._
import io.odin.slf4j.OdinLoggerBinder
import scala.concurrent.ExecutionContext
import _root_.monix.execution.Scheduler
//effect type should be specified inbefore
//log line will be recorded right after the call with no suspension
class StaticLoggerBinder extends OdinLoggerBinder[IO] {
val ec: ExecutionContext = Scheduler.global
implicit val timer: Timer[IO] = IO.timer(ec)
implicit val clock: Clock[IO] = timer.clock
implicit val cs: ContextShift[IO] = IO.contextShift(ec)
implicit val F: Effect[IO] = IO.ioEffect
val loggers: PartialFunction[String, Logger[IO]] = {
case "some.external.package.SpecificClass" =>
consoleLogger[IO](minLevel = Level.Warn) //disable noisy external logs
case "org.asynchttpclient.netty.channel.DefaultChannelPool" =>
consoleLogger[IO](minLevel = Level.Warn)
case _ => //if wildcard case isn't provided, default logger is no-op
consoleLogger[IO]()
}
}
object StaticLoggerBinder extends StaticLoggerBinder {
var REQUESTED_API_VERSION: String = "1.7"
def getSingleton: StaticLoggerBinder = this
}