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.

350 lines
12 KiB

  1. package nova.monadic_sfx
  2. import cats.syntax.eq._
  3. import io.odin.Logger
  4. import monix.bio.IO
  5. import monix.bio.Task
  6. import monix.eval.Coeval
  7. import monix.execution.cancelables.CompositeCancelable
  8. import monix.{eval => me}
  9. import nova.monadic_sfx.concurrent.Schedulers
  10. import nova.monadic_sfx.implicits._
  11. import nova.monadic_sfx.ui.FX
  12. import nova.monadic_sfx.ui.components.router.FXRouter
  13. import nova.monadic_sfx.ui.components.router.Page
  14. import nova.monadic_sfx.ui.components.todo.TodoListStore
  15. import nova.monadic_sfx.ui.components.todo.TodoListView
  16. import nova.monadic_sfx.util.MediaPlayerResource
  17. import nova.monadic_sfx.util.controls.JFXButton
  18. import nova.monadic_sfx.util.controls.JFXDialog
  19. import nova.monadic_sfx.util.controls.JFXTextField
  20. import nova.monadic_sfx.util.controls.VideoView
  21. import org.gerweck.scalafx.util._
  22. import org.kordamp.bootstrapfx.BootstrapFX
  23. import scalafx.Includes._
  24. import scalafx.application.JFXApp3.PrimaryStage
  25. import scalafx.beans.property.ObjectProperty
  26. import scalafx.beans.property.StringProperty
  27. import scalafx.collections.ObservableBuffer
  28. import scalafx.geometry.Insets
  29. import scalafx.geometry.Pos
  30. import scalafx.scene.Node
  31. import scalafx.scene.Parent
  32. import scalafx.scene.Scene
  33. import scalafx.scene.control.Label
  34. import scalafx.scene.control.TableColumn
  35. import scalafx.scene.control.TableView
  36. import scalafx.scene.control.Tooltip
  37. import scalafx.scene.layout.BorderPane
  38. import scalafx.scene.layout.HBox
  39. import scalafx.scene.layout.Priority
  40. import scalafx.scene.layout.StackPane
  41. import scalafx.util.Duration
  42. import nova.monadic_sfx.util.controls.ActionObservableDsl
  43. import java.util.concurrent.TimeUnit
  44. import scala.util.Random
  45. import cats.effect.Resource
  46. import nova.monadic_sfx.ui.FXComponent
  47. object App {
  48. val _scene = Coeval(new Scene {
  49. root = new HBox {
  50. padding = Insets(20)
  51. // style = """| -fx-background-color: rgb(38, 38, 38);
  52. // | -fx-text-fill: white;""".stripMargin
  53. stylesheets ++= Seq(
  54. BootstrapFX.bootstrapFXStylesheet,
  55. os.rel / "static" / "css" / "main.css"
  56. )
  57. }
  58. })
  59. val stage = Coeval(new PrimaryStage {
  60. title = "Simple ScalaFX App"
  61. scene = _scene.value()
  62. minWidth = 700
  63. minHeight = 520
  64. width = 1280
  65. height = 720
  66. // resizable = false
  67. })
  68. }
  69. final class App(
  70. // spawnProtocol: ActorSystem[SpawnProtocol.Command],
  71. fx: FX,
  72. schedulers: Schedulers,
  73. startTime: Long,
  74. mediaPlayer: MediaPlayerResource
  75. )(implicit logger: Logger[Task]) {
  76. // val program = MyFxApp
  77. // .resource(schedulers, stage)
  78. // .evalMap { fxApp =>
  79. // new MainAppDelegate(schedulers, mediaPlayer, _scene).init
  80. // .flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode))
  81. // .executeOn(schedulers.fx) >> Task.pure(fxApp)
  82. // }
  83. // .use { fxApp =>
  84. // for {
  85. // // _ <- Task(stage.resizable = false).executeOn(schedulers.fx)
  86. // currentTime <- IO.clock.realTime(TimeUnit.MILLISECONDS)
  87. // _ <- logger.info(
  88. // s"Application started in ${(currentTime - startTime) / 1000f} seconds"
  89. // )
  90. // // _ <- Task(CSSFX.start(stage))
  91. val buttonStyle = """| -fx-padding: 0.7em 0.57em;
  92. | -fx-font-size: 14px;
  93. | -jfx-button-type: RAISED;
  94. | -fx-background-color: rgb(77,102,204);
  95. | -fx-pref-width: 200;
  96. | -fx-text-fill: WHITE; """.stripMargin
  97. val router = new FXRouter[Page]
  98. // val players = mediaPlayerFactory.mediaPlayers
  99. // val videoPlayerController = players.newEmbeddedMediaPlayer()
  100. // mediaPlayer.controller.videoSurface.set(
  101. // videoSurfaceForImageView(videoView)
  102. // )
  103. // val videoPlayerControllerCleanup =
  104. // Task(videoPlayerController.controls().stop()) >>
  105. // Task(videoPlayerController.release())
  106. val videoPage = Coeval {
  107. new BorderPane { pane =>
  108. // val mp = new MediaPlayer(
  109. // new Media(
  110. // "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
  111. // // "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_5mb.mp4"
  112. // )
  113. // ) {
  114. // autoPlay = true
  115. // }
  116. // obs(pane).subscribe()(schedulers.async)
  117. hgrow = Priority.Always
  118. center = new VideoView(mediaPlayer.controller) {
  119. alignmentInParent = Pos.Center
  120. preserveRatio = true
  121. // hgrow = Priority.Always
  122. // this.prefWidth <== pane.prefWidth
  123. // fitWidth = _scene.width.value - 40
  124. // _scene.width
  125. // .map(_ - 40)
  126. // .onChange((_, _, value) => fitWidth.value = value)
  127. // (new DoubleProperty).<==(_scene.width.map(_ - 10))
  128. // fitWidth.<==(_scene.width.map(_ - 10))
  129. }
  130. // videoPlayerController.video().videoDimension().setSize()
  131. padding = Insets(0, 0, 5, 0)
  132. bottom = new HBox {
  133. val mrl = new StringProperty
  134. spacing = 5
  135. children ++= Seq(
  136. new JFXTextField {
  137. text = "https://www.youtube.com/watch?v=0QKQlf8r7ls"
  138. text ==> mrl
  139. prefWidth = 100
  140. minWidth = 80
  141. },
  142. new JFXButton {
  143. text = "Play Video"
  144. style = buttonStyle
  145. onAction = _ => {
  146. if (mediaPlayer.controller.media().isValid())
  147. mediaPlayer.controller.controls().stop()
  148. mediaPlayer.controller
  149. .media()
  150. // .play(
  151. // "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
  152. // )
  153. // .play("https://www.youtube.com/watch?v=yZIummTz9mM")
  154. .play(mrl.value)
  155. }
  156. },
  157. new JFXButton {
  158. text = "Resume"
  159. style = buttonStyle
  160. onAction = _ => mediaPlayer.controller.controls().play()
  161. },
  162. new JFXButton {
  163. text = "Pause"
  164. style = buttonStyle
  165. onAction = _ => mediaPlayer.controller.controls().pause()
  166. },
  167. new JFXButton {
  168. text = "Stop"
  169. style = buttonStyle
  170. tooltip = new Tooltip {
  171. text = "Stop"
  172. showDelay = Duration(200)
  173. }
  174. onAction = _ => mediaPlayer.controller.controls().stop()
  175. },
  176. new JFXButton {
  177. text = "Get Status"
  178. style = buttonStyle
  179. onAction = _ => {
  180. println(mediaPlayer.controller.status().state())
  181. }
  182. }
  183. )
  184. }
  185. }
  186. }
  187. val init = for {
  188. routerStore <- Resource.eval(router.store2(Page.Home, logger))
  189. todoStore <- Resource.eval(TodoListStore(logger))
  190. todoComponent <- TodoListView(todoStore)
  191. videoPage <- Resource.eval(videoPage.to[Task])
  192. resolver: PartialFunction[Page, Parent] = {
  193. case Page.Home => videoPage
  194. // engine.load("https://www.youtube.com/embed/qmlegXdlnqI")
  195. // engine.load("https://youtube.com/embed/aqz-KE-bpKQ")
  196. // engine.load("http://www.youtube.com/embed/IyaFEBI_L24")
  197. case Page.UserHome =>
  198. new Label {
  199. styleClass ++= Seq("text-white")
  200. text = s"User Home, Id = ${Random.nextInt()}"
  201. }
  202. case Page.Todo => todoComponent.node
  203. }
  204. routerNode <- FXComponent(implicit cc =>
  205. Task
  206. .deferAction(implicit s =>
  207. Task(new HBox { box =>
  208. // implicit val cc = CompositeCancelable()
  209. alignment = Pos.Center
  210. //TODO find a better way to do this
  211. // videoView.fitWidth <== box.prefWidth
  212. children <-- router
  213. .render2(resolver)(routerStore)
  214. // call cancel on the old component to cancel all subscriptions
  215. .scan[Parent](new Label("empty")) { case (a, b) => b }
  216. .doOnNextF(s => logger.debug(s"Actual receive: $s"))
  217. .map(_.delegate)
  218. })
  219. )
  220. )
  221. mainSceneNode <- FXComponent(implicit cc =>
  222. Task.deferAction(implicit s =>
  223. Task(new StackPane { root =>
  224. import ActionObservableDsl._
  225. alignment = Pos.Center
  226. hgrow = Priority.Always
  227. vgrow = Priority.Always
  228. children = new BorderPane {
  229. hgrow = Priority.Always
  230. vgrow = Priority.Always
  231. center = routerNode.node
  232. bottom = new HBox {
  233. // implicit val cc = CompositeCancelable()
  234. alignment = Pos.Center
  235. spacing = 20
  236. children = Seq(
  237. new JFXButton {
  238. text = "Forward"
  239. style = buttonStyle
  240. obsAction.map(_ => FXRouter.Forward) --> routerStore.sink
  241. disable <-- routerStore.source.map {
  242. case (_, FXRouter.State(_, h)) =>
  243. h.state.sp == h.state.values.size - 1
  244. }
  245. },
  246. new JFXButton {
  247. text = "Backward"
  248. style = buttonStyle
  249. obsAction.mapEval(_ =>
  250. me.Task(println("Fired")) >> me.Task.pure(FXRouter.Backward)
  251. ) --> routerStore.sink
  252. disable <-- routerStore.source
  253. .doOnNextF(b => Coeval(println(s"Received1: $b")))
  254. .map {
  255. case (_, FXRouter.State(_, h)) => h.state.sp == 0
  256. }
  257. },
  258. new JFXButton {
  259. text = "Home"
  260. style = buttonStyle
  261. disable <-- routerStore.source
  262. .map { case (_, FXRouter.State(p, _)) => p === Page.Home }
  263. obsAction.map(_ =>
  264. FXRouter.Replace(Page.Home)
  265. ) --> routerStore.sink
  266. },
  267. new JFXButton {
  268. text = "Todo"
  269. style = buttonStyle
  270. disable <-- routerStore.source
  271. .map { case (_, FXRouter.State(p, _)) => p === Page.Todo }
  272. obsAction.map(_ =>
  273. FXRouter.Replace(Page.Todo)
  274. ) --> routerStore.sink
  275. },
  276. new JFXButton {
  277. text = "UserHome"
  278. style = buttonStyle
  279. disable <-- routerStore.source
  280. .map {
  281. case (_, FXRouter.State(p, _)) => p === Page.UserHome
  282. }
  283. obsAction.map(_ =>
  284. FXRouter.Replace(Page.UserHome)
  285. ) --> routerStore.sink
  286. },
  287. new JFXButton {
  288. text = "Dialog"
  289. style = buttonStyle
  290. val d = new JFXDialog {
  291. content = new HBox {
  292. style = "-fx-background-color: black"
  293. children = Seq(new Label {
  294. styleClass ++= Seq("text-white")
  295. text = "Sample Dialog"
  296. })
  297. padding = Insets(20)
  298. }
  299. }
  300. onAction = () => d.show(root)
  301. }
  302. )
  303. }
  304. }
  305. })
  306. )
  307. )
  308. } yield mainSceneNode
  309. }
  310. final class TestModel(_name: String, _age: Int) {
  311. val name = StringProperty(_name).readOnly
  312. val age = ObjectProperty(_age).readOnly
  313. }
  314. object Test {
  315. val items = ObservableBuffer(
  316. new TestModel("hmm", 1),
  317. new TestModel("hmm2", 2)
  318. )
  319. val ttv = new TableView[TestModel](items) {
  320. columns ++= Seq(
  321. new TableColumn[TestModel, String] {
  322. text = "Name"
  323. cellValueFactory = { _.value.name }
  324. },
  325. new TableColumn[TestModel, Int] {
  326. text = "Age"
  327. cellValueFactory = { _.value.age }
  328. }
  329. )
  330. }
  331. }