org.scalafx.extras.mvcfx.package.scala Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2021, ScalaFX Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the ScalaFX Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE SCALAFX PROJECT OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.scalafx.extras
/**
* Package `mvcfx` helps in implementation of Model-View-Controller-like patters, we call it MVCfx.
* The pattern is build around use of views defined in FXML (the view), with binding to ScalaFX using ScalaFXML library.
*
* There are two cooperating classes `ControllerFX` for binding FXML to Scala code
* and `ModelFX` that contains logic for the component.
* An additional helper `MVCfx` class is provided to instantiate the `ControllerFX` and the corresponding `ModelFX`.
*
* The structure of the UI component is defined in a standard JavaFX FXML file.
* The Scala side of the FXML is in a class `ControllerFX`.
* Part of the `ControllerFX` is automatically generated by ScalaFXML macro, the rest can be customized as needed,
* including binding of the UI to appropriate parts of the component logic represented by the `ModelFX`.
* Use of the `MVCfx` is optional and primarily intended to simplify instantiation of the `ControllerFX`
* and the `ModelFX`.
*
* Below is an example using the classes in `mvcfx`. The code implements a simple Stop Watch.
* The complete code is in demos part of the ScalaFX Extras project.
*
* Note the recommended naming convention used:
* * StopWatch extends MVCfx
* * StopWatchModel extends ModelFX
* * StopWatchController extends ControllerFX
* * StopWatch.fxml the FXVM declaration of the view
*
* First we have the FXML definitions representing the structure of the user interface
* (`org/scalafx/extras/mvcfx/stopwatch/StopWatch.fxml`)
* {{{
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* }}}
*
* The `ControllerFX` creates connection of the FXML to Scala code and underlying `ModelFX` for the application logic.
* Note the `@sfxml` annotation and the constructor arguments corresponding to controls defined in FXML,
* like `minutesLabel`. The last argument to the constructor is the `ModelFX`.
* (`org.scalafx.extras.mvcfx.stopwatch.StopWatchController`)
* {{{
* package org.scalafx.extras.mvcfx.stopwatch
*
* import org.scalafx.extras.mvcfx.ControllerFX
*
* import scalafx.Includes._
* import scalafx.scene.control.{Button, Label}
* import scalafxml.core.macros.sfxml
*
* @sfxml
* class StopWatchController(minutesLabel: Label,
* secondsLabel: Label,
* fractionLabel: Label,
* startButton: Button,
* stopButton: Button,
* resetButton: Button,
* model: StopWatchModel) extends ControllerFX {
*
* minutesLabel.text.value = format2d(model.minutes.longValue)
* model.minutes.onChange { (_, _, newValue) =>
* minutesLabel.text.value = format2d(newValue.longValue)
* }
* secondsLabel.text.value = format2d(model.seconds.longValue())
* model.seconds.onChange { (_, _, newValue) =>
* secondsLabel.text.value = format2d(newValue.longValue())
* }
* fractionLabel.text.value = format2d(model.secondFraction.longValue() / 10)
* model.secondFraction.onChange { (_, _, newValue) =>
* fractionLabel.text.value = format2d(newValue.longValue() / 10)
* }
*
* startButton.disable <== model.running
* stopButton.disable <== !model.running
* resetButton.disable <== model.running
*
* startButton.onAction = handle {
* model.onStart()
* }
* stopButton.onAction = handle {
* model.onStop()
* }
* resetButton.onAction = handle {
* model.onReset()
* }
*
* private def format2d(t: Number) = f"${t.longValue()}%02d"
* }
* }}}
*
* The `ModelFX` implements the logic for the operation of the Stop Watch.
* Notice that there are no direct references to UI controls.
* The connection to the UI is through the properties (like `minutes`).
* The `ModelFX` is not aware how the `ControllerFX` is implemented.
* (`org.scalafx.extras.mvcfx.stopwatch.StopWatchModel`)
*
* {{{
* package org.scalafx.extras.mvcfx.stopwatch
*
* import javafx.{concurrent => jfxc}
*
* import org.scalafx.extras._
* import org.scalafx.extras.mvcfx.ModelFX
*
* import scalafx.Includes._
* import scalafx.beans.property.{LongProperty, ReadOnlyBooleanProperty, ReadOnlyBooleanWrapper}
*
* class StopWatchModel extends ModelFX {
*
* private val _running = ReadOnlyBooleanWrapper(false)
*
* val running: ReadOnlyBooleanProperty = _running.readOnlyProperty
*
* private val counterService = new CounterService()
* counterService.period = 10.ms
*
* val minutes = new LongProperty()
* val seconds = new LongProperty()
* val secondFraction = new LongProperty()
*
* counterService.elapsedTime.onChange { (_, _, newValue) =>
* val t = newValue.longValue()
* secondFraction.value = t % 1000
* seconds.value = (t / 1000) % 60
* minutes.value = t / 1000 / 60
* }
*
* def onStart(): Unit = {
* counterService.doResume()
* _running.value = true
* }
*
* def onStop(): Unit = {
* counterService.doPause()
* _running.value = false
* }
*
* def onReset(): Unit = counterService.doReset()
*
* private class CounterService extends jfxc.ScheduledService[Long] {
*
* private var timeAccumulator: Long = 0
* private var restartTime: Long = 0
*
* val elapsedTime = new LongProperty()
*
* override def createTask(): jfxc.Task[Long] = {
* new jfxc.Task[Long]() {
* override protected def call(): Long = {
* val ct = System.currentTimeMillis()
* val et = timeAccumulator + (ct - restartTime)
* onFX {elapsedTime.value = et}
* et
* }
* }
* }
*
* def doPause(): Unit = {
* val ct = System.currentTimeMillis()
* timeAccumulator += (ct - restartTime)
* onFX {elapsedTime.value = timeAccumulator}
* this.cancel()
* }
*
* def doResume(): Unit = {
* restartTime = System.currentTimeMillis()
* this.restart()
* }
*
* def doReset(): Unit = {
* timeAccumulator = 0
* onFX {elapsedTime.value = 0}
* }
* }
* }
* }}}
*
* The `MVCfx` implementation is very simple,
* it only needs instance of the model and information about location of the FXML resource.
* (`org.scalafx.extras.mvcfx.stopwatch.StopWatch`)
* {{{
* package org.scalafx.extras.mvcfx.stopwatch
*
* import org.scalafx.extras.mvcfx.MVCfx
*
* class StopWatch(val model: StopWatchModel = new StopWatchModel())
* extends MVCfx("/org/scalafx/extras/mvcfx/stopwatch/StopWatchView.fxml")
* }}}
*
* The `MVCfx` can be easily used in an Application class to create UI.
* (`org.scalafx.extras.mvcfx.stopwatch.StopWatchApp`)
* {{{
* package org.scalafx.extras.mvcfx.stopwatch
*
* import scala.language.implicitConversions
* import scalafx.application.JFXApp3
* import scalafx.application.JFXApp3.PrimaryStage
* import scalafx.scene.Scene
* import scalafx.scene.layout.BorderPane
*
* object StopWatchApp extends JFXApp3 {
* override def start(): Unit = {
* stage = new PrimaryStage {
* scene = new Scene {
* title = "StopWatch"
* root = new BorderPane {
* center = new StopWatch().view
* }
* }
* }
* }
* }
* }}}
*/
package object mvcfx
© 2015 - 2025 Weber Informatics LLC | Privacy Policy