├── project ├── build.properties └── plugin.sbt ├── media ├── earthrise.jpg └── omgrobots.mp4 ├── src └── proscalafx │ ├── ch10 │ └── fxml │ │ ├── cat.jpg │ │ ├── FXMLAdoptionForm.scala │ │ ├── AdoptionFormController.scala │ │ └── AdoptionForm.fxml │ ├── ch05 │ ├── ui │ │ ├── images │ │ │ └── paper.png │ │ └── starterApp.css │ └── model │ │ ├── Person.scala │ │ └── StarterAppModel.scala │ ├── ch08 │ ├── AudioPlayer2 │ │ ├── resources │ │ │ ├── cross.png │ │ │ └── defaultAlbum.png │ │ ├── media.css │ │ └── AudioPlayer2.scala │ ├── AudioPlayer3 │ │ ├── resources │ │ │ ├── cross.png │ │ │ ├── next.png │ │ │ ├── pause.png │ │ │ ├── play.png │ │ │ ├── prev.png │ │ │ ├── volHigh.png │ │ │ ├── volLow.png │ │ │ ├── music_note.png │ │ │ └── defaultAlbum.png │ │ ├── AbstractView.scala │ │ ├── media.css │ │ ├── MetadataView.scala │ │ ├── AudioPlayer3.scala │ │ └── SongModel.scala │ ├── AudioPlayer4 │ │ ├── resources │ │ │ ├── cross.png │ │ │ ├── next.png │ │ │ ├── pause.png │ │ │ ├── play.png │ │ │ ├── prev.png │ │ │ ├── volHigh.png │ │ │ ├── volLow.png │ │ │ ├── music_note.png │ │ │ └── defaultAlbum.png │ │ ├── AbstractView.scala │ │ ├── MetadataView.scala │ │ ├── media.css │ │ ├── SpectrumListener.scala │ │ ├── SongModel.scala │ │ ├── SpectrumBar.scala │ │ ├── AudioPlayer4.scala │ │ └── EqualizerView.scala │ ├── CodeMonkeyToDo │ │ ├── resources │ │ │ ├── job.mp3 │ │ │ ├── job.wav │ │ │ ├── cross.png │ │ │ ├── coffee.mp3 │ │ │ ├── coffee.wav │ │ │ ├── meeting.mp3 │ │ │ └── meeting.wav │ │ ├── media.css │ │ └── CodeMonkeyToDo.scala │ ├── VideoPlayer4 │ │ ├── resources │ │ │ ├── cross.png │ │ │ ├── next.png │ │ │ ├── pause.png │ │ │ ├── play.png │ │ │ ├── prev.png │ │ │ ├── thumb.png │ │ │ ├── volHigh.png │ │ │ ├── volLow.png │ │ │ ├── PlayPause.png │ │ │ ├── filmstrip.png │ │ │ ├── music_note.png │ │ │ └── defaultAlbum.png │ │ ├── VideoView.scala │ │ ├── AbstractView.scala │ │ ├── VideoPlayer4App.scala │ │ ├── VideoPlayer4.scala │ │ ├── media.css │ │ ├── SpectrumListener.scala │ │ ├── SpectrumBar.scala │ │ ├── MediaModel.scala │ │ └── EqualizerView.scala │ ├── AudioPlayer1 │ │ ├── resources │ │ │ └── keeper.mp3 │ │ └── AudioPlayer1.scala │ ├── BasicAudioClip │ │ ├── resources │ │ │ ├── beep.wav │ │ │ └── cross.png │ │ ├── media.css │ │ └── BasicAudioClip.scala │ ├── VideoPlayer2 │ │ ├── media.css │ │ └── VideoPlayer2.scala │ ├── VideoPlayer3 │ │ ├── media.css │ │ └── VideoPlayer3.scala │ ├── VideoPlayer1 │ │ └── VideoPlayer1.scala │ └── FullScreenVideoPlayer │ │ └── FullScreenVideoPlayer.scala │ ├── ch04 │ └── reversi │ │ ├── model │ │ ├── Owner.scala │ │ └── ReversiModel.scala │ │ ├── examples │ │ ├── CenterUsingBind.scala │ │ ├── CenterUsingStack.scala │ │ ├── AlignUsingStackAndTile.scala │ │ ├── PlayerScoreExample.scala │ │ └── BorderLayoutExample.scala │ │ ├── reversipieces │ │ ├── ReversiSquareTest.scala │ │ └── ReversiPieceTest.scala │ │ └── ui │ │ ├── ReversiApp.scala │ │ ├── ReversiPiece.scala │ │ └── ReversiSquare.scala │ ├── ch03 │ ├── scalafxbean │ │ ├── ScalaFXBeanMainExample.scala │ │ ├── ScalaFXBeanViewExample.scala │ │ ├── ScalaFXBeanControllerExample.scala │ │ └── ScalaFXBeanModelExample.scala │ ├── RectangleAreaExample.scala │ ├── BidirectionalBindingExample.scala │ ├── HeronsFormulaExample.scala │ ├── TriangleAreaFluentExample.scala │ ├── HeronsFormulaDirectExtensionExample.scala │ ├── NumericPropertiesExample.scala │ ├── TriangleAreaExample.scala │ └── MotivatingExample.scala │ ├── ch02 │ ├── zenpong │ │ └── ZenPongMain.scala │ ├── metronometransition │ │ └── MetronomeTransitionMain.scala │ ├── metronome1 │ │ └── Metronome1Main.scala │ ├── onthescene │ │ ├── changeOfScene.css │ │ └── onTheScene.css │ ├── metronomepathtransition │ │ └── MetronomePathTransitionMain.scala │ └── stagecoach │ │ └── StageCoachMain.scala │ ├── ch07 │ ├── ChartApp1.scala │ ├── ChartApp2.scala │ ├── ChartApp3.scala │ ├── ChartApp7.scala │ ├── ChartApp9.scala │ ├── ChartApp6.scala │ ├── ChartApp8.scala │ ├── ChartApp4.scala │ ├── ChartApp5.scala │ ├── ChartApp10.scala │ └── ChartApp11.scala │ ├── ch06 │ ├── SetChangeEventExample.scala │ ├── ObservableBufferExample.scala │ ├── MapChangeEventExample.scala │ ├── JavaFXThreadsExample.scala │ ├── UnresponsiveUIExample.scala │ ├── BufferChangeEventExample.scala │ ├── ResponsiveUIExample.scala │ ├── JavaFXSceneInSwingExample.scala │ └── FXCollectionsExample.scala │ └── ch01 │ ├── audioconfig │ ├── AudioConfigModel.scala │ └── AudioConfigMain.scala │ └── helloearthrise │ ├── HelloEarthRiseMain.scala │ └── HelloScrollPaneMain.scala ├── .gitattributes ├── .scalafmt.conf ├── .classpath ├── .project ├── README.md └── .gitignore /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.3 2 | 3 | -------------------------------------------------------------------------------- /project/plugin.sbt: -------------------------------------------------------------------------------- 1 | scalacOptions ++= Seq("-unchecked", "-deprecation") 2 | -------------------------------------------------------------------------------- /media/earthrise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/media/earthrise.jpg -------------------------------------------------------------------------------- /media/omgrobots.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/media/omgrobots.mp4 -------------------------------------------------------------------------------- /src/proscalafx/ch10/fxml/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch10/fxml/cat.jpg -------------------------------------------------------------------------------- /src/proscalafx/ch05/ui/images/paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch05/ui/images/paper.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer2/resources/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer2/resources/cross.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/cross.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/next.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/pause.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/play.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/prev.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/cross.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/next.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/pause.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/play.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/prev.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/resources/job.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/CodeMonkeyToDo/resources/job.mp3 -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/resources/job.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/CodeMonkeyToDo/resources/job.wav -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/cross.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/next.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/pause.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/play.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/prev.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/thumb.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer1/resources/keeper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer1/resources/keeper.mp3 -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/volHigh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/volHigh.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/volLow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/volLow.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/volHigh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/volHigh.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/volLow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/volLow.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/BasicAudioClip/resources/beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/BasicAudioClip/resources/beep.wav -------------------------------------------------------------------------------- /src/proscalafx/ch08/BasicAudioClip/resources/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/BasicAudioClip/resources/cross.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/resources/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/CodeMonkeyToDo/resources/cross.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/volHigh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/volHigh.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/volLow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/volLow.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/music_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/music_note.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/music_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/music_note.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.mp3 -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.wav -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.mp3 -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.wav -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/PlayPause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/PlayPause.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/filmstrip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/filmstrip.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/music_note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/music_note.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default line separator behavior, in case people don't have core.autocrlf set. 2 | # [[https://stackoverflow.com/a/42135910]] 3 | * text=auto eol=lf -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer2/resources/defaultAlbum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer2/resources/defaultAlbum.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/resources/defaultAlbum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer3/resources/defaultAlbum.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/resources/defaultAlbum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/AudioPlayer4/resources/defaultAlbum.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/resources/defaultAlbum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalafx/ProScalaFX/HEAD/src/proscalafx/ch08/VideoPlayer4/resources/defaultAlbum.png -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer2/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-background-color: black; 3 | } 4 | 5 | .label { 6 | -fx-padding: 20; 7 | -fx-font-size: 36pt; 8 | -fx-text-fill: white; 9 | } -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer3/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-background-color: black; 3 | } 4 | 5 | .label { 6 | -fx-padding: 20; 7 | -fx-font-size: 48pt; 8 | -fx-text-fill: darkred; 9 | } -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.9.8 2 | 3 | runner.dialect = scala213source3 4 | 5 | preset = IntelliJ 6 | align.preset = more 7 | maxColumn = 120 8 | docstrings.style = Asterisk 9 | docstrings.blankFirstLine = yes 10 | docstrings.wrap = no 11 | importSelectors = singleLine 12 | newlines.source = keep -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/AbstractView.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer3 2 | 3 | import scalafx.scene.Node 4 | 5 | /** 6 | * @author Jarek Sacha 7 | */ 8 | abstract class AbstractView(protected val songMadel: SongModel) { 9 | private val _viewNode: Node = initView() 10 | 11 | def viewNode: Node = _viewNode 12 | 13 | protected def initView(): Node 14 | } 15 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/VideoView.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer4 2 | 3 | import scalafx.scene.media.MediaView 4 | 5 | /** 6 | * @author Jarek Sacha 7 | */ 8 | class VideoView(mediaModel: MediaModel) extends AbstractView[MediaView](mediaModel) { 9 | 10 | protected def initView(): MediaView = { 11 | val mediaView = new MediaView 12 | mediaView.mediaPlayer <== mediaModel.mediaPlayer 13 | mediaView 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/AbstractView.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer4 2 | 3 | import scalafx.event.ActionEvent 4 | import scalafx.scene.Node 5 | 6 | /** 7 | * @author Jarek Sacha 8 | */ 9 | abstract class AbstractView[T <: Node](protected val songMadel: SongModel) { 10 | 11 | private val _viewNode: T = initView() 12 | 13 | def viewNode: T = _viewNode 14 | 15 | def onNextPageAction(nextHandler: ActionEvent => Unit): Unit = {} 16 | 17 | protected def initView(): T 18 | } 19 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/AbstractView.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer4 2 | 3 | import scalafx.event.ActionEvent 4 | import scalafx.scene.Node 5 | 6 | /** 7 | * @author Jarek Sacha 8 | */ 9 | abstract class AbstractView[T <: Node](protected val mediaModel: MediaModel) { 10 | 11 | private val _viewNode: T = initView() 12 | 13 | def viewNode: T = _viewNode 14 | 15 | def onNextPageAction(nextHandler: ActionEvent => Unit): Unit = {} 16 | 17 | protected def initView(): T 18 | } 19 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/model/Owner.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.model 2 | 3 | import scalafx.scene.paint.Color 4 | 5 | sealed case class Owner(color: Color, colorStyle: String) { 6 | 7 | def opposite: Owner = this match { 8 | case White => Black 9 | case Black => White 10 | case _ => NONE 11 | } 12 | 13 | } 14 | 15 | object NONE extends Owner(Color.Transparent, "") 16 | 17 | object White extends Owner(Color.White, "white") 18 | 19 | object Black extends Owner(Color.Black, "black") 20 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer2/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-base: #aaa; 3 | -fx-background-color: black; 4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000); 5 | -fx-background-image: url("resources/cross.png"); 6 | } 7 | 8 | .button { 9 | -fx-text-fill: #E0E0E0; 10 | -fx-font-size: 20pt; 11 | -fx-pref-width: 300px; 12 | } 13 | 14 | .label { 15 | -fx-font-size: 18pt; 16 | -fx-text-fill: #aaa; 17 | } 18 | 19 | #title { 20 | -fx-font-size: 24pt; 21 | -fx-text-fill: #ddd; 22 | } 23 | -------------------------------------------------------------------------------- /src/proscalafx/ch05/model/Person.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch05.model 2 | 3 | import scalafx.beans.property.StringProperty 4 | 5 | /** 6 | * @author Jarek Sacha 7 | */ 8 | class Person(firstName_ : String, lastName_ : String, phone_ : String) { 9 | 10 | val firstName = new StringProperty(this, "firstName", firstName_) 11 | val lastName = new StringProperty(this, "lastName", lastName_) 12 | val phone = new StringProperty(this, "phone", phone_) 13 | 14 | override def toString: String = { 15 | "Person: " + firstName() + " " + lastName() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/scalafxbean/ScalaFXBeanMainExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03.scalafxbean 2 | 3 | object ScalaFXBeanMainExample extends App { 4 | val model = new ScalaFXBeanModelExample() 5 | val view = new ScalaFXBeanViewExample(model) 6 | val controller = new ScalaFXBeanControllerExample(model, view) 7 | 8 | controller.incrementIPropertyOnModel() 9 | controller.changeStrPropertyOnModel() 10 | controller.switchColorPropertyOnModel() 11 | controller.incrementIPropertyOnModel() 12 | controller.changeStrPropertyOnModel() 13 | controller.switchColorPropertyOnModel() 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/CodeMonkeyToDo/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-base: #606060; 3 | -fx-background-color: radial-gradient(center 50% 25%, radius 60%, #666a6b, #2b2f32); 4 | -fx-background-image: url("resources/cross.png"); 5 | /* -fx-background-repeat: repeat;*/ 6 | -fx-background-position: left top; 7 | } 8 | 9 | .button { 10 | -fx-text-fill: #E0E0E0; 11 | -fx-font-size: 20pt; 12 | -fx-pref-width: 300px; 13 | } 14 | 15 | .label { 16 | -fx-text-fill: #E0E0E0; 17 | } 18 | 19 | #clipLabel { 20 | -fx-font-size: 24pt; 21 | } 22 | 23 | .hyperlink { 24 | -fx-text-fill: #808080; 25 | } -------------------------------------------------------------------------------- /src/proscalafx/ch08/BasicAudioClip/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-base: #606060; 3 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #666a6b, #2b2f32); 4 | -fx-background-image: url("resources/cross.png"); 5 | /* -fx-background-repeat: repeat; */ 6 | -fx-background-position: left top; 7 | } 8 | 9 | .button { 10 | -fx-text-fill: #E0E0E0; 11 | -fx-font-size: 20pt; 12 | -fx-pref-width: 300px; 13 | } 14 | 15 | .label { 16 | -fx-text-fill: #E0E0E0; 17 | } 18 | 19 | #clipLabel { 20 | -fx-font-size: 24pt; 21 | } 22 | 23 | .hyperlink { 24 | -fx-text-fill: #808080; 25 | } -------------------------------------------------------------------------------- /src/proscalafx/ch03/RectangleAreaExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.beans.property.DoubleProperty 4 | 5 | object RectangleAreaExample extends App { 6 | println("Constructing x with initial value of 2.0.") 7 | val x = new DoubleProperty(null, "x", 2.0) 8 | println("Constructing y with initial value of 3.0.") 9 | val y = new DoubleProperty(null, "y", 3.0) 10 | println("Creating binding area with dependencies x and y.") 11 | val area = x * y 12 | println("area = " + area()) 13 | println("Setting x to 5") 14 | x() = 5 15 | println("Setting y to 7") 16 | y() = 7 17 | println("area = " + area()) 18 | } 19 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/scalafxbean/ScalaFXBeanViewExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03.scalafxbean 2 | 3 | class ScalaFXBeanViewExample(val model: ScalaFXBeanModelExample) { 4 | 5 | model.i.onChange((_, oldValue, newValue) => { 6 | println("Property i changed: old value = " + oldValue + ", new value = " + newValue) 7 | }) 8 | 9 | model.str.onChange((_, oldValue, newValue) => { 10 | println("Property str changed: old value = " + oldValue + ", new value = " + newValue) 11 | }) 12 | 13 | model.color.onChange((_, oldValue, newValue) => { 14 | println("Property color changed: old value = " + oldValue + ", new value = " + newValue) 15 | }) 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/scalafxbean/ScalaFXBeanControllerExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03.scalafxbean 2 | 3 | import scalafx.scene.paint.Color 4 | 5 | class ScalaFXBeanControllerExample(model: ScalaFXBeanModelExample, view: ScalaFXBeanViewExample) { 6 | 7 | def incrementIPropertyOnModel(): Unit = { 8 | model.i() = model.i() + 1 9 | } 10 | 11 | def changeStrPropertyOnModel(): Unit = { 12 | val str = model.str() 13 | model.str() = if (str == "Hello") "World" else "Hello" 14 | } 15 | 16 | def switchColorPropertyOnModel(): Unit = { 17 | val color = model.color() 18 | model.color() = if (color == Color.Black) Color.White else Color.Black 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/scalafxbean/ScalaFXBeanModelExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03.scalafxbean 2 | 3 | import scalafx.beans.property.{IntegerProperty, ObjectProperty, StringProperty} 4 | import scalafx.scene.paint.Color 5 | 6 | class ScalaFXBeanModelExample { 7 | 8 | val i = new IntegerProperty(this, "i", 0) 9 | 10 | def i_=(value: Int): Unit = { 11 | i() = value 12 | } 13 | 14 | val str = new StringProperty(this, "str", "Hello") 15 | 16 | def str_=(value: String): Unit = { 17 | str() = value 18 | } 19 | 20 | val color = new ObjectProperty[Color](this, "color", Color.Black) 21 | 22 | def color_=(value: Color): Unit = { 23 | color() = value 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ProScalaFX 4 | 5 | 6 | 7 | 8 | 9 | org.scala-ide.sdt.core.scalabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.xtext.ui.shared.xtextBuilder 15 | 16 | 17 | 18 | 19 | 20 | org.scala-ide.sdt.core.scalanature 21 | org.eclipse.jdt.core.javanature 22 | org.eclipse.xtext.ui.shared.xtextNature 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer1/AudioPlayer1.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer1 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.scene.Scene 6 | import scalafx.scene.media.{Media, MediaPlayer} 7 | 8 | /** 9 | * A very simple application that plays an audio file. 10 | * 11 | * @author Jarek Sacha 12 | */ 13 | object AudioPlayer1 extends JFXApp3 { 14 | 15 | override def start(): Unit = { 16 | 17 | val resource = getClass.getResource("resources/keeper.mp3") 18 | val media = new Media(resource.toString) 19 | val mediaPlayer = new MediaPlayer(media) 20 | mediaPlayer.play() 21 | 22 | stage = new PrimaryStage { 23 | title = "Audio Player 1" 24 | scene = new Scene(200, 200) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/BidirectionalBindingExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.beans.property.StringProperty 4 | 5 | object BidirectionalBindingExample extends App { 6 | println("Constructing two StringProperty objects.") 7 | val prop1 = new StringProperty("") 8 | val prop2 = new StringProperty("") 9 | 10 | println("Calling bindBidirectional (<==>).") 11 | prop2 <==> prop1 12 | 13 | println("prop1.isBound = " + prop1.isBound) 14 | println("prop2.isBound = " + prop2.isBound) 15 | 16 | println("Calling prop1.set(\"prop1 says: Hi!\")") 17 | prop1() = "prop1 says: Hi!" 18 | println("prop2.get returned:") 19 | println(prop2()) 20 | 21 | println("""Calling prop2.set(prop2.get + "\nprop2 says: Bye!")""") 22 | prop2() = prop2() + "\nprop2 says: Bye!" 23 | println("prop1.get returned:") 24 | println(prop1()) 25 | } 26 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/examples/CenterUsingBind.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.examples 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.geometry.VPos 7 | import scalafx.scene.Scene 8 | import scalafx.scene.text.{Font, FontWeight, Text} 9 | 10 | object CenterUsingBind extends JFXApp3 { 11 | 12 | override def start(): Unit = { 13 | 14 | val text = new Text("ScalaFX Reversi") { 15 | textOrigin = VPos.Top 16 | font = Font.font(null, FontWeight.Bold, 18) 17 | } 18 | 19 | stage = new PrimaryStage { 20 | scene = new Scene(400, 100) { 21 | content = text 22 | } 23 | } 24 | 25 | text.layoutX <== (stage.scene().width - text.prefWidth(-1)) / 2 26 | text.layoutY <== (stage.scene().height - text.prefHeight(-1)) / 2 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer1/VideoPlayer1.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer1 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.scene.Scene 6 | import scalafx.scene.layout.StackPane 7 | import scalafx.scene.media.{Media, MediaPlayer, MediaView} 8 | 9 | import java.io.File 10 | 11 | /** 12 | * @author Jarek Sacha 13 | */ 14 | object VideoPlayer1 extends JFXApp3 { 15 | 16 | override def start(): Unit = { 17 | val file = new File("media/omgrobots.mp4") 18 | 19 | val media = new Media(file.toURI.toString) 20 | val mediaPlayer = new MediaPlayer(media) 21 | val mediaView = new MediaView(mediaPlayer) 22 | val root = new StackPane { 23 | children = mediaView 24 | } 25 | 26 | stage = new PrimaryStage { 27 | title = "Video Player 1" 28 | scene = new Scene(root, 960, 540) 29 | } 30 | 31 | mediaPlayer.play() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/VideoPlayer4App.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer4 2 | 3 | import com.sun.javafx.runtime as csjfxr 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.scene.Scene 8 | 9 | /** 10 | * @author Jarek Sacha 11 | */ 12 | object VideoPlayer4App extends JFXApp3 { 13 | 14 | override def start(): Unit = { 15 | 16 | println("JavaFX version: " + csjfxr.VersionInfo.getRuntimeVersion) 17 | 18 | val videoPlayer = new VideoPlayer4() 19 | 20 | val stylesheet = getClass.getResource("media.css") 21 | 22 | stage = new PrimaryStage { 23 | title = "Video Player 4" 24 | scene = new Scene(videoPlayer.rootNode, 1024, 680) { 25 | stylesheets += stylesheet.toString 26 | } 27 | videoPlayer.initSceneDragAndDrop(scene()) 28 | } 29 | 30 | videoPlayer.mediaModel.mediaPlayer().play() 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/proscalafx/ch02/zenpong/ZenPongMain.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch02.zenpong 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.scene.Scene 6 | import scalafx.scene.paint.{Color, CycleMethod, LinearGradient, Stop} 7 | 8 | import scala.language.postfixOps 9 | 10 | object ZenPongMain extends JFXApp3 { 11 | 12 | override def start(): Unit = { 13 | 14 | val zenPong = new ZenPong() 15 | 16 | stage = new PrimaryStage { 17 | title = "ZenPong Example" 18 | scene = new Scene(500, 500) { 19 | fill = LinearGradient( 20 | startX = 0.0, 21 | startY = 0.0, 22 | endX = 0.0, 23 | endY = 1.0, 24 | proportional = true, 25 | cycleMethod = CycleMethod.NoCycle, 26 | stops = List(Stop(0.0, Color.Black), Stop(0.0, Color.Gray)) 27 | ) 28 | content = zenPong.pongComponents 29 | } 30 | } 31 | 32 | zenPong.initialize() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/HeronsFormulaExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.Includes.* 4 | import scalafx.beans.property.DoubleProperty 5 | 6 | object HeronsFormulaExample extends App { 7 | val a = DoubleProperty(0.0) 8 | val b = DoubleProperty(0) 9 | val c = DoubleProperty(0) 10 | 11 | val s = (a + b + c) / 2.0 12 | 13 | val areaSquared = 14 | when(((a + b) > c) && ((b + c) > a) && ((a + c) > b)) choose (s * (s - a) * (s - b) * (s - c)) otherwise 0.0 15 | 16 | a() = 3 17 | b() = 4 18 | c() = 5 19 | 20 | printf( 21 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," + 22 | " the area of the triangle is %3.2f\n", 23 | a(), 24 | b(), 25 | c(), 26 | math.sqrt(areaSquared.get) 27 | ) 28 | 29 | a() = 2 30 | b() = 2 31 | c() = 2 32 | 33 | printf( 34 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," + 35 | " the area of the triangle is %3.2f\n", 36 | a(), 37 | b(), 38 | c(), 39 | math.sqrt(areaSquared.get) 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/examples/CenterUsingStack.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.examples 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.scene.Scene 7 | import scalafx.scene.layout.StackPane 8 | import scalafx.scene.paint.Color 9 | import scalafx.scene.shape.Ellipse 10 | import scalafx.scene.text.{Font, FontWeight, Text} 11 | 12 | object CenterUsingStack extends JFXApp3 { 13 | 14 | override def start(): Unit = { 15 | 16 | val ellipse = new Ellipse 17 | 18 | stage = new PrimaryStage { 19 | scene = new Scene(400, 100) { 20 | content = new StackPane { 21 | children = List( 22 | ellipse, 23 | new Text("ScalaFX Reversi") { 24 | font = Font.font(null, FontWeight.Bold, 18) 25 | fill = Color.White 26 | } 27 | ) 28 | } 29 | } 30 | } 31 | 32 | ellipse.radiusX <== stage.scene().width / 2 33 | ellipse.radiusY <== stage.scene().height / 2 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/TriangleAreaFluentExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.beans.binding.Bindings 4 | import scalafx.beans.property.IntegerProperty 5 | 6 | object TriangleAreaFluentExample extends App { 7 | val x1 = IntegerProperty(0) 8 | val y1 = IntegerProperty(0) 9 | val x2 = IntegerProperty(0) 10 | val y2 = IntegerProperty(0) 11 | val x3 = IntegerProperty(0) 12 | val y3 = IntegerProperty(0) 13 | 14 | val area = ((x1 * y2) + (x2 * y3) + (x3 * y1) - (x1 * y3) - (x2 * y1) - (x3 * y2)) / 2.0 15 | 16 | val output = Bindings.createStringBinding( 17 | () => 18 | f"For A(${x1()},${y1()}), B(${x2()},${y2()}), C(${x3()},${y3()}), the area of triangle ABC is ${area().doubleValue()}%.1f", 19 | x1, 20 | y1, 21 | x2, 22 | y2, 23 | x3, 24 | y3, 25 | area 26 | ) 27 | 28 | x1() = 0 29 | y1() = 0 30 | x2() = 6 31 | y2() = 0 32 | x3() = 4 33 | y3() = 3 34 | 35 | println(output()) 36 | 37 | x1() = 1 38 | y1() = 0 39 | x2() = 2 40 | y2() = 2 41 | x3() = 0 42 | y3() = 1 43 | 44 | println(output()) 45 | } 46 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/reversipieces/ReversiSquareTest.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.reversipieces 2 | 3 | import proscalafx.ch04.reversi.ui.ReversiSquare 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.scene.Scene 7 | import scalafx.scene.layout.StackPane 8 | 9 | object ReversiSquareTest extends JFXApp3 { 10 | 11 | override def start(): Unit = { 12 | // Unlike in many other examples, here content of the scene is assigned using `root` rather 13 | // than `content` properties. 14 | // `scalafx.scene.Scene` creates by default its root element as `Group`. If we assign using `content`, children 15 | // are added to that group. That may have undesired layout complications. In our case it will prevent automatic 16 | // resizing of the content. 17 | // To work around this, we assign to `root` directly. 18 | stage = new PrimaryStage { 19 | scene = new Scene() { 20 | root = new StackPane { 21 | children = new ReversiSquare(0, 0) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp1.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.collections.ObservableBuffer 6 | import scalafx.scene.Scene 7 | import scalafx.scene.chart.PieChart 8 | import scalafx.scene.layout.StackPane 9 | 10 | /** 11 | * @author Jarek Sacha 12 | */ 13 | object ChartApp1 extends JFXApp3 { 14 | 15 | override def start(): Unit = { 16 | 17 | stage = new PrimaryStage { 18 | title = "PieChart" 19 | scene = new Scene(400, 250) { 20 | root = new StackPane { 21 | children = new PieChart() { 22 | data = chartData() 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | private def chartData() = ObservableBuffer( 30 | PieChart.Data("java", 17.56), 31 | PieChart.Data("C", 17.06), 32 | PieChart.Data("C++", 8.25), 33 | PieChart.Data("C#", 8.20), 34 | PieChart.Data("ObjectiveC", 6.8), 35 | PieChart.Data("PHP", 6.0), 36 | PieChart.Data("(Visual)Basic", 4.76), 37 | PieChart.Data("Other", 31.37) 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/BasicAudioClip/BasicAudioClip.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.BasicAudioClip 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.geometry.Insets 7 | import scalafx.scene.Scene 8 | import scalafx.scene.control.Button 9 | import scalafx.scene.layout.StackPane 10 | import scalafx.scene.media.AudioClip 11 | 12 | /** 13 | * Playing an audio clip. 14 | * 15 | * @author Jarek Sacha 16 | */ 17 | object BasicAudioClip extends JFXApp3 { 18 | 19 | override def start(): Unit = { 20 | 21 | val resource = getClass.getResource("resources/beep.wav") 22 | val audioClip = new AudioClip(resource.toString) 23 | val stackPane = new StackPane { 24 | padding = Insets(10) 25 | children = new Button { 26 | text = "Bing Zzzzt!" 27 | onAction = () => audioClip.play(1.0) 28 | } 29 | } 30 | 31 | stage = new PrimaryStage { 32 | title = "Basic AudioClip Example" 33 | scene = new Scene(stackPane, 200, 200) { 34 | stylesheets += getClass.getResource("media.css").toString 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/FullScreenVideoPlayer/FullScreenVideoPlayer.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.FullScreenVideoPlayer 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.scene.Scene 6 | import scalafx.scene.layout.StackPane 7 | import scalafx.scene.media.{Media, MediaPlayer, MediaView} 8 | 9 | import java.io.File 10 | 11 | /** 12 | * @author Jarek Sacha 13 | */ 14 | object FullScreenVideoPlayer extends JFXApp3 { 15 | 16 | override def start(): Unit = { 17 | 18 | val file = new File("media/omgrobots.mp4") 19 | val media = new Media(file.toURI.toString) 20 | val mediaPlayer = new MediaPlayer(media) 21 | val mediaView = new MediaView(mediaPlayer) { 22 | fitWidth <== scene.selectDouble("width") 23 | fitHeight <== scene.selectDouble("height") 24 | preserveRatio = true 25 | } 26 | 27 | val root = new StackPane { 28 | children = mediaView 29 | } 30 | 31 | stage = new PrimaryStage { 32 | title = "Video Player 1" 33 | fullScreen = true 34 | scene = new Scene(root, 960, 540) 35 | } 36 | 37 | mediaPlayer.play() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/proscalafx/ch10/fxml/FXMLAdoptionForm.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch10.fxml 2 | 3 | import javafx.{fxml as jfxf, scene as jfxs} 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.scene.Scene 8 | 9 | import java.io.IOException 10 | 11 | /** 12 | * Example of using FXMLLoader from ScalaFX. 13 | * 14 | * @author Jarek Sacha 15 | */ 16 | object FXMLAdoptionForm extends JFXApp3 { 17 | 18 | def start(): Unit = { 19 | 20 | val resource = getClass.getResource("AdoptionForm.fxml") 21 | if (resource == null) { 22 | throw new IOException("Cannot load resource: AdoptionForm.fxml") 23 | } 24 | 25 | // NOTE: ScalaFX doe not yet provide a wrapper fro FXMLLoader (2012.11.12) 26 | // We load here FXML content using JavaFX directly. 27 | // It is important to provide type for the element loaded, 28 | // though it can be a generic, here use `javafx.scene.parent`. 29 | val root: jfxs.Parent = jfxf.FXMLLoader.load(resource) 30 | 31 | stage = new PrimaryStage() { 32 | title = "FXML GridPane Demo" 33 | scene = new Scene(root) 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ProScalaFX 2 | ========== 3 | 4 | [ScalaFX][1] version of Pro JavaFX books source code. 5 | 6 | Book editions: 7 | 8 | * [Pro JavaFX 9][b9p], book source [code][b9c] 9 | * [Pro JavaFX 8][b8p], book source [code][b8c]. 10 | * [Pro JavaFX 2][b2p], book source [code][b2c]. 11 | 12 | The original code was created for JavaFX 2.2 (Java 7). Current main branch was updated to work with ScalaFX 11 / JavaFX 13 | 14 | 11. ScalaFX 8 is still available on branch [SFX-8][sfx8]. Code for ScalaFX 2 is on branch [SFX-2][sfx2]. 15 | 16 | Please use [ScalaFX Users Group][5] to post questions. 17 | 18 | 19 | [1]: http://scalafx.org 20 | 21 | [5]: https://groups.google.com/forum/#!forum/scalafx-users 22 | 23 | [sfx2]: https://github.com/scalafx/ProScalaFX/tree/SFX-2 24 | 25 | [sfx8]: https://github.com/scalafx/ProScalaFX/tree/SFX-8 26 | 27 | [b2c]: http://www.apress.com/downloadable/download/sample/sample_id/1253 28 | 29 | [b2p]: http://www.apress.com/9781430268727 30 | 31 | [b8c]: http://www.apress.com/downloadable/download/sample/sample_id/1574 32 | 33 | [b8p]: http://www.apress.com/9781430265740 34 | 35 | [b9c]: https://github.com/Apress/pro-javafx-9 36 | 37 | [b9p]: https://www.apress.com/us/book/9781484230411 38 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/ui/ReversiApp.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.ui 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.scene.Scene 6 | import scalafx.scene.layout.* 7 | 8 | object ReversiApp extends JFXApp3 { 9 | 10 | override def start(): Unit = { 11 | 12 | val reversi = new Reversi() 13 | 14 | stage = new PrimaryStage() { 15 | scene = new Scene(400, 600) { 16 | root = new AnchorPane() { 17 | children = List( 18 | reversi.game, 19 | reversi.restart 20 | ) 21 | } 22 | } 23 | } 24 | 25 | AnchorPane.setTopAnchor(reversi.game, 0d) 26 | AnchorPane.setBottomAnchor(reversi.game, 0d) 27 | AnchorPane.setLeftAnchor(reversi.game, 0d) 28 | AnchorPane.setRightAnchor(reversi.game, 0d) 29 | AnchorPane.setRightAnchor(reversi.restart, 10d) 30 | AnchorPane.setTopAnchor(reversi.restart, 10d) 31 | 32 | // Code commended below was only used in the first edition of the book 33 | // if (Platform.isSupported(ConditionalFeature.Scene3D)) { 34 | // stage.scene().camera = new PerspectiveCamera() { 35 | // fieldOfView = 60 36 | // }.delegate 37 | // } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/SetChangeEventExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import scalafx.collections.ObservableSet 4 | import scalafx.collections.ObservableSet.* 5 | 6 | object SetChangeEventExample extends App { 7 | 8 | def prettyChange(change: Change[?]): String = { 9 | val sb = new StringBuffer("\tChange event data:\n") 10 | 11 | change match { 12 | case Add(added) => 13 | sb.append("\t\tWas added\n") 14 | sb.append("\t\tValue added : %s\n".format(added)) 15 | case Remove(removed) => 16 | sb.append("\t\tWas removed\n") 17 | sb.append("\t\tValue removed: %s\n".format(removed)) 18 | } 19 | 20 | sb.toString 21 | } 22 | 23 | def onChange[T](set: ObservableSet[T], change: Change[T]): Unit = { 24 | println("\tset = " + set) 25 | println(prettyChange(change)) 26 | } 27 | 28 | val set = ObservableSet.empty[String] 29 | set.onChange(onChange(_, _)) 30 | 31 | println("Calling set += \"First\": ") 32 | set += "First" 33 | 34 | println("Calling set ++= Seq(\"Second\", \"Third\"): ") 35 | set ++= Seq("Second", "Third") 36 | 37 | println("Calling set -= \"Second\": ") 38 | set -= "Second" 39 | 40 | println("Calling set --= Seq(\"First\", \"Third\"): ") 41 | set --= Seq("First", "Third") 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp2.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.collections.ObservableBuffer 6 | import scalafx.geometry.Side 7 | import scalafx.scene.Scene 8 | import scalafx.scene.chart.PieChart 9 | import scalafx.scene.layout.StackPane 10 | 11 | /** 12 | * @author Jarek Sacha 13 | */ 14 | object ChartApp2 extends JFXApp3 { 15 | 16 | override def start(): Unit = { 17 | 18 | stage = new PrimaryStage { 19 | title = "Chart App 2" 20 | scene = new Scene(400, 350) { 21 | root = new StackPane { 22 | children = new PieChart() { 23 | data = chartData() 24 | title = "Tiobe index" 25 | legendSide = Side.Left 26 | clockwise = false 27 | labelsVisible = false 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | private def chartData() = ObservableBuffer( 35 | PieChart.Data("java", 17.56), 36 | PieChart.Data("C", 17.06), 37 | PieChart.Data("C++", 8.25), 38 | PieChart.Data("C#", 8.20), 39 | PieChart.Data("ObjectiveC", 6.8), 40 | PieChart.Data("PHP", 6.0), 41 | PieChart.Data("(Visual)Basic", 4.76), 42 | PieChart.Data("Other", 31.37) 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/ui/ReversiPiece.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.ui 2 | 3 | import proscalafx.ch04.reversi.model.{NONE, Owner, White} 4 | import scalafx.Includes.* 5 | import scalafx.beans.property.ObjectProperty 6 | import scalafx.scene.effect.Reflection 7 | import scalafx.scene.layout.Region 8 | 9 | object ReversiPiece { 10 | 11 | private val nonePieceStyle = "radius: 0; " 12 | private val whitePieceStyle = "-fx-background-color: radial-gradient(radius 100%, white .4, gray .9, darkgray 1); " 13 | private val blackPieceStyle = "-fx-background-color: radial-gradient(radius 100%, white .0, black .6); " 14 | private val tileStyle = "-fx-background-radius: 1000em; -fx-background-insets: 5" 15 | 16 | } 17 | 18 | class ReversiPiece(_owner: Owner = NONE) extends Region { 19 | 20 | import ReversiPiece.* 21 | 22 | val owner = new ObjectProperty[Owner](this, "owner", _owner) 23 | 24 | def owner_=(value: Owner): Unit = { 25 | owner() = value 26 | } 27 | 28 | style <== ( 29 | when(owner === NONE) choose nonePieceStyle otherwise 30 | (when(owner === White) choose whitePieceStyle otherwise blackPieceStyle) 31 | ) + tileStyle 32 | 33 | effect = new Reflection { 34 | fraction = 1.0 35 | topOffset <== height * (-0.75) 36 | } 37 | 38 | prefWidth = 180 39 | prefHeight = 180 40 | mouseTransparent = true 41 | } 42 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/HeronsFormulaDirectExtensionExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.Includes.* 4 | import scalafx.beans.binding.Bindings 5 | import scalafx.beans.property.DoubleProperty 6 | 7 | object HeronsFormulaDirectExtensionExample extends App { 8 | val a = DoubleProperty(0) 9 | val b = DoubleProperty(0) 10 | val c = DoubleProperty(0) 11 | 12 | val area = Bindings.createDoubleBinding( 13 | () => { 14 | val a0 = a() 15 | val b0 = b() 16 | val c0 = c() 17 | 18 | if ((a0 + b0 > c0) && (b0 + c0 > a0) && (c0 + a0 > b0)) { 19 | val s = (a0 + b0 + c0) / 2.0d 20 | math.sqrt(s * (s - a0) * (s - b0) * (s - c0)) 21 | } else { 22 | 0 23 | } 24 | }, 25 | a, 26 | b, 27 | c 28 | ) 29 | 30 | // Use braces "()" to access values hold by properties, bindings, like `area`, and numeric expressions. 31 | a() = 3 32 | b() = 4 33 | c() = 5 34 | printf( 35 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," + 36 | " the area of the triangle is %3.2f\n", 37 | a(), 38 | b(), 39 | c(), 40 | area() 41 | ) 42 | 43 | a() = 2 44 | b() = 2 45 | c() = 2 46 | printf( 47 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," + 48 | " the area of the triangle is %3.2f\n", 49 | a(), 50 | b(), 51 | c(), 52 | area() 53 | ) 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/NumericPropertiesExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.beans.property.{DoubleProperty, FloatProperty, IntegerProperty, LongProperty} 4 | 5 | object NumericPropertiesExample extends App { 6 | val i = new IntegerProperty(null, "i", 1024) 7 | val l = new LongProperty(null, "l", 0L) 8 | val f = new FloatProperty(null, "f", 0.0f) 9 | val d = new DoubleProperty(null, "d", 0.0) 10 | println("Constructed numerical properties i, l, f, d.") 11 | 12 | println("i = " + i()) 13 | println("l = " + l()) 14 | println("f = " + f()) 15 | println("d = " + d()) 16 | 17 | l <== i 18 | f <== l 19 | d <== f 20 | println("Bound l to i, f to l, d to f.") 21 | 22 | println("i = " + i()) 23 | println("l = " + l()) 24 | println("f = " + f()) 25 | println("d = " + d()) 26 | 27 | println("Calling i.set(2048).") 28 | i() = 2048 29 | 30 | println("i = " + i()) 31 | println("l = " + l()) 32 | println("f = " + f()) 33 | println("d = " + d()) 34 | 35 | d.unbind() 36 | f.unbind() 37 | l.unbind() 38 | println("Unbound l to i, f to l, d to f.") 39 | 40 | f <== d 41 | l <== f 42 | i <== l 43 | println("Bound f to d, l to f, i to l.") 44 | 45 | println("Calling d.set(10000000000L).") 46 | d() = 10000000000L 47 | 48 | println("d = " + d()) 49 | println("f = " + f()) 50 | println("l = " + l()) 51 | println("i = " + i()) 52 | } 53 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/examples/AlignUsingStackAndTile.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.examples 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.geometry.Pos 7 | import scalafx.scene.Scene 8 | import scalafx.scene.layout.{StackPane, TilePane} 9 | import scalafx.scene.paint.Color 10 | import scalafx.scene.text.{Font, FontWeight, Text} 11 | 12 | object AlignUsingStackAndTile extends JFXApp3 { 13 | 14 | override def start(): Unit = { 15 | 16 | val left = new StackPane { 17 | style = "-fx-background-color: black" 18 | children = new Text { 19 | text = "ScalaFX" 20 | font = Font.font(null, FontWeight.Bold, 18) 21 | fill = Color.White 22 | alignmentInParent = Pos.CenterRight 23 | } 24 | } 25 | 26 | stage = new PrimaryStage { 27 | scene = new Scene(400, 100) { 28 | content = new TilePane { 29 | snapToPixel = false 30 | children = List( 31 | left, 32 | new Text { 33 | text = "Reversi" 34 | font = Font.font(null, FontWeight.Bold, 18) 35 | alignmentInParent = Pos.CenterLeft 36 | } 37 | ) 38 | } 39 | } 40 | } 41 | 42 | left.prefWidth <== stage.scene().width / 2 43 | left.prefHeight <== stage.scene().height 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-base: #222; 3 | -fx-background-color: black; 4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000); 5 | -fx-background-image: url("resources/cross.png"); 6 | } 7 | 8 | .button { 9 | -fx-text-fill: #E0E0E0; 10 | } 11 | 12 | .label { 13 | -fx-font-size: 18pt; 14 | -fx-text-fill: #aaa; 15 | } 16 | 17 | #title { 18 | -fx-font-size: 24pt; 19 | -fx-text-fill: #ddd; 20 | } 21 | 22 | #openButton { 23 | -fx-graphic: url("resources/music_note.png"); 24 | } 25 | 26 | #playPauseButton, #seekEndButton, #seekStartButton { 27 | -fx-background-color: transparent; 28 | } 29 | 30 | #playPauseButton:hover, #seekEndButton:hover, #seekStartButton:hover { 31 | -fx-background-color: rgb(255, 255, 255, 0.1); 32 | } 33 | 34 | #seekEndButton { 35 | -fx-graphic: url("resources/next.png"); 36 | } 37 | 38 | #seekStartButton { 39 | -fx-graphic: url("resources/prev.png"); 40 | } 41 | 42 | #volumeHigh { 43 | -fx-image: url("resources/volHigh.png"); 44 | } 45 | 46 | #volumeLow { 47 | -fx-image: url("resources/volLow.png"); 48 | } 49 | 50 | .statusDisplay { 51 | -fx-border-color: white; 52 | -fx-border-radius: 2; 53 | -fx-border-width: 1; 54 | -fx-text-fill: white; 55 | -fx-font-size: 10pt; 56 | } 57 | 58 | .mediaText { 59 | -fx-text-fill: white; 60 | -fx-font-size: 11pt; 61 | } 62 | -------------------------------------------------------------------------------- /src/proscalafx/ch01/audioconfig/AudioConfigModel.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch01.audioconfig 2 | 3 | import scalafx.beans.property.{BooleanProperty, IntegerProperty} 4 | import scalafx.collections.ObservableBuffer 5 | import scalafx.scene.control.SingleSelectionModel 6 | 7 | class AudioConfigModel { 8 | 9 | /** The minimum audio volume in decibels */ 10 | val minDecibels = 0.0 11 | 12 | /** The maximum audio volume in decibels */ 13 | val maxDecibels = 160.0 14 | 15 | /** The selected audio volume in decibels */ 16 | val selectedDBs = IntegerProperty(0) 17 | 18 | /** Indicates whether audio is muted */ 19 | val muting = BooleanProperty(false) 20 | 21 | /** 22 | * List of some musical genres 23 | */ 24 | val genres = ObservableBuffer("Chamber", "Country", "Cowbell", "Metal", "Polka", "Rock") 25 | 26 | /** A reference to the selection model used by the Slider */ 27 | var genreSelectionModel: SingleSelectionModel[String] = _ 28 | 29 | /** 30 | * Adds a change listener to the selection model of the ChoiceBox, and contains 31 | * code that executes when the selection in the ChoiceBox changes. 32 | */ 33 | def addListenerToGenreSelectionModel(): Unit = { 34 | this.genreSelectionModel.selectedIndex.onChange { 35 | selectedDBs.value = this.genreSelectionModel.selectedIndex() match { 36 | case 0 => 80 37 | case 1 => 100 38 | case 2 => 150 39 | case 3 => 140 40 | case 4 => 120 41 | case 5 => 130 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/proscalafx/ch05/model/StarterAppModel.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch05.model 2 | 3 | import scalafx.beans.property.DoubleProperty 4 | import scalafx.collections.ObservableBuffer 5 | 6 | import scala.util.Random 7 | 8 | /** 9 | * @author Jarek Sacha 10 | */ 11 | class StarterAppModel { 12 | 13 | val listViewItems = new ObservableBuffer[String]() 14 | val choiceBoxItems = ObservableBuffer("Choice A", "Choice B", "Choice C", "Choice D") 15 | val maxRpm: Double = 8000d 16 | val rpm = new DoubleProperty(this, "rpm", 0) 17 | val maxKph: Double = 300.0 18 | val kph = new DoubleProperty(this, "kph", 0) 19 | 20 | def getTeamMembers: ObservableBuffer[Person] = { 21 | 22 | val teamMembers = new ObservableBuffer[Person]() 23 | 24 | for (i <- 1 to 1000) { 25 | teamMembers += new Person("FirstName" + i, "LastName" + i, "Phone" + i) 26 | } 27 | 28 | teamMembers 29 | } 30 | 31 | def randomWebSite(): String = { 32 | 33 | val webSites: Array[String] = Array( 34 | "https://www.jfx-central.com/", 35 | "https://openjfx.io/", 36 | "https://github.com/mhrimaz/AwesomeJavaFX", 37 | "http://fxexperience.com", 38 | // "http://steveonjava.com", 39 | // "https://javafxpert.com", 40 | "https://pleasingsoftware.blogspot.com", 41 | "https://www.weiqigao.com/blog", 42 | // "https://edencoding.com/category/javafx/", 43 | "https://codingonthestaircase.wordpress.com/", 44 | "https://www.google.com/" 45 | ) 46 | 47 | val randomIdx = new Random().nextInt(webSites.length) 48 | webSites(randomIdx) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/ObservableBufferExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import scalafx.collections.ObservableBuffer 4 | 5 | /** 6 | * This example corresponds to JavaFX example `ObservableListExample`. 7 | * 8 | * In ScalaFX `ObservableBuffer` is a wrapper for JavaFX `ObservableList`. 9 | */ 10 | object ObservableBufferExample extends App { 11 | 12 | val strings = new ObservableBuffer[String]() 13 | strings.onInvalidate(println("\tlist invalidated")) 14 | strings.onChange((source, change) => println("\tstrings = " + source.mkString("[", ", ", "]"))) 15 | 16 | println("""Calling += "First": """) 17 | strings += "First" 18 | 19 | println("""Calling insert(0, "Zeroth"): """) 20 | strings.insert(0, "Zeroth") 21 | 22 | println(""""Calling ++= Seq("Second", "Third"): """) 23 | strings ++= Seq("Second", "Third") 24 | 25 | println("""Calling (1) = "New First"): """) 26 | strings(1) = "New First" 27 | 28 | val list = List("Second_1", "Second_2") 29 | println("Calling addAll(3, list): ") 30 | strings.insertAll(3, list) 31 | 32 | // There is a difference between meaning of the second argument of `remove` in JavaFX and ScalaFX. 33 | // ScalaFX `remove` corresponds to `scala.collection.mutable.Buffer.remove(n:Int, count:Int)`. 34 | // Equivalent of JavaFX `remove` is renamed in ScalaFX to `removeRange`. 35 | println("Calling removeRange(2, 4): ") 36 | strings.removeRange(2, 4) 37 | 38 | println("""Remove elements that contain letter "t"""") 39 | strings --= strings.filter(_.contains("t")) 40 | 41 | println("""Calling --= Seq("Third", "Fourth"): """) 42 | strings --= Seq("Third", "Fourth") 43 | } 44 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/TriangleAreaExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.beans.binding.NumberBinding 4 | import scalafx.beans.property.IntegerProperty 5 | 6 | object TriangleAreaExample extends App { 7 | 8 | def printResult( 9 | x1: IntegerProperty, 10 | y1: IntegerProperty, 11 | x2: IntegerProperty, 12 | y2: IntegerProperty, 13 | x3: IntegerProperty, 14 | y3: IntegerProperty, 15 | area: NumberBinding 16 | ): Unit = { 17 | println("For A(%d,%d), B(%d,%d), C(%d,%d), the area of triangle ABC is %1.1f".format( 18 | x1(), 19 | y1(), 20 | x2(), 21 | y2(), 22 | x3(), 23 | y3(), 24 | area() 25 | )) 26 | } 27 | 28 | val x1 = IntegerProperty(0) 29 | val y1 = IntegerProperty(0) 30 | val x2 = IntegerProperty(0) 31 | val y2 = IntegerProperty(0) 32 | val x3 = IntegerProperty(0) 33 | val y3 = IntegerProperty(0) 34 | 35 | val x1y2 = x1 * y2 36 | val x2y3 = x2 * y3 37 | val x3y1 = x3 * y1 38 | val x1y3 = x1 * y3 39 | val x2y1 = x2 * y1 40 | val x3y2 = x3 * y2 41 | 42 | val sum1 = x1y2 + x2y3 43 | val sum2 = sum1 + x3y1 44 | val sum3 = sum2 + x3y1 45 | val diff1 = sum3 - x1y3 46 | val diff2 = diff1 - x2y1 47 | val determinant = diff2 - x3y2 48 | val area = determinant / 2.0d 49 | 50 | x1() = 0 51 | y1() = 0 52 | x2() = 6 53 | y2() = 0 54 | x3() = 4 55 | y3() = 3 56 | 57 | printResult(x1, y1, x2, y2, x3, y3, area) 58 | 59 | x1() = 1 60 | y1() = 0 61 | x2() = 2 62 | y2() = 2 63 | x3() = 0 64 | y3() = 1 65 | 66 | printResult(x1, y1, x2, y2, x3, y3, area) 67 | } 68 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/reversipieces/ReversiPieceTest.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.reversipieces 2 | 3 | import proscalafx.ch04.reversi.model.{Black, White} 4 | import proscalafx.ch04.reversi.ui.{ReversiPiece, ReversiSquare} 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.scene.Scene 8 | import scalafx.scene.layout.{HBox, Priority, StackPane} 9 | 10 | object ReversiPieceTest extends JFXApp3 { 11 | 12 | override def start(): Unit = { 13 | 14 | // Unlike in many other examples, here content of the scene is assigned using `root` rather 15 | // than `content` properties. 16 | // `scalafx.scene.Scene` creates by default its root element as `Group`. If we assign using `content`, children 17 | // are added to that group. That may have undesired layout complications. In our case it will prevent automatic 18 | // resizing of the content. 19 | // To work around this, we assign to `root` directly. 20 | stage = new PrimaryStage() { 21 | scene = new Scene() { 22 | root = new HBox { 23 | snapToPixel = false 24 | children = List( 25 | new StackPane { 26 | children = List( 27 | new ReversiSquare(0, 0), 28 | new ReversiPiece(White) 29 | ) 30 | hgrow = Priority.Always 31 | }, 32 | new StackPane { 33 | children = List( 34 | new ReversiSquare(0, 0), 35 | new ReversiPiece(Black) 36 | ) 37 | hgrow = Priority.Always 38 | } 39 | ) 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/ui/ReversiSquare.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.ui 2 | 3 | import javafx.scene.layout as jfxsl 4 | import proscalafx.ch04.reversi.model.ReversiModel 5 | import scalafx.Includes.* 6 | import scalafx.animation.FadeTransition 7 | import scalafx.geometry.{HPos, VPos} 8 | import scalafx.scene.effect.{Light, Lighting} 9 | import scalafx.scene.layout.Region 10 | import scalafx.util.Duration 11 | 12 | class ReversiSquare(val x: Int, val y: Int) extends Region { 13 | 14 | private val highlight = new Region { 15 | opacity = 0 16 | style = "-fx-border-width: 3; -fx-border-color: dodgerblue" 17 | } 18 | 19 | override val delegate: jfxsl.Region = new jfxsl.Region { 20 | getChildren.add(highlight) 21 | 22 | protected override def layoutChildren(): Unit = { 23 | layoutInArea(highlight, 0, 0, getWidth, getHeight, getBaselineOffset, HPos.Center, VPos.Center) 24 | } 25 | } 26 | 27 | private val highlightTransition = new FadeTransition { 28 | node = highlight 29 | duration = Duration(200) 30 | fromValue = 0 31 | toValue = 1 32 | } 33 | 34 | style <== when(ReversiModel.legalMove(x, y)) choose 35 | "-fx-background-color: derive(dodgerblue, -60%)" otherwise 36 | "-fx-background-color: burlywood" 37 | 38 | effect = new Lighting { 39 | light = new Light.Distant { 40 | azimuth = -135 41 | elevation = 30 42 | } 43 | } 44 | 45 | prefHeight = 200 46 | prefWidth = 200 47 | 48 | onMouseEntered = () => { 49 | if (ReversiModel.legalMove(x, y).get) { 50 | highlightTransition.rate() = 1 51 | highlightTransition.play() 52 | } 53 | } 54 | 55 | onMouseExited = () => { 56 | highlightTransition.rate = -1 57 | highlightTransition.play() 58 | } 59 | 60 | onMouseClicked = () => { 61 | ReversiModel.play(x, y) 62 | highlightTransition.rate() = -1 63 | highlightTransition.play() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer2/VideoPlayer2.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer2 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.application.{JFXApp3, Platform} 6 | import scalafx.geometry.Pos 7 | import scalafx.scene.Scene 8 | import scalafx.scene.control.Label 9 | import scalafx.scene.layout.StackPane 10 | import scalafx.scene.media.{Media, MediaPlayer, MediaView} 11 | import scalafx.util.Duration 12 | 13 | import java.io.File 14 | import java.net.URL 15 | import scala.language.postfixOps 16 | 17 | /** 18 | * @author Jarek Sacha 19 | */ 20 | object VideoPlayer2 extends JFXApp3 { 21 | 22 | override def start(): Unit = { 23 | 24 | val markerText = new Label { 25 | alignmentInParent = Pos.TopCenter 26 | } 27 | 28 | val file = new File("media/omgrobots.mp4") 29 | val media = new Media(file.toURI.toString) { 30 | markers ++= Map( 31 | "Robot Finds Wall" -> (3100 ms), 32 | "Then Finds the Green Line" -> (5600 ms), 33 | "Robot Grabs Sled" -> (8000 ms), 34 | "And Heads for Home" -> (11500 ms) 35 | ) 36 | } 37 | 38 | val mediaPlayer = new MediaPlayer(media) { 39 | onMarker = event => 40 | Platform.runLater { 41 | markerText.text = event.marker.getKey 42 | } 43 | } 44 | 45 | val mediaView = new MediaView(mediaPlayer) 46 | val root = new StackPane { 47 | children ++= Seq(mediaView, markerText) 48 | onMouseClicked = () => { 49 | mediaPlayer.seek(Duration.Zero) 50 | markerText.text = "" 51 | } 52 | } 53 | 54 | stage = new PrimaryStage { 55 | title = "Video Player 2" 56 | scene = new Scene(root, 960, 540) { 57 | val stylesheet: URL = getClass.getResource("media.css") 58 | stylesheets += stylesheet.toString 59 | } 60 | } 61 | 62 | mediaPlayer.play() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/MetadataView.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer4 2 | 3 | import scalafx.Includes.* 4 | import scalafx.geometry.{Insets, VPos} 5 | import scalafx.scene.control.Label 6 | import scalafx.scene.effect.Reflection 7 | import scalafx.scene.image.ImageView 8 | import scalafx.scene.layout.{ColumnConstraints, GridPane, Priority, RowConstraints} 9 | 10 | /** 11 | * @author Jarek Sacha 12 | */ 13 | class MetadataView(songModel: SongModel) extends AbstractView[GridPane](songModel) { 14 | 15 | def initView(): GridPane = { 16 | val title = new Label { 17 | text <== songModel.title 18 | id = "title" 19 | } 20 | val artist = new Label { 21 | text <== songModel.artist 22 | id = "artist" 23 | } 24 | val album = new Label { 25 | text <== songModel.album 26 | id = "album" 27 | } 28 | val year = new Label { 29 | text <== songModel.year 30 | id = "year" 31 | } 32 | val albumCover = new ImageView { 33 | image <== songModel.albumCover 34 | fitWidth = 240 35 | preserveRatio = true 36 | smooth = true 37 | effect = new Reflection { 38 | fraction = 0.2 39 | } 40 | } 41 | 42 | new GridPane { 43 | padding = Insets(10) 44 | hgap = 20 45 | add(albumCover, 0, 0, 1, GridPane.Remaining) 46 | add(title, 1, 0) 47 | add(artist, 1, 1) 48 | add(album, 1, 2) 49 | add(year, 1, 3) 50 | columnConstraints ++= Seq( 51 | new ColumnConstraints(), 52 | // NOTE: the call to delegate to avoid compilation error. 53 | // Should `scalafx.scene.layout.GridPane.columnConstraints_=()` be fixed to work without call to delegate? 54 | new ColumnConstraints { 55 | hgrow = Priority.Always 56 | }.delegate 57 | ) 58 | val r0 = new RowConstraints { 59 | valignment = VPos.Top 60 | } 61 | rowConstraints ++= Seq(r0, r0, r0, r0) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/MetadataView.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer3 2 | 3 | import scalafx.Includes.* 4 | import scalafx.geometry.{Insets, VPos} 5 | import scalafx.scene.Node 6 | import scalafx.scene.control.Label 7 | import scalafx.scene.effect.Reflection 8 | import scalafx.scene.image.ImageView 9 | import scalafx.scene.layout.{ColumnConstraints, GridPane, Priority, RowConstraints} 10 | 11 | /** 12 | * @author Jarek Sacha 13 | */ 14 | class MetadataView(songModel: SongModel) extends AbstractView(songModel) { 15 | 16 | def initView(): Node = { 17 | val title = new Label { 18 | text <== songModel.title 19 | id = "title" 20 | } 21 | val artist = new Label { 22 | text <== songModel.artist 23 | id = "artist" 24 | } 25 | val album = new Label { 26 | text <== songModel.album 27 | id = "album" 28 | } 29 | val year = new Label { 30 | text <== songModel.year 31 | id = "year" 32 | } 33 | val albumCover = new ImageView { 34 | image <== songModel.albumCover 35 | fitWidth = 240 36 | preserveRatio = true 37 | smooth = true 38 | effect = new Reflection { 39 | fraction = 0.2 40 | } 41 | } 42 | 43 | new GridPane { 44 | padding = Insets(10) 45 | hgap = 20 46 | add(albumCover, 0, 0, 1, GridPane.Remaining) 47 | add(title, 1, 0) 48 | add(artist, 1, 1) 49 | add(album, 1, 2) 50 | add(year, 1, 3) 51 | columnConstraints ++= Seq( 52 | new ColumnConstraints(), 53 | // NOTE: the call to delegate to avoid compilation error. 54 | // Should `scalafx.scene.layout.GridPane.columnConstraints_=()` be fixed to work without call to delegate? 55 | new ColumnConstraints { 56 | hgrow = Priority.Always 57 | }.delegate 58 | ) 59 | val r0 = new RowConstraints { 60 | valignment = VPos.Top 61 | } 62 | rowConstraints ++= Seq(r0, r0, r0, r0) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/MapChangeEventExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import scalafx.collections.ObservableMap 4 | import scalafx.collections.ObservableMap.* 5 | 6 | /** 7 | * Example of processing of "change" notifications from ScalaFX `ObservableMap`, wrapper for JavaFX `ObservableMap`. 8 | * 9 | * ScalaFX used a different way of passing information about modification to `ObservableMap`. 10 | * Each modification is represented by a [[scalafx.collections.ObservableMap.Change]] object. 11 | */ 12 | object MapChangeEventExample extends App { 13 | 14 | val map = ObservableMap.empty[String, Int] 15 | map.onChange((map, change) => { 16 | println("\tmap = " + map.mkString("[", ", ", "]")) 17 | println(prettyChange(change)) 18 | }) 19 | 20 | println("""Calling map("First") = 1: """) 21 | map("First") = 1 22 | 23 | println("""Calling map(First") = 100: """) 24 | map("First") = 100 25 | 26 | val anotherMap = Map("Second" -> 2, "Third" -> 3) 27 | println("""Calling map ++= anotherMap: """) 28 | map ++= anotherMap 29 | 30 | println("""Removing by key: Calling map -= "Second"""") 31 | map -= "Second" 32 | 33 | println("Removing by value: Calling map retain({case (k, v) => v != 3})") 34 | map filterInPlace { case (_, v) => v != 3 } 35 | 36 | def prettyChange(change: Change[?, ?]): String = { 37 | val sb = new StringBuffer("\tChange event data:\n") 38 | 39 | change match { 40 | case Add(key, added) => 41 | sb.append("\t\tWas added\n") 42 | sb.append("\t\tKey : %s\n".format(key)) 43 | sb.append("\t\tValue added : %s\n".format(added)) 44 | case Remove(key, removed) => 45 | sb.append("\t\tWas removed\n") 46 | sb.append("\t\tKey : %s\n".format(key)) 47 | sb.append("\t\tValue removed: %s\n".format(removed)) 48 | case Replace(key, added, removed) => 49 | sb.append("\t\tWas replaced\n") 50 | sb.append("\t\tKey : %s\n".format(key)) 51 | sb.append("\t\tValue added : %s\n".format(added)) 52 | sb.append("\t\tValue removed: %s\n".format(removed)) 53 | } 54 | 55 | sb.toString 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp3.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.{NumberAxis, ScatterChart, XYChart} 10 | import scalafx.scene.layout.StackPane 11 | 12 | /** 13 | * @author Jarek Sacha 14 | */ 15 | object ChartApp3 extends JFXApp3 { 16 | 17 | override def start(): Unit = { 18 | 19 | val xAxis = new NumberAxis() 20 | val yAxis = new NumberAxis() 21 | val scatterChart = ScatterChart(xAxis, yAxis) 22 | 23 | scatterChart.data = createChartData() 24 | 25 | stage = new PrimaryStage { 26 | title = "Chart App 3" 27 | scene = new Scene(400, 250) { 28 | root = new StackPane { 29 | children = scatterChart 30 | } 31 | } 32 | } 33 | } 34 | 35 | // NOTE: explicit type signature using Number instead Int and Double 36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 37 | // signature for scalafx.scene.chart.XYChart.data used above. 38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = { 39 | var javaValue = 17.56 40 | var cValue = 17.06 41 | var cppValue = 8.25 42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]() 43 | val java = new XYChart.Series[Number, Number] { 44 | name = "Java" 45 | } 46 | val c = new XYChart.Series[Number, Number] { 47 | name = "C" 48 | } 49 | val cpp = new XYChart.Series[Number, Number] { 50 | name = "C++" 51 | } 52 | for (i <- 2011 to 2020) { 53 | java.data() += XYChart.Data[Number, Number](i, javaValue) 54 | javaValue += math.random() - .5 55 | 56 | c.data() += XYChart.Data[Number, Number](i, cValue) 57 | cValue += math.random() - .5 58 | 59 | cpp.data() += XYChart.Data[Number, Number](i, cppValue) 60 | cppValue += math.random() - .5 61 | } 62 | answer.addAll(java, c, cpp) 63 | answer 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp7.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.* 10 | import scalafx.scene.layout.StackPane 11 | 12 | /** 13 | * @author Jarek Sacha 14 | */ 15 | object ChartApp7 extends JFXApp3 { 16 | 17 | override def start(): Unit = { 18 | 19 | val xAxis = new CategoryAxis() 20 | val yAxis = new NumberAxis() 21 | val lineChart = LineChart(xAxis, yAxis) 22 | lineChart.title = "Speculations" 23 | lineChart.data = createChartData() 24 | 25 | stage = new PrimaryStage { 26 | title = "LineChart example" 27 | scene = new Scene(400, 250) { 28 | root = new StackPane { 29 | children = lineChart 30 | } 31 | } 32 | } 33 | } 34 | 35 | // NOTE: explicit type signature using Number instead Int and Double 36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 37 | // signature for scalafx.scene.chart.XYChart.data used above. 38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = { 39 | var javaValue = 17.56 40 | var cValue = 17.06 41 | var cppValue = 8.25 42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]() 43 | val java = new XYChart.Series[String, Number] { 44 | name = "Java" 45 | } 46 | val c = new XYChart.Series[String, Number] { 47 | name = "C" 48 | } 49 | val cpp = new XYChart.Series[String, Number] { 50 | name = "C++" 51 | } 52 | for (i <- 2011 to 2021) { 53 | java.data() += XYChart.Data[String, Number](i.toString, javaValue) 54 | javaValue += math.random() - .5 55 | 56 | c.data() += XYChart.Data[String, Number](i.toString, cValue) 57 | cValue += math.random() - .5 58 | 59 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue) 60 | cppValue += math.random() - .5 61 | } 62 | answer.addAll(java, c, cpp) 63 | answer 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp9.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.* 10 | import scalafx.scene.layout.StackPane 11 | 12 | /** 13 | * @author Jarek Sacha 14 | */ 15 | object ChartApp9 extends JFXApp3 { 16 | 17 | override def start(): Unit = { 18 | 19 | val xAxis = new CategoryAxis() 20 | val yAxis = new NumberAxis() 21 | val areaChart = AreaChart(xAxis, yAxis) 22 | areaChart.title = "Speculations" 23 | areaChart.data = createChartData() 24 | 25 | stage = new PrimaryStage { 26 | title = "AreaChart example" 27 | scene = new Scene(400, 250) { 28 | root = new StackPane { 29 | children = areaChart 30 | } 31 | } 32 | } 33 | } 34 | 35 | // NOTE: explicit type signature using Number instead Int and Double 36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 37 | // signature for scalafx.scene.chart.XYChart.data used above. 38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = { 39 | var javaValue = 17.56 40 | var cValue = 17.06 41 | var cppValue = 8.25 42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]() 43 | val java = new XYChart.Series[String, Number] { 44 | name = "Java" 45 | } 46 | val c = new XYChart.Series[String, Number] { 47 | name = "C" 48 | } 49 | val cpp = new XYChart.Series[String, Number] { 50 | name = "C++" 51 | } 52 | for (i <- 2011 to 2020) { 53 | java.data() += XYChart.Data[String, Number](i.toString, javaValue) 54 | javaValue += math.random() - .5 55 | 56 | c.data() += XYChart.Data[String, Number](i.toString, cValue) 57 | cValue += math.random() - .5 58 | 59 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue) 60 | cppValue += math.random() - .5 61 | } 62 | answer.addAll(java, c, cpp) 63 | answer 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp6.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.* 10 | import scalafx.scene.layout.StackPane 11 | 12 | /** 13 | * @author Jarek Sacha 14 | */ 15 | object ChartApp6 extends JFXApp3 { 16 | 17 | override def start(): Unit = { 18 | 19 | val xAxis = new CategoryAxis() 20 | val yAxis = new NumberAxis() 21 | val scatterChart = ScatterChart(xAxis, yAxis) 22 | scatterChart.title = "Speculations" 23 | scatterChart.data = createChartData() 24 | 25 | stage = new PrimaryStage { 26 | title = "ScatterChart example" 27 | scene = new Scene(400, 250) { 28 | root = new StackPane { 29 | children = scatterChart 30 | } 31 | } 32 | } 33 | } 34 | 35 | // NOTE: explicit type signature using Number instead Int and Double 36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 37 | // signature for scalafx.scene.chart.XYChart.data used above. 38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = { 39 | var javaValue = 17.56 40 | var cValue = 17.06 41 | var cppValue = 8.25 42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]() 43 | val java = new XYChart.Series[String, Number] { 44 | name = "Java" 45 | } 46 | val c = new XYChart.Series[String, Number] { 47 | name = "C" 48 | } 49 | val cpp = new XYChart.Series[String, Number] { 50 | name = "C++" 51 | } 52 | for (i <- 2011 to 2021) { 53 | java.data() += XYChart.Data[String, Number](i.toString, javaValue) 54 | javaValue += math.random() - .5 55 | 56 | c.data() += XYChart.Data[String, Number](i.toString, cValue) 57 | cValue += math.random() - .5 58 | 59 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue) 60 | cppValue += math.random() - .5 61 | } 62 | answer.addAll(java, c, cpp) 63 | answer 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp8.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.* 10 | import scalafx.scene.layout.StackPane 11 | 12 | /** 13 | * @author Jarek Sacha 14 | */ 15 | object ChartApp8 extends JFXApp3 { 16 | 17 | override def start(): Unit = { 18 | 19 | val xAxis = new CategoryAxis() 20 | val yAxis = new NumberAxis() 21 | val barChart = BarChart(xAxis, yAxis) 22 | barChart.title = "Speculations" 23 | barChart.data = createChartData() 24 | barChart.barGap = 1 25 | 26 | stage = new PrimaryStage { 27 | title = "BarChart example" 28 | scene = new Scene(400, 250) { 29 | root = new StackPane { 30 | children = barChart 31 | } 32 | } 33 | } 34 | } 35 | 36 | // NOTE: explicit type signature using Number instead Int and Double 37 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 38 | // signature for scalafx.scene.chart.XYChart.data used above. 39 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = { 40 | var javaValue = 17.56 41 | var cValue = 17.06 42 | var cppValue = 8.25 43 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]() 44 | val java = new XYChart.Series[String, Number] { 45 | name = "Java" 46 | } 47 | val c = new XYChart.Series[String, Number] { 48 | name = "C" 49 | } 50 | val cpp = new XYChart.Series[String, Number] { 51 | name = "C++" 52 | } 53 | for (i <- 2011 to 2016) { 54 | java.data() += XYChart.Data[String, Number](i.toString, javaValue) 55 | javaValue += math.random() - .5 56 | 57 | c.data() += XYChart.Data[String, Number](i.toString, cValue) 58 | cValue += math.random() - .5 59 | 60 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue) 61 | cppValue += math.random() - .5 62 | } 63 | answer.addAll(java, c, cpp) 64 | answer 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp4.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import scalafx.application.JFXApp3 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.collections.ObservableBuffer 6 | import scalafx.scene.Scene 7 | import scalafx.scene.chart.{NumberAxis, ScatterChart, XYChart} 8 | import scalafx.scene.layout.StackPane 9 | 10 | /** 11 | * @author Jarek Sacha 12 | */ 13 | object ChartApp4 extends JFXApp3 { 14 | 15 | override def start(): Unit = { 16 | 17 | val xAxis = new NumberAxis { 18 | autoRanging = false 19 | lowerBound = 2011 20 | upperBound = 2021 21 | } 22 | val yAxis = new NumberAxis() 23 | val scatterChart = ScatterChart[Number, Number](xAxis, yAxis, createChartData()) 24 | 25 | stage = new PrimaryStage { 26 | title = "Chart App 4" 27 | scene = new Scene(400, 250) { 28 | root = new StackPane { 29 | children = scatterChart 30 | } 31 | } 32 | } 33 | } 34 | 35 | private def createChartData() = { 36 | 37 | val years = 2011 to 2020 38 | 39 | // Generate trend by creating a cumulative sum of random values 40 | def generateTrend(startValue: Double) = years.map(_ => math.random() - .5).scanLeft(startValue)(_ + _) 41 | 42 | val javaTrend = generateTrend(17.56) 43 | val cTrend = generateTrend(17.06) 44 | val cppTrend = generateTrend(8.25) 45 | 46 | // NOTE: explicit type signature using Number instead Int and Double 47 | // We are deliberately using here factory methods, instead of "new", to create instances of 48 | // javafx.scene.chart.* types. 49 | val javaData = years zip javaTrend map { case (y, d) => XYChart.Data[Number, Number](y, d) } 50 | val cData = years zip cTrend map { case (y, d) => XYChart.Data[Number, Number](y, d) } 51 | val cppData = years zip cppTrend map { case (y, d) => XYChart.Data[Number, Number](y, d) } 52 | 53 | ObservableBuffer( 54 | XYChart.Series[Number, Number](name = "Java", data = ObservableBuffer.from(javaData)), 55 | XYChart.Series[Number, Number](name = "C", data = ObservableBuffer.from(cData)), 56 | XYChart.Series[Number, Number](name = "C++", data = ObservableBuffer.from(cppData)) 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/AudioPlayer3.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer3 2 | 3 | import com.sun.javafx.runtime.VersionInfo 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.scene.Scene 8 | import scalafx.scene.input.TransferMode 9 | import scalafx.scene.layout.BorderPane 10 | 11 | /** 12 | * @author Jarek Sacha 13 | */ 14 | object AudioPlayer3 extends JFXApp3 { 15 | 16 | override def start(): Unit = { 17 | 18 | val songModel = new SongModel() { 19 | url = "https://traffic.libsyn.com/dickwall/JavaPosse373.mp3" 20 | } 21 | 22 | println("JavaFX version: " + VersionInfo.getRuntimeVersion) 23 | 24 | val metaDataView = new MetadataView(songModel) 25 | val playerControlsView = new PlayerControlsView(songModel) 26 | 27 | val root = new BorderPane { 28 | center = metaDataView.viewNode 29 | bottom = playerControlsView.viewNode 30 | } 31 | 32 | stage = new PrimaryStage { 33 | title = "Audio Player 3" 34 | scene = new Scene(root, 800, 400) { 35 | val stylesheet = getClass.getResource("media.css") 36 | stylesheets += stylesheet.toString 37 | } 38 | initSceneDragAndDrop(scene(), songModel) 39 | } 40 | } 41 | 42 | private def initSceneDragAndDrop(scene: Scene, songModel: SongModel): Unit = { 43 | scene.onDragOver = event => { 44 | val db = event.dragboard 45 | if (db.hasFiles || db.hasUrl) { 46 | // NOTE: We need to pass in `TransferMode` as Java vararg, since we use `javafx.scene.input.DragEvent` 47 | event.acceptTransferModes(TransferMode.Any*) 48 | } 49 | event.consume() 50 | } 51 | 52 | scene.onDragDropped = event => { 53 | val db = event.dragboard 54 | val url = 55 | if (db.hasFiles) { 56 | db.getFiles.get(0).toURI.toString 57 | } else if (db.hasUrl) { 58 | db.getUrl 59 | } else { 60 | null 61 | } 62 | 63 | if (url != null) { 64 | songModel.url = url 65 | songModel.mediaPlayer().play() 66 | } 67 | event.dropCompleted = url != null 68 | event.consume() 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/VideoPlayer4.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer4 2 | 3 | import scalafx.Includes.* 4 | import scalafx.scene.input.TransferMode 5 | import scalafx.scene.layout.{BorderPane, StackPane} 6 | import scalafx.scene.{Node, Scene} 7 | 8 | /** 9 | * @author Jarek Sacha 10 | */ 11 | class VideoPlayer4 { 12 | 13 | private var playerControlsView: PlayerControlsView = _ 14 | private var videoView: VideoView = _ 15 | private var equalizerView: EqualizerView = _ 16 | 17 | val mediaModel: MediaModel = new MediaModel() { 18 | url = "https://download.oracle.com/otndocs/products/javafx/oow2010-2.mp4" 19 | } 20 | 21 | private val page1 = createPageOne() 22 | private val page2 = createPageTwo() 23 | val rootNode: StackPane = new StackPane { 24 | children = page1 25 | } 26 | 27 | private def createPageOne(): Node = { 28 | videoView = new VideoView(mediaModel) 29 | playerControlsView = new PlayerControlsView(mediaModel) 30 | playerControlsView.onNextPageAction { _ => rootNode.children = page2 } 31 | new BorderPane { 32 | center = videoView.viewNode 33 | bottom = playerControlsView.viewNode 34 | } 35 | } 36 | 37 | private def createPageTwo(): Node = { 38 | equalizerView = new EqualizerView(mediaModel) 39 | equalizerView.onNextPageAction { _ => rootNode.children = page1 } 40 | equalizerView.viewNode 41 | } 42 | 43 | def initSceneDragAndDrop(scene: Scene): Unit = { 44 | scene.onDragOver = event => { 45 | val db = event.dragboard 46 | if (db.hasFiles || db.hasUrl) { 47 | // NOTE: We need to pass in `TransferMode` as Java vararg, since we use `javafx.scene.input.DragEvent` 48 | event.acceptTransferModes(TransferMode.Any*) 49 | } 50 | event.consume() 51 | } 52 | 53 | scene.onDragDropped = event => { 54 | val db = event.dragboard 55 | val url = 56 | if (db.hasFiles) { 57 | db.getFiles.get(0).toURI.toString 58 | } else if (db.hasUrl) { 59 | db.getUrl 60 | } else { 61 | null 62 | } 63 | 64 | if (url != null) { 65 | mediaModel.url = url 66 | mediaModel.mediaPlayer().play() 67 | } 68 | event.dropCompleted = (url != null) 69 | event.consume() 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp5.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.{NumberAxis, ScatterChart, XYChart} 10 | import scalafx.scene.layout.StackPane 11 | 12 | /** 13 | * @author Jarek Sacha 14 | */ 15 | object ChartApp5 extends JFXApp3 { 16 | 17 | override def start(): Unit = { 18 | 19 | val xAxis = new NumberAxis { 20 | autoRanging = false 21 | lowerBound = 2011 22 | upperBound = 2021 23 | } 24 | val yAxis = new NumberAxis() 25 | 26 | val scatterChart = ScatterChart(xAxis, yAxis) 27 | scatterChart.title = "Speculations" 28 | scatterChart.data = createChartData() 29 | 30 | stage = new PrimaryStage { 31 | title = "ScatterChart example" 32 | scene = new Scene(400, 250) { 33 | root = new StackPane { 34 | children = scatterChart 35 | } 36 | } 37 | } 38 | } 39 | 40 | // NOTE: explicit type signature using Number instead Int and Double 41 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 42 | // signature for scalafx.scene.chart.XYChart.data used above. 43 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = { 44 | var javaValue: Double = 17.56 45 | var cValue: Double = 17.06 46 | var cppValue: Double = 8.25 47 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]() 48 | val java = new XYChart.Series[Number, Number] { 49 | name = "Java" 50 | } 51 | val c = new XYChart.Series[Number, Number] { 52 | name = "C" 53 | } 54 | val cpp = new XYChart.Series[Number, Number] { 55 | name = "C++" 56 | } 57 | for (i <- 2011 to 2021) { 58 | java.data() += XYChart.Data[Number, Number](i, javaValue) 59 | javaValue += math.random() - .5 60 | 61 | c.data() += XYChart.Data[Number, Number](i, cValue) 62 | cValue += math.random() - .5 63 | 64 | cpp.data() += XYChart.Data[Number, Number](i, cppValue) 65 | cppValue += math.random() - .5 66 | } 67 | answer.addAll(java, c, cpp) 68 | answer 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/proscalafx/ch02/metronometransition/MetronomeTransitionMain.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch02.metronometransition 2 | 3 | import javafx.animation.Animation.Status 4 | import scalafx.Includes.* 5 | import scalafx.animation.{Interpolator, Timeline, TranslateTransition} 6 | import scalafx.application.JFXApp3 7 | import scalafx.application.JFXApp3.PrimaryStage 8 | import scalafx.scene.Scene 9 | import scalafx.scene.control.Button 10 | import scalafx.scene.layout.HBox 11 | import scalafx.scene.paint.Color 12 | import scalafx.scene.shape.Circle 13 | import scalafx.util.Duration 14 | 15 | object MetronomeTransitionMain extends JFXApp3 { 16 | override def start(): Unit = { 17 | 18 | val circle = new Circle { 19 | centerX = 100 20 | centerY = 50 21 | radius = 4 22 | fill = Color.Blue 23 | } 24 | 25 | val anim = new TranslateTransition { 26 | duration = Duration(1000.0) 27 | node = circle 28 | fromX = 0 29 | toX = 200 30 | interpolator = Interpolator.Linear 31 | autoReverse = true 32 | cycleCount = Timeline.Indefinite 33 | } 34 | 35 | stage = new PrimaryStage { 36 | width = 400 37 | height = 500 38 | title = "Metronome using TranslateTransition" 39 | scene = new Scene(400, 500) { 40 | content = List( 41 | circle, 42 | new HBox { 43 | layoutX = 60 44 | layoutY = 420 45 | spacing = 10 46 | children = List( 47 | new Button { 48 | text = "Start" 49 | onAction = () => anim.playFromStart() 50 | disable <== anim.status.isNotEqualTo(Status.STOPPED) 51 | }, 52 | new Button { 53 | text = "Pause" 54 | onAction = () => anim.pause() 55 | disable <== anim.status.isNotEqualTo(Status.RUNNING) 56 | }, 57 | new Button { 58 | text = "Resume" 59 | onAction = () => anim.play() 60 | disable <== anim.status.isNotEqualTo(Status.PAUSED) 61 | }, 62 | new Button { 63 | text = "Stop" 64 | onAction = () => anim.stop() 65 | disable <== anim.status.isEqualTo(Status.STOPPED) 66 | } 67 | ) 68 | } 69 | ) 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/proscalafx/ch02/metronome1/Metronome1Main.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch02.metronome1 2 | 3 | import javafx.animation.Animation.Status 4 | import scalafx.Includes.* 5 | import scalafx.animation.{Interpolator, Timeline} 6 | import scalafx.application.JFXApp3 7 | import scalafx.application.JFXApp3.PrimaryStage 8 | import scalafx.beans.property.DoubleProperty 9 | import scalafx.scene.Scene 10 | import scalafx.scene.control.Button 11 | import scalafx.scene.layout.HBox 12 | import scalafx.scene.paint.Color 13 | import scalafx.scene.shape.Line 14 | 15 | import scala.language.postfixOps 16 | 17 | object Metronome1Main extends JFXApp3 { 18 | 19 | override def start(): Unit = { 20 | 21 | val startXVal = DoubleProperty(100.0) 22 | 23 | val anim = new Timeline { 24 | autoReverse = true 25 | keyFrames = Seq( 26 | at(0 s) { 27 | startXVal -> 100 28 | }, 29 | at(1 s) { 30 | startXVal -> 300 tween Interpolator.Linear 31 | } 32 | ) 33 | cycleCount = Timeline.Indefinite 34 | } 35 | 36 | stage = new PrimaryStage { 37 | title = "Metronome 1" 38 | scene = new Scene(400, 500) { 39 | content = List( 40 | new Line { 41 | startX <== startXVal 42 | startY = 50 43 | endX = 200 44 | endY = 400 45 | strokeWidth = 4 46 | stroke = Color.Blue 47 | }, 48 | new HBox { 49 | layoutX = 60 50 | layoutY = 420 51 | spacing = 10 52 | children = List( 53 | new Button { 54 | text = "Start" 55 | onAction = () => anim.playFromStart() 56 | disable <== anim.status =!= Status.STOPPED 57 | }, 58 | new Button { 59 | text = "Pause" 60 | onAction = () => anim.pause() 61 | disable <== anim.status =!= Status.RUNNING 62 | }, 63 | new Button { 64 | text = "Resume" 65 | onAction = () => anim.play() 66 | disable <== anim.status =!= Status.PAUSED 67 | }, 68 | new Button { 69 | text = "Stop" 70 | onAction = () => anim.stop() 71 | disable <== anim.status === Status.STOPPED 72 | } 73 | ) 74 | } 75 | ) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-base: #222; 3 | -fx-background-color: black; 4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000); 5 | -fx-background-image: url("resources/cross.png"); 6 | } 7 | 8 | .button { 9 | -fx-text-fill: #E0E0E0; 10 | } 11 | 12 | .label { 13 | -fx-font-size: 18pt; 14 | -fx-text-fill: #aaa; 15 | } 16 | 17 | #title { 18 | -fx-font-size: 24pt; 19 | -fx-text-fill: #ddd; 20 | } 21 | 22 | #openButton { 23 | -fx-graphic: url("resources/music_note.png"); 24 | } 25 | 26 | #eqButton { 27 | -fx-shape: "M 1,0 L 10,0 13,5 10,10 1,10 Q 0 10 0 9 L 0,1 Q 0 0 1 0"; 28 | -fx-font-size: 12pt; 29 | -fx-alignment: center-left; 30 | } 31 | 32 | .eqSlider { 33 | -fx-background-radius: 5; 34 | -fx-background-color: #222, #888, black; 35 | -fx-background-insets: 0, 1 0 0 1, 1; 36 | -fx-padding: 2 10; 37 | } 38 | 39 | .eqSlider .thumb { 40 | -fx-background-image: url("resources/thumb.png"); 41 | -fx-padding: 12; 42 | } 43 | 44 | .eqSlider:vertical .track { 45 | -fx-padding: 5; 46 | } 47 | 48 | .spectrumBar { 49 | -fx-background-radius: 5; 50 | -fx-background-color: #222, #888, black; 51 | -fx-background-insets: 0, 1 0 0 1, 1; 52 | -fx-padding: 2 10; 53 | } 54 | 55 | #backButton { 56 | -fx-shape: "M 3,0 L 12,0 Q 13,0 13,1 L 13,9 Q 13,10 12,10 L 3,10 1,5"; 57 | -fx-font-size: 12pt; 58 | -fx-alignment: center-right; 59 | } 60 | 61 | #playPauseButton, #seekEndButton, #seekStartButton { 62 | -fx-background-color: transparent; 63 | } 64 | 65 | #playPauseButton:hover, #seekEndButton:hover, #seekStartButton:hover { 66 | -fx-background-color: rgb(255, 255, 255, 0.1); 67 | } 68 | 69 | #seekEndButton { 70 | -fx-graphic: url("resources/next.png"); 71 | } 72 | 73 | #seekStartButton { 74 | -fx-graphic: url("resources/prev.png"); 75 | } 76 | 77 | #volumeHigh { 78 | -fx-image: url("resources/volHigh.png"); 79 | } 80 | 81 | #volumeLow { 82 | -fx-image: url("resources/volLow.png"); 83 | } 84 | 85 | .statusDisplay { 86 | -fx-border-color: white; 87 | -fx-border-radius: 2; 88 | -fx-border-width: 1; 89 | -fx-text-fill: white; 90 | -fx-font-size: 10pt; 91 | } 92 | 93 | .mediaText { 94 | -fx-text-fill: white; 95 | -fx-font-size: 11pt; 96 | } 97 | 98 | .eqLabel { 99 | -fx-padding: 8 3; 100 | } -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/media.css: -------------------------------------------------------------------------------- 1 | .root { 2 | -fx-base: #222; 3 | -fx-background-color: black; 4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000); 5 | -fx-background-image: url("resources/cross.png"); 6 | } 7 | 8 | .button { 9 | -fx-text-fill: #E0E0E0; 10 | } 11 | 12 | .label { 13 | -fx-font-size: 18pt; 14 | -fx-text-fill: #aaa; 15 | } 16 | 17 | #title { 18 | -fx-font-size: 24pt; 19 | -fx-text-fill: #ddd; 20 | } 21 | 22 | #openButton { 23 | -fx-graphic: url("resources/filmstrip.png"); 24 | } 25 | 26 | #eqButton { 27 | -fx-shape: "M 1,0 L 10,0 13,5 10,10 1,10 Q 0 10 0 9 L 0,1 Q 0 0 1 0"; 28 | -fx-font-size: 12pt; 29 | -fx-alignment: center-left; 30 | } 31 | 32 | .eqSlider { 33 | -fx-background-radius: 5; 34 | -fx-background-color: #222, #888, black; 35 | -fx-background-insets: 0, 1 0 0 1, 1; 36 | -fx-padding: 2 10; 37 | } 38 | 39 | .eqSlider .thumb { 40 | -fx-background-image: url("resources/thumb.png"); 41 | -fx-padding: 12; 42 | } 43 | 44 | .eqSlider:vertical .track { 45 | -fx-padding: 5; 46 | } 47 | 48 | .spectrumBar { 49 | -fx-background-radius: 5; 50 | -fx-background-color: #222, #888, black; 51 | -fx-background-insets: 0, 1 0 0 1, 1; 52 | -fx-padding: 2 10; 53 | } 54 | 55 | #backButton { 56 | -fx-shape: "M 3,0 L 12,0 Q 13,0 13,1 L 13,9 Q 13,10 12,10 L 3,10 1,5"; 57 | -fx-font-size: 12pt; 58 | -fx-alignment: center-right; 59 | } 60 | 61 | #playPauseButton, #seekEndButton, #seekStartButton { 62 | -fx-background-color: transparent; 63 | } 64 | 65 | #playPauseButton:hover, #seekEndButton:hover, #seekStartButton:hover { 66 | -fx-background-color: rgb(255, 255, 255, 0.1); 67 | } 68 | 69 | #seekEndButton { 70 | -fx-graphic: url("resources/next.png"); 71 | } 72 | 73 | #seekStartButton { 74 | -fx-graphic: url("resources/prev.png"); 75 | } 76 | 77 | #volumeHigh { 78 | -fx-image: url("resources/volHigh.png"); 79 | } 80 | 81 | #volumeLow { 82 | -fx-image: url("resources/volLow.png"); 83 | } 84 | 85 | .statusDisplay { 86 | -fx-border-color: white; 87 | -fx-border-radius: 2; 88 | -fx-border-width: 1; 89 | -fx-text-fill: white; 90 | -fx-font-size: 10pt; 91 | } 92 | 93 | .mediaText { 94 | -fx-text-fill: white; 95 | -fx-font-size: 11pt; 96 | } 97 | 98 | .eqLabel { 99 | -fx-padding: 8 3; 100 | } -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/SpectrumListener.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer4 2 | 3 | import javafx.scene.media as jfxcm 4 | import scalafx.scene.media.MediaPlayer 5 | 6 | /** 7 | * @author Jarek Sacha 8 | */ 9 | class SpectrumListener(startFreq: Double, mp: MediaPlayer, bars: Array[SpectrumBar]) 10 | extends jfxcm.AudioSpectrumListener { 11 | private val minValue = mp.audioSpectrumThreshold() 12 | private val norms = createNormArray() 13 | private val bandCount = mp.audioSpectrumNumBands() 14 | private val spectrumBucketCounts = createBucketCounts(startFreq, bandCount) 15 | 16 | def spectrumDataUpdate(timestamp: Double, duration: Double, magnitudes: Array[Float], phases: Array[Float]): Unit = { 17 | var index = 0 18 | var bucketIndex = 0 19 | var currentBucketCount = 0 20 | var sum = 0.0 21 | 22 | while (index < magnitudes.length) { 23 | sum += magnitudes(index) - minValue 24 | currentBucketCount += 1 25 | 26 | if (currentBucketCount >= spectrumBucketCounts(bucketIndex)) { 27 | bars(bucketIndex).setValue(sum / norms(bucketIndex)) 28 | currentBucketCount = 0 29 | sum = 0.0 30 | bucketIndex += 1 31 | } 32 | 33 | index += 1 34 | } 35 | } 36 | 37 | private def createNormArray(): Array[Double] = { 38 | val normArray = new Array[Double](bars.length) 39 | var currentNorm = 0.05 40 | for (i <- normArray.indices) { 41 | normArray(i) = 1 + currentNorm 42 | currentNorm *= 2 43 | } 44 | normArray 45 | } 46 | 47 | private def createBucketCounts(startFreq: Double, bandCount: Int): Array[Int] = { 48 | val bucketCounts = new Array[Int](bars.length) 49 | 50 | val bandwidth = 22050.0 / bandCount 51 | val centerFreq = bandwidth / 2 52 | var currentSpectrumFreq = centerFreq 53 | var currentEQFreq = startFreq / 2 54 | var currentCutoff = 0d 55 | var currentBucketIndex = -1 56 | 57 | for (_ <- 0 until bandCount) { 58 | if (currentSpectrumFreq > currentCutoff) { 59 | currentEQFreq *= 2 60 | currentCutoff = currentEQFreq + currentEQFreq / 2 61 | currentBucketIndex += 1 62 | } 63 | 64 | if (currentBucketIndex < bucketCounts.length) { 65 | bucketCounts(currentBucketIndex) += 1 66 | currentSpectrumFreq += bandwidth 67 | } 68 | } 69 | 70 | bucketCounts 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/SpectrumListener.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer4 2 | 3 | import javafx.scene.media as jfxcm 4 | import scalafx.scene.media.MediaPlayer 5 | 6 | /** 7 | * @author Jarek Sacha 8 | */ 9 | class SpectrumListener(startFreq: Double, mp: MediaPlayer, bars: Array[SpectrumBar]) 10 | extends jfxcm.AudioSpectrumListener { 11 | private val minValue = mp.audioSpectrumThreshold() 12 | private val norms = createNormArray() 13 | private val bandCount = mp.audioSpectrumNumBands() 14 | private val spectrumBucketCounts = createBucketCounts(startFreq, bandCount) 15 | 16 | def spectrumDataUpdate(timestamp: Double, duration: Double, magnitudes: Array[Float], phases: Array[Float]): Unit = { 17 | var index = 0 18 | var bucketIndex = 0 19 | var currentBucketCount = 0 20 | var sum = 0.0 21 | 22 | while (index < magnitudes.length) { 23 | sum += magnitudes(index) - minValue 24 | currentBucketCount += 1 25 | 26 | if (currentBucketCount >= spectrumBucketCounts(bucketIndex)) { 27 | bars(bucketIndex).setValue(sum / norms(bucketIndex)) 28 | currentBucketCount = 0 29 | sum = 0.0 30 | bucketIndex += 1 31 | } 32 | 33 | index += 1 34 | } 35 | } 36 | 37 | private def createNormArray(): Array[Double] = { 38 | val normArray = new Array[Double](bars.length) 39 | var currentNorm = 0.05 40 | for (i <- normArray.indices) { 41 | normArray(i) = 1 + currentNorm 42 | currentNorm *= 2 43 | } 44 | normArray 45 | } 46 | 47 | private def createBucketCounts(startFreq: Double, bandCount: Int): Array[Int] = { 48 | val bucketCounts = new Array[Int](bars.length) 49 | 50 | val bandwidth = 22050.0 / bandCount 51 | val centerFreq = bandwidth / 2 52 | var currentSpectrumFreq = centerFreq 53 | var currentEQFreq = startFreq / 2 54 | var currentCutoff = 0d 55 | var currentBucketIndex = -1 56 | 57 | for (_ <- 0 until bandCount) { 58 | if (currentSpectrumFreq > currentCutoff) { 59 | currentEQFreq *= 2 60 | currentCutoff = currentEQFreq + currentEQFreq / 2 61 | currentBucketIndex += 1 62 | } 63 | 64 | if (currentBucketIndex < bucketCounts.length) { 65 | bucketCounts(currentBucketIndex) += 1 66 | currentSpectrumFreq += bandwidth 67 | } 68 | } 69 | 70 | bucketCounts 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/JavaFXThreadsExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.collections.ObservableBuffer 7 | import scalafx.geometry.Insets 8 | import scalafx.scene.Scene 9 | import scalafx.scene.control.{Button, ListView, TextArea} 10 | import scalafx.scene.layout.VBox 11 | 12 | import scala.jdk.CollectionConverters.* 13 | 14 | /** 15 | * ScalaFX version of `JavaFXThreadsExample` from "Pro JavaFX 2" book. 16 | * 17 | * @author Jarek Sacha 18 | */ 19 | object JavaFXThreadsExample extends JFXApp3 { 20 | 21 | class Model { 22 | val threadNames = new ObservableBuffer[String]() 23 | val stackTraces = new ObservableBuffer[String]() 24 | update() 25 | 26 | def update(): Unit = { 27 | threadNames.clear() 28 | stackTraces.clear() 29 | val map = Thread.getAllStackTraces.asScala 30 | for ((k, v) <- map) { 31 | threadNames += "\"" + k.getName + "\"" 32 | stackTraces += formatStackTrace(v) 33 | } 34 | } 35 | 36 | private def formatStackTrace(v: Array[StackTraceElement]): String = { 37 | val sb = new StringBuilder("StackTrace: \n") 38 | for (stackTraceElement <- v) { 39 | sb ++= " at " ++= stackTraceElement.toString ++= "\n" 40 | } 41 | sb.toString() 42 | } 43 | } 44 | 45 | class View(model: Model) { 46 | val threadNames = new ListView(model.threadNames) 47 | val stackTrace = new TextArea() 48 | val updateButton = new Button("Update") 49 | val scene = new Scene(440, 640) { 50 | root = new VBox { 51 | spacing = 10 52 | padding = Insets(10) 53 | children = List( 54 | threadNames, 55 | stackTrace, 56 | updateButton 57 | ) 58 | } 59 | } 60 | } 61 | 62 | override def start(): Unit = { 63 | 64 | val model = new Model() 65 | val view = new View(model) 66 | 67 | hookupEvents() 68 | stage = new PrimaryStage { 69 | title = "JavaFX Threads Information" 70 | scene = view.scene 71 | } 72 | 73 | def hookupEvents(): Unit = { 74 | view.updateButton.onAction = () => model.update() 75 | view.threadNames.selectionModel().selectedItem.onChange { 76 | val index = view.threadNames.selectionModel().getSelectedIndex 77 | if (index >= 0) { 78 | view.stackTrace.text = model.stackTraces(index) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/proscalafx/ch05/ui/starterApp.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Pro JavaFX Authors 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 1. Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 3. Neither the name of JFXtras nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * starterApp.css - A CSS (Cascading Style Sheet) file the is referred to by 29 | * the StarterAppMain.java program to apply styles to the scene and its nodes. 30 | * Note: Syntax recommendation http://www.w3.org/TR/REC-CSS2/ 31 | * 32 | * Developed 2011 by James L. Weaver jim.weaver [at] javafxpert.com 33 | * as a JavaFX SDK 2.0 example for the Pro JavaFX book. 34 | */ 35 | 36 | #newButton { 37 | -fx-padding: 4 4 4 4; 38 | } 39 | 40 | #editButton { 41 | -fx-padding: 4 4 4 4; 42 | } 43 | 44 | #deleteButton { 45 | -fx-padding: 4 4 4 4; 46 | } 47 | 48 | #boldButton { 49 | -fx-padding: 4 4 4 4; 50 | } 51 | 52 | #italicButton { 53 | -fx-padding: 4 4 4 4; 54 | } 55 | 56 | #leftAlignButton { 57 | -fx-padding: 4 4 4 4; 58 | } 59 | 60 | #centerAlignButton { 61 | -fx-padding: 4 4 4 4; 62 | } 63 | 64 | #rightAlignButton { 65 | -fx-padding: 4 4 4 4; 66 | } 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | 4 | *.pydevproject 5 | .project 6 | .metadata 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .classpath 15 | .settings/ 16 | .loadpath 17 | # External tool builders 18 | .externalToolBuilders/ 19 | # Locally stored "Eclipse launch configurations" 20 | *.launch 21 | # CDT-specific 22 | .cproject 23 | # PDT-specific 24 | .buildpath 25 | ## Visual Studio 26 | ## Ignore Visual Studio temporary files, build results, and 27 | ## files generated by popular Visual Studio add-ons. 28 | # User-specific files 29 | *.suo 30 | *.user 31 | *.sln.docstates 32 | # Build results 33 | [Dd]ebug/ 34 | [Rr]elease/ 35 | *_i.c 36 | *_p.c 37 | *.ilk 38 | *.meta 39 | *.obj 40 | *.pch 41 | *.pdb 42 | *.pgc 43 | *.pgd 44 | *.rsp 45 | *.sbr 46 | *.tlb 47 | *.tli 48 | *.tlh 49 | *.vspscc 50 | .builds 51 | *.dotCover 52 | ## If you have NuGet Package Restore enabled, uncomment this 53 | #packages/ 54 | # Visual C++ cache files 55 | ipch/ 56 | *.aps 57 | *.ncb 58 | *.opensdf 59 | *.sdf 60 | # Visual Studio profiler 61 | *.psess 62 | *.vsp 63 | # ReSharper is a .NET coding add-in 64 | _ReSharper* 65 | # Installshield output folder 66 | [Ee]xpress 67 | # DocProject is a documentation generator add-in 68 | DocProject/buildhelp/ 69 | DocProject/Help/*.HxT 70 | DocProject/Help/*.HxC 71 | DocProject/Help/*.hhc 72 | DocProject/Help/*.hhk 73 | DocProject/Help/*.hhp 74 | DocProject/Help/Html2 75 | DocProject/Help/html 76 | # Click-Once directory 77 | publish 78 | # Others 79 | [Bb]in 80 | [Oo]bj 81 | sql 82 | TestResults 83 | *.Cache 84 | ClientBin 85 | stylecop.* 86 | ~$* 87 | *.dbmdl 88 | Generated_Code #added for RIA/Silverlight projects 89 | # Backup & report files from converting an old project file to a newer 90 | # Visual Studio version. Backup files are not needed, because we have git ;-) 91 | _UpgradeReport_Files/ 92 | Backup*/ 93 | UpgradeLog*.XML 94 | ############ 95 | ## Windows 96 | # Windows image file caches 97 | Thumbs.db 98 | # Folder config file 99 | Desktop.ini 100 | ############# 101 | ## Python 102 | *.py[co] 103 | # Packages 104 | *.egg 105 | *.egg-info 106 | dist 107 | build 108 | eggs 109 | parts 110 | bin 111 | var 112 | sdist 113 | develop-eggs 114 | .installed.cfg 115 | # Installer logs 116 | pip-log.txt 117 | # Unit test / coverage reports 118 | .coverage 119 | .tox 120 | #Translations 121 | *.mo 122 | #Mr Developer 123 | .mr.developer.cfg 124 | # Mac crap 125 | .DS_Store 126 | .scala_dependencies 127 | /.idea/ 128 | /local/ 129 | /out/ 130 | /target/ 131 | /.idea_modules/ 132 | target 133 | /.bsp/ 134 | -------------------------------------------------------------------------------- /src/proscalafx/ch01/helloearthrise/HelloEarthRiseMain.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch01.helloearthrise 2 | 3 | import scalafx.animation.{Interpolator, Timeline, TranslateTransition} 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.geometry.VPos 7 | import scalafx.scene.image.{Image, ImageView} 8 | import scalafx.scene.paint.Color 9 | import scalafx.scene.shape.Rectangle 10 | import scalafx.scene.text.{Font, FontWeight, Text, TextAlignment} 11 | import scalafx.scene.{Group, Scene} 12 | import scalafx.util.Duration 13 | 14 | import java.io.File 15 | 16 | /** Main class for the "Hello World" style example. */ 17 | object HelloEarthRiseMain extends JFXApp3 { 18 | 19 | override def start(): Unit = { 20 | 21 | val message = 22 | """Earthrise at Christmas: 23 | |[Forty] years ago this Christmas, a turbulent world 24 | |looked to the heavens for a unique view of our home 25 | |planet. This photo of Earthrise over the lunar horizon 26 | |was taken by the Apollo 8 crew in December 1968, showing 27 | |Earth for the first time as it appears from deep space. 28 | |Astronauts Frank Borman, Jim Lovell and William Anders 29 | |had become the first humans to leave Earth orbit, 30 | |entering lunar orbit on Christmas Eve. In a historic live 31 | |broadcast that night, the crew took turns reading from 32 | |the Book of Genesis, closing with a holiday wish from 33 | |Commander Borman: "We close with good night, good luck, 34 | |a Merry Christmas, and God bless all of you -- all of 35 | |you on the good Earth." """.stripMargin 36 | 37 | val textRef = new Text { 38 | layoutY = 100 39 | textOrigin = VPos.Top 40 | textAlignment = TextAlignment.Justify 41 | wrappingWidth = 400 42 | text = message 43 | fill = Color.rgb(187, 195, 107) 44 | font = Font.font("SansSerif", FontWeight.Bold, 24.0) 45 | } 46 | 47 | stage = new PrimaryStage { 48 | title = "Hello Earthrise" 49 | scene = new Scene(516, 387) { 50 | content = List( 51 | new ImageView(image = new Image(new File("media/earthrise.jpg").toURI.toString)), 52 | new Group { 53 | layoutX = 50 54 | layoutY = 180 55 | children = List(textRef) 56 | clip = Rectangle(430, 85) 57 | } 58 | ) 59 | } 60 | } 61 | 62 | new TranslateTransition { 63 | cycleCount = Timeline.Indefinite 64 | duration = Duration(75000) 65 | node = textRef 66 | toY = -820 67 | interpolator = Interpolator.Linear 68 | }.play() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/UnresponsiveUIExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.beans.property.ObjectProperty 7 | import scalafx.geometry.{Insets, Pos} 8 | import scalafx.scene.Scene 9 | import scalafx.scene.control.Button 10 | import scalafx.scene.layout.{BorderPane, HBox} 11 | import scalafx.scene.paint.Color 12 | import scalafx.scene.shape.Rectangle 13 | 14 | /** 15 | * @author Jarek Sacha 16 | */ 17 | object UnresponsiveUIExample extends JFXApp3 { 18 | 19 | override def start(): Unit = { 20 | 21 | hookupEvents() 22 | 23 | stage = new PrimaryStage { 24 | title = "Unresponsive UI Example" 25 | scene = View.scene 26 | } 27 | } 28 | 29 | def hookupEvents(): Unit = { 30 | View.changeFillButton.onAction = () => { 31 | val fillPaint = Model.fillPaint() 32 | Model.fillPaint() = if (fillPaint == Color.LightGray.delegate) Color.Gray else Color.LightGray 33 | // Bad code that will cause the UI to be unresponsive 34 | try { 35 | Thread.sleep(Long.MaxValue) 36 | } catch { 37 | case _: InterruptedException => /* Properly handle exception */ 38 | } 39 | } 40 | 41 | View.changeStrokeButton.onAction = () => { 42 | val strokePaint = Model.strokePaint() 43 | Model.strokePaint() = if (strokePaint == Color.DarkGray.delegate) Color.Black else Color.DarkGray 44 | } 45 | } 46 | 47 | private object Model { 48 | // `fill` and `stroke` are created using ObjectProperty factory method to ensure proper type parameter 49 | // to ObjectProperty. We use here, implicitly, JavaFX Paint as the type for `ObjectProperty`. 50 | val fillPaint = ObjectProperty(this, "fillPaint", Color.LightGray) 51 | val strokePaint = ObjectProperty(this, "strokePaint", Color.DarkGray) 52 | } 53 | 54 | private object View { 55 | val rectangle = new Rectangle { 56 | width = 200 57 | height = 200 58 | strokeWidth = 10 59 | fill <== Model.fillPaint 60 | stroke <== Model.strokePaint 61 | } 62 | 63 | val changeFillButton = new Button("Change Fill") 64 | val changeStrokeButton = new Button("Chang Stroke") 65 | 66 | val buttonHBox = new HBox { 67 | padding = Insets(10) 68 | spacing = 10 69 | alignment = Pos.Center 70 | children = List( 71 | changeFillButton, 72 | changeStrokeButton 73 | ) 74 | } 75 | 76 | val scene = new Scene { 77 | root = new BorderPane { 78 | padding = Insets(10) 79 | center = rectangle 80 | bottom = buttonHBox 81 | } 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/proscalafx/ch03/MotivatingExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch03 2 | 3 | import scalafx.beans.property.IntegerProperty 4 | 5 | object MotivatingExample extends App { 6 | 7 | var intProperty: IntegerProperty = _ 8 | 9 | def createProperty(): Unit = { 10 | println() 11 | intProperty = IntegerProperty(1024) 12 | println("intProperty = " + intProperty) 13 | println("intProperty.get = " + intProperty.get) 14 | println("intProperty.value = " + intProperty.value) 15 | println("intProperty() = " + intProperty()) 16 | } 17 | 18 | def addAndRemoveInvalidationListener(): Unit = { 19 | println() 20 | val subscription = intProperty.onInvalidate { 21 | observable => println("The observable has been invalidated: " + observable + ".") 22 | } 23 | 24 | println("Added invalidation listener.") 25 | 26 | println("Calling intProperty.set(2048).") 27 | intProperty() = 2048 28 | 29 | println("Calling intProperty.setValue(3072).") 30 | intProperty() = Integer.valueOf(3072) 31 | 32 | subscription.cancel() 33 | System.out.println("Removed invalidation listener.") 34 | 35 | println("Calling intProperty.set(4096).") 36 | intProperty() = 4096 37 | } 38 | 39 | def addAndRemoveChangeListener(): Unit = { 40 | println() 41 | val subscription = intProperty.onChange { 42 | (_, oldValue, newValue) => 43 | println("The observableValue has changed: oldValue = " + oldValue + ", newValue = " + newValue) 44 | } 45 | println("Added change listener.") 46 | 47 | println("Calling intProperty.set(5120).") 48 | intProperty() = 5120 49 | 50 | subscription.cancel() 51 | println("Removed change listener.") 52 | 53 | println("Calling intProperty.set(6144).") 54 | intProperty() = 6144 55 | } 56 | 57 | def bindAndUnbindOnePropertyToAnother(): Unit = { 58 | println() 59 | val otherProperty = IntegerProperty(0) 60 | println("otherProperty() = " + otherProperty()) 61 | 62 | println("Binding otherProperty to intProperty.") 63 | otherProperty <== intProperty 64 | println("otherProperty() = " + otherProperty()) 65 | 66 | println("Calling intProperty.set(7168).") 67 | intProperty() = 7168 68 | println("otherProperty.get = " + otherProperty.get) 69 | 70 | println("Unbinding otherProperty from intProperty.") 71 | otherProperty.unbind() 72 | println("otherProperty() = " + otherProperty()) 73 | 74 | println("Calling intProperty.set(8192).") 75 | intProperty() = 8192 76 | println("otherProperty() = " + otherProperty()) 77 | } 78 | 79 | createProperty() 80 | addAndRemoveInvalidationListener() 81 | addAndRemoveChangeListener() 82 | bindAndUnbindOnePropertyToAnother() 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/proscalafx/ch02/onthescene/changeOfScene.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Pro JavaFX Authors 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 1. Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 3. Neither the name of JFXtras nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * changeOfScene.css - A CSS (Cascading Style Sheet) file the is referred to by 29 | * the OnTheSceneMain.fx program to apply styles to the scene and its nodes. 30 | * Note: Syntax recommendation http://www.w3.org/TR/REC-CSS2/ 31 | * 32 | * Developed 2011 by James L. Weaver jim.weaver [at] javafxpert.com 33 | * as a JavaFX SDK 2.0 example for the Pro JavaFX book. 34 | */ 35 | 36 | #stageX, #stageY { 37 | -fx-padding: 3; 38 | -fx-border-color: blue; 39 | -fx-stroke-dash-array: 12 2 4 2; 40 | -fx-border-width: 4; 41 | -fx-border-radius: 5; 42 | } 43 | 44 | .emphasized-text { 45 | -fx-font-size: 14pt; 46 | -fx-font-weight: bold; 47 | -fx-font-style: normal; 48 | } 49 | 50 | .radio-button *.radio { 51 | -fx-padding: 10; 52 | -fx-background-color: red, yellow; 53 | -fx-background-insets: 0, 5; 54 | -fx-background-radius: 30, 20; 55 | } 56 | 57 | .radio-button:focused *.radio { 58 | -fx-background-color: blue, red, yellow; 59 | -fx-background-insets: -5, 0, 5; 60 | -fx-background-radius: 40, 30, 20; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/BufferChangeEventExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import scalafx.collections.ObservableBuffer 4 | import scalafx.collections.ObservableBuffer.* 5 | 6 | /** 7 | * Example of processing of "change" notifications from ScalaFX `ObservableBuffer`, wrapper for JavaFX `ObservableList`. 8 | * 9 | * This example corresponds to JavaFX example `ListChangeEventExample`. 10 | * 11 | * ScalaFX is using a different way of passing information about modification to `ObservableBuffer`. 12 | * Each modification is represented by a [[scalafx.collections.ObservableBuffer.Change]] object. 13 | */ 14 | object BufferChangeEventExample extends App { 15 | 16 | val strings = new ObservableBuffer[String] 17 | strings.onChange((buffer, changes) => { 18 | println("\tbuffer = " + buffer.mkString("[", ", ", "]")) 19 | val log = changes 20 | .zipWithIndex 21 | .foldLeft("\tChange event data:\n")({ case (string, (change, index)) => string + prettyPrint(index, change) }) 22 | println(log) 23 | }) 24 | 25 | println("""Calling strings ++= Seq("Zero", "One", "Two", "Three"): """) 26 | strings ++= Seq("Zero", "One", "Two", "Three") 27 | 28 | println("Calling strings.sort: ") 29 | strings.sort() 30 | 31 | println("""Calling strings(1) = "Three_1": """) 32 | strings(1) = "Three_1" 33 | 34 | println("""Calling strings.setAll("One_1", "Three_1", "Two_1", "Zero_1"): """) 35 | strings.setAll("One_1", "Three_1", "Two_1", "Zero_1") 36 | 37 | println("""Calling strings --= Seq("One_1", "Two_1", "Zero_1"): """) 38 | strings --= Seq("One_1", "Two_1", "Zero_1") 39 | 40 | private def prettyPrint(index: Int, change: Change[String]): String = { 41 | val sb = new StringBuffer("\t\tcursor = " + index + "\n") 42 | sb.append("\t\tKind of change: ") 43 | 44 | change match { 45 | case Add(position, added) => 46 | sb.append("added\n") 47 | sb.append("\t\tPosition: " + position + "\n") 48 | sb.append("\t\tElement : " + added + "\n") 49 | case Remove(position, removed) => 50 | sb.append("removed\n") 51 | sb.append("\t\tPosition: " + position + "\n") 52 | sb.append("\t\tElement : " + removed + "\n") 53 | case Reorder(start, end, permutation) => 54 | sb.append("reordered\n") 55 | sb.append("\t\tAffected Range: [%d, %d]\n".format(start, end)) 56 | val strLog = (start until end) 57 | .map(i => "%d->%s".format(i, permutation(i))) 58 | .mkString("\t\tPermutation: [", ", ", "]\n") 59 | sb.append(strLog) 60 | case Update(from, to) => 61 | sb.append("updated\n") 62 | sb.append("\t\tfrom: " + from + "\n") 63 | sb.append("\t\tto : " + to + "\n") 64 | } 65 | 66 | sb.toString 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/proscalafx/ch02/metronomepathtransition/MetronomePathTransitionMain.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch02.metronomepathtransition 2 | 3 | import scalafx.Includes.* 4 | import scalafx.animation.Animation.Status 5 | import scalafx.animation.PathTransition.OrientationType 6 | import scalafx.animation.{Interpolator, PathTransition, Timeline} 7 | import scalafx.application.JFXApp3 8 | import scalafx.application.JFXApp3.PrimaryStage 9 | import scalafx.scene.Scene 10 | import scalafx.scene.control.Button 11 | import scalafx.scene.layout.HBox 12 | import scalafx.scene.paint.Color 13 | import scalafx.scene.shape.{ArcTo, Ellipse, MoveTo, Path} 14 | import scalafx.util.Duration 15 | 16 | object MetronomePathTransitionMain extends JFXApp3 { 17 | 18 | override def start(): Unit = { 19 | val ellipse = new Ellipse { 20 | centerX = 100 21 | centerY = 50 22 | radiusX = 4 23 | radiusY = 8 24 | fill = Color.Blue 25 | } 26 | 27 | val anim = new PathTransition { 28 | duration = Duration(1000.0) 29 | node = ellipse 30 | path = new Path { 31 | elements = List( 32 | MoveTo(100, 50), 33 | ArcTo(350, 350, 0, 300, 50, largeArcFlag = false, sweepFlag = true) 34 | ) 35 | } 36 | orientation = OrientationType.OrthogonalToTangent 37 | interpolator = Interpolator.Linear 38 | autoReverse = true 39 | cycleCount = Timeline.Indefinite 40 | } 41 | 42 | stage = new PrimaryStage { 43 | title = "Metronome using PathTransition" 44 | scene = new Scene(400, 500) { 45 | content = List( 46 | ellipse, 47 | new HBox { 48 | layoutX = 60 49 | layoutY = 420 50 | spacing = 10 51 | // NOTE: the `disable` bindings below compare value of a property to JavaFX constant 52 | children = List( 53 | new Button { 54 | text = "Start" 55 | onAction = () => anim.playFromStart() 56 | disable <== (anim.status =!= Status.Stopped.delegate) 57 | }, 58 | new Button { 59 | text = "Pause" 60 | onAction = () => anim.pause() 61 | disable <== (anim.status =!= Status.Running.delegate) 62 | }, 63 | new Button { 64 | text = "Resume" 65 | onAction = () => anim.play() 66 | disable <== (anim.status =!= Status.Paused.delegate) 67 | }, 68 | new Button { 69 | text = "Stop" 70 | onAction = () => anim.stop() 71 | disable <== (anim.status === Status.Stopped.delegate) 72 | } 73 | ) 74 | } 75 | ) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/proscalafx/ch01/helloearthrise/HelloScrollPaneMain.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch01.helloearthrise 2 | 3 | import scalafx.animation.{Interpolator, Timeline, TranslateTransition} 4 | import scalafx.application.JFXApp3 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.geometry.VPos 7 | import scalafx.scene.Scene 8 | import scalafx.scene.control.ScrollPane 9 | import scalafx.scene.control.ScrollPane.ScrollBarPolicy 10 | import scalafx.scene.image.{Image, ImageView} 11 | import scalafx.scene.paint.Color 12 | import scalafx.scene.text.{Font, FontWeight, Text, TextAlignment} 13 | import scalafx.util.Duration 14 | 15 | import java.io.File 16 | 17 | object HelloScrollPaneMain extends JFXApp3 { 18 | 19 | override def start(): Unit = { 20 | val message = 21 | """Earthrise at Christmas: 22 | [Forty] years ago this Christmas, a turbulent world 23 | looked to the heavens for a unique view of our home 24 | planet. This photo of Earthrise over the lunar horizon 25 | was taken by the Apollo 8 crew in December 1968, showing 26 | Earth for the first time as it appears from deep space. 27 | Astronauts Frank Borman, Jim Lovell and William Anders 28 | had become the first humans to leave Earth orbit, 29 | entering lunar orbit on Christmas Eve. In a historic live 30 | broadcast that night, the crew took turns reading from 31 | the Book of Genesis, closing with a holiday wish from 32 | Commander Borman: "We close with good night, good luck, 33 | a Merry Christmas, and God bless all of you -- all of 34 | you on the good Earth." """.replace("\n", "") 35 | 36 | val textRef = new Text { 37 | layoutY = 100 38 | textOrigin = VPos.Top 39 | textAlignment = TextAlignment.Justify 40 | wrappingWidth = 400 41 | text = message 42 | fill = Color.rgb(187, 195, 107) 43 | font = Font.font("SansSerif", FontWeight.Bold, 24.0) 44 | } 45 | 46 | stage = new PrimaryStage { 47 | title = "Hello Earthrise" 48 | scene = new Scene(516, 387) { 49 | content = List( 50 | new ImageView(new Image(new File("media/earthrise.jpg").toURI.toString)), 51 | new ScrollPane { 52 | layoutX = 50 53 | layoutY = 180 54 | prefWidth = 440 55 | prefHeight = 85 56 | hbarPolicy = ScrollBarPolicy.Never 57 | vbarPolicy = ScrollBarPolicy.Never 58 | pannable = true 59 | content = textRef 60 | style = "-fx-background-color: transparent;" 61 | } 62 | ) 63 | } 64 | } 65 | 66 | new TranslateTransition { 67 | cycleCount = Timeline.Indefinite 68 | duration = Duration(75000) 69 | node = textRef 70 | toY = -820 71 | interpolator = Interpolator.Linear 72 | }.play() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/proscalafx/ch10/fxml/AdoptionFormController.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch10.fxml 2 | 3 | import javafx.scene.{control as jfxsc, layout as jfxsl} 4 | import javafx.{event as jfxe, fxml as jfxf} 5 | import scalafx.Includes.* 6 | import scalafx.scene.layout.GridPane 7 | 8 | import java.net.URL 9 | import java.util 10 | 11 | /** 12 | * Example of a controlled initialized through FXML. 13 | * 14 | * When working with FXML, due to the nature of JavaFX FXMLLoader, we need to expose variables and methods that 15 | * FXMLLoader will be using with JavaFX signatures. 16 | * 17 | * The FXMLLoader injects JavaFX objects as values of member variables marked with annotation `@jfxf.FXML`. 18 | * We need to declare those variables using JavaFX types (not ScalaFX types). 19 | * We can use those variables directly or wrap them in ScalaFX objects. 20 | * Here, for the sake of illustration, we only wrap one variable `gridDelegate` (it is not strictly necessary). 21 | * The most convenient place to do wrapping is in the overloaded method `initialize`. It is executed after 22 | * FXMLLoader injects its objects. 23 | * 24 | * We can rely on ScalaFX "magic" to use ScalaFX methods on variables that were not explicitly wrapped. 25 | * All we need to do is to "summon the magic" using "import scalafx.Includes._". 26 | * This is demonstrated in method "handleClear" where we access properties on 27 | * JavaFX objects using ScalaFX way, no `get` or `set` involved. 28 | * 29 | * Methods annotated with `@jfxf.FXML`, that will be wired to event handlers by FLXMLoader. 30 | * They need to use JavaFX method signatures. This is illustrated in methods: `handleSubmit` and `handleClear`. 31 | * 32 | * In the rest of the code we can use ScalaFX, for instance, to create more in the event handlers or bind 33 | * properties. 34 | * 35 | * @author Jarek Sacha 36 | */ 37 | class AdoptionFormController extends jfxf.Initializable { 38 | 39 | @jfxf.FXML 40 | private var sizeTextField: jfxsc.TextField = _ 41 | @jfxf.FXML 42 | private var breedTextField: jfxsc.TextField = _ 43 | @jfxf.FXML 44 | private var sexChoiceBox: jfxsc.ChoiceBox[String] = _ 45 | @jfxf.FXML 46 | private var additionalInfoTextArea: jfxsc.TextArea = _ 47 | 48 | @jfxf.FXML 49 | private var gridDelegate: jfxsl.GridPane = _ 50 | private var grid: GridPane = _ 51 | 52 | @jfxf.FXML 53 | private def handleSubmit(event: jfxe.ActionEvent): Unit = { 54 | grid.gridLinesVisible() = !grid.gridLinesVisible() 55 | } 56 | 57 | @jfxf.FXML 58 | private def handleClear(event: jfxe.ActionEvent): Unit = { 59 | sizeTextField.text = "" 60 | breedTextField.text = "" 61 | sexChoiceBox.selectionModel().clearSelection() 62 | additionalInfoTextArea.text = "" 63 | } 64 | 65 | override def initialize(url: URL, rb: util.ResourceBundle): Unit = { 66 | grid = new GridPane(gridDelegate) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/proscalafx/ch02/onthescene/onTheScene.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011, Pro JavaFX Authors 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 1. Redistributions of source code must retain the above copyright notice, 8 | * this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright notice, 10 | * this list of conditions and the following disclaimer in the documentation 11 | * and/or other materials provided with the distribution. 12 | * 3. Neither the name of JFXtras nor the names of its contributors may be used 13 | * to endorse or promote products derived from this software without 14 | * specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * onTheScene.css - A CSS (Cascading Style Sheet) file the is referred to by 29 | * the OnTheSceneMain.fx program to apply styles to the scene and its nodes. 30 | * Note: Syntax recommendation http://www.w3.org/TR/REC-CSS2/ 31 | * 32 | * Developed 2011 by James L. Weaver jim.weaver [at] javafxpert.com 33 | * as a JavaFX SDK 2.0 example for the Pro JavaFX book. 34 | */ 35 | 36 | #stageX, #stageY { 37 | -fx-padding: 1; 38 | -fx-border-color: black; 39 | -fx-border-style: dashed; 40 | -fx-border-width: 2; 41 | -fx-border-radius: 5; 42 | } 43 | 44 | .emphasized-text { 45 | -fx-font-size: 14pt; 46 | -fx-font-weight: normal; 47 | -fx-font-style: italic; 48 | } 49 | 50 | .choice-box:hover { 51 | -fx-scale-x: 1.1; 52 | -fx-scale-y: 1.1; 53 | } 54 | 55 | .radio-button .radio { 56 | -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, 57 | -fx-inner-border, -fx-body-color; 58 | -fx-background-insets: 0 0 -1 0, 0, 1, 2; 59 | -fx-background-radius: 1.0em; 60 | -fx-padding: 0.333333em; 61 | } 62 | 63 | .radio-button:focused .radio { 64 | -fx-background-color: -fx-focus-color, -fx-outer-border, 65 | -fx-inner-border, -fx-body-color; 66 | -fx-background-radius: 1.0em; 67 | -fx-background-insets: -1.4, 0, 1, 2; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/proscalafx/ch04/reversi/examples/PlayerScoreExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch04.reversi.examples 2 | 3 | import proscalafx.ch04.reversi.model.{Black, Owner, ReversiModel, White} 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.geometry.Pos 8 | import scalafx.scene.Scene 9 | import scalafx.scene.effect.{DropShadow, InnerShadow} 10 | import scalafx.scene.layout.* 11 | import scalafx.scene.paint.Color 12 | import scalafx.scene.shape.Ellipse 13 | import scalafx.scene.text.{Font, FontWeight, Text} 14 | 15 | object PlayerScoreExample extends JFXApp3 { 16 | 17 | override def start(): Unit = { 18 | 19 | val tiles = new TilePane() { 20 | snapToPixel = false 21 | children = List( 22 | createScore(Black), 23 | createScore(White) 24 | ) 25 | } 26 | 27 | stage = new PrimaryStage() { 28 | scene = new Scene(600, 140) { 29 | content = tiles 30 | } 31 | } 32 | 33 | tiles.prefTileWidth <== stage.scene().width / 2 34 | tiles.prefTileHeight <== stage.scene().height 35 | } 36 | 37 | // --------------------------------------------------------------------------- 38 | 39 | private def createScore(owner: Owner): StackPane = { 40 | 41 | val innerShadow = new InnerShadow() { 42 | color = Color.DodgerBlue 43 | choke = 0.5 44 | } 45 | 46 | val backgroundRegion = new Region() { 47 | style = "-fx-background-color: " + owner.opposite.colorStyle 48 | if (Black == owner) { 49 | effect = innerShadow 50 | } 51 | } 52 | 53 | val dropShadow = new DropShadow() { 54 | color = Color.DodgerBlue 55 | spread = 0.2 56 | } 57 | 58 | val piece = new Ellipse() { 59 | radiusX = 32 60 | radiusY = 20 61 | fill = owner.color 62 | if (Black == owner) { 63 | effect = dropShadow 64 | } 65 | } 66 | 67 | val score = new Text() { 68 | font = Font.font(null, FontWeight.Bold, 100) 69 | fill = owner.color 70 | text <== ReversiModel.score(owner).asString() 71 | } 72 | 73 | val remaining = new Text() { 74 | font = Font.font(null, FontWeight.Bold, 12) 75 | fill = owner.color 76 | text <== ReversiModel.turnsRemaining(owner).asString() + " turns remaining" 77 | } 78 | 79 | new StackPane() { 80 | children = List( 81 | backgroundRegion, 82 | new FlowPane() { 83 | hgap = 20 84 | vgap = 10 85 | alignment = Pos.Center 86 | children = List( 87 | score, 88 | new VBox() { 89 | alignment = Pos.Center 90 | spacing = 10 91 | children = List( 92 | piece, 93 | remaining 94 | ) 95 | } 96 | ) 97 | } 98 | ) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer3/SongModel.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer3 2 | 3 | import javafx.scene.image as jfxsi 4 | import scalafx.beans.property.{ObjectProperty, ReadOnlyObjectWrapper, StringProperty} 5 | import scalafx.collections.ObservableMap.Add 6 | import scalafx.scene.image.Image 7 | import scalafx.scene.media.{Media, MediaPlayer} 8 | 9 | /** 10 | * @author Jarek Sacha 11 | */ 12 | class SongModel { 13 | private val DefaultImageURL = classOf[SongModel].getResource("resources/defaultAlbum.png").toString 14 | private val DefaultImageCover = new Image(DefaultImageURL) 15 | val album = new StringProperty(this, "album") 16 | val artist = new StringProperty(this, "artist") 17 | val title = new StringProperty(this, "title") 18 | val year = new StringProperty(this, "year") 19 | // NOTE: use of `javafx.scene.image.Image` instead of `scalafx.scene.image.Image`, this is required for binding in 20 | // MetadataView to compile. 21 | val albumCover = new ObjectProperty[jfxsi.Image](this, "albumCover") 22 | private val _mediaPlayer = new ReadOnlyObjectWrapper[MediaPlayer](this, "mediaPlayer") 23 | 24 | resetProperties() 25 | 26 | def mediaPlayer = _mediaPlayer.readOnlyProperty 27 | 28 | def url: String = if (mediaPlayer() != null) mediaPlayer().media.source else null 29 | 30 | def url_=(url: String): Unit = { 31 | if (mediaPlayer() != null) mediaPlayer().stop() 32 | 33 | initializeMedia(url) 34 | } 35 | 36 | private def resetProperties(): Unit = { 37 | artist() = "" 38 | album() = "" 39 | title() = "" 40 | year() = "" 41 | albumCover() = DefaultImageCover 42 | } 43 | 44 | private def initializeMedia(url: String): Unit = { 45 | resetProperties() 46 | 47 | try { 48 | val media = new Media(url) { 49 | metadata.onChange((_, change) => { 50 | change match { 51 | case Add(key, added) => handleMetadata(key, added) 52 | case _ => 53 | } 54 | }) 55 | } 56 | 57 | _mediaPlayer() = new MediaPlayer(media) { 58 | self => 59 | // Handle errors during playback 60 | onError = { 61 | val errorMessage = self.media.error().getMessage 62 | println("MediaPlayer Error: " + errorMessage) 63 | } 64 | } 65 | } catch { 66 | // Handle construction errors 67 | case re: RuntimeException => println("Caught Exception: " + re.getMessage) 68 | } 69 | } 70 | 71 | private def handleMetadata(key: String, value: AnyRef): Unit = { 72 | key match { 73 | case "album" => album() = value.toString 74 | case "artist" => artist() = value.toString 75 | case "title" => title() = value.toString 76 | case "year" => year() = value.toString 77 | case "image" => albumCover() = value.asInstanceOf[javafx.scene.image.Image] 78 | case _ => println("Unhandled metadata key: " + key + ", value: " + value) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/SongModel.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer4 2 | 3 | import javafx.scene.image as jfxsi 4 | import scalafx.beans.property.{ObjectProperty, ReadOnlyObjectWrapper, StringProperty} 5 | import scalafx.collections.ObservableMap.Add 6 | import scalafx.scene.image.Image 7 | import scalafx.scene.media.{Media, MediaPlayer} 8 | 9 | /** 10 | * @author Jarek Sacha 11 | */ 12 | class SongModel { 13 | private val DefaultImageURL = classOf[SongModel].getResource("resources/defaultAlbum.png").toString 14 | private val DefaultImageCover = new Image(DefaultImageURL) 15 | val album = new StringProperty(this, "album") 16 | val artist = new StringProperty(this, "artist") 17 | val title = new StringProperty(this, "title") 18 | val year = new StringProperty(this, "year") 19 | // NOTE: use of `javafx.scene.image.Image` instead of `scalafx.scene.image.Image`, this is required for binding in 20 | // MetadataView to compile. 21 | val albumCover = new ObjectProperty[jfxsi.Image](this, "albumCover") 22 | private val _mediaPlayer = new ReadOnlyObjectWrapper[MediaPlayer](this, "mediaPlayer") 23 | 24 | resetProperties() 25 | 26 | def mediaPlayer = _mediaPlayer.readOnlyProperty 27 | 28 | def url: String = if (mediaPlayer() != null) mediaPlayer().media.source else null 29 | 30 | def url_=(url: String): Unit = { 31 | if (mediaPlayer() != null) mediaPlayer().stop() 32 | 33 | initializeMedia(url) 34 | } 35 | 36 | private def resetProperties(): Unit = { 37 | artist() = "" 38 | album() = "" 39 | title() = "" 40 | year() = "" 41 | albumCover() = DefaultImageCover 42 | } 43 | 44 | private def initializeMedia(url: String): Unit = { 45 | resetProperties() 46 | 47 | try { 48 | val media = new Media(url) { 49 | metadata.onChange((_, change) => { 50 | change match { 51 | case Add(key, added) => handleMetadata(key, added) 52 | case _ => 53 | } 54 | }) 55 | } 56 | 57 | _mediaPlayer() = new MediaPlayer(media) { 58 | self => 59 | // Handle errors during playback 60 | onError = { 61 | val errorMessage = self.media.error().getMessage 62 | println("MediaPlayer Error: " + errorMessage) 63 | } 64 | } 65 | } catch { 66 | // Handle construction errors 67 | case re: RuntimeException => println("Caught Exception: " + re.getMessage) 68 | } 69 | } 70 | 71 | private def handleMetadata(key: String, value: AnyRef): Unit = { 72 | key match { 73 | case "album" => album() = value.toString 74 | case "artist" => artist() = value.toString 75 | case "title" => title() = value.toString 76 | case "year" => year() = value.toString 77 | case "image" => albumCover() = value.asInstanceOf[javafx.scene.image.Image] 78 | case _ => println("Unhandled metadata key: " + key + ", value: " + value) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/SpectrumBar.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer4 2 | 3 | import javafx.scene.layout as jfxsl 4 | import scalafx.Includes.* 5 | import scalafx.geometry.Pos 6 | import scalafx.scene.shape.Rectangle 7 | 8 | /** 9 | * Custom component for displaying a spectrum bar. 10 | * 11 | * @author Jarek Sacha 12 | */ 13 | class SpectrumBar(maxValue: Int, barCount: Int) extends jfxsl.VBox { 14 | private val SPACING = 1.0 15 | private val ASPECT_RATIO = 3.0 16 | private val MIN_BAR_HEIGHT = 3.0 17 | private var lastWidth = 0.0 18 | private var lastHeight = 0.0 19 | getStyleClass.add("spectrumBar") 20 | 21 | setSpacing(SPACING) 22 | setAlignment(Pos.BottomCenter) 23 | 24 | for (i <- 0 until barCount) { 25 | val c = (i.toDouble / barCount.toDouble * 255.0).toInt 26 | val r = new Rectangle { 27 | visible = false 28 | style = s"-fx-fill: ladder( rgb($c, $c, $c), red 30%, yellow 70%, #56F32B 90%);" 29 | arcWidth = 2 30 | arcHeight = 2 31 | } 32 | 33 | getChildren.add(r) 34 | } 35 | 36 | protected override def computeMinHeight(width: Double) = computeHeight(MIN_BAR_HEIGHT) 37 | 38 | protected override def computeMinWidth(height: Double) = computeWidthForHeight(MIN_BAR_HEIGHT) 39 | 40 | protected override def computePrefHeight(width: Double) = computeHeight(5) 41 | 42 | protected override def computePrefWidth(height: Double) = computeWidthForHeight(5) 43 | 44 | def setValue(value: Double): Unit = { 45 | val barsLit = math.min(barCount, math.round(value / maxValue * barCount).toInt) 46 | val childList = getChildren 47 | for (i <- 0 until childList.size) { 48 | childList.get(i).setVisible(i > barCount - barsLit) 49 | } 50 | } 51 | 52 | protected override def layoutChildren(): Unit = { 53 | if (lastWidth != getWidth || lastHeight != getHeight) { 54 | val spacing = SPACING * (barCount - 1) 55 | val barHeight = (getHeight - getVerticalPadding - spacing) / barCount 56 | val barWidth = math.min(barHeight * ASPECT_RATIO, getWidth - getHorizontalPadding) 57 | 58 | for (node <- getChildren) { 59 | val r = node.asInstanceOf[javafx.scene.shape.Rectangle] 60 | r.width = barWidth 61 | r.height = barHeight 62 | } 63 | 64 | lastWidth = getWidth 65 | lastHeight = getHeight 66 | } 67 | 68 | super.layoutChildren() 69 | } 70 | 71 | private def computeWidthForHeight(barHeight: Double) = barHeight * ASPECT_RATIO + getHorizontalPadding 72 | 73 | private def computeHeight(barHeight: Double): Double = { 74 | val vPadding = getVerticalPadding 75 | val barHeights = barHeight * barCount 76 | val spacing = SPACING * (barCount - 1) 77 | barHeights + spacing + vPadding 78 | } 79 | 80 | private def getVerticalPadding: Double = { 81 | val padding = getPadding 82 | padding.top + padding.bottom 83 | } 84 | 85 | private def getHorizontalPadding: Double = { 86 | val padding = getPadding 87 | padding.left + padding.right 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/SpectrumBar.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer4 2 | 3 | import javafx.scene.layout as jfxsl 4 | import scalafx.Includes.* 5 | import scalafx.geometry.Pos 6 | import scalafx.scene.shape.Rectangle 7 | 8 | /** 9 | * Custom component for displaying a spectrum bar. 10 | * 11 | * @author Jarek Sacha 12 | */ 13 | class SpectrumBar(maxValue: Int, barCount: Int) extends jfxsl.VBox { 14 | private val SPACING = 1.0 15 | private val ASPECT_RATIO = 3.0 16 | private val MIN_BAR_HEIGHT = 3.0 17 | private var lastWidth = 0.0 18 | private var lastHeight = 0.0 19 | getStyleClass.add("spectrumBar") 20 | 21 | setSpacing(SPACING) 22 | setAlignment(Pos.BottomCenter) 23 | 24 | for (i <- 0 until barCount) { 25 | val c = (i.toDouble / barCount.toDouble * 255.0).toInt 26 | val r = new Rectangle { 27 | visible = false 28 | style = s"-fx-fill: ladder( rgb($c, $c, $c), red 30%, yellow 70%, #56F32B 90%);" 29 | arcWidth = 2 30 | arcHeight = 2 31 | } 32 | 33 | getChildren.add(r) 34 | } 35 | 36 | protected override def computeMinHeight(width: Double) = computeHeight(MIN_BAR_HEIGHT) 37 | 38 | protected override def computeMinWidth(height: Double) = computeWidthForHeight(MIN_BAR_HEIGHT) 39 | 40 | protected override def computePrefHeight(width: Double) = computeHeight(5) 41 | 42 | protected override def computePrefWidth(height: Double) = computeWidthForHeight(5) 43 | 44 | def setValue(value: Double): Unit = { 45 | val barsLit = math.min(barCount, math.round(value / maxValue * barCount).toInt) 46 | val childList = getChildren 47 | for (i <- 0 until childList.size) { 48 | childList.get(i).visible = (i > barCount - barsLit) 49 | } 50 | } 51 | 52 | protected override def layoutChildren(): Unit = { 53 | if (lastWidth != getWidth || lastHeight != getHeight) { 54 | val spacing = SPACING * (barCount - 1) 55 | val barHeight = (getHeight - getVerticalPadding - spacing) / barCount 56 | val barWidth = math.min(barHeight * ASPECT_RATIO, getWidth - getHorizontalPadding) 57 | 58 | for (node <- getChildren) { 59 | val r = node.asInstanceOf[javafx.scene.shape.Rectangle] 60 | r.width = barWidth 61 | r.height = barHeight 62 | } 63 | 64 | lastWidth = getWidth 65 | lastHeight = getHeight 66 | } 67 | 68 | super.layoutChildren() 69 | } 70 | 71 | private def computeWidthForHeight(barHeight: Double) = barHeight * ASPECT_RATIO + getHorizontalPadding 72 | 73 | private def computeHeight(barHeight: Double): Double = { 74 | val vPadding = getVerticalPadding 75 | val barHeights = barHeight * barCount 76 | val spacing = SPACING * (barCount - 1) 77 | barHeights + spacing + vPadding 78 | } 79 | 80 | private def getVerticalPadding: Double = { 81 | val padding = getPadding 82 | padding.top + padding.bottom 83 | } 84 | 85 | private def getHorizontalPadding: Double = { 86 | val padding = getPadding 87 | padding.left + padding.right 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/ResponsiveUIExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import scalafx.Includes.* 4 | import scalafx.application.JFXApp3.PrimaryStage 5 | import scalafx.application.{JFXApp3, Platform} 6 | import scalafx.beans.property.ObjectProperty 7 | import scalafx.geometry.{Insets, Pos} 8 | import scalafx.scene.Scene 9 | import scalafx.scene.control.Button 10 | import scalafx.scene.layout.{BorderPane, HBox} 11 | import scalafx.scene.paint.Color 12 | import scalafx.scene.shape.Rectangle 13 | 14 | /** 15 | * @author Jarek Sacha 16 | */ 17 | object ResponsiveUIExample extends JFXApp3 { 18 | 19 | override def start(): Unit = { 20 | 21 | hookupEvents() 22 | 23 | stage = new PrimaryStage { 24 | title = "Unresponsive UI Example" 25 | scene = View.scene 26 | } 27 | } 28 | 29 | def hookupEvents(): Unit = { 30 | View.changeFillButton.onAction = () => { 31 | val fillPaint = Model.fillPaint() 32 | Model.fillPaint() = if (Color.LightGray.delegate == fillPaint) Color.Gray else Color.LightGray 33 | 34 | val task = new Runnable { 35 | def run(): Unit = { 36 | try { 37 | Thread.sleep(3000) 38 | Platform.runLater { 39 | val rect = View.rectangle 40 | val newArcSize = if (rect.arcHeight() < 20) 30 else 0 41 | rect.arcWidth() = newArcSize 42 | rect.arcHeight() = newArcSize 43 | } 44 | } catch { 45 | case e: InterruptedException => /* Properly handle exception */ 46 | } 47 | } 48 | } 49 | new Thread(task).start() 50 | } 51 | 52 | View.changeStrokeButton.onAction = () => { 53 | val strokePaint = Model.strokePaint() 54 | Model.strokePaint() = if (strokePaint == Color.DarkGray.delegate) Color.Black else Color.DarkGray 55 | } 56 | } 57 | 58 | private object Model { 59 | // `fill` and `stroke` are created using ObjectProperty factory method to ensure proper type parameter 60 | // to ObjectProperty. We use here, implicitly, JavaFX Paint as the type for `ObjectProperty`. 61 | val fillPaint = ObjectProperty(this, "fillPaint", Color.LightGray) 62 | val strokePaint = ObjectProperty(this, "strokePaint", Color.DarkGray) 63 | } 64 | 65 | private object View { 66 | val rectangle = new Rectangle { 67 | width = 200 68 | height = 200 69 | strokeWidth = 10 70 | fill <== Model.fillPaint 71 | stroke <== Model.strokePaint 72 | } 73 | 74 | val changeFillButton = new Button("Change Fill") 75 | val changeStrokeButton = new Button("Chang Stroke") 76 | 77 | val buttonHBox = new HBox { 78 | padding = Insets(10) 79 | spacing = 10 80 | alignment = Pos.Center 81 | children = List( 82 | changeFillButton, 83 | changeStrokeButton 84 | ) 85 | } 86 | 87 | val scene = new Scene { 88 | root = new BorderPane { 89 | padding = Insets(10) 90 | center = rectangle 91 | bottom = buttonHBox 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp10.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.* 10 | import scalafx.scene.layout.StackPane 11 | import scalafx.util.StringConverter 12 | 13 | /** 14 | * We use here the version of the example that is described in the book, it scales axis so bubbles appear as spheres 15 | * rather than ellipses (in the downloadable Java code). 16 | * 17 | * @author Jarek Sacha 18 | */ 19 | object ChartApp10 extends JFXApp3 { 20 | 21 | private val xStep = 10 22 | private val xMin = 2010 * xStep 23 | private val xMax = 2016 * xStep 24 | 25 | override def start(): Unit = { 26 | 27 | val xAxis = new NumberAxis { 28 | autoRanging = false 29 | lowerBound = xMin - xStep 30 | upperBound = xMax + xStep 31 | tickUnit = xStep 32 | // The xAxis ranges from 20110 till 20210, but of course we want to show the years at the axis. This can 33 | // be achieved by calling 34 | tickLabelFormatter = new StringConverter[Number] { 35 | // Here we do not need to convert from string. 36 | def fromString(string: String): Number = throw new UnsupportedOperationException("Not implemented.") 37 | 38 | def toString(t: Number): String = (t.intValue() / xStep).toString 39 | } 40 | } 41 | val yAxis = new NumberAxis() 42 | val bubbleChart = BubbleChart(xAxis, yAxis) 43 | bubbleChart.title = "Speculations" 44 | bubbleChart.data = createChartData() 45 | 46 | stage = new PrimaryStage { 47 | title = "BubbleChart example" 48 | scene = new Scene(400, 250) { 49 | root = new StackPane { 50 | children = bubbleChart 51 | } 52 | } 53 | } 54 | } 55 | 56 | // NOTE: explicit type signature using Number instead Int and Double 57 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 58 | // signature for scalafx.scene.chart.XYChart.data used above. 59 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = { 60 | var javaValue = 17.56 61 | var cValue = 17.06 62 | var cppValue = 8.25 63 | val scale = 10 64 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]() 65 | val java = new XYChart.Series[Number, Number] { 66 | name = "Java" 67 | } 68 | val c = new XYChart.Series[Number, Number] { 69 | name = "C" 70 | } 71 | val cpp = new XYChart.Series[Number, Number] { 72 | name = "C++" 73 | } 74 | for (i <- xMin to xMax by xStep) { 75 | java.data() += XYChart.Data[Number, Number](i, javaValue) 76 | javaValue += +scale * math.random() - scale / 2 77 | 78 | c.data() += XYChart.Data[Number, Number](i, cValue) 79 | cValue += scale * math.random() - scale / 2 80 | 81 | cpp.data() += XYChart.Data[Number, Number](i, cppValue) 82 | cppValue += scale * math.random() - scale / 2 83 | } 84 | answer.addAll(java, c, cpp) 85 | answer 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/AudioPlayer4/AudioPlayer4.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.AudioPlayer4 2 | 3 | import com.sun.javafx.runtime as csjfxr 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.scene.input.TransferMode 8 | import scalafx.scene.layout.{BorderPane, StackPane} 9 | import scalafx.scene.{Node, Scene} 10 | 11 | /** 12 | * @author Jarek Sacha 13 | */ 14 | object AudioPlayer4 extends JFXApp3 { 15 | println("JavaFX version: " + csjfxr.VersionInfo.getRuntimeVersion) 16 | 17 | private var songModel: SongModel = _ 18 | private var playerControlsView: PlayerControlsView = _ 19 | private var metaDataView: MetadataView = _ 20 | private var equalizerView: EqualizerView = _ 21 | private var page1: Node = _ 22 | private var page2: Node = _ 23 | private var rootNode: StackPane = _ 24 | 25 | override def start(): Unit = { 26 | 27 | songModel = new SongModel() { 28 | url = "https://traffic.libsyn.com/dickwall/JavaPosse373.mp3" 29 | } 30 | 31 | page1 = createPageOne() 32 | page2 = createPageTwo() 33 | rootNode = new StackPane { 34 | children = page1 35 | } 36 | 37 | stage = new PrimaryStage { 38 | title = "Audio Player 4" 39 | scene = new Scene(rootNode, 800, 400) { 40 | val stylesheet = getClass.getResource("media.css") 41 | stylesheets += stylesheet.toString 42 | } 43 | initSceneDragAndDrop(scene()) 44 | } 45 | 46 | songModel.mediaPlayer().play() 47 | } 48 | 49 | private def createPageOne(): Node = { 50 | metaDataView = new MetadataView(songModel) 51 | playerControlsView = new PlayerControlsView(songModel) 52 | playerControlsView.onNextPageAction { _ => rootNode.children = page2 } 53 | new BorderPane { 54 | center = metaDataView.viewNode 55 | bottom = playerControlsView.viewNode 56 | } 57 | } 58 | 59 | private def createPageTwo(): Node = { 60 | equalizerView = new EqualizerView(songModel) 61 | equalizerView.onNextPageAction { _ => 62 | println("Got back button click") 63 | rootNode.children = page1 64 | } 65 | equalizerView.viewNode 66 | } 67 | 68 | private def initSceneDragAndDrop(scene: Scene): Unit = { 69 | scene.onDragOver = event => { 70 | val db = event.dragboard 71 | if (db.hasFiles || db.hasUrl) { 72 | // NOTE: We need to pass in `TransferMode` as Java vararg, since we use `javafx.scene.input.DragEvent` 73 | event.acceptTransferModes(TransferMode.Any*) 74 | } 75 | event.consume() 76 | } 77 | 78 | scene.onDragDropped = event => { 79 | val db = event.dragboard 80 | val url = 81 | if (db.hasFiles) { 82 | db.getFiles.get(0).toURI.toString 83 | } else if (db.hasUrl) { 84 | db.getUrl 85 | } else { 86 | null 87 | } 88 | 89 | if (url != null) { 90 | songModel.url = url 91 | songModel.mediaPlayer().play() 92 | } 93 | event.dropCompleted = (url != null) 94 | event.consume() 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer4/MediaModel.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer4 2 | 3 | import javafx.scene.{image as jfxsi, media as jfxsm} 4 | import scalafx.Includes.* 5 | import scalafx.beans.property.{ObjectProperty, ReadOnlyObjectWrapper, StringProperty} 6 | import scalafx.collections.ObservableMap.Add 7 | import scalafx.scene.image.Image 8 | import scalafx.scene.media.{Media, MediaPlayer} 9 | 10 | /** 11 | * @author Jarek Sacha 12 | */ 13 | class MediaModel { 14 | private val DefaultImageURL = classOf[MediaModel].getResource("resources/defaultAlbum.png").toString 15 | private val DefaultImageCover = new Image(DefaultImageURL) 16 | 17 | val album = new StringProperty(this, "album") 18 | val artist = new StringProperty(this, "artist") 19 | val title = new StringProperty(this, "title") 20 | val year = new StringProperty(this, "year") 21 | 22 | // NOTE: use of `javafx.scene.image.Image` instead of `scalafx.scene.image.Image`, this is required for binding in 23 | // MetadataView to compile. 24 | val albumCover = new ObjectProperty[jfxsi.Image](this, "albumCover") 25 | 26 | // NOTE We use jfxsm.MediaPlayer as type parameter to ReadOnlyObjectWrapper so we can bind to MediaView.mediaPlayer 27 | // see proscalafx.ch08.VideoPlayer4.VideoView.initView 28 | private val _mediaPlayer = new ReadOnlyObjectWrapper[jfxsm.MediaPlayer](this, "mediaPlayer") 29 | 30 | resetProperties() 31 | 32 | def mediaPlayer = _mediaPlayer.readOnlyProperty 33 | 34 | def url: String = if (mediaPlayer() != null) mediaPlayer().media.source else null 35 | 36 | def url_=(url: String): Unit = { 37 | if (mediaPlayer() != null) mediaPlayer().stop() 38 | 39 | initializeMedia(url) 40 | } 41 | 42 | private def resetProperties(): Unit = { 43 | artist() = "" 44 | album() = "" 45 | title() = "" 46 | year() = "" 47 | albumCover() = DefaultImageCover 48 | } 49 | 50 | private def initializeMedia(url: String): Unit = { 51 | resetProperties() 52 | 53 | try { 54 | val media = new Media(url) { 55 | metadata.onChange((_, change) => { 56 | change match { 57 | case Add(key, added) => handleMetadata(key, added) 58 | case _ => 59 | } 60 | }) 61 | } 62 | 63 | _mediaPlayer() = new MediaPlayer(media) { 64 | self => 65 | // Handle errors during playback 66 | onError = { 67 | val errorMessage = self.media.error().getMessage 68 | println("MediaPlayer Error: " + errorMessage) 69 | } 70 | }.delegate 71 | } catch { 72 | // Handle construction errors 73 | case re: RuntimeException => println("Caught Exception: " + re.getMessage) 74 | } 75 | } 76 | 77 | private def handleMetadata(key: String, value: AnyRef): Unit = { 78 | key match { 79 | case "album" => album() = value.toString 80 | case "artist" => artist() = value.toString 81 | case "title" => title() = value.toString 82 | case "year" => year() = value.toString 83 | case "image" => albumCover() = value.asInstanceOf[javafx.scene.image.Image] 84 | case _ => println("Unhandled metadata key: " + key + ", value: " + value) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/JavaFXSceneInSwingExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import javafx.embed.swing as jfxes 4 | import scalafx.application.Platform 5 | import scalafx.beans.property.ObjectProperty 6 | import scalafx.scene.Scene 7 | import scalafx.scene.layout.VBox 8 | import scalafx.scene.paint.Color 9 | import scalafx.scene.shape.Rectangle 10 | 11 | import java.awt.event.ActionEvent 12 | import java.awt.{BorderLayout, Dimension, FlowLayout} 13 | import javax.swing.{JButton, JFrame, JPanel, WindowConstants} 14 | 15 | /** 16 | * @author Jarek Sacha 17 | */ 18 | object JavaFXSceneInSwingExample extends App { 19 | 20 | private val model = new Model() 21 | new Controller(model, new View(model)).mainLoop() 22 | 23 | private class Model { 24 | // `fill` and `stroke` are created using ObjectProperty factory method to ensure proper type parameter 25 | // to ObjectProperty. We use here, implicitly, JavaFX Paint as the type for `ObjectProperty`. 26 | val fill = ObjectProperty(this, "fillPaint", Color.LightGray) 27 | val stroke = ObjectProperty(this, "strokePaint", Color.DarkGray) 28 | } 29 | 30 | private class View(model: Model) { 31 | val frame = new JFrame("ScalaFX in Swing Example") 32 | // NOTE: ScalaFX does not currently implement JFXPanel, so we use JavaFX/Swing directly. 33 | val canvas = new jfxes.JFXPanel() { 34 | setPreferredSize(new Dimension(210, 210)) 35 | } 36 | // Execute JavaFX code on JavaFX Application Thread. 37 | Platform.runLater { 38 | val rectangle = new Rectangle { 39 | width = 200 40 | height = 200 41 | strokeWidth = 10 42 | fill <== model.fill 43 | stroke <== model.stroke 44 | } 45 | 46 | canvas.setScene(new Scene { 47 | root = new VBox { 48 | children = List(rectangle) 49 | } 50 | }.delegate) 51 | } 52 | 53 | val canvasPanelLayout = new FlowLayout(FlowLayout.CENTER, 10, 10) 54 | val canvasPanel = new JPanel(canvasPanelLayout) 55 | canvasPanel.add(canvas) 56 | val changeFillButton = new JButton("Change Fill") 57 | val changeStrokeButton = new JButton("Change Stroke") 58 | val buttonPanelLayout = new FlowLayout(FlowLayout.CENTER, 10, 10) 59 | val buttonPanel = new JPanel(buttonPanelLayout) 60 | buttonPanel.add(changeFillButton) 61 | buttonPanel.add(changeStrokeButton) 62 | frame.add(canvasPanel, BorderLayout.CENTER) 63 | frame.add(buttonPanel, BorderLayout.SOUTH) 64 | frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) 65 | frame.setLocationByPlatform(true) 66 | frame.pack() 67 | } 68 | 69 | private class Controller(model: Model, view: View) { 70 | view.changeFillButton.addActionListener((_: ActionEvent) => { 71 | Platform.runLater { 72 | model.fill() = if (model.fill() == Color.LightGray) Color.Gray else Color.LightGray 73 | } 74 | }) 75 | view.changeStrokeButton.addActionListener((_: ActionEvent) => { 76 | Platform.runLater { 77 | model.stroke() = if (model.stroke() == Color.DarkGray) Color.Black else Color.DarkGray 78 | } 79 | }) 80 | 81 | def mainLoop(): Unit = { 82 | view.frame.setVisible(true) 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/proscalafx/ch06/FXCollectionsExample.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch06 2 | 3 | import javafx.collections as jfxc 4 | import scalafx.collections.ObservableBuffer 5 | import scalafx.collections.ObservableBuffer.* 6 | 7 | import java.util as ju 8 | import scala.jdk.CollectionConverters.* 9 | 10 | /** 11 | * ScalaFX version of `FXCollectionsExample` from "Pro JavaFX 2" book. 12 | * 13 | * ScalaFX does not have a direct equivalent of JavaFX `FXCollections`. 14 | * Some of static methods from `FXCollections` are members of relevant observable collection in ScalaFX, 15 | * for instance `replaceAll`. Some are in companion objects, for instance, `shuffle`. 16 | * Some are not implemented and require use of `FXCollections`. 17 | * 18 | * @author Jarek Sacha 19 | */ 20 | object FXCollectionsExample extends App { 21 | 22 | // `println` statements show JavaFX API, for easier comparison to ScalaFX API used in the code. 23 | 24 | val strings = new ObservableBuffer[String]() 25 | strings.onChange((source, changes) => { 26 | println("\tlist = " + source.mkString("[", ", ", "]")) 27 | changes.foreach(prettyPrint) 28 | println() 29 | }) 30 | 31 | println("""Calling addAll("Zero", "One", "Two", "Three"): """) 32 | strings.addAll("Zero", "One", "Two", "Three") 33 | 34 | println("Calling copy: ") 35 | jfxc.FXCollections.copy(strings, List("Four", "Five").asJava) 36 | 37 | println("Calling replaceAll: ") 38 | strings.replaceAll("Two", "Two_1") 39 | 40 | println("Calling reverse: ") 41 | ObservableBuffer.revertBuffer(strings) 42 | 43 | println("Calling rotate(strings, 2: ") 44 | jfxc.FXCollections.rotate(strings.delegate, 2) 45 | 46 | println("Calling shuffle(strings): ") 47 | ObservableBuffer.shuffle(strings) 48 | 49 | println("Calling shuffle(strings, new Random(0L)): ") 50 | ObservableBuffer.shuffle(strings, new ju.Random(0L)) 51 | 52 | println("Calling sort(strings): ") 53 | strings.sort() 54 | 55 | println("Calling sort(strings, c) with custom comparator: ") 56 | strings.sort((lhs, rhs) => lhs > rhs) 57 | 58 | println("""Calling fill(strings, "Ten"): """) 59 | ObservableBuffer.fillAll(strings, "Ten") 60 | 61 | def prettyPrint(change: Change[String]): Unit = { 62 | change match { 63 | case Add(_, added) => 64 | println("\t\tKind of change: added") 65 | println("\t\tAdded size : " + added.size) 66 | println("\t\tAdded sublist : " + mkString(added)) 67 | case Remove(_, removed) => 68 | println("\t\tKind of change: removed") 69 | println("\t\tRemoved size : " + removed.size) 70 | println("\t\tRemoved : " + mkString(removed)) 71 | case Reorder(start, end, permutation) => 72 | val permutations = for (i <- start until end) yield s"$i -> ${permutation(i)}" 73 | println("\t\tKind of change: permuted") 74 | println("\t\tPermutation : " + mkString(permutations)) 75 | case Update(from, to) => 76 | println("\t\tKind of change: updated\n") 77 | println("\t\t\tfrom: " + from + "\n") 78 | println("\t\t\tto : " + to + "\n") 79 | 80 | } 81 | } 82 | 83 | def mkString[T](seq: IterableOnce[T]): String = 84 | if (seq.iterator.isEmpty) "[]" else seq.iterator.mkString("[", ", ", "]") 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/proscalafx/ch08/VideoPlayer3/VideoPlayer3.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch08.VideoPlayer3 2 | 3 | import scalafx.Includes.* 4 | import scalafx.animation.{ParallelTransition, TranslateTransition} 5 | import scalafx.application.JFXApp3.PrimaryStage 6 | import scalafx.application.{JFXApp3, Platform} 7 | import scalafx.geometry.{Pos, Rectangle2D} 8 | import scalafx.scene.control.Label 9 | import scalafx.scene.layout.StackPane 10 | import scalafx.scene.media.{Media, MediaPlayer, MediaView} 11 | import scalafx.scene.{Node, Scene} 12 | import scalafx.util.Duration 13 | 14 | import java.io.File 15 | import java.net.URL 16 | import scala.language.postfixOps 17 | 18 | /** 19 | * @author Jarek Sacha 20 | */ 21 | object VideoPlayer3 extends JFXApp3 { 22 | 23 | override def start(): Unit = { 24 | 25 | val message = new Label { 26 | text = "I \u2764 Robots" 27 | visible = false 28 | } 29 | 30 | val file = new File("media/omgrobots.mp4") 31 | val media = new Media(file.toURI.toString) { 32 | markers ++= Map( 33 | "Split" -> (3000 ms), 34 | "Join" -> (9000 ms) 35 | ) 36 | } 37 | 38 | val mediaPlayer = new MediaPlayer(media) 39 | 40 | val mediaView1 = new MediaView(mediaPlayer) { 41 | viewport = new Rectangle2D(0, 0, 960 / 2, 540) 42 | alignmentInParent = Pos.CenterLeft 43 | } 44 | 45 | val mediaView2 = new MediaView(mediaPlayer) { 46 | viewport = new Rectangle2D(960 / 2, 0, 960 / 2, 540) 47 | alignmentInParent = Pos.CenterRight 48 | } 49 | 50 | val root = new StackPane { 51 | children ++= Seq(message, mediaView1, mediaView2) 52 | onMouseClicked = () => { 53 | mediaPlayer.seek(Duration.Zero) 54 | message.visible = false 55 | } 56 | } 57 | 58 | stage = new PrimaryStage { 59 | title = "Video Player 3" 60 | scene = new Scene(root, 960, 540) { 61 | val stylesheet: URL = getClass.getResource("media.css") 62 | stylesheets += stylesheet.toString 63 | } 64 | } 65 | 66 | mediaPlayer.onMarker = event => 67 | Platform.runLater { 68 | event.marker.getKey match { 69 | case "Split" => 70 | message.visible = true 71 | buildSplitTransition(mediaView1, mediaView2).play() 72 | case _ => buildJoinTransition(mediaView1, mediaView2).play() 73 | } 74 | } 75 | 76 | mediaPlayer.play() 77 | } 78 | 79 | private def buildJoinTransition(one: Node, two: Node) = new ParallelTransition { 80 | children = List( 81 | new TranslateTransition { 82 | duration = (1000 ms) 83 | node = one 84 | byX = 200 85 | }, 86 | new TranslateTransition { 87 | duration = (1000 ms) 88 | node = two 89 | byX = -200 90 | } 91 | ) 92 | } 93 | 94 | private def buildSplitTransition(one: Node, two: Node) = new ParallelTransition { 95 | children = List( 96 | new TranslateTransition { 97 | duration = (1000 ms) 98 | node = one 99 | byX = -200 100 | }, 101 | new TranslateTransition { 102 | duration = (1000 ms) 103 | node = two 104 | byX = 200 105 | } 106 | ) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/proscalafx/ch07/ChartApp11.scala: -------------------------------------------------------------------------------- 1 | package proscalafx.ch07 2 | 3 | import javafx.scene.chart as jfxsc 4 | import scalafx.Includes.* 5 | import scalafx.application.JFXApp3 6 | import scalafx.application.JFXApp3.PrimaryStage 7 | import scalafx.collections.ObservableBuffer 8 | import scalafx.scene.Scene 9 | import scalafx.scene.chart.* 10 | import scalafx.scene.layout.StackPane 11 | import scalafx.util.StringConverter 12 | 13 | /** 14 | * We use here the version of the example that is described in the book, it scales axis so bubbles appear as spheres 15 | * rather than ellipses (in the downloadable Java code). 16 | * 17 | * @author Jarek Sacha 18 | */ 19 | object ChartApp11 extends JFXApp3 { 20 | 21 | val xStep = 10 22 | val xMin = 2010 * xStep 23 | val xMax = 2016 * xStep 24 | 25 | override def start(): Unit = { 26 | 27 | val xAxis = new NumberAxis() { 28 | autoRanging = false 29 | lowerBound = xMin - xStep 30 | upperBound = xMax + xStep 31 | tickUnit = xStep 32 | // The xAxis ranges from 20110 till 20210, but of course we want to show the years at the axis. This can 33 | // be achieved by calling 34 | tickLabelFormatter = new StringConverter[Number] { 35 | // Here we do not need to convert from string. 36 | def fromString(string: String): Number = throw new UnsupportedOperationException("Not implemented.") 37 | 38 | def toString(t: Number): String = (t.intValue() / xStep).toString 39 | } 40 | } 41 | val yAxis = new NumberAxis() 42 | val bubbleChart = BubbleChart(xAxis, yAxis) 43 | bubbleChart.title = "Speculations" 44 | bubbleChart.data = createChartData() 45 | 46 | stage = new PrimaryStage { 47 | title = "BubbleChart example" 48 | scene = new Scene(400, 250) { 49 | root = new StackPane { 50 | children = bubbleChart 51 | } 52 | } 53 | } 54 | } 55 | 56 | // NOTE: explicit type signature using Number instead Int and Double 57 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as 58 | // signature for scalafx.scene.chart.XYChart.data used above. 59 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = { 60 | var javaValue = 17.56 61 | var cValue = 17.06 62 | var cppValue = 8.25 63 | val scale = 10 64 | 65 | def dif: Double = scale * math.random() / 4 66 | 67 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]() 68 | val java = new XYChart.Series[Number, Number] { 69 | name = "Java" 70 | } 71 | val c = new XYChart.Series[Number, Number] { 72 | name = "C" 73 | } 74 | val cpp = new XYChart.Series[Number, Number] { 75 | name = "C++" 76 | } 77 | for (i <- xMin to xMax by xStep) { 78 | // NOTE: XYChart.Data declares its third parameter as a `AnyRef`. A Double returned by `math.rand` 79 | // is not a subclass of `AnyRef`, so we need to explicitly convert it to a `java.lang.Number` to make it work. 80 | java.data() += XYChart.Data[Number, Number](i, javaValue, dif.asInstanceOf[Number]) 81 | javaValue += +scale * math.random() - scale / 2 82 | 83 | c.data() += XYChart.Data[Number, Number](i, cValue, dif.asInstanceOf[Number]) 84 | cValue += scale * math.random() - scale / 2 85 | 86 | cpp.data() += XYChart.Data[Number, Number](i, cppValue, dif.asInstanceOf[Number]) 87 | cppValue += scale * math.random() - scale / 2 88 | } 89 | answer.addAll(java, c, cpp) 90 | answer 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/proscalafx/ch10/fxml/AdoptionForm.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |