You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
12 KiB
361 lines
12 KiB
package nova.monadic_sfx
|
|
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
import scala.util.Random
|
|
|
|
import cats.effect.Resource
|
|
import cats.syntax.eq._
|
|
import cats.syntax.option._
|
|
import com.softwaremill.macwire._
|
|
import io.odin.Logger
|
|
import monix.bio.IO
|
|
import monix.bio.Task
|
|
import monix.eval.Coeval
|
|
import monix.execution.cancelables.CompositeCancelable
|
|
import monix.{eval => me}
|
|
import nova.monadic_sfx.executors.Schedulers
|
|
import nova.monadic_sfx.implicits._
|
|
import nova.monadic_sfx.ui.MyFxApp
|
|
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 nova.monadic_sfx.util.MediaPlayerResource
|
|
import nova.monadic_sfx.util.controls.JFXButton
|
|
import nova.monadic_sfx.util.controls.JFXDialog
|
|
import nova.monadic_sfx.util.controls.JFXTextField
|
|
import nova.monadic_sfx.util.controls.VideoView
|
|
import org.gerweck.scalafx.util._
|
|
import org.kordamp.bootstrapfx.BootstrapFX
|
|
import scalafx.Includes._
|
|
import scalafx.application.JFXApp3.PrimaryStage
|
|
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.Node
|
|
import scalafx.scene.Parent
|
|
import scalafx.scene.Scene
|
|
import scalafx.scene.control.Button
|
|
import scalafx.scene.control.Label
|
|
import scalafx.scene.control.TableColumn
|
|
import scalafx.scene.control.TableView
|
|
import scalafx.scene.control.Tooltip
|
|
import scalafx.scene.layout.BorderPane
|
|
import scalafx.scene.layout.HBox
|
|
import scalafx.scene.layout.Priority
|
|
import scalafx.scene.layout.StackPane
|
|
import scalafx.util.Duration
|
|
class MainApp(
|
|
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
|
schedulers: Schedulers,
|
|
startTime: Long,
|
|
mediaPlayer: MediaPlayerResource
|
|
)(implicit logger: Logger[Task]) {
|
|
|
|
private lazy val _scene = new Scene {
|
|
root = new HBox {
|
|
padding = Insets(20)
|
|
// style = """| -fx-background-color: rgb(38, 38, 38);
|
|
// | -fx-text-fill: white;""".stripMargin
|
|
stylesheets ++= Seq(
|
|
BootstrapFX.bootstrapFXStylesheet,
|
|
os.rel / "static" / "css" / "main.css"
|
|
)
|
|
}
|
|
}
|
|
|
|
private lazy val stage = new PrimaryStage {
|
|
title = "Simple ScalaFX App"
|
|
scene = _scene
|
|
minWidth = 700
|
|
minHeight = 520
|
|
width = 1280
|
|
height = 720
|
|
// resizable = false
|
|
}
|
|
|
|
(for {
|
|
(stopSignal, fxAppFib) <- MyFxApp.resource(schedulers, stage)
|
|
i <- Resource.make(Task(1))(_ => Task.unit)
|
|
} yield (stopSignal, fxAppFib, i)).use {
|
|
case (a, b, c) => Task.unit
|
|
}
|
|
|
|
val program = MyFxApp
|
|
.resource(schedulers, stage)
|
|
.evalMap {
|
|
case (stopSignal, fxAppFib) =>
|
|
wire[MainAppDelegate].init
|
|
.flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode))
|
|
.executeOn(schedulers.fx) >> Task.pure(stopSignal -> fxAppFib)
|
|
}
|
|
.use {
|
|
case (stopSignal, fxAppFib) =>
|
|
for {
|
|
// _ <- Task(stage.resizable = false).executeOn(schedulers.fx)
|
|
currentTime <- IO.clock.realTime(TimeUnit.MILLISECONDS)
|
|
_ <- logger.info(
|
|
s"Application started in ${(currentTime - startTime) / 1000f} seconds"
|
|
)
|
|
// _ <- Task(CSSFX.start(stage))
|
|
_ <- fxAppFib.join
|
|
} yield ()
|
|
}
|
|
|
|
}
|
|
|
|
class MainAppDelegate(
|
|
schedulers: Schedulers,
|
|
mediaPlayer: MediaPlayerResource,
|
|
_scene: Scene
|
|
)(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
|
|
val router = new FXRouter[Page]
|
|
|
|
// val players = mediaPlayerFactory.mediaPlayers
|
|
// val videoPlayerController = players.newEmbeddedMediaPlayer()
|
|
// mediaPlayer.controller.videoSurface.set(
|
|
// videoSurfaceForImageView(videoView)
|
|
// )
|
|
|
|
// val videoPlayerControllerCleanup =
|
|
// Task(videoPlayerController.controls().stop()) >>
|
|
// Task(videoPlayerController.release())
|
|
|
|
val videoPage = new BorderPane { pane =>
|
|
// val mp = new MediaPlayer(
|
|
// new Media(
|
|
// "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
|
|
// // "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_5mb.mp4"
|
|
// )
|
|
// ) {
|
|
// autoPlay = true
|
|
// }
|
|
// obs(pane).subscribe()(schedulers.async)
|
|
hgrow = Priority.Always
|
|
center = new VideoView(mediaPlayer.controller) {
|
|
alignmentInParent = Pos.Center
|
|
preserveRatio = true
|
|
// hgrow = Priority.Always
|
|
// this.prefWidth <== pane.prefWidth
|
|
fitWidth = _scene.width.value - 40
|
|
_scene.width
|
|
.map(_ - 40)
|
|
.onChange((_, _, value) => fitWidth.value = value)
|
|
// (new DoubleProperty).<==(_scene.width.map(_ - 10))
|
|
// fitWidth.<==(_scene.width.map(_ - 10))
|
|
}
|
|
// videoPlayerController.video().videoDimension().setSize()
|
|
padding = Insets(0, 0, 5, 0)
|
|
bottom = new HBox {
|
|
val mrl = new StringProperty
|
|
spacing = 5
|
|
children ++= Seq(
|
|
new JFXTextField {
|
|
text = "https://www.youtube.com/watch?v=0QKQlf8r7ls"
|
|
text ==> mrl
|
|
prefWidth = 100
|
|
minWidth = 80
|
|
},
|
|
new JFXButton {
|
|
text = "Play Video"
|
|
style = buttonStyle
|
|
onAction = _ => {
|
|
if (mediaPlayer.controller.media().isValid())
|
|
mediaPlayer.controller.controls().stop()
|
|
|
|
mediaPlayer.controller
|
|
.media()
|
|
// .play(
|
|
// "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
|
|
// )
|
|
// .play("https://www.youtube.com/watch?v=yZIummTz9mM")
|
|
.play(mrl.value)
|
|
}
|
|
},
|
|
new JFXButton {
|
|
text = "Resume"
|
|
style = buttonStyle
|
|
onAction = _ => mediaPlayer.controller.controls().play()
|
|
},
|
|
new JFXButton {
|
|
text = "Pause"
|
|
style = buttonStyle
|
|
onAction = _ => mediaPlayer.controller.controls().pause()
|
|
},
|
|
new JFXButton {
|
|
text = "Stop"
|
|
style = buttonStyle
|
|
tooltip = new Tooltip {
|
|
text = "Stop"
|
|
showDelay = Duration(200)
|
|
}
|
|
onAction = _ => mediaPlayer.controller.controls().stop()
|
|
},
|
|
new JFXButton {
|
|
text = "Get Status"
|
|
style = buttonStyle
|
|
onAction = _ => {
|
|
println(mediaPlayer.controller.status().state())
|
|
}
|
|
}
|
|
)
|
|
|
|
}
|
|
}
|
|
|
|
val init: Task[Node] = for {
|
|
routerStore <- router.store(Page.Home, logger)
|
|
todoStore <- TodoListStore(logger)
|
|
todoComponent <- TodoListView(todoStore)
|
|
resolver: PartialFunction[Page, Parent] = {
|
|
case Page.Home => videoPage
|
|
// engine.load("https://www.youtube.com/embed/qmlegXdlnqI")
|
|
// engine.load("https://youtube.com/embed/aqz-KE-bpKQ")
|
|
// engine.load("http://www.youtube.com/embed/IyaFEBI_L24")
|
|
case Page.UserHome =>
|
|
new Label {
|
|
styleClass ++= Seq("text-white")
|
|
text = s"User Home, Id = ${Random.nextInt()}"
|
|
}
|
|
case Page.Todo => todoComponent
|
|
}
|
|
routerNode: Node <-
|
|
Task
|
|
.deferAction(implicit s =>
|
|
Task(new HBox { box =>
|
|
alignment = Pos.Center
|
|
//TODO find a better way to do this
|
|
// videoView.fitWidth <== box.prefWidth
|
|
children <-- router
|
|
.render(resolver)(routerStore)
|
|
// call cancel on the old component to cancel all subscriptions
|
|
.scan[Parent](new Label("empty")) { case (a, b) => b }
|
|
.doOnNextF(s => logger.debug(s"Actual receive: $s"))
|
|
.map(_.delegate)
|
|
})
|
|
)
|
|
|
|
mainSceneNode <- Task.deferAction(implicit s =>
|
|
Task(new StackPane { root =>
|
|
alignment = Pos.Center
|
|
hgrow = Priority.Always
|
|
vgrow = Priority.Always
|
|
children = new BorderPane {
|
|
hgrow = Priority.Always
|
|
vgrow = Priority.Always
|
|
center = routerNode
|
|
bottom = new HBox {
|
|
implicit val cc = CompositeCancelable()
|
|
alignment = Pos.Center
|
|
spacing = 20
|
|
children = Seq(
|
|
new JFXButton {
|
|
text = "Forward"
|
|
style = buttonStyle
|
|
obsAction.useLazyEval(
|
|
me.Task.pure(FXRouter.Forward)
|
|
) --> routerStore
|
|
disable <-- routerStore.map {
|
|
case (_, FXRouter.State(_, h)) =>
|
|
h.state.sp == h.state.values.size - 1
|
|
}
|
|
},
|
|
new JFXButton {
|
|
text = "Backward"
|
|
style = buttonStyle
|
|
|
|
obsAction.useLazyEval(
|
|
me.Task(println("Fired")) >> me.Task.pure(FXRouter.Backward)
|
|
) --> routerStore
|
|
disable <-- routerStore
|
|
.doOnNextF(b => Coeval(println(s"Received1: $b")))
|
|
.map {
|
|
case (_, FXRouter.State(_, h)) => h.state.sp == 0
|
|
}
|
|
},
|
|
new JFXButton {
|
|
text = "Home"
|
|
style = buttonStyle
|
|
disable <-- routerStore
|
|
.map { case (_, FXRouter.State(p, _)) => p === Page.Home }
|
|
obsAction
|
|
.useLazyEval(
|
|
me.Task.pure(FXRouter.Replace(Page.Home))
|
|
) --> routerStore
|
|
},
|
|
new JFXButton {
|
|
text = "Todo"
|
|
style = buttonStyle
|
|
disable <-- routerStore
|
|
.map { case (_, FXRouter.State(p, _)) => p === Page.Todo }
|
|
obsAction
|
|
.useLazyEval(
|
|
me.Task.pure(FXRouter.Replace(Page.Todo))
|
|
) --> routerStore
|
|
},
|
|
new JFXButton {
|
|
text = "UserHome"
|
|
style = buttonStyle
|
|
disable <-- routerStore
|
|
.map { case (_, FXRouter.State(p, _)) => p == Page.UserHome }
|
|
obsAction
|
|
.useLazyEval(
|
|
me.Task.pure(FXRouter.Replace(Page.UserHome))
|
|
) --> routerStore
|
|
},
|
|
new JFXButton {
|
|
text = "Dialog"
|
|
style = buttonStyle
|
|
val d = new JFXDialog {
|
|
content = new HBox {
|
|
style = "-fx-background-color: black"
|
|
children = Seq(new Label {
|
|
styleClass ++= Seq("text-white")
|
|
text = "Sample Dialog"
|
|
})
|
|
padding = Insets(20)
|
|
}
|
|
}
|
|
onAction = () => d.show(root)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
})
|
|
)
|
|
} yield mainSceneNode
|
|
}
|
|
|
|
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 }
|
|
}
|
|
)
|
|
}
|
|
}
|