Refactored FX code into it's own class +
misc changes
This commit is contained in:
parent
029cbbd5ac
commit
6ef5c9778e
@ -1,14 +1,21 @@
|
||||
package nova.monadic_sfx
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
import com.softwaremill.macwire._
|
||||
import io.odin.Logger
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.eval.Coeval
|
||||
import monix.{eval => me}
|
||||
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.router.Page
|
||||
import nova.monadic_sfx.ui.components.todo.TodoListStore
|
||||
import nova.monadic_sfx.ui.components.todo.TodoListView
|
||||
import org.gerweck.scalafx.util._
|
||||
@ -18,12 +25,16 @@ import scalafx.beans.property.ObjectProperty
|
||||
import scalafx.beans.property.StringProperty
|
||||
import scalafx.collections.ObservableBuffer
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos
|
||||
import scalafx.scene.Parent
|
||||
import scalafx.scene.Scene
|
||||
import scalafx.scene.control.Label
|
||||
import scalafx.scene.control.TableColumn
|
||||
import scalafx.scene.control.TableView
|
||||
import scalafx.scene.layout.BorderPane
|
||||
import scalafx.scene.layout.FlowPane
|
||||
import scalafx.scene.layout.HBox
|
||||
import scalafx.scene.paint.Color
|
||||
import scalafx.scene.shape.Rectangle
|
||||
import scalafx.scene.layout.Priority
|
||||
|
||||
class MainApp(
|
||||
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
||||
@ -31,136 +42,108 @@ class MainApp(
|
||||
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
|
||||
width = 640
|
||||
height = 480
|
||||
}
|
||||
|
||||
// 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)
|
||||
_ <-
|
||||
wire[MainAppDelegate].init
|
||||
.flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode))
|
||||
.executeOn(schedulers.fx)
|
||||
currentTime <- IO.clock.realTime(TimeUnit.MILLISECONDS)
|
||||
_ <- logger.info(
|
||||
s"Application started in ${(System.currentTimeMillis() - startTime) / 1000f} seconds"
|
||||
s"Application started in ${(currentTime - 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] =
|
||||
class MainAppDelegate(schedulers: Schedulers)(implicit logger: Logger[Task]) {
|
||||
val buttonStyle = """| -fx-padding: 0.7em 0.57em;
|
||||
| -fx-font-size: 14px;
|
||||
| -jfx-button-type: RAISED;
|
||||
| -fx-background-color: rgb(77,102,204);
|
||||
| -fx-pref-width: 200;
|
||||
| -fx-text-fill: WHITE; """.stripMargin
|
||||
|
||||
def init =
|
||||
for {
|
||||
store <- TodoListStore(logger)
|
||||
rootNode <- TodoListView(store)
|
||||
_ <- Task(_scene.getChildren += rootNode)
|
||||
} yield ()
|
||||
router <- Task.pure(new FXRouter[Page])
|
||||
routerStore <- router.store(Page.Home, logger)
|
||||
todoStore <- TodoListStore(logger)
|
||||
todoComponent <- TodoListView(todoStore)
|
||||
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"
|
||||
})
|
||||
case Page.Todo =>
|
||||
Task(todoComponent)
|
||||
}
|
||||
routerNode <-
|
||||
Task
|
||||
.deferAction(implicit s =>
|
||||
Task(new HBox {
|
||||
//TODO find a better way to do this
|
||||
var oldValue: Option[Parent] = None
|
||||
children <-- router
|
||||
.render(resolver)(routerStore)
|
||||
// call cancel on the old component to cancel all subscriptions
|
||||
.doOnNextF(newValue =>
|
||||
Coeval { oldValue.foreach(_ => ()) } >> Coeval {
|
||||
oldValue = Some(newValue)
|
||||
}
|
||||
)
|
||||
.map(_.delegate)
|
||||
})
|
||||
)
|
||||
|
||||
mainSceneNode <- Task.deferAction(implicit s =>
|
||||
Task(new BorderPane {
|
||||
hgrow = Priority.Always
|
||||
vgrow = Priority.Always
|
||||
center = routerNode
|
||||
bottom = new FlowPane {
|
||||
alignment = Pos.Center
|
||||
hgap = 20
|
||||
children = Seq(
|
||||
new JFXButton {
|
||||
text = "Todo"
|
||||
style = buttonStyle
|
||||
obsAction
|
||||
.useLazyEval(
|
||||
me.Task.pure(FXRouter.Replace(Page.Todo))
|
||||
) --> routerStore
|
||||
},
|
||||
new JFXButton {
|
||||
text = "UserHome"
|
||||
style = buttonStyle
|
||||
obsAction
|
||||
.useLazyEval(
|
||||
me.Task(FXRouter.Replace(Page.UserHome(Random.nextInt(20))))
|
||||
) --> routerStore
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
} yield mainSceneNode
|
||||
}
|
||||
|
||||
class TestModel(_name: String, _age: Int) {
|
||||
|
@ -19,23 +19,23 @@ trait MainModule extends ActorModule with UiModule with HttpModule {
|
||||
"nova.monadic_sfx.util.reactive.Store" -> storeLogger
|
||||
)
|
||||
.withFallback(defaultLogger)
|
||||
.withAsync()
|
||||
.withAsync(timeWindow = 1.millis)
|
||||
|
||||
def makeLogger =
|
||||
for {
|
||||
defaultLogger <- consoleLogger[Task]()
|
||||
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
|
||||
"application.log"
|
||||
).withAsync()
|
||||
).withAsync(timeWindow = 1.millis)
|
||||
middlewareLogger <-
|
||||
consoleLogger[
|
||||
Task
|
||||
](formatter = Middlewares.format)
|
||||
.withMinimalLevel(Level.Trace)
|
||||
.withAsync() |+| fileLogger[Task](
|
||||
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
|
||||
"stores.log",
|
||||
formatter = Middlewares.format
|
||||
).withAsync()
|
||||
).withAsync(timeWindow = 1.millis)
|
||||
routerLogger <- routerLogger(defaultLogger, middlewareLogger)
|
||||
} yield (routerLogger)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class ActionObservableExecutor[T](
|
||||
) extends AnyVal {
|
||||
def -->(sub: Observer[T])(implicit s: Scheduler) =
|
||||
delegate
|
||||
.doOnNext(el => me.Task(sub.onNext(el)))
|
||||
.doOnNext(el => me.Task.deferFuture(sub.onNext(el)) >> me.Task.unit)
|
||||
.subscribe()
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) {
|
||||
def init(stage: => PrimaryStage, delay: FiniteDuration = 200.millis) =
|
||||
for {
|
||||
_ <- logger.info("Starting FX App")
|
||||
fib <- Task(internal.main(Array.empty)).start
|
||||
fib <-
|
||||
Task(internal.main(Array.empty)).start.executeOn(schedulers.blocking)
|
||||
_ <- Task.sleep(200.millis)
|
||||
_ <- Task(internal.stage = stage)
|
||||
.executeOn(schedulers.fx)
|
||||
|
@ -1,47 +1,52 @@
|
||||
package nova.monadic_sfx.ui.components.router
|
||||
|
||||
import enumeratum._
|
||||
import io.circe.Codec
|
||||
import io.circe.Decoder
|
||||
import io.circe.Encoder
|
||||
import io.circe.generic.JsonCodec
|
||||
import io.circe.generic.semiauto._
|
||||
import io.odin.Logger
|
||||
import monix.bio.Task
|
||||
import nova.monadic_sfx.util.IOUtils
|
||||
import nova.monadic_sfx.util.reactive.Middlewares
|
||||
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
|
||||
// @JsonCodec
|
||||
sealed abstract class Action[+T]
|
||||
final case class Replace[T](p: T) extends Action[T]
|
||||
|
||||
object Action {
|
||||
implicit def codec[T: Encoder: Decoder]: Codec[Action[T]] = deriveCodec
|
||||
}
|
||||
|
||||
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]) {
|
||||
class FXRouter[P](
|
||||
)(implicit E: Encoder[P], D: Decoder[P]) {
|
||||
import FXRouter._
|
||||
|
||||
def store(initialPage: P, logger: Logger[Task]) =
|
||||
def store(initialPage: P, logger: Logger[Task]): Task[FXStore[P]] =
|
||||
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"))
|
||||
)
|
||||
for {
|
||||
mw <- Middlewares.actionLoggerMiddleware[Action[P], State[P]](
|
||||
logger,
|
||||
"RouterStore"
|
||||
)
|
||||
store <- Store.createL[Action[P], State[P]](
|
||||
Replace(initialPage),
|
||||
State(initialPage),
|
||||
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _),
|
||||
Seq(mw)
|
||||
)
|
||||
} yield store
|
||||
)
|
||||
|
||||
def reducer(
|
||||
@ -67,28 +72,12 @@ class FXRouter[P <: EnumEntry](
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
@JsonCodec
|
||||
sealed trait Page
|
||||
object Page {
|
||||
final case object Home extends Page
|
||||
final case class UserHome(id: Int) extends Page
|
||||
final case object Todo extends Page
|
||||
}
|
||||
|
||||
// case class State()
|
||||
|
@ -16,7 +16,7 @@ import scalafx.Includes._
|
||||
import scalafx.beans.property.ObjectProperty
|
||||
import scalafx.beans.property.StringProperty
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.scene.Node
|
||||
import scalafx.scene.Parent
|
||||
import scalafx.scene.control.ContextMenu
|
||||
import scalafx.scene.control.ListCell
|
||||
import scalafx.scene.control.SelectionMode
|
||||
@ -25,17 +25,12 @@ import scalafx.scene.text.Text
|
||||
|
||||
object TodoListView {
|
||||
def apply(
|
||||
store: MonixProSubject[
|
||||
TodoListStore.Action,
|
||||
(TodoListStore.Action, TodoListStore.State)
|
||||
]
|
||||
): Task[Node] =
|
||||
store: Store[TodoListStore.Action, TodoListStore.State]
|
||||
): Task[Parent] =
|
||||
Task.deferAction(implicit s =>
|
||||
Task {
|
||||
val cc = CompositeCancelable()
|
||||
val todos =
|
||||
store.map { case (_, state) => state.todos }
|
||||
// Todo(-1, "").some
|
||||
val todos = store.map { case (_, state) => state.todos }
|
||||
val _selectedItems = ObjectProperty(Seq.empty[Todo])
|
||||
|
||||
new HBox {
|
||||
|
44
src/main/scala/nova/monadic_sfx/util/SynchedObject.scala
Normal file
44
src/main/scala/nova/monadic_sfx/util/SynchedObject.scala
Normal file
@ -0,0 +1,44 @@
|
||||
package nova.monadic_sfx.util
|
||||
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.catnap.MVar
|
||||
|
||||
/**
|
||||
* Synchronization wrapper for a mutable object
|
||||
*
|
||||
* @param obj the mutable object
|
||||
* @param lock lock for synchronization
|
||||
*/
|
||||
class SynchedObject[A](obj: A, lock: MLock) {
|
||||
|
||||
def modify(f: A => Task[Unit]): Task[Unit] =
|
||||
lock.greenLight(f(obj))
|
||||
|
||||
def get: Task[A] = lock.greenLight(Task(obj))
|
||||
}
|
||||
|
||||
object SynchedObject {
|
||||
def apply[A](obj: A) =
|
||||
MVar[Task]
|
||||
.of(())
|
||||
.map(m => new MLock(m))
|
||||
.flatMap(lock => Task(new SynchedObject(obj, lock)))
|
||||
}
|
||||
|
||||
final class MLock(mvar: MVar[Task, Unit]) {
|
||||
def acquire: Task[Unit] =
|
||||
mvar.take
|
||||
|
||||
def release: Task[Unit] =
|
||||
mvar.put(())
|
||||
|
||||
def greenLight[A](fa: Task[A]): Task[A] =
|
||||
for {
|
||||
_ <- acquire
|
||||
a <- fa.doOnCancel(
|
||||
release.onErrorHandleWith(ex => UIO(println(ex.getMessage())))
|
||||
)
|
||||
_ <- release
|
||||
} yield a
|
||||
}
|
@ -21,7 +21,7 @@ import monix.reactive.Observable
|
||||
final case class StoreInfo[A](
|
||||
name: String,
|
||||
action: A,
|
||||
time: LocalDateTime = LocalDateTime.now()
|
||||
time: LocalDateTime
|
||||
)
|
||||
|
||||
object StoreInfo {
|
||||
@ -68,7 +68,8 @@ object Middlewares {
|
||||
Task((obs: Observable[(A, M)]) =>
|
||||
obs.doOnNextF {
|
||||
case (a, _) =>
|
||||
logger.debug(StoreInfo(name, a))
|
||||
Task(LocalDateTime.now())
|
||||
.flatMap(curTime => logger.debug(StoreInfo(name, a, curTime)))
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package nova.monadic_sfx.util.reactive
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
import io.circe.Encoder
|
||||
import io.odin.Logger
|
||||
import monix.bio.Task
|
||||
@ -72,9 +74,11 @@ object Store {
|
||||
|
||||
val obs = subject
|
||||
.doOnNextF(action =>
|
||||
logger.debug(
|
||||
StoreInfo(storeName, action)
|
||||
) // .executeOn(Scheduler.global)
|
||||
Task(LocalDateTime.now()).flatMap(curTime =>
|
||||
logger.debug(
|
||||
StoreInfo(storeName, action, curTime)
|
||||
)
|
||||
)
|
||||
)
|
||||
// .doOnNextF(action => Coeval(println(action)))
|
||||
.scanEvalF[Task, (A, M)](Task.pure(initialAction -> initialState))(
|
||||
|
Loading…
Reference in New Issue
Block a user