9 Commits
e2f5dc15c3
...
c59d48f6ec
Author | SHA1 | Message | Date |
---|---|---|---|
Rohan Sircar | c59d48f6ec |
Added split combinator to actionObservable
|
3 years ago |
Rohan Sircar | 935ca358e6 |
Many changes
Updated store code - wrapped fold function in Task Added JSON logging functionality to store middleware Initial attempt at creating filter combinator for fx observables Made ListStore code use Effects Made a router using the store pattern Misc updates to fx monix implicits |
3 years ago |
Rohan Sircar | 857fd03bf1 |
Cleanup and added some methods to actionObservable
|
3 years ago |
Rohan Sircar | f95f50574e |
Added proper todo buttons reactively connected
|
3 years ago |
Rohan Sircar | f95ecc2cb3 |
Wrapped error handle in UIO
|
3 years ago |
Rohan Sircar | 1f06c536c8 |
Updated monix version to 3.3.0 (was 3.2.2)
|
3 years ago |
Rohan Sircar | 8f1fe0cc84 |
Cleanup and refactoring
|
3 years ago |
Rohan Sircar | b988ad267e |
Added store pattern using monix
also added relevant implicits to use it |
3 years ago |
Rohan Sircar | 536f1b0af3 |
Added scalafx wrappers for jfoenix and ikonli
|
3 years ago |
48 changed files with 2631 additions and 377 deletions
-
2.gitignore
-
61build.sbt
-
2project/build.properties
-
1project/plugin.sbt
-
53src/main/scala/nova/monadic_sfx/Main.scala
-
189src/main/scala/nova/monadic_sfx/MainApp.scala
-
38src/main/scala/nova/monadic_sfx/MainModule.scala
-
11src/main/scala/nova/monadic_sfx/Types.scala
-
70src/main/scala/nova/monadic_sfx/actors/ActorModule.scala
-
20src/main/scala/nova/monadic_sfx/actors/TestActor.scala
-
26src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala
-
25src/main/scala/nova/monadic_sfx/executors/Schedulers.scala
-
4src/main/scala/nova/monadic_sfx/http/HttpModule.scala
-
38src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala
-
98src/main/scala/nova/monadic_sfx/implicits/ActionObservable.scala
-
44src/main/scala/nova/monadic_sfx/implicits/FontIcon.scala
-
42src/main/scala/nova/monadic_sfx/implicits/JFXButton.scala
-
36src/main/scala/nova/monadic_sfx/implicits/JFXListCell.scala
-
48src/main/scala/nova/monadic_sfx/implicits/JFXListView.scala
-
29src/main/scala/nova/monadic_sfx/implicits/JFXSpinner.scala
-
40src/main/scala/nova/monadic_sfx/implicits/JFXTextArea.scala
-
35src/main/scala/nova/monadic_sfx/implicits/JFXTextField.scala
-
62src/main/scala/nova/monadic_sfx/implicits/JFXTreeTableView.scala
-
327src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
-
9src/main/scala/nova/monadic_sfx/implicits/MenuItem.scala
-
50src/main/scala/nova/monadic_sfx/implicits/package.scala
-
7src/main/scala/nova/monadic_sfx/models/DummyModels.scala
-
65src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala
-
146src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
-
129src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
-
18src/main/scala/nova/monadic_sfx/ui/UiModule.scala
-
107src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
-
378src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala
-
89src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
-
134src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
-
95src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
-
28src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala
-
34src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala
-
2src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala
-
4src/main/scala/nova/monadic_sfx/util/Action.scala
-
21src/main/scala/nova/monadic_sfx/util/IOUtils.scala
-
63src/main/scala/nova/monadic_sfx/util/Misc.scala
-
75src/main/scala/nova/monadic_sfx/util/reactive/Middlewares.scala
-
23src/main/scala/nova/monadic_sfx/util/reactive/MonixProSubject.scala
-
39src/main/scala/nova/monadic_sfx/util/reactive/Reducer.scala
-
97src/main/scala/nova/monadic_sfx/util/reactive/Store.scala
-
18src/main/scala/nova/monadic_sfx/util/reactive/package.scala
-
76src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala
@ -1,2 +1,2 @@ |
|||
sbt.version=1.3.10 |
|||
sbt.version=1.4.3 |
|||
|
@ -1,37 +1,32 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import monix.eval.Task |
|||
// import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend |
|||
// import sttp.client._ |
|||
// import sttp.client.circe._ |
|||
// import io.circe.generic.auto._ |
|||
import nova.monadic_sfx.executors._ |
|||
import cats.effect.Resource |
|||
import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend |
|||
import io.odin.syntax._ |
|||
import io.odin.monix._ |
|||
import monix.eval.TaskApp |
|||
import _root_.monix.bio.BIOApp |
|||
import _root_.monix.bio.Task |
|||
import _root_.monix.bio.UIO |
|||
import cats.effect.ExitCode |
|||
import cats.implicits._ |
|||
import cats.effect.Resource |
|||
import com.softwaremill.macwire._ |
|||
import io.odin._ |
|||
import nova.monadic_sfx.executors._ |
|||
// import nova.monadic_sfx.util.IOUtils._ |
|||
// import sttp.client.httpclient.monix.HttpClientMonixBackend |
|||
object Main extends MainModule with BIOApp { |
|||
|
|||
object Main extends MainModule with TaskApp { |
|||
def appResource(startTime: Long) = |
|||
for { |
|||
implicit0(logger: Logger[Task]) <- makeLogger |
|||
schedulers = new Schedulers() |
|||
// backend <- Resource.make( |
|||
// toIO(HttpClientMonixBackend()(schedulers.async)) |
|||
// )(c => toIO(c.close())) |
|||
// actorSystem <- actorSystemResource(logger) |
|||
_ <- Resource.liftF(wire[MainApp].program) |
|||
} yield () |
|||
|
|||
override def run(args: List[String]): Task[ExitCode] = { |
|||
// val startTime = Task.clock |
|||
// .monotonic(scala.concurrent.duration.MILLISECONDS) |
|||
// .map(Duration.fromNanos(_)) |
|||
lazy val appResource = for { |
|||
// clock <- Resource.liftF(Task(Task.clock)) |
|||
logger <- consoleLogger().withAsync() |
|||
backend <- AsyncHttpClientMonixBackend.resource() |
|||
actorSystem <- actorSystemResource(logger) |
|||
reqs <- Resource.liftF(Task(wireWith(requesters _))) |
|||
schedulers <- Resource.liftF(Task(new Schedulers())) |
|||
fxApp <- wireWith(fxAppResource _) |
|||
} yield (fxApp) |
|||
appResource |
|||
.use(fxApp => Task(fxApp.main(args.toArray))) |
|||
override def run(args: List[String]): UIO[ExitCode] = |
|||
appResource(System.currentTimeMillis()) |
|||
.use(_ => Task.unit) |
|||
.onErrorHandleWith(ex => UIO(ex.printStackTrace())) |
|||
.as(ExitCode.Success) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,189 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import com.softwaremill.macwire._ |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import nova.monadic_sfx.implicits.JFXButton |
|||
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
|||
import nova.monadic_sfx.ui.MyFxApp |
|||
import nova.monadic_sfx.ui.components.router.BrainNotWorking |
|||
import nova.monadic_sfx.ui.components.router.FXRouter |
|||
import nova.monadic_sfx.ui.components.todo.TodoListStore |
|||
import nova.monadic_sfx.ui.components.todo.TodoListView |
|||
import org.gerweck.scalafx.util._ |
|||
import scalafx.Includes._ |
|||
import scalafx.application.JFXApp.PrimaryStage |
|||
import scalafx.beans.property.ObjectProperty |
|||
import scalafx.beans.property.StringProperty |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.geometry.Insets |
|||
import scalafx.scene.Scene |
|||
import scalafx.scene.control.TableColumn |
|||
import scalafx.scene.control.TableView |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.paint.Color |
|||
import scalafx.scene.shape.Rectangle |
|||
|
|||
class MainApp( |
|||
// spawnProtocol: ActorSystem[SpawnProtocol.Command], |
|||
schedulers: Schedulers, |
|||
startTime: Long |
|||
)(implicit logger: Logger[Task]) { |
|||
|
|||
lazy val addTodoButton = new JFXButton { |
|||
text = "Add" |
|||
} |
|||
|
|||
lazy val addTodoObs = addTodoButton.observableAction |
|||
|
|||
// lazy val todoListView = TodoListView.defaultListView |
|||
|
|||
lazy val _scene = new Scene { |
|||
root = new HBox { |
|||
padding = Insets(20) |
|||
content = new Rectangle { |
|||
width = 400 |
|||
height = 200 |
|||
fill = Color.DeepSkyBlue |
|||
} |
|||
children ++= Seq( |
|||
// new JFXButton { |
|||
// text = "DummyButton" |
|||
// }, |
|||
// new JFXButton { |
|||
// text = "DummyButton2" |
|||
// }, |
|||
// addTodoButton, |
|||
// Test.ttv |
|||
// todoListView |
|||
) |
|||
} |
|||
} |
|||
|
|||
private lazy val stage = new PrimaryStage { |
|||
title = "Simple ScalaFX App" |
|||
scene = _scene |
|||
width = 1000 |
|||
height = 400 |
|||
} |
|||
|
|||
// implicit val l = logger |
|||
// implicit val sp = spawnProtocol |
|||
|
|||
val program = for { |
|||
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage) |
|||
// _ <- Task(fxApp.stage = stage) |
|||
// .executeOn(schedulers.fx) |
|||
// .delayExecution(2000.millis) |
|||
// todoComponent <- createTodoComponent |
|||
// _ <- toIO( |
|||
// addTodoObs |
|||
// .mapEval(_ => |
|||
// toTask(todoComponent.send(TodoListComponent.Add(Todo(1, "blah")))) |
|||
// ) |
|||
// .completedL |
|||
// .executeOn(schedulers.fx) |
|||
// .startAndForget |
|||
// ) |
|||
_ <- createTodoComponent.executeOn(schedulers.fx) |
|||
router <- Task(BrainNotWorking.router) |
|||
routerStore <- router.store(BrainNotWorking.Page.Home, logger) |
|||
routerNode <- for { |
|||
node <- |
|||
Task |
|||
.deferAction(implicit s => |
|||
Task(new HBox { |
|||
children <-- router |
|||
.render(BrainNotWorking.resolver)(routerStore) |
|||
.map(_.delegate) |
|||
}) |
|||
) |
|||
.executeOn(schedulers.fx) |
|||
_ <- Task.deferFuture( |
|||
routerStore.onNext(FXRouter.Replace(BrainNotWorking.Page.UserHome(1))) |
|||
) |
|||
|
|||
} yield node |
|||
// _ <- |
|||
// BrainNotWorking |
|||
// .routerTask(logger) |
|||
// .flatMap(node => Task(_scene.getChildren += node)) |
|||
// .executeOn(schedulers.fx) |
|||
_ <- Task(_scene.getChildren += routerNode).executeOn(schedulers.fx) |
|||
_ <- logger.info( |
|||
s"Application started in ${(System.currentTimeMillis() - startTime) / 1000f} seconds" |
|||
) |
|||
_ <- fxAppFib.join |
|||
} yield () |
|||
|
|||
// def createTodoComponent: Task[TodoListComponent] = { |
|||
// for { |
|||
// channel <- |
|||
// ConcurrentChannel |
|||
// .of[Task, TodoListComponent.Complete, TodoListComponent.Command] |
|||
// scheduler = schedulers.fx |
|||
// lv <- TodoListView.defaultListView2.executeOn(scheduler) |
|||
// // todoLV = new TodoListView(lv) |
|||
// todoComponent <- wire[TodoListComponent.Props].create |
|||
// // TODO make this a "message pass" instead of mutating directly |
|||
// _ <- Task(_scene.getChildren += lv).executeOn(scheduler) |
|||
// // _ <- toIO( |
|||
// // delObs |
|||
// // .doOnNext(_ => toTask(logger.debug("Pressed delete"))) |
|||
// // .doOnNext(todo => |
|||
// // toTask( |
|||
// // for { |
|||
// // _ <- logger.debug(s"Got todo $todo") |
|||
// // _ <- todoComponent.send(TodoListComponent.Delete(todo.id)) |
|||
// // // _ <- Task.sequence( |
|||
// // // lst.map(todo => |
|||
// // // todoComponent.send(TodoListComponent.Delete(todo.id)) |
|||
// // // ) |
|||
// // // ) |
|||
// // } yield () |
|||
// // ) |
|||
// // ) |
|||
// // .completedL |
|||
// // ).startAndForget |
|||
// // _ <- toIO( |
|||
// // editObs |
|||
// // .doOnNext(_ => toTask(logger.debug("Pressed edit"))) |
|||
// // .completedL |
|||
// // ).startAndForget |
|||
// } yield todoComponent |
|||
// } |
|||
|
|||
def createTodoComponent: Task[Unit] = |
|||
for { |
|||
store <- TodoListStore(logger) |
|||
rootNode <- TodoListView(store) |
|||
_ <- Task(_scene.getChildren += rootNode) |
|||
} yield () |
|||
|
|||
} |
|||
|
|||
class TestModel(_name: String, _age: Int) { |
|||
val name = StringProperty(_name).readOnly |
|||
val age = ObjectProperty(_age).readOnly |
|||
} |
|||
|
|||
object Test { |
|||
val items = ObservableBuffer( |
|||
new TestModel("hmm", 1), |
|||
new TestModel("hmm2", 2) |
|||
) |
|||
|
|||
val ttv = new TableView[TestModel](items) { |
|||
columns ++= Seq( |
|||
new TableColumn[TestModel, String] { |
|||
text = "Name" |
|||
cellValueFactory = { _.value.name } |
|||
}, |
|||
new TableColumn[TestModel, Int] { |
|||
text = "Age" |
|||
cellValueFactory = { _.value.age } |
|||
} |
|||
) |
|||
} |
|||
} |
@ -1,7 +1,41 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import scala.concurrent.duration._ |
|||
|
|||
import _root_.monix.bio.Task |
|||
import cats.implicits._ |
|||
import io.odin._ |
|||
import io.odin.config._ |
|||
import io.odin.syntax._ |
|||
import nova.monadic_sfx.actors.ActorModule |
|||
import nova.monadic_sfx.ui.UiModule |
|||
import nova.monadic_sfx.http.HttpModule |
|||
import nova.monadic_sfx.ui.UiModule |
|||
import nova.monadic_sfx.util.reactive.Middlewares |
|||
|
|||
trait MainModule extends ActorModule with UiModule with HttpModule { |
|||
def routerLogger(defaultLogger: Logger[Task], storeLogger: Logger[Task]) = |
|||
enclosureRouting[Task]( |
|||
"nova.monadic_sfx.util.reactive.Middlewares" -> storeLogger, |
|||
"nova.monadic_sfx.util.reactive.Store" -> storeLogger |
|||
) |
|||
.withFallback(defaultLogger) |
|||
.withAsync() |
|||
|
|||
trait MainModule extends ActorModule with UiModule with HttpModule |
|||
def makeLogger = |
|||
for { |
|||
defaultLogger <- consoleLogger[Task]() |
|||
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task]( |
|||
"application.log" |
|||
).withAsync() |
|||
middlewareLogger <- |
|||
consoleLogger[ |
|||
Task |
|||
](formatter = Middlewares.format) |
|||
.withMinimalLevel(Level.Trace) |
|||
.withAsync() |+| fileLogger[Task]( |
|||
"stores.log", |
|||
formatter = Middlewares.format |
|||
).withAsync() |
|||
routerLogger <- routerLogger(defaultLogger, middlewareLogger) |
|||
} yield (routerLogger) |
|||
} |
@ -1,13 +1,14 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import java.nio.ByteBuffer |
|||
|
|||
import monix.eval.Task |
|||
import sttp.client.SttpBackend |
|||
import monix.reactive.Observable |
|||
import sttp.client.asynchttpclient.WebSocketHandler |
|||
import java.nio.ByteBuffer |
|||
import sttp.client.SttpBackend |
|||
import sttp.client.httpclient.WebSocketHandler |
|||
|
|||
trait AppTypes {} |
|||
object AppTypes { |
|||
trait AppTypes { |
|||
type HttpBackend = |
|||
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] |
|||
} |
|||
object AppTypes extends AppTypes {} |
@ -1,74 +1,28 @@ |
|||
package nova.monadic_sfx.actors |
|||
|
|||
import io.odin.Logger |
|||
import monix.eval.Task |
|||
import cats.effect.Resource |
|||
import akka.actor.typed.scaladsl.Behaviors |
|||
import com.softwaremill.macwire._ |
|||
import akka.util.Timeout |
|||
import scala.concurrent.duration._ |
|||
import scala.concurrent.Future |
|||
|
|||
import akka.actor.typed._ |
|||
import akka.actor.typed.scaladsl.AskPattern._ |
|||
import scala.concurrent.Await |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import akka.util.Timeout |
|||
import cats.effect.Resource |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
|
|||
trait ActorModule { |
|||
import scala.concurrent.ExecutionContext |
|||
|
|||
implicit val timeout: Timeout = Timeout(3.seconds) |
|||
implicit def timeout: Timeout = Timeout(3.seconds) |
|||
|
|||
def actorSystemResource( |
|||
logger: Logger[Task] |
|||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] = |
|||
Resource.make(logger.info("Creating Actor System") >> Task { |
|||
ActorSystem(HelloWorldMain(), name = "FXActorSystem") |
|||
ActorSystem(SpawnProtocol(), name = "FXActorSystem") |
|||
})(sys => |
|||
logger.info("Shutting down actor system") >> Task( |
|||
sys.terminate() |
|||
) >> logger.info("Actor System terminated") |
|||
for { |
|||
_ <- Task(sys.terminate()) |
|||
_ <- Task.fromFuture(sys.whenTerminated) |
|||
_ <- logger.info("Actor System Terminated") |
|||
} yield () |
|||
) |
|||
|
|||
// def actorsResource( |
|||
// system: ActorSystem[SpawnProtocol.Command], |
|||
// logger: Logger[Task], |
|||
// schedulers: Schedulers |
|||
// ): Resource[Task, Task[ActorRef[Counter.Command]]] = { |
|||
// implicit val ec: ExecutionContext = system.executionContext |
|||
// implicit val scheduler = system.scheduler |
|||
// Resource.make( |
|||
// Task { |
|||
// val actor = Task.deferFuture { |
|||
// system.ask[ActorRef[Counter.Command]]( |
|||
// SpawnProtocol.Spawn( |
|||
// behavior = Counter(), |
|||
// name = "counterActor", |
|||
// // DispatcherSelector.fromConfig("javafx-dispatcher"), |
|||
// // Props.empty, |
|||
// _ |
|||
// ) |
|||
// ) |
|||
// } |
|||
// // system. |
|||
// actor |
|||
// } |
|||
// )(actorTask => |
|||
// for { |
|||
// actor <- actorTask |
|||
// _ <- logger.info("Stopping actor counter") |
|||
// t <- Task(actor ! Counter.Stop) |
|||
// _ <- logger.info("Counter actor stopped") |
|||
// } yield () |
|||
// ) |
|||
// } |
|||
|
|||
} |
|||
object HelloWorldMain { |
|||
def apply(): Behavior[SpawnProtocol.Command] = |
|||
Behaviors.setup { context => |
|||
// Start initial tasks |
|||
// context.spawn(...) |
|||
|
|||
SpawnProtocol() |
|||
} |
|||
} |
@ -1,9 +1,28 @@ |
|||
package nova.monadic_sfx.executors |
|||
|
|||
import com.typesafe.scalalogging.Logger |
|||
import monix.execution.Scheduler |
|||
import monix.execution.UncaughtExceptionReporter |
|||
import monix.execution.schedulers.TracingScheduler |
|||
|
|||
class Schedulers( |
|||
val blockingIO: Scheduler = Scheduler.io(), |
|||
val cpu: Scheduler = Scheduler.global, |
|||
val fx: Scheduler = JFXExecutionContexts.fxScheduler |
|||
val blocking: Scheduler = TracingScheduler( |
|||
Scheduler |
|||
.io() |
|||
.withUncaughtExceptionReporter(Schedulers.reporter) |
|||
), |
|||
val async: Scheduler = Scheduler.traced |
|||
.withUncaughtExceptionReporter(Schedulers.reporter), |
|||
val fx: Scheduler = TracingScheduler( |
|||
JFXExecutionContexts.fxScheduler |
|||
.withUncaughtExceptionReporter(Schedulers.reporter) |
|||
) |
|||
) |
|||
|
|||
object Schedulers { |
|||
val reporter = UncaughtExceptionReporter { ex => |
|||
val logger = Logger[Schedulers] |
|||
logger.error("Uncaught exception", ex) |
|||
} |
|||
|
|||
} |
@ -1,43 +1,17 @@ |
|||
package nova.monadic_sfx.http.requests |
|||
|
|||
import nova.monadic_sfx.AppTypes |
|||
|
|||
import nova.monadic_sfx.AppTypes.HttpBackend |
|||
import monix.eval.Task |
|||
import nova.monadic_sfx.models._ |
|||
import sttp.client._ |
|||
import sttp.client.circe._ |
|||
import io.circe.generic.auto._ |
|||
import nova.monadic_sfx.models._ |
|||
import cats.data.EitherT |
|||
|
|||
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 (req) |
|||
} |
|||
|
|||
} |
|||
def send = |
|||
basicRequest |
|||
.get(uri"https://httpbin.org/get") |
|||
.response(asJson[HttpBinResponse]) |
|||
.send() |
|||
|
|||
def test() = { |
|||
for { |
|||
res <- send() |
|||
res3 <- Task { res.body } |
|||
res2 <- Task { |
|||
res3.fold( |
|||
err => { |
|||
err.toString() |
|||
}, |
|||
value => value.toString() |
|||
) |
|||
} |
|||
} yield (res3) |
|||
} |
|||
} |
@ -0,0 +1,98 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import monix.execution.Cancelable |
|||
import monix.execution.Scheduler |
|||
import monix.execution.cancelables.CompositeCancelable |
|||
import monix.reactive.Observable |
|||
import monix.reactive.Observer |
|||
import monix.{eval => me} |
|||
|
|||
class ActionObservableExecutor[T]( |
|||
private val delegate: Observable[T] |
|||
) extends AnyVal { |
|||
def -->(sub: Observer[T])(implicit s: Scheduler) = |
|||
delegate |
|||
.doOnNext(el => me.Task(sub.onNext(el))) |
|||
.subscribe() |
|||
|
|||
} |
|||
class ActionObservableBuilder[A]( |
|||
private val observableAction: Observable[A] |
|||
) extends AnyVal { |
|||
def useLazyEval[T](v: => me.Task[T]) = |
|||
new ActionObservableExecutor[T](observableAction.mapEval(_ => v)) |
|||
|
|||
def useEval[T](cb: A => me.Task[T]) = |
|||
new ActionObservableExecutor[T]( |
|||
observableAction.mapEval(cb) |
|||
) |
|||
|
|||
def useIterableEval[T](cb: A => collection.immutable.Iterable[T]) = |
|||
new ActionObservableExecutor[T]( |
|||
observableAction.flatMap(a => |
|||
Observable.suspend(Observable.fromIterable(cb(a))) |
|||
) |
|||
) |
|||
|
|||
def doOnNext(cb: A => me.Task[Unit]): ActionObservableBuilder[A] = |
|||
new ActionObservableBuilder(observableAction.doOnNext(cb)) |
|||
|
|||
def mapEval[B](cb: A => me.Task[B]) = |
|||
new ActionObservableBuilder(observableAction.mapEval(cb)) |
|||
|
|||
def underlying = observableAction |
|||
|
|||
// Caution: Experimental stuff below.. |
|||
|
|||
def useEval2[B, C](f: A => me.Task[B], g: A => me.Task[C]) = |
|||
new ActionObservableExecutor[(B, C)]( |
|||
observableAction.publishSelector(conn => |
|||
conn |
|||
.mapEval(f) |
|||
.switchMap(b => |
|||
conn.mapEval(a => |
|||
for { |
|||
c <- g(a) |
|||
} yield (b, c) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
|
|||
def bifurcate[B, C]( |
|||
f: ActionObservableBuilder[A] => B, |
|||
g: ActionObservableBuilder[A] => C |
|||
)(implicit s: Scheduler) = |
|||
observableAction |
|||
.publishSelector(conn => |
|||
Observable( |
|||
Observable.unit.doOnNext(_ => |
|||
me.Task(f(new ActionObservableBuilder[A](conn))) >> me.Task.unit |
|||
), |
|||
Observable.unit.doOnNext(_ => |
|||
me.Task(g(new ActionObservableBuilder[A](conn))) >> me.Task.unit |
|||
) |
|||
).merge |
|||
) |
|||
.subscribe() |
|||
|
|||
def split( |
|||
lst: (ActionObservableBuilder[A] => Cancelable)* |
|||
)(implicit s: Scheduler): Cancelable = { |
|||
val comp = CompositeCancelable() |
|||
comp += observableAction |
|||
.publishSelector(conn => |
|||
Observable( |
|||
lst.map(f => |
|||
Observable.unit.doOnNext(_ => |
|||
me.Task( |
|||
comp += f(new ActionObservableBuilder[A](conn)) |
|||
) >> me.Task.unit |
|||
) |
|||
): _* |
|||
).merge |
|||
) |
|||
.subscribe() |
|||
} |
|||
|
|||
} |
@ -0,0 +1,44 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import javafx.{scene => jfxs} |
|||
import org.kordamp.ikonli.{javafx => ikonlifx} |
|||
import scalafx.scene.paint.Paint |
|||
import scalafx.scene.text.Text |
|||
|
|||
object FontIcon { |
|||
implicit def sfxText2jfx(v: FontIcon): jfxs.text.Text = |
|||
if (v != null) v.delegate else null |
|||
|
|||
} |
|||
|
|||
// extends Shape(delegate) |
|||
// with PositionDelegate[ikonlifx.FontIcon] |
|||
// with SFXDelegate[ikonlifx.FontIcon] |
|||
|
|||
class FontIcon(override val delegate: ikonlifx.FontIcon = new ikonlifx.FontIcon) |
|||
extends Text(delegate) { |
|||
|
|||
// def iconCode_=(v: Ikon) = delegate.setIconCode(v) |
|||
|
|||
def iconColor = delegate.getIconColor() |
|||
|
|||
def iconColor_=(color: Paint) = delegate.setIconColor(color) |
|||
|
|||
def iconSize = delegate.getIconSize() |
|||
|
|||
def iconSize_=(size: Int) = delegate.setIconSize(size) |
|||
|
|||
def iconLiteral = delegate.getIconLiteral() |
|||
|
|||
def iconLiteral_=(literal: IconLiteral) = |
|||
delegate.setIconLiteral(literal.value) |
|||
|
|||
def iconLiteral_=(literal: String) = delegate.setIconLiteral(literal) |
|||
|
|||
} |
|||
|
|||
sealed abstract class IconLiteral(val value: String) |
|||
object IconLiteral { |
|||
// fab-accusoft |
|||
case object Gmi10k extends IconLiteral("gmi-10k") |
|||
} |
@ -0,0 +1,42 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import javafx.{scene => jfxs} |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.ObjectProperty |
|||
import scalafx.scene.Node |
|||
import scalafx.scene.control.Button |
|||
|
|||
import jfxs.{paint => jfxsp} |
|||
|
|||
object JFXButton { |
|||
implicit def sfxButton2jfx(v: JFXButton): jfoenixc.JFXButton = |
|||
if (v != null) v.delegate else null |
|||
} |
|||
|
|||
class JFXButton( |
|||
override val delegate: jfoenixc.JFXButton = new jfoenixc.JFXButton |
|||
) extends Button(delegate) { |
|||
|
|||
/** |
|||
* Creates a button with the specified text as its label. |
|||
*/ |
|||
def this(text: String) = this(new jfoenixc.JFXButton(text)) |
|||
|
|||
/** |
|||
* Creates a button with the specified text and icon for its label. |
|||
*/ |
|||
def this(text: String, graphic: Node) = |
|||
this(new jfoenixc.JFXButton(text, graphic)) |
|||
|
|||
def ripplerFill: ObjectProperty[jfxsp.Paint] = |
|||
jfxObjectProperty2sfx(delegate.ripplerFillProperty) |
|||
|
|||
def ripplerFill_=(b: jfxsp.Paint): Unit = { |
|||
ripplerFill() = b |
|||
} |
|||
|
|||
def obsAction = |
|||
new ActionObservableBuilder(this.observableAction()) |
|||
|
|||
} |
@ -0,0 +1,36 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import javafx.scene.{control => jfxsc} |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.ReadOnlyObjectProperty |
|||
import scalafx.delegate.SFXDelegate |
|||
import scalafx.scene.control.IndexedCell |
|||
import scalafx.scene.control.ListView |
|||
|
|||
object JFXListCell { |
|||
implicit def sfxListCell2jfx[T]( |
|||
l: JFXListCell[T] |
|||
): jfoenixc.JFXListCell[T] = |
|||
if (l != null) l.delegate else null |
|||
} |
|||
|
|||
class JFXListCell[T]( |
|||
override val delegate: jfoenixc.JFXListCell[T] = new jfoenixc.JFXListCell[T] |
|||
) extends IndexedCell(delegate) |
|||
with SFXDelegate[jfoenixc.JFXListCell[T]] { |
|||
|
|||
/** |
|||
* The ListView associated with this Cell. |
|||
*/ |
|||
def listView: ReadOnlyObjectProperty[jfxsc.ListView[T]] = |
|||
delegate.listViewProperty |
|||
|
|||
/** |
|||
* Updates the ListView associated with this Cell. |
|||
*/ |
|||
def updateListView(listView: ListView[T]): Unit = { |
|||
delegate.updateListView(listView) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,48 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import monix.execution.Scheduler |
|||
import monix.reactive.Observable |
|||
import scalafx.Includes._ |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.scene.control.ListView |
|||
|
|||
object JFXListView { |
|||
implicit def sfxListView2jfx[T](l: JFXListView[T]): jfoenixc.JFXListView[T] = |
|||
if (l != null) l.delegate else null |
|||
} |
|||
|
|||
// extends Control(delegate) |
|||
// with SFXDelegate[jfoenixc.JFXListView[T]] |
|||
|
|||
class JFXListView[T]( |
|||
override val delegate: jfoenixc.JFXListView[T] = new jfoenixc.JFXListView[T] |
|||
) extends ListView[T] { |
|||
|
|||
// def items_=( |
|||
// v: Observable[ObservableBuffer[T]] |
|||
// )(implicit s: Scheduler): Unit = { |
|||
// v.foreach { items() = _ } |
|||
// } |
|||
|
|||
def items_=( |
|||
v: Observable[Seq[T]] |
|||
)(implicit s: Scheduler): Unit = { |
|||
v |
|||
.map { |
|||
// case buf: ObservableBuffer[T] => buf |
|||
case other => ObservableBuffer.from(other) |
|||
} |
|||
// .map(myDiff(items(), _)) |
|||
.foreach { items() = _ } |
|||
} |
|||
|
|||
def depth = delegate.depthProperty() |
|||
def depth_=(depth: Int) = delegate.setDepth(depth) |
|||
|
|||
def expanded = delegate.expandedProperty() |
|||
def expanded_=(v: Boolean) = expanded() = v |
|||
|
|||
|
|||
|
|||
} |
@ -0,0 +1,29 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import scalafx.scene.control.ProgressIndicator |
|||
|
|||
object JFXSpinner { |
|||
implicit def sfxSpinner2jfx( |
|||
v: JFXSpinner |
|||
): jfoenixc.JFXSpinner = if (v != null) v.delegate else null |
|||
|
|||
} |
|||
|
|||
// extends Control(delegate) |
|||
// with SFXDelegate[jfoenixc.JFXSpinner] |
|||
|
|||
/** |
|||
* Wraps [[JFoenix JFXSpinner]] |
|||
*/ |
|||
class JFXSpinner( |
|||
override val delegate: jfoenixc.JFXSpinner = new jfoenixc.JFXSpinner |
|||
) extends ProgressIndicator(delegate) { |
|||
|
|||
def radius = delegate.getRadius() |
|||
def radius_=(radius: Double) = delegate.setRadius(radius) |
|||
|
|||
def startingAngle = delegate.startingAngleProperty() |
|||
def startingAngle_=(angle: Double) = delegate.setStartingAngle(angle) |
|||
|
|||
} |
@ -0,0 +1,40 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.BooleanProperty |
|||
import scalafx.scene.control.TextArea |
|||
import scalafx.scene.paint.Paint |
|||
|
|||
object JFXTextArea { |
|||
implicit def sfxTextArea2jfx(v: JFXTextArea): jfoenixc.JFXTextArea = |
|||
if (v != null) v.delegate else null |
|||
} |
|||
// extends TextInputControl(delegate) |
|||
// with SFXDelegate[jfoenixc.JFXTextArea] |
|||
class JFXTextArea( |
|||
override val delegate: jfoenixc.JFXTextArea = new jfoenixc.JFXTextArea() |
|||
) extends TextArea(delegate) { |
|||
|
|||
/** |
|||
* Creates a TextArea with initial text content. |
|||
* |
|||
* @param text - A string for text content. |
|||
*/ |
|||
def this(text: String) = this(new jfoenixc.JFXTextArea(text)) |
|||
|
|||
def labelFloat = delegate.labelFloatProperty() |
|||
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v) |
|||
|
|||
def focusColor: Paint = delegate.getFocusColor() |
|||
def focusColor_=(color: Paint) = delegate.setFocusColor(color) |
|||
|
|||
def unFocusColor = delegate.getUnFocusColor() |
|||
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color) |
|||
|
|||
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty() |
|||
|
|||
def disableAnimation_=(disable: Boolean) = |
|||
delegate.setDisableAnimation(disable) |
|||
|
|||
} |
@ -0,0 +1,35 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.BooleanProperty |
|||
import scalafx.scene.control.TextField |
|||
import scalafx.scene.paint.Paint |
|||
|
|||
object JFXTextField { |
|||
implicit def sfxTextField2jfx(v: JFXTextField): jfoenixc.JFXTextField = |
|||
if (v != null) v.delegate else null |
|||
} |
|||
|
|||
// TextInputControl(delegate) |
|||
// with AlignmentDelegate[jfoenixc.JFXTextField] |
|||
// with SFXDelegate[jfoenixc.JFXTextField] { |
|||
|
|||
class JFXTextField( |
|||
override val delegate: jfoenixc.JFXTextField = new jfoenixc.JFXTextField |
|||
) extends TextField(delegate) { |
|||
|
|||
def labelFloat = delegate.labelFloatProperty() |
|||
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v) |
|||
|
|||
def focusColor: Paint = delegate.getFocusColor() |
|||
def focusColor_=(color: Paint) = delegate.setFocusColor(color) |
|||
|
|||
def unFocusColor = delegate.getUnFocusColor() |
|||
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color) |
|||
|
|||
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty() |
|||
|
|||
def disableAnimation_=(disable: Boolean) = |
|||
delegate.setDisableAnimation(disable) |
|||
} |
@ -0,0 +1,62 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject |
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import javafx.scene.{control => jfxsc} |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.scene.control.TreeItem |
|||
import scalafx.scene.control.TreeTableView |
|||
|
|||
class RecursiveTreeItem[G <: RecursiveTreeObject[G]]( |
|||
override val delegate: jfoenixc.RecursiveTreeItem[G] = |
|||
new jfoenixc.RecursiveTreeItem[G]((item: RecursiveTreeObject[G]) => |
|||
item.getChildren() |
|||
) |
|||
) extends TreeItem[G](delegate) { |
|||
def this(value: G) = |
|||
this( |
|||
new jfoenixc.RecursiveTreeItem[G]( |
|||
value, |
|||
(item: RecursiveTreeObject[G]) => item.getChildren() |
|||
) |
|||
) |
|||
def this(items: ObservableBuffer[G]) = |
|||
this( |
|||
new jfoenixc.RecursiveTreeItem[G]( |
|||
items, |
|||
(item: RecursiveTreeObject[G]) => item.getChildren() |
|||
) |
|||
) |
|||
} |
|||
|
|||
object RecursiveTreeItem { |
|||
implicit def sfxTreeItem2jfxTreeItem[G <: RecursiveTreeObject[G]]( |
|||
v: RecursiveTreeItem[G] |
|||
): jfoenixc.RecursiveTreeItem[G] = v.delegate |
|||
} |
|||
|
|||
// @formatter:off |
|||
class JFXTreeTableView[S <: RecursiveTreeObject[S]]( |
|||
override val delegate: jfoenixc.JFXTreeTableView[S] = new jfoenixc.JFXTreeTableView[S] |
|||
) extends TreeTableView(delegate) { |
|||
|
|||
|
|||
def this(root: TreeItem[S]) = this(new jfoenixc.JFXTreeTableView[S](root)) |
|||
// def this(root: TreeItem[S], items: ObservableBuffer[S]) = this(new jfoenixc.JFXTreeTableView[S](root, items)) |
|||
|
|||
// @formatter:on |
|||
def currentItemsCount = delegate.currentItemsCountProperty() |
|||
def predicate = delegate.predicateProperty() |
|||
// delegate.set |
|||
|
|||
// override def treeColumn_=(v: TreeTableColumn[S, _]): Unit = ??? |
|||
// delegate.setTreeColumn() |
|||
} |
|||
|
|||
// @formatter:off |
|||
object JFXTreeTableView { |
|||
implicit def sfxTreeTableView2jfx[S <: RecursiveTreeObject[S]]( |
|||
v: JFXTreeTableView[S] |
|||
): jfxsc.TreeTableView[S] = if (v != null) v.delegate else null |
|||
} |
|||
// @formatter:on |
@ -0,0 +1,327 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import javafx.beans.property.ObjectProperty |
|||
import javafx.collections.ObservableList |
|||
import javafx.event.ActionEvent |
|||
import javafx.event.EventHandler |
|||
import javafx.scene.{input => jfxsi} |
|||
import javafx.{event => jfxe} |
|||
import monix.bio.Task |
|||
import monix.eval.Coeval |
|||
import monix.execution.Ack |
|||
import monix.execution.Cancelable |
|||
import monix.execution.Scheduler |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
import monix.reactive.Observable |
|||
import monix.reactive.Observer |
|||
import monix.reactive.OverflowStrategy |
|||
import monix.tail.Iterant |
|||
import monix.{eval => me} |
|||
import org.gerweck.scalafx.util._ |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.Property |
|||
import scalafx.beans.property.ReadOnlyProperty |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.event.subscriptions.Subscription |
|||
import scalafx.scene.Scene |
|||
import scalafx.scene.control.ButtonBase |
|||
import scalafx.scene.control.MenuItem |
|||
|
|||
object JavaFXMonixObservables { |
|||
|
|||
implicit final class SceneObservables(private val scene: Scene) |
|||
extends AnyVal { |
|||
|
|||
def observableMousePressed(): Observable[jfxsi.MouseEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { |
|||
override def handle(event: jfxsi.MouseEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
scene.onMousePressed = l |
|||
c := Cancelable(() => |
|||
scene.removeEventHandler( |
|||
jfxsi.MouseEvent.MOUSE_PRESSED, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
|
|||
def observableMouseDragged(): Observable[jfxsi.MouseEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { |
|||
override def handle(event: jfxsi.MouseEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
scene.onMouseDragged = l |
|||
c := Cancelable(() => |
|||
scene.removeEventHandler( |
|||
jfxsi.MouseEvent.MOUSE_DRAGGED, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
implicit final class BindObs[T, J](private val prop: Property[T, J]) |
|||
extends AnyVal { |
|||
def -->(op: Observer[T]) = { |
|||
op.onNext(prop.value) |
|||
} |
|||
|
|||
def ==>(op: Property[T, J]) = { |
|||
op <== prop |
|||
} |
|||
|
|||
def <--(obs: Observable[T])(implicit s: Scheduler) = { |
|||
obs.doOnNextF(v => Coeval(prop.value = v)).subscribe() |
|||
} |
|||
|
|||
def asOption = prop.map(Option(_)) |
|||
|
|||
def observableChange[J1 >: J]: Observable[J1] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
|
|||
val canc = |
|||
prop.onChange((a, b, c1) => |
|||
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() |
|||
) |
|||
|
|||
c := Cancelable(() => canc.cancel()) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
implicit final class BindObs2[A](private val prop: ObjectProperty[A]) |
|||
extends AnyVal { |
|||
|
|||
def -->(sub: Observer[A]) = |
|||
prop.onChange((a, b, c) => if (c != null) sub.onNext(c)) |
|||
|
|||
def -->(op: Property[A, A]) = { |
|||
prop.onChange((a, b, c) => if (c != null) op() = c) |
|||
} |
|||
|
|||
def <--(obs: Observable[A])(implicit s: Scheduler) = { |
|||
obs.doOnNextF(v => Coeval(prop() = v)).subscribe() |
|||
} |
|||
|
|||
def observableChange[J1 >: A]: Observable[J1] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
|
|||
val canc = prop.onChange((_, _, c1) => |
|||
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() |
|||
) |
|||
|
|||
c := Cancelable(() => canc.cancel()) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
implicit final class ObservableListExt[A]( |
|||
private val buffer: ObservableList[A] |
|||
) extends AnyVal { |
|||
|
|||
// def -->(sub: Observer[A]) = |
|||
// buffer.onChange((a, b, c) => if (c != null) sub.onNext(c)) |
|||
|
|||
// def -->(op: Property[A, A]) = { |
|||
// buffer.onChange((a, b, c) => if (c != null) op() = c) |
|||
// } |
|||
|
|||
def <--(obs: Observable[A])(implicit s: Scheduler) = { |
|||
obs |
|||
.doOnNextF(v => |
|||
for { |
|||
_ <- Coeval(buffer.clear()) |
|||
_ <- Coeval(buffer += v) |
|||
} yield () |
|||
) |
|||
.subscribe() |
|||
} |
|||
|
|||
def observableChange[J1 >: A]: Observable[J1] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
|
|||
implicit val s = sub.scheduler |
|||
|
|||
val canc = |
|||
buffer.onChange((buf, _) => |
|||
loop(sub, buf.toIterable.iterator, c).runToFuture |
|||
) |
|||
|
|||
c := Cancelable(() => canc.cancel()) |
|||
c |
|||
} |
|||
} |
|||
|
|||
private def loop( |
|||
sub: Observer[A], |
|||
it: Iterator[A], |
|||
c: Cancelable |
|||
): Task[Unit] = |
|||
if (it.hasNext) { |
|||
val next = it.next() |
|||
Task.deferFuture(sub.onNext(next)).flatMap { |
|||
case Ack.Continue => loop(sub, it, c) |
|||
case Ack.Stop => Task(c.cancel()) |
|||
} |
|||
} else Task.unit |
|||
} |
|||
|
|||
implicit final class BindObs3[T, J](private val prop: ReadOnlyProperty[T, J]) |
|||
extends AnyVal { |
|||
def -->(op: Observer[T]) = { |
|||
op.onNext(prop.value) |
|||
} |
|||
|
|||
def ==>(op: Property[T, J]) = { |
|||
op <== prop |
|||
} |
|||
|
|||
def observableChange[J1 >: J]: Observable[J1] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
|
|||
val canc = prop.onChange((a, b, c1) => |
|||
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() |
|||
) |
|||
|
|||
c := Cancelable(() => canc.cancel()) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
implicit final class ObjectPropertyObservableListExt[A]( |
|||
private val prop: ObjectProperty[ObservableList[A]] |
|||
) extends AnyVal { |
|||
def <--(obs: Observable[Seq[A]])(implicit s: Scheduler) = { |
|||
obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe() |
|||
} |
|||
|
|||
def -->(sub: Observer[A])(implicit s: Scheduler) = { |
|||
val c = SingleAssignCancelable() |
|||
val subs: Subscription = prop.onChange((a, b, c1) => |
|||
if (c1 != null) |
|||
Iterant[Task] |
|||
.fromIterable(c1.toIterable) |
|||
.consume |
|||
.use(consume(sub, c, _)) |
|||
.runToFuture |
|||
) |
|||
c := Cancelable(() => subs.cancel()) |
|||
} |
|||
|
|||
private def loop(sub: Observer[A], it: Iterator[A]): Task[Unit] = |
|||
if (it.hasNext) { |
|||
val next = it.next() |
|||
Task.deferFuture(sub.onNext(next)).flatMap { |
|||
case Ack.Continue => loop(sub, it) |
|||
case Ack.Stop => Task.unit |
|||
} |
|||
} else Task.unit |
|||
|
|||
private def consume( |
|||
sub: Observer[A], |
|||
c: Cancelable, |
|||
consumer: Iterant.Consumer[Task, A] |
|||
): Task[Unit] = |
|||
consumer.pull.flatMap { |
|||
case Left(value) => Task.unit |
|||
case Right(value) => |
|||
Task.deferFuture(sub.onNext(value)).flatMap { |
|||
case Ack.Continue => consume(sub, c, consumer) |
|||
case Ack.Stop => Task(c.cancel()) |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
implicit final class ObjectPropertyActionEvent( |
|||
private val prop: ObjectProperty[EventHandler[ActionEvent]] |
|||
) extends AnyVal { |
|||
// def <--(obs: Observable[ActionEvent])(implicit s: Scheduler) = { |
|||
// obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe() |
|||
// } |
|||
|
|||
// def -->(sub: Observer[ActionEvent]) = |
|||
// prop(). |
|||
|
|||
} |
|||
|
|||
// def emit(prop: ObjectProperty[EventHandler[ActionEvent]]) = |
|||
|
|||
implicit final class OnActionObservable( |
|||
private val button: ButtonBase |
|||
) extends AnyVal { |
|||
|
|||
def observableAction: Observable[jfxe.ActionEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new jfxe.EventHandler[jfxe.ActionEvent] { |
|||
override def handle(event: jfxe.ActionEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
button.onAction = l |
|||
c := Cancelable(() => |
|||
button.removeEventHandler( |
|||
jfxe.ActionEvent.ACTION, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
implicit final class MenuItemActionObservable( |
|||
private val item: MenuItem |
|||
) extends AnyVal { |
|||
|
|||
def observableAction: Observable[jfxe.ActionEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new jfxe.EventHandler[jfxe.ActionEvent] { |
|||
override def handle(event: jfxe.ActionEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
item.onAction = l |
|||
c := Cancelable(() => |
|||
item.removeEventHandler( |
|||
jfxe.ActionEvent.ACTION, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
package nova.monadic_sfx.implicits |
|||
|
|||
import scalafx.scene.{control => sfxc} |
|||
|
|||
import JavaFXMonixObservables._ |
|||
|
|||
class MenuItem extends sfxc.MenuItem { |
|||
def obsAction = new ActionObservableBuilder(this.observableAction) |
|||
} |
@ -0,0 +1,50 @@ |
|||
package nova.monadic_sfx |
|||
|
|||
import javafx.event.ActionEvent |
|||
import monix.execution.Ack |
|||
import monix.execution.Cancelable |
|||
import monix.reactive.Observable |
|||
import monix.reactive.OverflowStrategy |
|||
import scalafx.scene.control._ |
|||
|
|||
package object implicits { |
|||
|
|||
implicit class MyButtonExt(val button: Button) extends AnyVal { |
|||
def observableAction(): Observable[ActionEvent] = { |
|||
import monix.execution.cancelables.SingleAssignCancelable |
|||
Observable.create(OverflowStrategy.Unbounded) { sub => |
|||
val c = SingleAssignCancelable() |
|||
val l = new javafx.event.EventHandler[ActionEvent] { |
|||
override def handle(event: ActionEvent): Unit = { |
|||
if (sub.onNext(event) == Ack.Stop) c.cancel() |
|||
} |
|||
} |
|||
|
|||
button.onAction = l |
|||
c := Cancelable(() => |
|||
button.removeEventHandler( |
|||
ActionEvent.ACTION, |
|||
l |
|||
) |
|||
) |
|||
c |
|||
} |
|||
} |
|||
} |
|||
|
|||
// implicit class NodeExt(val node: Node) { |
|||
// def lookup2[T <: SFXDelegate[_]]( |
|||
// selector: String |
|||
// )(implicit c: ClassTag[T]) = { |
|||
// val t = c.runtimeClass |
|||
// Option(node.delegate.lookup(selector)) match { |
|||
// case Some(value) => |
|||
// if (value.getClass == t) Some(value) else None |
|||
// case None => None |
|||
// } |
|||
// } |
|||
|
|||
// val x = node.lookup2("") |
|||
// } |
|||
|
|||
} |
@ -1,7 +1,10 @@ |
|||
package nova.monadic_sfx.models |
|||
|
|||
case class RequestPayload(data: String) |
|||
final case class HttpBinResponse( |
|||
import io.circe.generic.JsonCodec |
|||
|
|||
final case class RequestPayload(data: String) |
|||
|
|||
@JsonCodec final case class HttpBinResponse( |
|||
url: String, |
|||
origin: String, |
|||
headers: Map[String, String] |
|||
|
@ -1,55 +1,52 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import nova.monadic_sfx.implicits.JFXSpinner |
|||
import scalafx.geometry.Insets |
|||
import scalafx.geometry.Pos |
|||
import scalafx.scene.Scene |
|||
import scalafx.scene.effect.DropShadow |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.layout.VBox |
|||
import scalafx.scene.paint.Color._ |
|||
import scalafx.scene.paint._ |
|||
import scalafx.scene.text.Text |
|||
import monix.eval.Coeval |
|||
|
|||
class DefaultUI { |
|||
object DefaultUI { |
|||
val scene = |
|||
new Scene { |
|||
fill = Color.rgb(38, 38, 38) |
|||
content = new HBox { |
|||
content = new VBox { |
|||
alignment = Pos.Center |
|||
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) |
|||
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 |
|||
} |
|||
} |
|||
) |
|||
effect = new DropShadow { |
|||
color = DarkGray |
|||
radius = 15 |
|||
spread = 0.25 |
|||
} |
|||
}, |
|||
new JFXSpinner { |
|||
radius = 50 |
|||
// style = "-fx-text-fill: red" |
|||
} |
|||
) |
|||
} |
|||
} |
|||
|
|||
// 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 |
|||
|
|||
// } |
|||
// } |
|||
// } |
|||
|
|||
} |
@ -1,129 +1,41 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import scalafx.application.JFXApp |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import monix.execution.Scheduler |
|||
import monix.eval.Task |
|||
import nova.monadic_sfx.screens.LoginScreen |
|||
import nova.monadic_sfx.AppTypes |
|||
import scala.concurrent.duration._ |
|||
import io.odin.Logger |
|||
import monix.execution.Callback |
|||
import com.softwaremill.macwire._ |
|||
import nova.monadic_sfx.http.Requesters |
|||
|
|||
import akka.actor.typed._ |
|||
import nova.monadic_sfx.actors.Counter |
|||
import akka.util.Timeout |
|||
|
|||
class MyFxApp( |
|||
logger: Logger[Task], |
|||
backend: AppTypes.HttpBackend, |
|||
actorSystem: ActorSystem[SpawnProtocol.Command], |
|||
requesters: Requesters, |
|||
schedulers: Schedulers |
|||
) extends JFXApp { |
|||
|
|||
implicit lazy val defaultScheduler: Scheduler = schedulers.fx |
|||
|
|||
lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith( |
|||
MyFxApp.makeCounterActor _ |
|||
) |
|||
|
|||
lazy val application = |
|||
for { |
|||
appStage <- Task(wireWith(UiModule.makePrimaryStage _)) |
|||
|
|||
// _ <- Task { |
|||
// val counterActor = testActor(actorSystem) |
|||
// counterActor ! (Counter.Increment) |
|||
// } |
|||
// ta <- testActor2(actorSystem) |
|||
// actor <- |
|||
// actorTask.bracket(actor => Task(actor ! (Counter.Increment)))(actor => |
|||
// Task(actor ! (Counter.Stop)) |
|||
// ) |
|||
// actor <- actorTask |
|||
actor <- fxActor |
|||
_ <- Task(actor ! (Counter.Increment)) |
|||
_ <- Task { stage = appStage } |
|||
_ <- Task.sleep(2.seconds) |
|||
loginScene <- wire[LoginScreen].render |
|||
_ <- Task { |
|||
// appStage.maximized = true |
|||
appStage.height = 800 |
|||
appStage.width = 800 |
|||
appStage |
|||
.scene() |
|||
.setRoot( |
|||
loginScene |
|||
) |
|||
} |
|||
} yield () |
|||
|
|||
// def testActor( |
|||
// system: ActorSystem |
|||
// ): akka.actor.typed.ActorRef[Counter.Command] = { |
|||
// val behaviour: Behavior[Counter.Command] = |
|||
// Behaviors.setup(context => wire[Counter]) |
|||
// system.spawn( |
|||
// behaviour, |
|||
// "CounterActor", |
|||
// DispatcherSelector.fromConfig("javafx-dispatcher") |
|||
// ) |
|||
// } |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import nova.monadic_sfx.ui.DefaultUI |
|||
import scalafx.application.JFXApp |
|||
import scalafx.application.JFXApp.PrimaryStage |
|||
|
|||
application.timed.runAsync( |
|||
new Callback[Throwable, (FiniteDuration, Unit)] { |
|||
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) { |
|||
|
|||
override def onSuccess(value: (FiniteDuration, Unit)): Unit = { |
|||
val (duration, _) = value |
|||
println( |
|||
s"Application started successfully in ${duration.toSeconds} seconds" |
|||
) |
|||
} |
|||
private lazy val internal = new JFXApp { |
|||
stage = new PrimaryStage { |
|||
scene = DefaultUI.scene |
|||
} |
|||
} |
|||
|
|||
override def onError(e: Throwable): Unit = { |
|||
println("Application start failed. Reason -") |
|||
e.printStackTrace() |
|||
} |
|||
// def stage = Task(internal.stage) |
|||
|
|||
} |
|||
) |
|||
// def stage_=(stage: PrimaryStage) = Task(internal.stage = stage) |
|||
|
|||
override def stopApp() = { |
|||
val stop = for { |
|||
actor <- fxActor |
|||
_ <- logger.info("Stopping actor counter") |
|||
// _ <- Task.fromFuture { actor.ask[Counter.Value](Counter.GetValue) } |
|||
t <- Task(actor ! Counter.Stop) |
|||
// _ <- Task.sleep(1.second) |
|||
_ <- logger.info("Counter actor stopped") |
|||
} yield () |
|||
stop.runAsyncAndForget |
|||
// Platform.exit() |
|||
} |
|||
} |
|||
// def useInternal[T](f: JFXApp => Task[T]): Task[T] = |
|||
// for { |
|||
// _ <- logger.debug("Request for using internal value") |
|||
// res <- f(internal).executeOn(schedulers.fx) |
|||
// _ <- logger.debug(s"Result was ${res.toString()}") |
|||
// } yield (res) |
|||
|
|||
object MyFxApp { |
|||
def makeCounterActor( |
|||
system: ActorSystem[SpawnProtocol.Command] |
|||
): Task[ActorRef[Counter.Command]] = { |
|||
import akka.actor.typed.scaladsl.AskPattern._ |
|||
import scala.concurrent.ExecutionContext |
|||
def init(stage: => PrimaryStage, delay: FiniteDuration = 200.millis) = |
|||
for { |
|||
_ <- logger.info("Starting FX App") |
|||
fib <- Task(internal.main(Array.empty)).start |
|||
_ <- Task.sleep(200.millis) |
|||
_ <- Task(internal.stage = stage) |
|||
.executeOn(schedulers.fx) |
|||
.delayExecution(delay) |
|||
} yield (this, fib) |
|||
|
|||
implicit val timeout: Timeout = Timeout(3.seconds) |
|||
implicit val ec: ExecutionContext = system.executionContext |
|||
implicit val scheduler = system.scheduler |
|||
Task.fromFuture { |
|||
system.ask( |
|||
SpawnProtocol.Spawn( |
|||
behavior = wireWith(Counter.apply _), |
|||
name = "counterActor", |
|||
DispatcherSelector.fromConfig("javafx-dispatcher"), |
|||
_ |
|||
) |
|||
) |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,129 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import scala.concurrent.duration._ |
|||
|
|||
import akka.actor.typed._ |
|||
import akka.util.Timeout |
|||
import com.softwaremill.macwire._ |
|||
import io.odin.Logger |
|||
import monix.eval.Task |
|||
import monix.execution.Callback |
|||
import monix.execution.Scheduler |
|||
import nova.monadic_sfx.AppTypes |
|||
import nova.monadic_sfx.actors.Counter |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import nova.monadic_sfx.http.Requesters |
|||
import scalafx.application.JFXApp |
|||
|
|||
class MyFxAppOld( |
|||
logger: Logger[Task], |
|||
backend: AppTypes.HttpBackend, |
|||
actorSystem: ActorSystem[SpawnProtocol.Command], |
|||
requesters: Requesters, |
|||
schedulers: Schedulers |
|||
) extends JFXApp { |
|||
|
|||
implicit lazy val defaultScheduler: Scheduler = schedulers.fx |
|||
|
|||
// lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith( |
|||
// MyFxApp.makeCounterActor _ |
|||
// ) |
|||
|
|||
lazy val application = |
|||
for { |
|||
appStage <- Task(wireWith(UiModule.makePrimaryStage _)) |
|||
|
|||
// _ <- Task { |
|||
// val counterActor = testActor(actorSystem) |
|||
// counterActor ! (Counter.Increment) |
|||
// } |
|||
// ta <- testActor2(actorSystem) |
|||
// actor <- |
|||
// actorTask.bracket(actor => Task(actor ! (Counter.Increment)))(actor => |
|||
// Task(actor ! (Counter.Stop)) |
|||
// ) |
|||
// actor <- actorTask |
|||
// actor <- fxActor |
|||
// _ <- Task(actor ! Counter.Increment) |
|||
_ <- Task { stage = appStage } |
|||
_ <- Task.sleep(2.seconds) |
|||
// loginScene <- wire[LoginScreen].render |
|||
// _ <- Task { |
|||
// // appStage.maximized = true |
|||
// appStage.height = 800 |
|||
// appStage.width = 800 |
|||
// appStage |
|||
// .scene() |
|||
// .setRoot( |
|||
// loginScene |
|||
// ) |
|||
// } |
|||
} yield () |
|||
|
|||
// def testActor( |
|||
// system: ActorSystem |
|||
// ): akka.actor.typed.ActorRef[Counter.Command] = { |
|||
// val behaviour: Behavior[Counter.Command] = |
|||
// Behaviors.setup(context => wire[Counter]) |
|||
// system.spawn( |
|||
// behaviour, |
|||
// "CounterActor", |
|||
// DispatcherSelector.fromConfig("javafx-dispatcher") |
|||
// ) |
|||
// } |
|||
|
|||
application.timed.runAsync( |
|||
new Callback[Throwable, (FiniteDuration, Unit)] { |
|||
|
|||
override def onSuccess(value: (FiniteDuration, Unit)): Unit = { |
|||
val (duration, _) = value |
|||
println( |
|||
s"Application started successfully in ${duration.toSeconds} seconds" |
|||
) |
|||
} |
|||
|
|||
override def onError(e: Throwable): Unit = { |
|||
println("Application start failed. Reason -") |
|||
e.printStackTrace() |
|||
} |
|||
|
|||
} |
|||
) |
|||
|
|||
override def stopApp() = { |
|||
// val stop = for { |
|||
// actor <- fxActor |
|||
// _ <- logger.info("Stopping actor counter") |
|||
// // _ <- Task.fromFuture { actor.ask[Counter.Value](Counter.GetValue) } |
|||
// t <- Task(actor ! Counter.Stop) |
|||
// // _ <- Task.sleep(1.second) |
|||
// _ <- logger.info("Counter actor stopped") |
|||
// } yield () |
|||
// stop.runAsyncAndForget |
|||
// // Platform.exit() |
|||
} |
|||
} |
|||
|
|||
object MyFxAppOld { |
|||
def makeCounterActor( |
|||
system: ActorSystem[SpawnProtocol.Command], |
|||
logger: Logger[Task] |
|||
): Task[ActorRef[Counter.Command]] = { |
|||
import akka.actor.typed.scaladsl.AskPattern._ |
|||
import scala.concurrent.ExecutionContext |
|||
|
|||
implicit val timeout: Timeout = Timeout(3.seconds) |
|||
implicit val ec: ExecutionContext = system.executionContext |
|||
implicit val scheduler = system.scheduler |
|||
Task.fromFuture { |
|||
system.ask( |
|||
SpawnProtocol.Spawn( |
|||
behavior = wireWith(Counter.apply _), |
|||
name = "counterActor", |
|||
DispatcherSelector.fromConfig("javafx-dispatcher"), |
|||
_ |
|||
) |
|||
) |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,107 @@ |
|||
package nova.monadic_sfx.ui.components.router |
|||
|
|||
import enumeratum._ |
|||
import io.circe.Encoder |
|||
import io.circe.generic.JsonCodec |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import nova.monadic_sfx.util.IOUtils |
|||
import nova.monadic_sfx.util.reactive.Reducer |
|||
import nova.monadic_sfx.util.reactive.Store |
|||
import scalafx.scene.Parent |
|||
import scalafx.scene.control.Label |
|||
|
|||
object FXRouter { |
|||
|
|||
final case class State[P](page: P) |
|||
|
|||
@JsonCodec |
|||
sealed abstract class Action[T] |
|||
// final case object Init extends Action |
|||
final case class Replace[T](p: T) extends Action[T] |
|||
|
|||
type FXStore[P] = Store[Action[P], State[P]] |
|||
|
|||
// def resolver2 = resolver.lift.andThen(_.getOrElse(notFound)) |
|||
|
|||
// def resolver: PartialFunction[P <: Enum[P]][P, Parent] = { |
|||
// case Home => new TextField |
|||
// } |
|||
|
|||
} |
|||
|
|||
class FXRouter[P <: EnumEntry]( |
|||
)(implicit E: Encoder[P]) { |
|||
import FXRouter._ |
|||
|
|||
def store(initialPage: P, logger: Logger[Task]) = |
|||
Task.deferAction(implicit s => |
|||
Store.createL[Action[P], State[P]]( |
|||
Replace(initialPage), |
|||
State(initialPage), |
|||
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _) |
|||
// Seq(actionLoggerMiddleware(logger, "RouterStore")) |
|||
) |
|||
) |
|||
|
|||
def reducer( |
|||
state: State[P], |
|||
action: Action[P] |
|||
): (State[P], Option[Task[Action[P]]]) = |
|||
action match { |
|||
// case Init => (state, None) |
|||
case Replace(p) => |
|||
(state.copy(page = p), None) |
|||
} |
|||
|
|||
def render( |
|||
resolver: P => Task[Parent] |
|||
)(implicit store: FXStore[P]) = |
|||
store.mapEval { case (_, FXRouter.State(p)) => IOUtils.toTask(resolver(p)) } |
|||
|
|||
def link( |
|||
page: P, |
|||
store: FXStore[P] |
|||
) = { |
|||
store.onNext(Replace(page)) |
|||
} |
|||
} |
|||
|
|||
object BrainNotWorking { |
|||
|
|||
@JsonCodec |
|||
sealed trait Page extends EnumEntry |
|||
object Page extends Enum[Page] { |
|||
val values = findValues |
|||
final case object Home extends Page |
|||
final case class UserHome(id: Int) extends Page |
|||
} |
|||
|
|||
def resolver: PartialFunction[Page, Task[Parent]] = { |
|||
case Page.Home => |
|||
Task(new Label { |
|||
text = "HomePage" |
|||
}) |
|||
case Page.UserHome(id0) => |
|||
Task(new Label { |
|||
text = s"User Home, Id = $id0" |
|||
}) |
|||
} |
|||
|
|||
val router = new FXRouter[Page] |
|||
} |
|||
|
|||
// case class State() |
|||
// object RouterStore { |
|||
|
|||
// sealed trait Action |
|||
// case object Init extends Action |
|||
|
|||
// def reducer(state: State, action: Action) = |
|||
// action match { |
|||
// case Init => state |
|||
|
|||
// } |
|||
// def apply() = |
|||
// Store.createL[Action, State](Init, State(), Reducer(reducer _), Seq.empty) |
|||
// } |
@ -0,0 +1,378 @@ |
|||
package nova.monadic_sfx.ui.components.todo |
|||
|
|||
import scala.concurrent.duration.FiniteDuration |
|||
|
|||
import cats.effect.concurrent.Deferred |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import monix.catnap.ConcurrentChannel |
|||
import monix.catnap.ConsumerF |
|||
import monix.execution.Scheduler |
|||
import monix.reactive.Observable |
|||
import monix.reactive.Observer |
|||
import nova.monadic_sfx.implicits.FontIcon |
|||
import nova.monadic_sfx.implicits.IconLiteral |
|||
import nova.monadic_sfx.implicits.JFXListView |
|||
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
|||
import nova.monadic_sfx.util.reactive._ |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.StringProperty |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.scene.control.ContextMenu |
|||
import scalafx.scene.control.ListCell |
|||
import scalafx.scene.control.MenuItem |
|||
import scalafx.scene.control.SelectionMode |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.text.Text |
|||
|
|||
class TodoListViewOld( |
|||
val listView: JFXListView[Todo] = TodoListViewOld.defaultListView, |
|||
val lvObs: ObservableBuffer[Todo] = ObservableBuffer.empty |
|||
) { |
|||
listView.items = lvObs |
|||
} |
|||
|
|||
object TodoListViewOld { |
|||
def defaultListView = |
|||
new JFXListView[Todo] { |
|||
contextMenu = new ContextMenu { |
|||
items ++= Seq( |
|||
new MenuItem { |
|||
text = "delete" |
|||
}, |
|||
new MenuItem { |
|||
text = "edit" |
|||
} |
|||
) |
|||
} |
|||
} |
|||
|
|||
implicit class Operations[A](val sink: Observer[A]) extends AnyVal {} |
|||
|
|||
def defaultListView2( |
|||
store: MonixProSubject[ |
|||
TodoListComponentOld.Command, |
|||
(TodoListComponentOld.Command, Vector[Todo]) |
|||
] |
|||
): Task[JFXListView[Todo]] = |
|||
Task.deferAction(implicit s => |
|||
Task { |
|||
val todos = |
|||
store.map { case (_, items) => items } |
|||
|
|||
val listView = new JFXListView[Todo] { lv => |
|||
def selectedItems = lv.selectionModel().selectedItems.view |
|||
// items = todos |
|||
items <-- todos |
|||
// .map(ObservableBuffer.from(_)) |
|||
cellFactory = _ => |
|||
new ListCell[Todo] { |
|||
val _text = StringProperty("") |
|||
val _graphic = new HBox { |
|||
children = Seq( |
|||
new FontIcon { |
|||
iconSize = 10 |
|||
iconLiteral = IconLiteral.Gmi10k |
|||
}, |
|||
new Text { |
|||
text <== _text |
|||
} |
|||
) |
|||
} |
|||
item.onChange((_, _, todo) => { |
|||
println("called") |
|||
if (todo != null) { |
|||
_text() = s"${todo.id} - ${todo.content}" |
|||
graphic = _graphic |
|||
} else { |
|||
_text() = "" |
|||
graphic = null |
|||
} |
|||
}) |
|||
|
|||
} |
|||
selectionModel().selectionMode = SelectionMode.Multiple |
|||
contextMenu = new ContextMenu { |
|||
items ++= Seq( |
|||
new MenuItem { |
|||
text = "Add" |
|||
onAction = _ => |
|||
store.sink |
|||
.onNext(TodoListComponentOld.Add(Todo(1, "blah3"))) |
|||
}, |
|||
new MenuItem { |
|||
text = "Delete" |
|||
// onAction = _ => |
|||
// for { |
|||
// items <- Option(lv.selectionModel().selectedItems) |
|||
// _ <- Some(items.foreach(item => deleteSub.onNext(item))) |
|||
// } yield () |
|||
onAction = _ => |
|||
selectedItems |
|||
.map(todo => TodoListComponentOld.Delete(todo.id)) |
|||
.foreach(store.sink.onNext) |
|||
}, |
|||
new MenuItem { |
|||
text = "Edit" |
|||
// onAction = _ => |
|||
// Option(lv.selectionModel().selectedItems).foreach(items => |
|||
// items.foreach(item => editSub.onNext(item)) |
|||
// ) |
|||
} |
|||
) |
|||
} |
|||
} |
|||
|
|||
listView |
|||
} |
|||
) |
|||
|
|||
} |
|||
|
|||
private[todo] class TodoListComponentImpure( |
|||
todoListView: TodoListViewOld |
|||
) { |
|||
def add(todo: Todo) = todoListView.lvObs += todo |
|||
def find(id: Int) = todoListView.lvObs.find(_.id == id) |
|||
def edit(id: Int, content: String) = |
|||
find(id) |
|||
.map(todo => |
|||
todoListView.lvObs.replaceAll( |
|||
todo, |
|||
Todo(id, content) |
|||
) |
|||
) |
|||
.getOrElse(false) |
|||
} |
|||
|
|||
class TodoListOps private ( |
|||
props: TodoListOps.Props |
|||
) { |
|||
import props._ |
|||
// lazy val internal = new TodoListComponentImpure(todoListView) |
|||
|
|||
// def add(todo: Todo) = Task(internal.add(todo)) |
|||
def add(todo: Todo) = Task(todoListView.lvObs += todo).executeOn(fxScheduler) |
|||
def find(id: Int) = |
|||
Task(todoListView.lvObs.find(_.id == id)).executeOn(fxScheduler) |
|||
def delete(id: Int) = |
|||
(for { |
|||
mbTodo <- find(id) |
|||
_ <- logger.debug(mbTodo.toString()) |
|||
res <- Task( |
|||
mbTodo.map(todo => todoListView.lvObs.removeAll(todo)) |
|||
) |
|||
_ <- logger.debug(todoListView.lvObs.toString()) |
|||
} yield res.getOrElse(false)).executeOn(fxScheduler) |
|||
def edit(id: Int, content: String) = |
|||
(for { |
|||
mbTodo <- find(id) |
|||
res <- Task( |
|||
mbTodo.map(todo => |
|||
todoListView.lvObs.replaceAll( |
|||
todo, |
|||
Todo(id, content) |
|||
) |
|||
) |
|||
) |
|||
} yield res.getOrElse(false)).executeOn(fxScheduler) |
|||
} |
|||
|
|||
object TodoListOps { |
|||
class Props( |
|||
val todoListView: TodoListViewOld, |
|||
val fxScheduler: Scheduler, |
|||
val logger: Logger[Task] |
|||
) { |
|||
def create = Task(new TodoListOps(this)) |
|||
} |
|||
} |
|||
|
|||
object TodoListComponentOld { |
|||
sealed trait Complete |
|||
object Complete extends Complete |
|||
|
|||
sealed trait Command |
|||
// sealed trait Tell extends Command |
|||
// sealed abstract class Ask extends Command |
|||
|
|||
case class Add(todo: Todo) extends Command |
|||
case class Find(id: Int, result: Deferred[Task, Option[Todo]]) extends Command |
|||
case class Edit(id: Int, content: String) extends Command |
|||
case class Delete(id: Int) extends Command |
|||
// private case class FindInternal(id: Int, result: Deferred[Task, Todo]) |
|||
// extends Ask |
|||
|
|||
def reducer( |
|||
state: Vector[Todo], |
|||
action: TodoListComponentOld.Command |
|||
) = |
|||
action match { |
|||
case Add(todo) => state :+ todo |
|||
// case Find(id, result) => |
|||
case Edit(id, content) => state |
|||
case Delete(id) => |
|||
state.filterNot(_.id == id) |
|||
case _ => state |
|||
} |
|||
|
|||
val store = |
|||
Store |
|||
.createL[TodoListComponentOld.Command, Vector[Todo]]( |
|||
TodoListComponentOld.Delete(0), |
|||
Vector.empty[Todo], |
|||
(s: Vector[Todo], a: TodoListComponentOld.Command) => |
|||
reducer(s, a) -> Observable.empty |
|||
) |
|||
|
|||
class Props( |
|||
val todoListView: TodoListViewOld, |
|||
val fxScheduler: Scheduler, |
|||
val channel: ConcurrentChannel[ |
|||
Task, |
|||
TodoListComponentOld.Complete, |
|||
TodoListComponentOld.Command |
|||
], |
|||
val logger: Logger[Task] |
|||
) { |
|||
|
|||
def create = |
|||
for { |
|||
todoListOps <- |
|||
new TodoListOps.Props(todoListView, fxScheduler, logger).create |
|||
consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps)) |
|||
_ <- consumer.startAndForget |
|||
} yield (new TodoListComponentOld(this)) |
|||
|
|||
private def todoConsumer( |
|||
consumer: ConsumerF[Task, Complete, Command], |
|||
ops: TodoListOps |
|||
): Task[Unit] = |
|||
consumer.pull |
|||
.flatMap { |
|||
case Left(complete) => logger.info("Received `Complete` event") |
|||
case Right(command) => |
|||
logger.debug(s"Received command $command") >> |
|||
(command match { |
|||
// case t: Tell => |
|||
// t match { |
|||
// case Add(todo) => ops.add(todo) |
|||
// case _ => Task.unit |
|||
// } |
|||
case Add(todo) => ops.add(todo) |
|||
// case Find(id) => |
|||
// for { |
|||
// p <- Deferred[Task, Todo] |
|||
// _ <- channel.push(FindInternal(id, p)) |
|||
// res <- p.get |
|||
// } yield (res) |
|||
case Find(id, result) => |
|||
for { |
|||
mbTodo <- ops.find(id) |
|||
} yield result.complete(mbTodo) |
|||
// case _ => Task.unit |
|||
|
|||
case Delete(id) => ops.delete(id) |
|||
|
|||
case Edit(id, content) => ops.edit(id, content) |
|||
}) |
|||
} |
|||
.flatMap(_ => todoConsumer(consumer, ops)) |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
class TodoListComponentOld(props: TodoListComponentOld.Props) { |
|||
import props._ |
|||
import TodoListComponentOld._ |
|||
|
|||
def send(command: Command) = channel.push(command) |
|||
|
|||
def ask[T]( |
|||
commandBuilder: Deferred[Task, T] => Command |
|||
)(implicit timeout: FiniteDuration) = |
|||
for { |
|||
p <- Deferred[Task, T] |
|||
_ <- channel.push(commandBuilder(p)) |
|||
res <- p.get.timeout(timeout) |
|||
} yield res |
|||
|
|||
def stop = channel.halt(Complete) |
|||
|
|||
// import scala.concurrent.duration._ |
|||
// val x = ask(FindInternal(0, _))(2.seconds) |
|||
|
|||
} |
|||
|
|||
// object TodoListComponent { |
|||
// sealed trait Complete |
|||
// object Complete extends Complete |
|||
|
|||
// sealed trait Command |
|||
|
|||
// class Props( |
|||
// val todoListView: TodoListView, |
|||
// val fxScheduler: Scheduler, |
|||
// val channel: ConcurrentChannel[ |
|||
// Task, |
|||
// TodoListComponent.Complete, |
|||
// TodoListComponent.Command |
|||
// ] |
|||
// ) { |
|||
// def create = Task(new TodoListComponent(this)) |
|||
// } |
|||
// } |
|||
|
|||
// class TodoListComponent(props: TodoListComponent.Props) { |
|||
// import props._ |
|||
// import TodoListComponent._ |
|||
// def init = |
|||
// for { |
|||
// todoListOps <- new TodoListOps.Props(todoListView, fxScheduler).create |
|||
// consumer = channel.consume.use(ref => todoConsumer(ref, todoListOps)) |
|||
// _ <- consumer.startAndForget |
|||
// } yield () |
|||
|
|||
// def send(command: Command) = channel.push(command) |
|||
|
|||
// def todoConsumer( |
|||
// consumer: ConsumerF[Task, Complete, Command], |
|||
// ops: TodoListOps |
|||
// ) = |
|||
// consumer.pull.flatMap { |
|||
// case Left(value) => Task.unit |
|||
// case Right(value) => Task.unit |
|||
// } |
|||
// } |
|||
|
|||
// def askHandler( |
|||
// channel: ConcurrentChannel[ |
|||
// Task, |
|||
// TodoListComponent.Complete, |
|||
// TodoListComponent.Command |
|||
// ], |
|||
// consumer: ConsumerF[Task, Complete, Command], |
|||
// ops: TodoListOps |
|||
// ) = |
|||
// consumer.pull.flatMap { |
|||
// case Left(complete) => Task.unit |
|||
// case Right(command) => |
|||
// command match { |
|||
// case a: Ask => |
|||
// a match { |
|||
// case Find(id) => |
|||
// for { |
|||
// p <- Deferred[Task, Todo] |
|||
// _ <- channel.push(FindInternal(id, p)) |
|||
// res <- p.get |
|||
// } yield (res) |
|||
// case FindInternal(id, result) => |
|||
// for { |
|||
// mb <- ops.find(id) |
|||
// } yield result.complete(mb.get) |
|||
// case _ => Task.unit |
|||
// } |
|||
// case _ => Task.unit |
|||
// } |
|||
// } |
@ -0,0 +1,89 @@ |
|||
package nova.monadic_sfx.ui.components.todo |
|||
|
|||
import com.softwaremill.quicklens._ |
|||
import io.circe.generic.JsonCodec |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import nova.monadic_sfx.util.reactive.Middlewares.actionLoggerMiddleware |
|||
import nova.monadic_sfx.util.reactive.Reducer |
|||
import nova.monadic_sfx.util.reactive.Store |
|||
|
|||
case class Todo(id: Int, content: String) |
|||
|
|||
object TodoListStore { |
|||
|
|||
@JsonCodec |
|||
sealed trait Action |
|||
case object Init extends Action |
|||
case class Add(content: String) extends Action |
|||
case class Edit(id: Int, content: String) extends Action |
|||
case class Delete(id: Int) extends Action |
|||
|
|||
private case class InternalAdd(content: String) extends Action |
|||
private case object End extends Action |
|||
|
|||
case class State(todos: Vector[Todo], counter: Int) |
|||
|
|||
def reducer(logger: Logger[Task])( |
|||
state: State, |
|||
action: Action |
|||
): (State, Option[Task[Action]]) = |
|||
action match { |
|||
case Init => (state, None) |
|||
case Add(content) => |
|||
val nextAction = Some(for { |
|||
// _ <- logger.debug(s"Received $content") |
|||
res <- Task.pure(InternalAdd(content)) |
|||
} yield res) |
|||
(state, nextAction) |
|||
case Edit(_id, content) => |
|||
val condition: Todo => Boolean = _.id == _id |
|||
val nextState = state |
|||
.modify(_.todos.eachWhere(condition)) |
|||
.using(_.copy(content = content)) |
|||
(nextState, None) |
|||
case Delete(id) => |
|||
(state.copy(state.todos.filterNot(_.id == id)), None) |
|||
|
|||
case InternalAdd(content) => |
|||
val nextState = state.copy( |
|||
todos = state.todos :+ Todo(state.counter, content), |
|||
counter = state.counter + 1 |
|||
) |
|||
(nextState, Some(logger.debug(s"Received $content") >> Task.pure(End))) |
|||
case End => (state, None) |
|||
} |
|||
|
|||
def apply(logger: Logger[Task]) = |
|||
Task.deferAction(implicit s => |
|||
for { |
|||
logMware <- actionLoggerMiddleware[Action, State](logger, "TodoStore") |
|||
store <- |
|||
Store |
|||
.createL[Action, State]( |
|||
Init, |
|||
State(Vector.empty[Todo], 0), |
|||
Reducer.withOptionalEffects(reducer(logger) _), |
|||
Seq( |
|||
// actionLoggerMiddleware(logger, "TodoStore2") |
|||
logMware |
|||
) |
|||
) |
|||
} yield (store) |
|||
) |
|||
} |
|||
|
|||
// Task.deferAction(implicit s => |
|||
// Store |
|||
// .createJsonL[Action, State]( |
|||
// Init, |
|||
// State(Vector.empty[Todo], 0), |
|||
// Reducer.withOptionalEffects(reducer(logger) _), |
|||
// "TodoStore", |
|||
// logger |
|||
// // Seq( |
|||
// // actionLoggerMiddleware(logger, "TodoStore") |
|||
// // // actionLoggerMiddleware(logger, "TodoStore2") |
|||
// // ) |
|||
// ) |
|||
// ) |
@ -0,0 +1,134 @@ |
|||
package nova.monadic_sfx.ui.components.todo |
|||
|
|||
import monix.bio.Task |
|||
import monix.execution.cancelables.CompositeCancelable |
|||
import monix.{eval => me} |
|||
import nova.monadic_sfx.implicits.FontIcon |
|||
import nova.monadic_sfx.implicits.IconLiteral |
|||
import nova.monadic_sfx.implicits.JFXButton |
|||
import nova.monadic_sfx.implicits.JFXListView |
|||
import nova.monadic_sfx.implicits.JFXTextField |
|||
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ |
|||
import nova.monadic_sfx.implicits.MenuItem |
|||
import nova.monadic_sfx.util.reactive._ |
|||
import org.gerweck.scalafx.util._ |
|||
import scalafx.Includes._ |
|||
import scalafx.beans.property.ObjectProperty |
|||
import scalafx.beans.property.StringProperty |
|||
import scalafx.geometry.Insets |
|||
import scalafx.scene.Node |
|||
import scalafx.scene.control.ContextMenu |
|||
import scalafx.scene.control.ListCell |
|||
import scalafx.scene.control.SelectionMode |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.text.Text |
|||
|
|||
object TodoListView { |
|||
def apply( |
|||
store: MonixProSubject[ |
|||
TodoListStore.Action, |
|||
(TodoListStore.Action, TodoListStore.State) |
|||
] |
|||
): Task[Node] = |
|||
Task.deferAction(implicit s => |
|||
Task { |
|||
val cc = CompositeCancelable() |
|||
val todos = |
|||
store.map { case (_, state) => state.todos } |
|||
// Todo(-1, "").some |
|||
val _selectedItems = ObjectProperty(Seq.empty[Todo]) |
|||
|
|||
new HBox { |
|||
padding = Insets(5) |
|||
val _content = StringProperty("") |
|||
children = Seq( |
|||
new JFXTextField { |
|||
text ==> _content |
|||
}, |
|||
new JFXListView[Todo] { |
|||
def selectedItems = selectionModel().selectedItems.view |
|||
|
|||
selectionModel().selectionMode = SelectionMode.Multiple |
|||
selectionModel().selectedItems.observableSeqValue ==> _selectedItems |
|||
|
|||
cc += items <-- todos |
|||
|
|||
val emptyCell = ObjectProperty(new HBox) |
|||
cellFactory = _ => |
|||
new ListCell[Todo] { |
|||
val _text = StringProperty("") |
|||
val _graphic = ObjectProperty( |
|||
new HBox { |
|||
children = Seq( |
|||
new FontIcon { |
|||
iconSize = 10 |
|||
iconLiteral = IconLiteral.Gmi10k |
|||
}, |
|||
new Text { |
|||
text <== _text |
|||
} |
|||
) |
|||
} |
|||
) |
|||
|
|||
item.asOption.map( |
|||
_.fold("")(todo => s"${todo.id} - ${todo.content}") |
|||
) ==> _text |
|||
|
|||
graphic <== item.asOption.flatMap( |
|||
_.fold(emptyCell)(_ => _graphic) |
|||
) |
|||
|
|||
} |
|||
|
|||
contextMenu = new ContextMenu { |
|||
items ++= Seq( |
|||
new MenuItem { |
|||
text = "Add" |
|||
// obsAction.useLazyEval(TodoListStore.Add("blah3")) --> store |
|||
}, |
|||
new MenuItem { |
|||
text = "Delete" |
|||
obsAction.useIterableEval(_ => |
|||
selectedItems |
|||
.map(todo => TodoListStore.Delete(todo.id)) |
|||
.toList |
|||
) --> store |
|||
|
|||
// obsAction.split( |
|||
// _.useLazyEval(me.Task(TodoListStore.Delete(0))) --> store, |
|||
// _.useLazyEval(me.Task(TodoListStore.Delete(0))) --> store, |
|||
// _.useLazyEval(me.Task(TodoListStore.Delete(0))) --> store |
|||
// ) |
|||
}, |
|||
new MenuItem { |
|||
text = "Edit" |
|||
} |
|||
) |
|||
} |
|||
}, |
|||
new JFXButton { |
|||
text = "Add" |
|||
// disable <== _selectedItems.map(_.length > 0) |
|||
obsAction |
|||
.useLazyEval(me.Task(TodoListStore.Add(_content()))) --> store |
|||
}, |
|||
new JFXButton { |
|||
text = "Edit" |
|||
disable <== _selectedItems.map(_.length > 1) |
|||
obsAction.useLazyEval( |
|||
me.Task( |
|||
TodoListStore.Edit( |
|||
_selectedItems |
|||
.map(_.headOption.map(_.id).getOrElse(-1)) |
|||
.value, |
|||
_content() |
|||
) |
|||
) |
|||
) --> store |
|||
} |
|||
) |
|||
} |
|||
} |
|||
) |
|||
} |
@ -0,0 +1,95 @@ |
|||
package nova.monadic_sfx.ui.controller |
|||
|
|||
import animatefx.animation.AnimationFX |
|||
import animatefx.animation.Bounce |
|||
import animatefx.animation.FadeIn |
|||
import animatefx.util.{SequentialAnimationFX => SeqFX} |
|||
import cats.effect.Sync |
|||
import monix.eval.Task |
|||
import nova.monadic_sfx.implicits.FontIcon |
|||
import nova.monadic_sfx.implicits.IconLiteral |
|||
import nova.monadic_sfx.implicits.JFXButton |
|||
import nova.monadic_sfx.implicits.JFXListView |
|||
import nova.monadic_sfx.implicits.JFXTextArea |
|||
import nova.monadic_sfx.implicits.JFXTextField |
|||
import nova.monadic_sfx.ui.components.todo.TodoListComponentOld |
|||
import scalafx.collections.ObservableBuffer |
|||
import scalafx.scene.control.Label |
|||
import scalafx.scene.layout.HBox |
|||
import scalafx.scene.paint.Color |
|||
|
|||
class TodoController(todoListComponent: TodoListComponentOld) { |
|||
import AnimFX._ |
|||
def root = |
|||
new HBox { |
|||
children = Seq( |
|||
new Label { |
|||
text = "Todo" |
|||
}, |
|||
new JFXButton { |
|||
text = " Click me" |
|||
onAction = _ => { |
|||
new FadeIn(new Label("hello")).play() |
|||
val anim = new SeqFX( |
|||
new Bounce(new Label("hello")), |
|||
new FadeIn(new Label("hello")) |
|||
).toAnimFX[Task] |
|||
for { |
|||
_ <- Task.unit |
|||
_ <- anim.playL |
|||
} yield () |
|||
} |
|||
}, |
|||
new JFXTextField { |
|||
text = "hello" |
|||
labelFloat = true |
|||
}, |
|||
new JFXListView[String] { |
|||
items = ObservableBuffer("hello") |
|||
}, |
|||
new JFXTextArea { |
|||
prefWidth = 400 |
|||
text = "blah" |
|||
labelFloat = true |
|||
}, |
|||
new FontIcon { |
|||
iconSize = 50 |
|||
iconColor = Color.Black |
|||
iconLiteral = IconLiteral.Gmi10k |
|||
} |
|||
) |
|||
} |
|||
|
|||
// def test() = { |
|||
// new TextField("hello").lookup() |
|||
// } |
|||
|
|||
// def test2() = { |
|||
// new Label().lookup() |
|||
// } |
|||
} |
|||
|
|||
abstract class AnimFX[F[_]: Sync] { |
|||
def playL: F[Unit] |
|||
} |
|||
object AnimFX { |
|||
implicit class AnimationFXExt(anim: AnimationFX) { |
|||
def toAnimFX[F[_]](implicit |
|||
F: Sync[F] |
|||
) = |
|||
new AnimFX[F] { |
|||
override def playL: F[Unit] = |
|||
F.delay(anim.play()) |
|||
} |
|||
} |
|||
|
|||
implicit class SeqAnimationFXExt(anim: SeqFX) { |
|||
def toAnimFX[F[_]](implicit |
|||
F: Sync[F] |
|||
) = |
|||
new AnimFX[F] { |
|||
override def playL: F[Unit] = |
|||
F.delay(anim.play()) |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
import cats.arrow.FunctionK |
|||
import monix.bio.IO |
|||
|
|||
object IOUtils { |
|||
def toIO[T](task: monix.eval.Task[T]) = |
|||
IO.deferAction(implicit s => IO.from(task)) |
|||
|
|||
def toTask[T](bio: monix.bio.IO[Throwable, T]) = |
|||
monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task]) |
|||
|
|||
val ioTaskMapk = |
|||
new FunctionK[monix.eval.Task, monix.bio.Task] { |
|||
|
|||
override def apply[A]( |
|||
fa: monix.eval.Task[A] |
|||
): monix.bio.Task[A] = toIO(fa) |
|||
|
|||
} |
|||
} |
@ -0,0 +1,63 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
import scalafx.beans.property.ObjectProperty |
|||
import scalafx.beans.property.ReadOnlyObjectProperty |
|||
import scalafx.beans.value.ObservableValue |
|||
|
|||
object Misc { |
|||
|
|||
implicit final class MyRichObservable[A, C](val self: ObservableValue[A, C]) |
|||
extends AnyVal { |
|||
def filter(f: A => Boolean): ReadOnlyObjectProperty[A] = |
|||
Method.filter(self)(f) |
|||
def filterNull: ReadOnlyObjectProperty[A] = Method.filterNull(self) |
|||
} |
|||
|
|||
} |
|||
object Method { |
|||
type Observable[A] = ObservableValue[A, _] |
|||
|
|||
def filter[B]( |
|||
a: Observable[B] |
|||
)(f: B => Boolean): ReadOnlyObjectProperty[B] = { |
|||
|
|||
val prop = ObjectProperty[B](a.value) |
|||
|
|||
def changeHandler() = |
|||
prop.synchronized { |
|||
if (f(a.value)) { |
|||
prop.value = a.value |
|||
} |
|||
} |
|||
|
|||
a onChange changeHandler() |
|||
prop |
|||
} |
|||
|
|||
/** |
|||
* Simply creates a new observable that mirrors the source observable but |
|||
* doesn't emit null values. JavaFX likes to work with null values in scene |
|||
* nodes/properties (shrugs) and observables by default emit null values |
|||
* that can cause crashes. ScalaFX does not offer any *fixes* for this |
|||
* |
|||
* @param a |
|||
* @return |
|||
*/ |
|||
def filterNull[B]( |
|||
a: Observable[B] |
|||
): ReadOnlyObjectProperty[B] = { |
|||
|
|||
val prop = ObjectProperty[B](a.value) |
|||
|
|||
def changeHandler() = |
|||
prop.synchronized { |
|||
if (a.value != null) { |
|||
prop.value = a.value |
|||
} |
|||
} |
|||
|
|||
a onChange changeHandler() |
|||
prop |
|||
} |
|||
|
|||
} |
@ -0,0 +1,75 @@ |
|||
package nova.monadic_sfx.util.reactive |
|||
|
|||
import java.time.LocalDateTime |
|||
|
|||
import io.circe.Encoder |
|||
import io.circe.Printer |
|||
import io.circe.generic.JsonCodec |
|||
import io.circe.syntax._ |
|||
import io.odin.Logger |
|||
import io.odin.LoggerMessage |
|||
import io.odin.formatter.options.PositionFormat |
|||
import io.odin.formatter.options.ThrowableFormat |
|||
import io.odin.meta.Render |
|||
import monix.bio.Task |
|||
import monix.reactive.Observable |
|||
// object Middleware { |
|||
// def apply[A,M,T](ob: Observable[(A,M)], cb: (A,M) => T): Observable[(A,M)] = ob |
|||
// } |
|||
|
|||
@JsonCodec |
|||
final case class StoreInfo[A]( |
|||
name: String, |
|||
action: A, |
|||
time: LocalDateTime = LocalDateTime.now() |
|||
) |
|||
|
|||
object StoreInfo { |
|||
val printer = Printer.noSpaces |
|||
implicit def render[T: Encoder]: Render[StoreInfo[T]] = |
|||
new Render[StoreInfo[T]] { |
|||
override def render(m: StoreInfo[T]): String = printer.print(m.asJson) |
|||
} |
|||
} |
|||
|
|||
object Middlewares { |
|||
|
|||
// val encoder: Encoder[LoggerMessage] = |
|||
// Encoder.forProduct1("message")(m => m.message.value) |
|||
|
|||
val format = create(ThrowableFormat.Default, PositionFormat.Full) |
|||
|
|||
def create( |
|||
throwableFormat: ThrowableFormat, |
|||
positionFormat: PositionFormat |
|||
): io.odin.formatter.Formatter = { |
|||
// val encoder: Encoder[LoggerMessage] = |
|||
// Encoder.forProduct1("message")(m => m.message.value) |
|||
(msg: LoggerMessage) => msg.message.value |
|||
} |
|||
|
|||
def actionStateLoggerMiddleware[A, M]( |
|||
logger: Logger[Task] |
|||
): Task[Middleware[A, M]] = |
|||
Task.deferAction(implicit s => |
|||
Task((obs: Observable[(A, M)]) => |
|||
obs.doOnNextF { |
|||
case (a, m) => |
|||
logger.debug(s"Received action $a with state $m") |
|||
} |
|||
) |
|||
) |
|||
|
|||
def actionLoggerMiddleware[A: Encoder, M]( |
|||
logger: Logger[Task], |
|||
name: String |
|||
): Task[Middleware[A, M]] = |
|||
Task.deferAction(implicit s => |
|||
Task((obs: Observable[(A, M)]) => |
|||
obs.doOnNextF { |
|||
case (a, _) => |
|||
logger.debug(StoreInfo(name, a)) |
|||
} |
|||
) |
|||
) |
|||
} |
@ -0,0 +1,23 @@ |
|||
package nova.monadic_sfx.util.reactive |
|||
|
|||
import scala.concurrent.Future |
|||
|
|||
import monix.execution.Ack |
|||
import monix.execution.Cancelable |
|||
import monix.reactive.Observable |
|||
import monix.reactive.Observer |
|||
import monix.reactive.observers.Subscriber |
|||
|
|||
object MonixProSubject { |
|||
def from[I, O]( |
|||
observer: Observer[I], |
|||
observable: Observable[O] |
|||
): MonixProSubject[I, O] = |
|||
new Observable[O] with Observer[I] { |
|||
override def onNext(elem: I): Future[Ack] = observer.onNext(elem) |
|||
override def onError(ex: Throwable): Unit = observer.onError(ex) |
|||
override def onComplete(): Unit = observer.onComplete() |
|||
override def unsafeSubscribeFn(subscriber: Subscriber[O]): Cancelable = |
|||
observable.unsafeSubscribeFn(subscriber) |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
package nova.monadic_sfx.util.reactive |
|||
|
|||
import cats.implicits._ |
|||
import monix.reactive.Observable |
|||
import monix.reactive.ObservableLike |
|||
|
|||
object Reducer { |
|||
|
|||
/** |
|||
* Creates a Reducer which yields a new State, as-well as an Observable of Effects |
|||
* Effects are Actions which will be executed after the Action that caused them to occur. |
|||
* This is accomplished by subscribing to the Effects Observable within the stores scan loop. |
|||
* |
|||
* CAUTION: There is currently a bug which causes the Effect-States to emit, |
|||
* before the State of the action that caused the effects is emitted. |
|||
* However, this only effects immediate emissions of the Effects Observable, delayed emissions should be fine. |
|||
* @param f The Reducing Function returning the (Model, Effects) tuple. |
|||
*/ |
|||
def withEffects[F[_]: ObservableLike, A, M]( |
|||
f: (M, A) => (M, F[A]) |
|||
): Reducer[A, M] = (s: M, a: A) => f(s, a).map(ObservableLike[F].apply) |
|||
|
|||
/** |
|||
* Creates a reducer which just transforms the state, without additional effects. |
|||
*/ |
|||
def apply[A, M](f: (M, A) => M): Reducer[A, M] = |
|||
(s: M, a: A) => f(s, a) -> Observable.empty |
|||
|
|||
/** |
|||
* Creates a Reducer with an optional effect. |
|||
*/ |
|||
def withOptionalEffects[F[_]: ObservableLike, A, M]( |
|||
f: (M, A) => (M, Option[F[A]]) |
|||
): Reducer[A, M] = |
|||
(s: M, a: A) => |
|||
f(s, a).map( |
|||
_.fold[Observable[A]](Observable.empty)(ObservableLike[F].apply) |
|||
) |
|||
} |
@ -0,0 +1,97 @@ |
|||
package nova.monadic_sfx.util.reactive |
|||
|
|||
import io.circe.Encoder |
|||
import io.odin.Logger |
|||
import monix.bio.Task |
|||
import monix.reactive.OverflowStrategy |
|||
import monix.reactive.subjects.ConcurrentSubject |
|||
|
|||
object Store { |
|||
def createL[A, M]( |
|||
initialAction: A, |
|||
initialState: M, |
|||
reducer: Reducer[A, M], |
|||
middlewares: Seq[Middleware[A, M]] = Seq.empty, |
|||
overflowStrategy: OverflowStrategy.Synchronous[A] = |
|||
OverflowStrategy.DropOld(50) |
|||
): Task[Store[A, M]] = |
|||
Task.deferAction { implicit s => |
|||
Task { |
|||
val subject = ConcurrentSubject.publish[A](overflowStrategy) |
|||
|
|||
val fold: ((A, M), A) => (A, M) = { |
|||
case ((_, state), action) => { |
|||
val (newState, effects) = reducer(state, action) |
|||
|
|||
effects.subscribe(subject.onNext _) |
|||
|
|||
action -> newState |
|||
} |
|||
} |
|||
|
|||
val obs = subject |
|||
.scan[(A, M)](initialAction -> initialState)(fold) |
|||
.behavior(initialAction -> initialState) |
|||
.refCount |
|||
|
|||
val res = middlewares.foldLeft(obs) { |
|||
case (obs, middleware) => middleware(obs) |
|||
} |
|||
|
|||
MonixProSubject.from( |
|||
subject, |
|||
res |
|||
) |
|||
} |
|||
} |
|||
|
|||
def createJsonL[A: Encoder, M]( |
|||
initialAction: A, |
|||
initialState: M, |
|||
reducer: Reducer[A, M], |
|||
storeName: String, |
|||
logger: Logger[Task], |
|||
middlewares: Seq[Middleware[A, M]] = Seq.empty, |
|||
overflowStrategy: OverflowStrategy.Synchronous[A] = |
|||
OverflowStrategy.DropOld(50) |
|||
): Task[Store[A, M]] = |
|||
Task.deferAction { implicit s => |
|||
Task { |
|||
val subject = ConcurrentSubject.publish[A](overflowStrategy) |
|||
|
|||
val fold: ((A, M), A) => Task[(A, M)] = { |
|||
case ((_, state), action) => |
|||
Task { |
|||
val (newState, effects) = reducer(state, action) |
|||
|
|||
effects.subscribe(subject.onNext _) |
|||
|
|||
action -> newState |
|||
} |
|||
} |
|||
|
|||
val obs = subject |
|||
.doOnNextF(action => |
|||
logger.debug( |
|||
StoreInfo(storeName, action) |
|||
) // .executeOn(Scheduler.global) |
|||
) |
|||
// .doOnNextF(action => Coeval(println(action))) |
|||
.scanEvalF[Task, (A, M)](Task.pure(initialAction -> initialState))( |
|||
fold |
|||
) |
|||
.behavior(initialAction -> initialState) |
|||
.refCount |
|||
|
|||
// val res = middlewares.foldLeft(obs) { |
|||
// case (obs, middleware) => middleware(obs) |
|||
// } |
|||
|
|||
MonixProSubject.from( |
|||
subject, |
|||
obs |
|||
) |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,18 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
import monix.reactive.Observable |
|||
import monix.reactive.Observer |
|||
|
|||
package object reactive { |
|||
type MonixProSubject[-I, +O] = Observable[O] with Observer[I] |
|||
type Middleware[A, M] = Observable[(A, M)] => Observable[(A, M)] |
|||
type Store[A, M] = MonixProSubject[A, (A, M)] |
|||
|
|||
/** |
|||
* A Function that applies an Action onto the Stores current state. |
|||
* @param reducer The reducing function |
|||
* @tparam A The Action Type |
|||
* @tparam M The Model Type |
|||
*/ |
|||
type Reducer[A, M] = (M, A) => (M, Observable[A]) |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue