Skip to content

Commit c9aa8c4

Browse files
committed
[Scala 3] auto generate input dialogs from simple case classes #18
1 parent 161ec85 commit c9aa8c4

File tree

6 files changed

+387
-18
lines changed

6 files changed

+387
-18
lines changed

ReadMe.md

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,30 @@ Extras do not have direct corresponding concepts in JavaFX.
1010

1111
**Contents**
1212

13-
0. [Project Structure](#project-structure)
14-
0. [SBT](#sbt)
15-
0. [Features](#features)
16-
1. [Helper Methods](#helper-methods)
17-
1. [Simpler Display of Standard Dialogs](#simpler-display-of-standard-dialogs)
18-
1. [Easy Custom Dialogs](#easy-custom-dialogs)
19-
1. [BusyWorker](#busyworker)
20-
1. [Simpler Use of FXML with MVCfx Pattern](#simpler-use-of-fxml-with-mvcfx-pattern)
21-
1. [Image Display Component](#imagedisplay-component)
22-
0. [Demos](#demos)
23-
1. [StopWatch Application](#stopwatch-application)
24-
1. [ShowMessage Demo](#showmessage-demo)
25-
1. [BusyWorker Demo](#busyworker-demo)
26-
1. [ImageDisplay Demo](#imagedisplay-demo)
27-
0. [Status](#status)
28-
0. [Discussion and Support](#discussion-and-support)
29-
0. [License](#license)
13+
<!-- TOC -->
14+
15+
* [Project Structure](#project-structure)
16+
* [SBT](#sbt)
17+
* [Features](#features)
18+
* [Helper Methods](#helper-methods)
19+
* [Simpler Display of Standard Dialogs](#simpler-display-of-standard-dialogs)
20+
* [Easy Custom Dialogs](#easy-custom-dialogs)
21+
* [Edit a Case Class object with AutoDialog](#edit-a-case-class-object-with-autodialog)
22+
* [BusyWorker](#busyworker)
23+
* [Example 1](#example-1)
24+
* [Example 2](#example-2)
25+
* [Simpler Use of FXML with MVCfx Pattern](#simpler-use-of-fxml-with-mvcfx-pattern)
26+
* [ImageDisplay Component](#imagedisplay-component)
27+
* [Demos](#demos)
28+
* [StopWatch Application](#stopwatch-application)
29+
* [ShowMessage Demo](#showmessage-demo)
30+
* [BusyWorker Demo](#busyworker-demo)
31+
* [ImageDisplay Demo](#imagedisplay-demo)
32+
* [Status](#status)
33+
* [Discussion and Support](#discussion-and-support)
34+
* [License](#license)
35+
36+
<!-- TOC -->
3037

3138
Project Structure
3239
-----------------
@@ -173,6 +180,34 @@ if (dialog.wasOKed) {
173180

174181
A more elaborate example is in the `GenericDialogFXDemo`.
175182

183+
### Edit a Case Class object with AutoDialog
184+
185+
`AutoDialog` can be used too quickly open auto generated dialog from case class. After closing, the dialog will return
186+
edited version of the input case class.
187+
188+
Here is an example of usage:
189+
190+
```scala
191+
import org.scalafx.extras.auto_dialog.AutoDialog
192+
193+
case class FilterOptions(kernelSize: Int = 7,
194+
start: Double = 3.14,
195+
tag: String = "alpha",
196+
debugMode: Boolean = false)
197+
198+
val filterOptions = FilterOptions()
199+
200+
val result: Option[FilterOptions] =
201+
new AutoDialog(filterOptions)
202+
.showDialog(
203+
"AutoDialog Demo",
204+
"Fields are auto generated from `FilterOptions` object")
205+
206+
println(s"Result: $result")
207+
```
208+
209+
![GenericDialogFX Demo](notes/assets/AutoDialog.png)
210+
176211
### BusyWorker
177212

178213
BusyWorker helps running a UI task on separate threads (other than the JavaFX Application thread). It will show busy
@@ -193,7 +228,7 @@ new BusyWorker("Simple Task", parentWindow).doTask { () =>
193228
}
194229
```
195230

196-
#### Examnple 2
231+
#### Example 2
197232

198233
Here is a little more elaborated example. It updates a progress message and progress indicator.
199234

notes/assets/AutoDialog.png

13.7 KB
Loading
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2011-2022, ScalaFX Project
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+
* * Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* * Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
* * Neither the name of the ScalaFX Project nor the
13+
* names of its contributors may be used to endorse or promote products
14+
* derived from this software without specific prior written permission.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
* DISCLAIMED. IN NO EVENT SHALL THE SCALAFX PROJECT OR ITS CONTRIBUTORS BE LIABLE
20+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23+
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
28+
package org.scalafx.extras.auto_dialog
29+
30+
import org.scalafx.extras.initFX
31+
32+
object AutoDialogDemo:
33+
34+
case class FilterOptions(kernelSize: Int = 7,
35+
start: Double = 3.14,
36+
tag: String = "alpha",
37+
debugMode: Boolean = false)
38+
39+
def main(args: Array[String]): Unit =
40+
41+
initFX()
42+
43+
val filterOptions = FilterOptions()
44+
45+
val result: Option[FilterOptions] =
46+
new AutoDialog(filterOptions)
47+
.showDialog(
48+
"AutoDialog Demo",
49+
"Fields are auto generated from `FilterOptions` object")
50+
51+
println(s"Result: $result")
52+
53+
System.exit(0)
54+
55+
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2011-2022, ScalaFX Project
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+
* * Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* * Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
* * Neither the name of the ScalaFX Project nor the
13+
* names of its contributors may be used to endorse or promote products
14+
* derived from this software without specific prior written permission.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
* DISCLAIMED. IN NO EVENT SHALL THE SCALAFX PROJECT OR ITS CONTRIBUTORS BE LIABLE
20+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23+
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
28+
package org.scalafx.extras.auto_dialog
29+
30+
import org.scalafx.extras.auto_dialog.{DialogDecoder, DialogEncoder}
31+
import org.scalafx.extras.generic_dialog.GenericDialogFX
32+
import scalafx.stage.Window
33+
34+
35+
/** Automatically created a dialog from a case class.
36+
* Names of the members of the input `data` will be used as labels for fields corresponding to their values.
37+
* Initially case with members of `Boolean`, `Int`, `Double`, and `String` are supported.
38+
*
39+
* Use methods [[#showDialog]] to display the dialog and retrieve edited content.
40+
*
41+
* {{{
42+
* case class FilterOptions(kernelSize: Int = 7,
43+
* start: Double = 3.14,
44+
* tag: String = "alpha",
45+
* debugMode: Boolean = false)
46+
*
47+
* val result: Option[FilterOptions] =
48+
* new AutoDialog(FilterOptions())
49+
* .showDialog(
50+
* "Filter Options",
51+
* "Fields below were auto generated from `FilterOptions` object")
52+
*
53+
* println(s"Result: result")
54+
* }}}
55+
*
56+
* @param data data that will define the dialog and its initial values
57+
*/
58+
class AutoDialog[A >: Null : DialogEncoder : DialogDecoder](data: A):
59+
60+
/**
61+
* Display the dialog and block till the dialog is closed.
62+
*
63+
* @param title dialog title
64+
* @param header dialog header
65+
* @param parentWindow optional parent window that will be blocked when dialog is displayed
66+
* @return when dialog was confirmed with OK it will return content of the fields of the dialog.
67+
* If dialog was cancelled it will return `None`
68+
*/
69+
def showDialog(title: String,
70+
header: String = "",
71+
parentWindow: Option[Window] = None): Option[A] =
72+
val gd = new GenericDialogFX(title, header = header, parentWindow = parentWindow)
73+
74+
// Add fields to the dialog
75+
summon[DialogEncoder[A]].addEditor(gd, "", data)
76+
77+
gd.showDialog()
78+
79+
if gd.wasOKed then
80+
val r = summon[DialogDecoder[A]].decode(gd)
81+
Option(r)
82+
else
83+
None
84+
85+
end AutoDialog
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2011-2022, ScalaFX Project
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+
* * Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* * Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
* * Neither the name of the ScalaFX Project nor the
13+
* names of its contributors may be used to endorse or promote products
14+
* derived from this software without specific prior written permission.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
* DISCLAIMED. IN NO EVENT SHALL THE SCALAFX PROJECT OR ITS CONTRIBUTORS BE LIABLE
20+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23+
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
28+
package org.scalafx.extras.auto_dialog
29+
30+
import org.scalafx.extras.generic_dialog.GenericDialogFX
31+
32+
import scala.compiletime.{constValueTuple, summonAll}
33+
import scala.deriving.Mirror
34+
35+
/** Decode current fields from a `GenericDialog` corresponding to type `T` */
36+
trait DialogDecoder[T]:
37+
def decode(dialog: GenericDialogFX): T
38+
39+
object DialogDecoder:
40+
41+
/**
42+
* Decode numeric field as integer
43+
*/
44+
given DialogDecoder[Boolean] with
45+
override def decode(dialog: GenericDialogFX): Boolean =
46+
dialog.nextBoolean()
47+
48+
/**
49+
* Decode numeric field as integer
50+
*/
51+
given DialogDecoder[Int] with
52+
override def decode(dialog: GenericDialogFX): Int =
53+
math.round(dialog.nextNumber()).toInt
54+
55+
/**
56+
* Decode numeric field as double
57+
*/
58+
given DialogDecoder[Double] with
59+
override def decode(dialog: GenericDialogFX): Double =
60+
dialog.nextNumber()
61+
62+
given DialogDecoder[String] with
63+
override def decode(dialog: GenericDialogFX): String =
64+
dialog.nextString()
65+
66+
/**
67+
* Decode set of fields corresponding to member of a case class
68+
*/
69+
inline given[A <: Product](using m: Mirror.ProductOf[A]): DialogDecoder[A] =
70+
new DialogDecoder[A] :
71+
type ElemDecoder = Tuple.Map[m.MirroredElemTypes, DialogDecoder]
72+
private val elemDecoders: List[DialogDecoder[Any]] =
73+
summonAll[ElemDecoder].toList.asInstanceOf[List[DialogDecoder[Any]]]
74+
75+
def decode(dialog: GenericDialogFX): A =
76+
val decoded = elemDecoders.map(_.decode(dialog))
77+
val tuple = decoded.foldRight[Tuple](EmptyTuple)(_ *: _)
78+
m.fromProduct(tuple)

0 commit comments

Comments
 (0)