├── Scopes.png
├── readme.png
├── src
└── main
│ ├── resources
│ ├── img
│ │ ├── Tile1.png
│ │ ├── Tile2.png
│ │ ├── Tile3.png
│ │ ├── Tile4.png
│ │ ├── Tile5.png
│ │ └── Tile6.png
│ ├── home_icons
│ │ ├── data-lock.png
│ │ ├── data-usage.png
│ │ ├── cloud-network.png
│ │ ├── data-analysis.png
│ │ ├── data-servers.png
│ │ ├── customer-service.png
│ │ ├── data-connection.png
│ │ └── 24-technical-support.png
│ └── JSON
│ │ ├── TileBuilder.json
│ │ └── GridInfo.json
│ └── kotlin
│ └── com
│ └── example
│ └── demo
│ ├── app
│ ├── MyApp.kt
│ └── Styles.kt
│ ├── controllers
│ ├── WorkbenchController.kt
│ ├── LoginController.kt
│ ├── TileBuilderController.kt
│ └── TileGUIController.kt
│ ├── views
│ ├── MyTiles.kt
│ ├── Workbench.kt
│ ├── LoginScreen.kt
│ └── TileGUI.kt
│ └── model
│ ├── GridInfo.kt
│ ├── TilePlacement.kt
│ ├── DragTile.kt
│ └── HomepageBuilderModel.kt
├── conf
└── com.example.demo.controllers.LoginController.properties
├── .gitignore
├── .idea
└── libraries
│ └── Maven__eu_hansolo_tilesfx_1_5_2.xml
├── scenicView.properties
├── README.md
└── pom.xml
/Scopes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/Scopes.png
--------------------------------------------------------------------------------
/readme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/readme.png
--------------------------------------------------------------------------------
/src/main/resources/img/Tile1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/img/Tile1.png
--------------------------------------------------------------------------------
/src/main/resources/img/Tile2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/img/Tile2.png
--------------------------------------------------------------------------------
/src/main/resources/img/Tile3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/img/Tile3.png
--------------------------------------------------------------------------------
/src/main/resources/img/Tile4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/img/Tile4.png
--------------------------------------------------------------------------------
/src/main/resources/img/Tile5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/img/Tile5.png
--------------------------------------------------------------------------------
/src/main/resources/img/Tile6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/img/Tile6.png
--------------------------------------------------------------------------------
/conf/com.example.demo.controllers.LoginController.properties:
--------------------------------------------------------------------------------
1 | #
2 | #Wed Apr 25 23:18:25 EDT 2018
3 | password=secret
4 | username=admin
5 |
--------------------------------------------------------------------------------
/src/main/resources/home_icons/data-lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/data-lock.png
--------------------------------------------------------------------------------
/src/main/resources/home_icons/data-usage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/data-usage.png
--------------------------------------------------------------------------------
/src/main/resources/home_icons/cloud-network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/cloud-network.png
--------------------------------------------------------------------------------
/src/main/resources/home_icons/data-analysis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/data-analysis.png
--------------------------------------------------------------------------------
/src/main/resources/home_icons/data-servers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/data-servers.png
--------------------------------------------------------------------------------
/src/main/resources/home_icons/customer-service.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/customer-service.png
--------------------------------------------------------------------------------
/src/main/resources/home_icons/data-connection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/data-connection.png
--------------------------------------------------------------------------------
/src/main/resources/home_icons/24-technical-support.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahinchman1/TornadoFX-DnD-TilesFX/HEAD/src/main/resources/home_icons/24-technical-support.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IntelliJ IDEA
2 | .idea/
3 | out/
4 | *.iml
5 |
6 | # Maven
7 | target/
8 |
9 | # MacOS
10 | .DS_Store
11 |
12 | # Project specific
13 | conf/com.example.demo.controllers.LoginController.properties
14 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__eu_hansolo_tilesfx_1_5_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/scenicView.properties:
--------------------------------------------------------------------------------
1 | #ScenicView properties
2 | #Sun Jan 28 20:40:48 EST 2018
3 | showDefaultProperties=true
4 | autoRefreshStyleSheets=false
5 | showSearchBar=true
6 | showBounds=true
7 | ignoreMouseTransparentNodes=true
8 | splitPaneDividerPosition=0.2966706302021403
9 | collapseControls=true
10 | showFilteredNodesInTree=true
11 | showCSSProperties=false
12 | collapseContainerControls=false
13 | showInvisibleNodes=false
14 | showNodesIdInTree=false
15 | automaticScenegraphStructureRefreshing=true
16 | registerShortcuts=true
17 | stageWidth=841.0
18 | stageHeight=778.0
19 | showBaseline=false
20 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/app/MyApp.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.app
2 |
3 | import com.example.demo.controllers.LoginController
4 | import com.example.demo.views.LoginScreen
5 | import javafx.application.Application
6 | import javafx.stage.Stage
7 | import tornadofx.*
8 |
9 | class GridCreatorApp : App(LoginScreen::class, Styles::class) {
10 | private val loginController: LoginController by inject()
11 |
12 | override fun start(stage: Stage) {
13 | importStylesheet(Styles::class)
14 | super.start(stage)
15 | loginController.init()
16 | }
17 | }
18 |
19 | fun main(args: Array) {
20 | Application.launch(GridCreatorApp::class.java, *args)
21 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TornadoFX-DnD-TilesFX
2 |
3 | Native app displaying TornadoFX's latest features and the ease of integrating existing JavaFX libraries with TornadoFX.
4 | A basic drag-and-drop for a custom object without the use of Clipboard for custom grid creations using TilesFX.
5 |
6 | ## Notable TornadoFX features:
7 | - Datagrid paginators: paginate cells with a set cell count per page
8 | - JSON Support: Lightweight support for reading JSON files and parsing them into files. Define your grids and tiles using JSON files for grid/tile construction
9 |
10 | ```
11 | {
12 | "title": "a_block",
13 | "width": 100.0,
14 | "height": 100.0,
15 | "color": "#00AEEF"
16 | }
17 | ```
18 | ## Models and Scopes:
19 | - TileBuilder: builds a single tile using TilesFX
20 | - TilePlacement and GridInfo: used to create custom grids with TilesFX
21 | - GridScope: used to pass information from a selected datagrid to generate a custom grid
22 |
23 | 
24 |
--------------------------------------------------------------------------------
/src/main/resources/JSON/TileBuilder.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "a_block",
4 | "width": 100.0,
5 | "height": 100.0,
6 | "color": "#00AEEF",
7 | "hoverColor": "#26baf1"
8 | },
9 | {
10 | "title": "b_block",
11 | "width": 100.0,
12 | "height": 100.0,
13 | "color": "#4B226B",
14 | "hoverColor": "#65368B"
15 | },
16 | {
17 | "title": "c_block",
18 | "width": 100.0,
19 | "height": 100.0,
20 | "color": "#CF2F44",
21 | "hoverColor": "#DB4357"
22 | },
23 | {
24 | "title": "d_block",
25 | "width": 100.0,
26 | "height": 100.0,
27 | "color": "#000000",
28 | "hoverColor": "#333333"
29 | },
30 | {
31 | "title": "e_block",
32 | "width": 100.0,
33 | "height": 100.0,
34 | "color": "#535654",
35 | "hoverColor": "#3f4c4a"
36 | },
37 | {
38 | "title": "f_block",
39 | "width": 100.0,
40 | "height": 100.0,
41 | "color": "#009900",
42 | "hoverColor": "#098209"
43 | },
44 | {
45 | "title": "g_block",
46 | "width": 100.0,
47 | "height": 100.0,
48 | "color": "#811251",
49 | "hoverColor": "#AA2277"
50 | },
51 | {
52 | "module": "h_block",
53 | "width": 100.0,
54 | "height": 100.0,
55 | "color": "#78CBDB",
56 | "hoverColor": "#77CCBB"
57 | }
58 | ]
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/controllers/WorkbenchController.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.controllers
2 |
3 | import com.example.demo.model.GridInfoModel
4 | import com.example.demo.model.HomepageGridBuilder
5 | import com.example.demo.views.*
6 | import tornadofx.*
7 |
8 | class WorkbenchController : Controller() {
9 |
10 | /***** Global Variables *****/
11 | private val workbench: Workbench by inject()
12 | private val gridInfo: GridInfoModel by inject()
13 |
14 | private val grids = resources.jsonArray("/JSON/GridInfo.json").toModel()
15 |
16 | /**
17 | * Decide which grid to use and switch views from workbench to demo gui
18 | *
19 | * @property String image
20 | */
21 | fun goToEditor(image: String) {
22 | gridInfo.useTileGrid(grids, parseImage(image))
23 | val scope = Scope()
24 | scope.set(gridInfo)
25 | workbench.replaceWith(find(scope), centerOnScreen = true, sizeToScene = true)
26 | }
27 |
28 | /**
29 | * Determine which grid had been picked
30 | *
31 | * @property String image
32 | */
33 | private fun parseImage(image: String): Int {
34 | val separate = image.split("/",".")
35 | val size = separate[2].length - 1
36 | return separate[2].substring(size).toInt()
37 | }
38 |
39 | /**
40 | * Determine which grid had been picked
41 | *
42 | * @property UIComponent className
43 | */
44 | fun returnToWorkbench(className: UIComponent) {
45 | (className).replaceWith(workbench, centerOnScreen = true, sizeToScene = true)
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/views/MyTiles.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.views
2 |
3 | import com.example.demo.app.Styles
4 | import com.example.demo.model.GridInfoModel
5 | import com.example.demo.model.GridScope
6 | import javafx.geometry.HPos
7 | import javafx.geometry.Pos
8 | import javafx.geometry.VPos
9 | import javafx.scene.layout.ColumnConstraints
10 | import javafx.scene.layout.Priority
11 | import javafx.scene.layout.RowConstraints
12 | import tornadofx.*
13 |
14 | class MyTiles : View() {
15 | /***** Global Variables *****/
16 | private val model: GridInfoModel by inject()
17 |
18 | /***** View *****/
19 | override val root = gridpane {
20 | addClass(Styles.grid)
21 | alignment = Pos.CENTER
22 | hgap = 5.0
23 | vgap = 5.0
24 |
25 | columnConstraints.clear()
26 | rowConstraints.clear()
27 |
28 | for (i in 0 until model.item.columns) {
29 | val c = ColumnConstraints().apply {
30 | halignment = HPos.CENTER
31 | hgrow = Priority.NEVER
32 | minWidth = 100.0
33 | maxWidth = 100.0
34 | }
35 | columnConstraints.add(c)
36 | }
37 |
38 | for (i in 0 until model.item.rows) {
39 | val r = RowConstraints().apply {
40 | valignment = VPos.CENTER
41 | vgrow = Priority.NEVER
42 | minHeight = 100.0
43 | maxHeight = 100.0
44 | }
45 | rowConstraints.add(r)
46 | }
47 | }
48 |
49 | init {
50 | model.item.moduleTiles.forEach {
51 | root.add(it.tile, it.colIndex, it.rowIndex, it.colSpan, it.rowSpan)
52 | }
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/app/Styles.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.app
2 |
3 | import javafx.scene.effect.BlurType
4 | import javafx.scene.effect.DropShadow
5 | import javafx.scene.effect.InnerShadow
6 | import javafx.scene.paint.Color
7 | import tornadofx.*
8 |
9 | class Styles : Stylesheet() {
10 | // holds class-level properties that can easily be retrieved
11 | companion object {
12 | val loginScreen by cssclass()
13 | val workbenchScreen by cssclass()
14 | val tileGUI by cssclass()
15 | val grid by cssclass()
16 | val module by cssclass()
17 | val highlightTile by cssclass()
18 | val workAreaSelected by cssclass()
19 | val inflight by cssclass()
20 | val transparentOverlay by cssclass()
21 | val selectedTile by cssclass()
22 | }
23 |
24 | // apply styling to classes
25 | init {
26 | reloadStylesheetsOnFocus()
27 |
28 | select(loginScreen) {
29 | padding = box(15.px)
30 | vgap = 7.px
31 | hgap = 10.px
32 | }
33 |
34 | workbenchScreen {
35 | backgroundColor += Color.rgb(34, 34, 34)
36 | }
37 |
38 | tileGUI {
39 | backgroundColor += Color.rgb(34, 34, 34)
40 | }
41 |
42 | grid {
43 | backgroundColor += Color.WHITE
44 | }
45 |
46 | module {
47 | borderColor += box(Color.BLACK)
48 | }
49 |
50 | tileGUI {
51 | form {
52 | label {
53 | textFill = Color.WHITE
54 | }
55 | }
56 | }
57 |
58 | highlightTile {
59 | and (hover) {
60 | opacity = 0.4
61 | }
62 | }
63 |
64 | inflight {
65 | opacity = 1.0
66 | effect = DropShadow()
67 | }
68 |
69 | workAreaSelected {
70 | opacity = 0.9
71 | }
72 |
73 | transparentOverlay {
74 | backgroundColor += c(0, 100, 100, 0.05)
75 | }
76 |
77 | selectedTile {
78 | cell {
79 | effect = InnerShadow(BlurType.GAUSSIAN, Color.GREENYELLOW, 7.0, 1.0, 1.0, 1.0)
80 | }
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/controllers/LoginController.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.controllers
2 |
3 | import com.example.demo.views.LoginScreen
4 | import com.example.demo.views.Workbench
5 | import javafx.application.Platform
6 | import tornadofx.Controller
7 | import tornadofx.FX
8 |
9 | class LoginController : Controller() {
10 | private val loginScreen: LoginScreen by inject()
11 | private val workbench: Workbench by inject()
12 |
13 | fun init() {
14 | with (config) {
15 | if (containsKey(USERNAME) && containsKey(PASSWORD))
16 | tryLogin(string(USERNAME), string(PASSWORD), true)
17 | else
18 | showLoginScreen("Please log in")
19 | }
20 | }
21 |
22 | private fun showLoginScreen(message: String, shake: Boolean = false) {
23 | if (FX.primaryStage.scene.root != loginScreen.root) {
24 | FX.primaryStage.scene.root = loginScreen.root
25 | FX.primaryStage.sizeToScene()
26 | FX.primaryStage.centerOnScreen()
27 | }
28 |
29 | loginScreen.title = message
30 |
31 | Platform.runLater {
32 | if (shake) loginScreen.shakeStage()
33 | }
34 | }
35 |
36 | fun tryLogin(username: String, password: String, remember: Boolean) {
37 | runAsync {
38 | username == "admin" && password == "secret"
39 | } ui { successfulLogin ->
40 | if (successfulLogin) {
41 | loginScreen.clear()
42 |
43 | if (remember) {
44 | with (config) {
45 | set(USERNAME to username)
46 | set(PASSWORD to password)
47 | save()
48 | }
49 | }
50 |
51 | loginScreen.replaceWith(workbench, null, sizeToScene = true, centerOnScreen = true)
52 | } else {
53 | showLoginScreen("Incorrect credentials. Try again.", true)
54 | }
55 | }
56 | }
57 |
58 | fun logout() {
59 | with (config) {
60 | remove(USERNAME)
61 | remove(PASSWORD)
62 | save()
63 | }
64 | showLoginScreen("Log in as another user")
65 | }
66 |
67 | companion object {
68 | val USERNAME = "username"
69 | val PASSWORD = "password"
70 | }
71 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/model/GridInfo.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.model
2 |
3 | import eu.hansolo.tilesfx.Tile
4 | import eu.hansolo.tilesfx.TileBuilder
5 | import javafx.beans.property.Property
6 | import tornadofx.*
7 |
8 | /***** Data classes and models intended for grid rendering *****/
9 |
10 | class GridInfo(info: Pair, List>) {
11 |
12 | private var coordinates by property(info.first)
13 | fun coordinatesProperty() = getProperty(GridInfo::coordinates)
14 |
15 | var rows: Int by property(info.first.first)
16 | var columns: Int by property(info.first.second)
17 | var moduleTiles by property(info.second)
18 | }
19 |
20 | class GridInfoModel : ItemViewModel() {
21 | private val coordinates = bind { item?.coordinatesProperty() }
22 |
23 | override fun onCommit(commits: List) {
24 | super.onCommit(commits)
25 |
26 | // The println will only be called if findChanged is not null
27 | commits.findChanged(coordinates)?.let { println("Grid Info changed from ${it.first} to ${it.second}")}
28 | }
29 |
30 | private fun List.findChanged(ref: Property): Pair? {
31 | val commit = find { it.property == ref && it.changed}
32 | return commit?.let { (it.newValue as T) to (it.oldValue as T) }
33 | }
34 |
35 | fun useTileGrid(grids: List, grid: Int) {
36 | grids.asSequence().forEach {
37 | if (grid == it.grid) {
38 | val gridTiles = it.tiles.map(::createTilePlacement)
39 | item = GridInfo(Pair(Pair(it.rows, it.columns), gridTiles))
40 | }
41 | }
42 | }
43 |
44 | private fun createTilePlacement(hpb: HomepageTileBuilder): TilePlacement {
45 | return TilePlacement(gridTileBuilder(hpb.title, hpb.width, hpb.height),
46 | hpb.colIndex, hpb.rowIndex, hpb.colSpan, hpb.rowSpan)
47 | }
48 |
49 | /**
50 | * Grid Tile Builder, simplified tile
51 | *
52 | * @property String title
53 | * @property Double width
54 | */
55 | private fun gridTileBuilder(title: String, width: Double = 100.0, height: Double = 100.0) : Tile
56 | = TileBuilder.create()
57 | .skinType(Tile.SkinType.TEXT)
58 | .title(title)
59 | .maxSize(width, height)
60 | .roundedCorners(false)
61 | .build()
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/model/TilePlacement.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.model
2 |
3 | import eu.hansolo.tilesfx.Tile
4 | import javafx.beans.property.Property
5 | import tornadofx.*
6 |
7 |
8 | /***** Data classes and models intended for tile and grid location and properties
9 | * NOTE: revisit models for refactoring *****/
10 | class TilePlacement(tile: Tile, colIndex: Int, rowIndex: Int, colSpan: Int, rowSpan: Int) {
11 | var tile: Tile by property(tile)
12 | fun tileProperty() = getProperty(TilePlacement::tile)
13 |
14 | var colIndex: Int by property(colIndex)
15 | fun colIndexProperty() = getProperty(TilePlacement::colIndex)
16 |
17 | var rowIndex: Int by property(rowIndex)
18 | fun rowIndexProperty() = getProperty(TilePlacement::rowIndex)
19 |
20 | var colSpan: Int by property(colSpan)
21 | fun colSpanProperty() = getProperty(TilePlacement::colSpan)
22 |
23 | var rowSpan: Int by property(rowSpan)
24 | fun rowSpanProperty() = getProperty(TilePlacement::rowSpan)
25 | }
26 |
27 | class TilePlacementModel : ItemViewModel() {
28 | private val tile = bind { item?.tileProperty() }
29 | private val colIndex = bind { item?.colIndexProperty() }
30 | private val rowIndex = bind { item?.rowIndexProperty() }
31 | private val colSpan = bind { item?.colSpanProperty() }
32 | private val rowSpan = bind { item?.rowSpanProperty() }
33 |
34 | override fun onCommit(commits: List) {
35 | super.onCommit(commits)
36 |
37 | // The println will only be called if findChanged is not null
38 | commits.findChanged(tile)?.let { println("Module Tile changed from ${it.first} to ${it.second}")}
39 | commits.findChanged(colIndex)?.let { println("Column Index changed from ${it.first} to ${it.second}")}
40 | commits.findChanged(rowIndex)?.let { println("Row Index changed from ${it.first} to ${it.second}")}
41 | commits.findChanged(colSpan)?.let { println("Column Span changed from ${it.first} to ${it.second}")}
42 | commits.findChanged(rowSpan)?.let { println("Row Span changed from ${it.first} to ${it.second}")}
43 | }
44 |
45 | private fun List.findChanged(ref: Property): Pair? {
46 | val commit = find { it.property == ref && it.changed}
47 | return commit?.let { (it.newValue as T) to (it.oldValue as T) }
48 | }
49 | }
50 |
51 | class GridScope: Scope() {
52 | val model = GridInfoModel()
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/views/Workbench.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.views
2 |
3 | import com.example.demo.app.Styles.Companion.workbenchScreen
4 | import com.example.demo.controllers.LoginController
5 | import com.example.demo.controllers.WorkbenchController
6 | import javafx.application.Platform
7 | import javafx.scene.text.Font
8 | import tornadofx.*
9 |
10 |
11 | class Workbench : View() {
12 |
13 | private val loginController: LoginController by inject()
14 | private val workbenchController: WorkbenchController by inject()
15 |
16 | /***** Global Variables *****/
17 | private val tiles = listOf("/img/Tile1.png", "/img/Tile2.png",
18 | "/img/Tile3.png", "/img/Tile4.png", "/img/Tile5.png",
19 | "/img/Tile6.png").observable()
20 |
21 | private val paginator = DataGridPaginator(tiles, itemsPerPage = 4)
22 |
23 | /***** View *****/
24 | override val root = borderpane {
25 | title = "Secure Workbench"
26 |
27 | addClass(workbenchScreen)
28 | setPrefSize(450.0, 550.0)
29 |
30 | top {
31 | label(title) {
32 | font = Font.font(22.0)
33 | }
34 | menubar {
35 | menu("File") {
36 | item("Logout").action {
37 | loginController.logout()
38 | }
39 | item("Quit").action {
40 | Platform.exit()
41 | }
42 | }
43 | }
44 | }
45 |
46 | center {
47 | datagrid(paginator.items) {
48 | maxCellsInRow = 2
49 |
50 | cellWidth = 180.0
51 | cellHeight = 180.0
52 | paddingLeft = 30.0
53 | paddingTop = 40.0
54 |
55 | cellFormat {
56 | graphic = cache {
57 | imageview(it) {
58 | fitWidth = 180.0
59 | fitHeight = 180.0
60 | isPreserveRatio = true
61 | center
62 | }
63 | }
64 | }
65 | onUserSelect(2) { workbenchController.goToEditor(it) }
66 | }
67 | }
68 |
69 | bottom {
70 | stackpane {
71 | add(paginator)
72 | paddingBottom = 25.0
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/model/DragTile.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.model
2 |
3 | import eu.hansolo.tilesfx.Tile
4 | import javafx.beans.property.Property
5 | import tornadofx.Commit
6 | import tornadofx.ItemViewModel
7 | import tornadofx.getProperty
8 | import tornadofx.property
9 |
10 | /***** Data classes and models intended for tile and grid location, saving
11 | * tile objects needed for module rendering,
12 | * dragging, and copying in a view *****/
13 |
14 | class DragTile(tile: Tile, colSpan: Int, rowSpan: Int, colIndex: Int,
15 | rowIndex: Int) {
16 | var tile by property(tile)
17 | fun tileProperty() = getProperty(DragTile::tile)
18 |
19 | var colSpan by property(colSpan)
20 | fun colSpanProperty() = getProperty(DragTile::colSpan)
21 |
22 | var rowSpan by property(rowSpan)
23 | fun rowSpanProperty() = getProperty(DragTile::rowSpan)
24 |
25 | var colIndex by property(colIndex)
26 | fun colIndexProperty() = getProperty(DragTile::colIndex)
27 |
28 | var rowIndex by property(rowIndex)
29 | fun rowIndexProperty() = getProperty(DragTile::rowIndex)
30 | }
31 |
32 | class DragTileModel : ItemViewModel() {
33 | private val tile = bind { item?.tileProperty() }
34 | private val colSpan = bind { item?.colSpanProperty() }
35 | private val rowSpan = bind { item?.rowSpanProperty() }
36 | private val colIndex = bind { item?.colIndexProperty() }
37 | private val rowIndex = bind { item?.rowIndexProperty() }
38 |
39 | override fun onCommit(commits: List) {
40 | super.onCommit(commits)
41 |
42 | // The println will only be called if findChanged is not null
43 | commits.findChanged(tile)?.let { println("Module Tile changed from ${it.first} to ${it.second}")}
44 | commits.findChanged(colSpan)?.let { println("Column Span changed from ${it.first} to ${it.second}")}
45 | commits.findChanged(rowSpan)?.let { println("Row Span changed from ${it.first} to ${it.second}")}
46 | commits.findChanged(colIndex)?.let { println("Column Index changed from ${it.first} to ${it.second}")}
47 | commits.findChanged(rowIndex)?.let { println("Row Index changed from ${it.first} to ${it.second}")}
48 | }
49 |
50 | private fun List.findChanged(ref: Property): Pair? {
51 | val commit = find { it.property == ref && it.changed}
52 | return commit?.let { (it.newValue as T) to (it.oldValue as T) }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/views/LoginScreen.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.views
2 |
3 | import com.example.demo.app.Styles.Companion.loginScreen
4 | import com.example.demo.controllers.LoginController
5 | import javafx.animation.KeyFrame
6 | import javafx.animation.Timeline
7 | import javafx.beans.property.SimpleBooleanProperty
8 | import javafx.beans.property.SimpleStringProperty
9 | import javafx.event.EventHandler
10 | import javafx.util.Duration
11 | import tornadofx.*
12 |
13 | class LoginScreen : View("Please log in") {
14 | private val loginController: LoginController by inject()
15 |
16 | /***** Global Variables *****/
17 | private val username = SimpleStringProperty()
18 | private val password = SimpleStringProperty()
19 | private val remember = SimpleBooleanProperty()
20 |
21 | /***** View *****/
22 | override val root = form {
23 | addClass(loginScreen)
24 |
25 | fieldset {
26 | field("Username") {
27 | textfield(username) {
28 | whenDocked {
29 | requestFocus()
30 | }
31 | }
32 | }
33 |
34 | field("Password") {
35 | passwordfield(password)
36 | }
37 |
38 | field("Remember me") {
39 | checkbox(property = remember)
40 | }
41 |
42 | button("Login") {
43 | isDefaultButton = true
44 |
45 | action {
46 | loginController.tryLogin(
47 | username.valueSafe,
48 | password.valueSafe,
49 | remember.value
50 | )
51 | }
52 | }
53 | }
54 | }
55 |
56 | /**
57 | * Permanently removes contents of text fields.
58 | *
59 | */
60 | fun clear() {
61 | username.value = null
62 | password.value = null
63 | remember.value = false
64 | }
65 |
66 | /**
67 | * Shakes the stage upon incorrect login
68 | *
69 | */
70 | fun shakeStage() {
71 | var moved = false
72 | val cycleCount = 10
73 | val move = 10
74 | val keyframeDuration = Duration.seconds(0.04)
75 |
76 | val stage = FX.primaryStage
77 |
78 | Timeline(KeyFrame(keyframeDuration, EventHandler {
79 | if (!moved) {
80 | stage.x = stage.x + move
81 | stage.y = stage.y + move
82 | } else {
83 | stage.x = stage.x - move
84 | stage.y = stage.y - move
85 | }
86 | moved = moved.not()
87 | })).apply {
88 | setCycleCount(cycleCount)
89 | isAutoReverse = false
90 | play()
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/controllers/TileBuilderController.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.controllers
2 |
3 | import com.example.demo.model.TileBuilder
4 | import eu.hansolo.tilesfx.Tile
5 | import javafx.collections.FXCollections
6 | import javafx.collections.ObservableList
7 | import javafx.scene.image.ImageView
8 | import javafx.scene.paint.Color
9 | import tornadofx.*
10 |
11 | class TileBuilderController : Controller() {
12 |
13 | /***** Global Variables *****/
14 |
15 | var hashmap = HashMap()
16 | var tileList = ArrayList()
17 | var imageList = ArrayList()
18 | var createGridTilesHashMap = HashMap()
19 | private val modules = loadJsonArray(javaClass.getResource("/JSON/TileBuilder.json")).toModel()
20 |
21 | private var homeIcons= listOf("/home_icons/24-technical-support.png", "/home_icons/cloud-network.png",
22 | "/home_icons/customer-service.png", "/home_icons/data-analysis.png",
23 | "/home_icons/data-connection.png", "/home_icons/data-lock.png",
24 | "/home_icons/data-servers.png", "/home_icons/data-usage.png")
25 |
26 | init {
27 | renderModule()
28 | renderImages()
29 | }
30 |
31 | /**
32 | * Create a tile using PageBuilder data
33 | *
34 | */
35 | private fun renderModule() {
36 | modules.forEach {
37 | hashmap.put(it.title, it)
38 | tileList.add(moduleTileBuilder(it))
39 | }
40 | }
41 |
42 | /**
43 | * Create a tile using PageBuilder data
44 | *
45 | */
46 | private fun renderImages() {
47 | homeIcons.forEach {
48 | imageList.add(imageTileBuilder(it))
49 | }
50 | }
51 |
52 | /**
53 | * Create tile graphic
54 | *
55 | * @property String url
56 | */
57 | private fun createGraphic(url: String): ImageView {
58 | return ImageView(javaClass.getResource(url).toExternalForm()).apply {
59 | fitWidth = 20.0
60 | fitHeight = 20.0
61 | isPreserveRatio = true
62 | }.apply {
63 | this.fitWidth = 50.0
64 | this.fitHeight = 50.0
65 | this.isPreserveRatio = true
66 | }
67 | }
68 |
69 |
70 | /**
71 | * Create a tile using PageBuilder data
72 | *
73 | * @property TileBuilder module
74 | */
75 | private fun imageTileBuilder(image: String): Tile {
76 | return eu.hansolo.tilesfx.TileBuilder.create()
77 | .skinType(Tile.SkinType.CUSTOM)
78 | .maxSize(100.0, 100.0)
79 | .minSize(100.0, 100.0)
80 | .graphic(createGraphic(image))
81 | .title(image)
82 | .titleColor(Color.TRANSPARENT)
83 | .backgroundColor(Color.TRANSPARENT)
84 | .roundedCorners(false)
85 | .build()
86 | }
87 |
88 | /**
89 | * Create a tile using PageBuilder data
90 | *
91 | * @property TileBuilder module
92 | */
93 | fun moduleTileBuilder(module: TileBuilder, titleColor: Color = Color.WHITE): Tile {
94 | val color = c(module.color)
95 | var title = module.title
96 | if (title == "Hi Admin") {
97 | title = ""
98 | }
99 | return eu.hansolo.tilesfx.TileBuilder.create()
100 | .skinType(Tile.SkinType.CUSTOM)
101 | .title(title)
102 | .titleColor(titleColor)
103 | .maxSize(module.width, module.height)
104 | .minSize(module.width, module.height)
105 | .graphic(createGraphic(module.image))
106 | .backgroundColor(color)
107 | .roundedCorners(false)
108 | .build()
109 | }
110 |
111 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.example
8 | DemoCreator-Accent
9 | 1.0
10 | jar
11 |
12 |
13 | 1.2.31
14 | 1.7.13
15 |
16 |
17 |
18 |
19 | DemoCreator
20 |
21 |
22 |
23 |
24 | no.tornado
25 | tornadofx
26 | ${tornadofx.version}
27 |
28 |
33 |
34 |
35 | org.osgi
36 | org.osgi.core
37 | 6.0.0
38 | provided
39 |
40 |
41 | eu.hansolo
42 | tilesfx
43 | 1.5.2
44 |
45 |
46 | org.jetbrains.kotlin
47 | kotlin-osgi-bundle
48 | ${kotlin.version}
49 |
50 |
51 | org.jetbrains.kotlin
52 | kotlin-test
53 | ${kotlin.version}
54 | test
55 |
56 |
57 | org.jetbrains.kotlin
58 | kotlin-stdlib-jdk8
59 | ${kotlin.version}
60 |
61 |
62 |
63 |
64 | src/main/kotlin
65 |
66 |
67 | org.jetbrains.kotlin
68 | kotlin-maven-plugin
69 | ${kotlin.version}
70 |
71 |
72 | compile
73 | compile
74 |
75 | compile
76 |
77 |
78 | 1.8
79 |
80 |
81 |
82 | test-compile
83 | test-compile
84 |
85 | test-compile
86 |
87 |
88 |
89 |
90 | 1.8
91 |
92 |
93 |
94 | org.apache.maven.plugins
95 | maven-compiler-plugin
96 |
97 | 1.8
98 | 1.8
99 |
100 |
101 |
102 |
103 | com.zenjava
104 | javafx-maven-plugin
105 | 8.8.3
106 |
107 | com.example.demo.app.MyAppKt
108 | DemoCreator.jar
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | oss-sonatype
117 | oss-sonatype
118 | https://oss.sonatype.org/content/repositories/snapshots/
119 |
120 | true
121 |
122 |
123 |
124 | jitpack.io
125 | https://jitpack.io
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/views/TileGUI.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.views
2 | import com.example.demo.app.Styles
3 | import com.example.demo.app.Styles.Companion.tileGUI
4 | import com.example.demo.app.Styles.Companion.transparentOverlay
5 | import com.example.demo.controllers.LoginController
6 | import com.example.demo.controllers.TileBuilderController
7 | import com.example.demo.controllers.TileGUIController
8 | import com.example.demo.model.TileBuilder
9 | import javafx.application.Platform
10 | import javafx.geometry.Pos
11 | import javafx.geometry.Side
12 | import javafx.scene.Node
13 | import javafx.scene.input.MouseEvent
14 | import javafx.scene.text.Font
15 | import tornadofx.*
16 |
17 | class TileGUI : View() {
18 |
19 | /***** Global Variables *****/
20 | private val loginController: LoginController by inject()
21 | private val tileBuilderController: TileBuilderController by inject()
22 | private val controller: TileGUIController by inject()
23 | var module = TileBuilder()
24 |
25 | // drag variables
26 | var moduleBoxItems = mutableListOf()
27 |
28 | /***** View *****/
29 | override val root = stackpane {
30 | setPrefSize(1000.0, 650.0)
31 |
32 | borderpane {
33 | addClass(tileGUI)
34 | top {
35 | label(title) {
36 | font = Font.font(22.0)
37 | }
38 | menubar {
39 | menu("File") {
40 | item("Logout").action(loginController::logout)
41 | item("Quit").action(Platform::exit)
42 | }
43 | }
44 | }
45 |
46 | center(MyTiles::class)
47 |
48 | right {
49 | vbox {
50 | maxWidth = 300.0
51 | drawer(side = Side.RIGHT) {
52 | item("Tiles", expanded = true) {
53 | datagrid(tileBuilderController.tileList) {
54 | maxCellsInRow = 2
55 | cellWidth = 100.0
56 | cellHeight = 100.0
57 |
58 | paddingTop = 15.0
59 | paddingLeft = 35.0
60 | minWidth = 300.0
61 |
62 | cellFormat {
63 | graphic = it.apply { addClass(Styles.highlightTile) }
64 | }
65 | }
66 | }
67 | item("Icons") {
68 | datagrid(tileBuilderController.imageList) {
69 | maxCellsInRow = 2
70 |
71 | cellWidth = 100.0
72 | cellHeight = 100.0
73 | paddingLeft = 35.0
74 | minWidth = 300.0
75 |
76 | cellFormat {
77 | graphic = it
78 | style { backgroundColor += c("#222222") }
79 | }
80 | }
81 | }
82 | }
83 |
84 | form {
85 | paddingLeft = 20.0
86 | paddingTop = 20.0
87 | hbox(20) {
88 | fieldset("Select a tile to customize.") {
89 | hbox(20) {
90 | vbox {
91 | field("Title") { textfield().bind(module.titleProperty) }
92 | field("Color") { textfield().bind(module.colorProperty) }
93 | field("HoverColor") { textfield().bind(module.hoverColorProperty) }
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | hbox {
101 | hboxConstraints { alignment = Pos.BASELINE_RIGHT }
102 | button("Return to Workbench") {
103 | hboxConstraints {
104 | marginLeftRight(10.0)
105 | marginTop = 70.0
106 | }
107 | }
108 | }
109 | }
110 | }
111 | }
112 |
113 | pane {
114 | addClass(transparentOverlay)
115 | isMouseTransparent = true
116 | }
117 |
118 | addEventFilter(MouseEvent.ANY, ::hoverBehavior)
119 | addEventFilter(MouseEvent.MOUSE_PRESSED, ::startDrag)
120 | addEventFilter(MouseEvent.MOUSE_DRAGGED, ::animateDrag)
121 | addEventFilter(MouseEvent.MOUSE_RELEASED, ::stopDrag)
122 | addEventFilter(MouseEvent.MOUSE_RELEASED, ::drop)
123 | addEventFilter(MouseEvent.MOUSE_CLICKED, ::selectTile)
124 | }
125 |
126 | /***** Methods *****/
127 |
128 | private fun hoverBehavior(evt: MouseEvent) {
129 | controller.hoverBehavior(evt)
130 | }
131 | private fun startDrag(evt: MouseEvent) {
132 | controller.startDrag(evt)
133 | }
134 | private fun animateDrag(evt: MouseEvent) {
135 | controller.animateDrag(evt)
136 | }
137 | private fun stopDrag(evt: MouseEvent) {
138 | controller.stopDrag(evt)
139 | }
140 | private fun drop(evt: MouseEvent) {
141 | controller.drop(evt, this@TileGUI)
142 | }
143 | private fun selectTile(evt: MouseEvent) {
144 | controller.selectTile(evt, module)
145 | }
146 |
147 | // init
148 | init {
149 | moduleBoxItems.addAll( tileBuilderController.tileList )
150 | }
151 | }
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/model/HomepageBuilderModel.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.model
2 |
3 | import javafx.beans.property.SimpleDoubleProperty
4 | import javafx.beans.property.SimpleIntegerProperty
5 | import javafx.beans.property.SimpleStringProperty
6 | import javax.json.JsonObject
7 | import tornadofx.*
8 |
9 | class HomepageGridBuilder: JsonModel {
10 | private val gridProperty = SimpleIntegerProperty()
11 | var grid by gridProperty
12 |
13 | private val rowsProperty = SimpleIntegerProperty()
14 | var rows by rowsProperty
15 |
16 | private val columnsProperty = SimpleIntegerProperty()
17 | var columns by columnsProperty
18 |
19 | var tiles = listOf()
20 |
21 | override fun toString(): String {
22 | return "HomepageGrid(grid=$grid, rows=$rows, columns=$columns, tiles=$tiles)"
23 | }
24 |
25 | override fun updateModel(json: JsonObject) {
26 | with(json) {
27 | grid = int("grid") ?: 0
28 | rows = int("rows") ?: 1
29 | columns = int("columns") ?: 1
30 | tiles = getJsonArray("tiles").toModel()
31 | }
32 | }
33 |
34 | override fun toJSON(json: JsonBuilder) {
35 | with(json) {
36 | add("grid", grid)
37 | add("rows", rows)
38 | add("columns", columns)
39 | add("tiles", tiles.toJSON())
40 | }
41 | }
42 | }
43 |
44 | class HomepageTileBuilder: JsonModel {
45 | private val titleProperty = SimpleStringProperty()
46 | var title by titleProperty
47 |
48 | private val colIndexProperty = SimpleIntegerProperty()
49 | var colIndex by colIndexProperty
50 |
51 | private val rowIndexProperty = SimpleIntegerProperty()
52 | var rowIndex by rowIndexProperty
53 |
54 | private val colSpanProperty = SimpleIntegerProperty()
55 | var colSpan by colSpanProperty
56 |
57 | private val rowSpanProperty = SimpleIntegerProperty()
58 | var rowSpan by rowSpanProperty
59 |
60 | private val widthProperty = SimpleDoubleProperty()
61 | var width by widthProperty
62 |
63 | private val heightProperty = SimpleDoubleProperty()
64 | var height by heightProperty
65 |
66 | override fun toString(): String {
67 | return "Tile(title=$title, colIndex=$colIndex, rowIndex=$rowIndex," +
68 | "colSpan=$colSpan, rowSpan=$rowSpan, width=$width, height=$height)"
69 | }
70 |
71 | override fun updateModel(json: JsonObject) {
72 | with (json) {
73 | title = string("title")
74 | colIndex = int("colIndex") ?: 1
75 | rowIndex = int("rowIndex") ?: 1
76 | colSpan = int("colSpan") ?: 1
77 | rowSpan = int("rowSpan") ?: 1
78 | width = double("width") ?: 100.0
79 | height = double("height") ?: 100.0
80 | }
81 | }
82 |
83 | override fun toJSON(json: JsonBuilder) {
84 | with(json) {
85 | add("title", title)
86 | add("colIndex", colIndex)
87 | add("rowIndex", rowIndex)
88 | add("colSpan", colSpan)
89 | add("rowSpan", rowSpan)
90 | add("width", width)
91 | add("height", height)
92 | }
93 | }
94 | }
95 |
96 | class TileBuilder: JsonModel {
97 |
98 | val titleProperty = SimpleStringProperty()
99 | var title: String by titleProperty
100 |
101 | val tilePositionProperty = SimpleIntegerProperty()
102 | var tilePosition: Int by tilePositionProperty
103 |
104 | val widthProperty = SimpleDoubleProperty()
105 | var width: Double by widthProperty
106 |
107 | val heightProperty = SimpleDoubleProperty()
108 | var height: Double by heightProperty
109 |
110 | val colorProperty = SimpleStringProperty()
111 | var color: String by colorProperty
112 |
113 | val hoverColorProperty = SimpleStringProperty()
114 | var hoverColor: String by hoverColorProperty
115 |
116 | val colIndexProperty = SimpleIntegerProperty()
117 | var colIndex: Int by colIndexProperty
118 |
119 | val rowIndexProperty = SimpleIntegerProperty()
120 | var rowIndex: Int by rowIndexProperty
121 |
122 | val colSpanProperty = SimpleIntegerProperty()
123 | var colSpan: Int by colSpanProperty
124 |
125 | val rowSpanProperty = SimpleIntegerProperty()
126 | var rowSpan: Int by rowSpanProperty
127 |
128 | val imageProperty = SimpleStringProperty()
129 | var image: String by imageProperty
130 |
131 | override fun toString(): String {
132 | return "Tile(module=$title, " +
133 | "width=$width, height=$height, color=$color," +
134 | "hoverColor=$hoverColor, colIndex=$colIndex," +
135 | "row=$rowIndex, colSpan=$colSpan, rowSpan=$rowSpan)"
136 | }
137 |
138 | override fun updateModel(json: JsonObject) {
139 | with (json) {
140 | title = string("title") ?: "title"
141 | tilePosition = int("modulePosition") ?: 1
142 | width = double("width") ?: 100.0
143 | height = double("height") ?: 100.0
144 | color = string("color") ?: "#FFFFFF"
145 | hoverColor = string("hoverColor") ?: "#FFFFFF"
146 | colIndex = int("colIndex") ?: 1
147 | rowIndex = int("rowIndex") ?: 1
148 | colSpan = int("colSpan") ?: 1
149 | rowSpan = int("rowSpan") ?: 1
150 | image = string("image") ?: ""
151 | }
152 | }
153 |
154 | override fun toJSON(json: JsonBuilder) {
155 | with(json) {
156 | add("title", title)
157 | add("modulePosition", tilePosition)
158 | add("width", width)
159 | add("height", height)
160 | add("color", color)
161 | add("hoverColor", hoverColor)
162 | add("colIndex", colIndex)
163 | add("rowIndex", rowIndex)
164 | add("colSpan", colSpan)
165 | add("rowSpan", rowSpan)
166 | add("image", image)
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/main/kotlin/com/example/demo/controllers/TileGUIController.kt:
--------------------------------------------------------------------------------
1 | package com.example.demo.controllers
2 |
3 | import com.example.demo.app.Styles
4 | import com.example.demo.model.DragTile
5 | import com.example.demo.model.GridInfoModel
6 | import com.example.demo.views.MyTiles
7 | import com.example.demo.views.TileGUI
8 | import eu.hansolo.tilesfx.Tile
9 | import eu.hansolo.tilesfx.TileBuilder
10 | import javafx.geometry.Point2D
11 | import javafx.scene.Node
12 | import javafx.scene.control.Button
13 | import javafx.scene.input.MouseEvent
14 | import javafx.scene.layout.GridPane
15 | import javafx.scene.layout.StackPane
16 | import javafx.scene.paint.Color
17 | import tornadofx.*
18 |
19 | class TileGUIController : Controller() {
20 |
21 | private lateinit var hoverTile: Tile
22 | private lateinit var hoverTileProperties: com.example.demo.model.TileBuilder
23 | private val controller: TileBuilderController by inject()
24 | private val workbenchController: WorkbenchController by inject()
25 | private val view: TileGUI by inject()
26 | private val myTiles: MyTiles by inject()
27 | private val gridInfoModel: GridInfoModel by inject()
28 |
29 | // drag variables
30 | private lateinit var dragTile: DragTile
31 | private var isDragAndDrop = true
32 | private var isImageTile = false
33 | private lateinit var inFlightTile: Tile
34 | private lateinit var inFlightTileProperties: com.example.demo.model.TileBuilder
35 | private lateinit var originalTileProperties: com.example.demo.model.TileBuilder
36 |
37 |
38 | /**
39 | * Grid Tile Builder, simplified tile
40 | *
41 | * @property MouseEvent evt
42 | * @property StackPane workArea
43 | * @property Tile tile
44 | * @property TileBuilder property
45 | */
46 | private fun addHoverTile(evt: MouseEvent, workArea: StackPane, tile: Tile, property: com.example.demo.model.TileBuilder) {
47 |
48 | val bounds = tile.layoutBounds
49 | val coordinates = tile.localToScene(bounds.minX, bounds.minY)
50 | val tileX = coordinates.x
51 | val tileY = coordinates.y
52 |
53 | hoverTileProperties = property.copy()
54 | hoverTileProperties.color = property.hoverColor
55 | hoverTileProperties.width = tile.widthProperty().value
56 | hoverTileProperties.height = tile.heightProperty().value
57 | hoverTileProperties.colSpan = property.colSpan
58 | hoverTileProperties.rowSpan = property.rowSpan
59 | hoverTile = controller.moduleTileBuilder(hoverTileProperties)
60 | workArea.children[1].add(hoverTile)
61 | hoverTile.isMouseTransparent = true
62 | hoverTile.translateX = tileX
63 | hoverTile.translateY = tileY
64 | evt.consume()
65 | }
66 |
67 | /**
68 | * Grid Tile Builder, simplified tile
69 | *
70 | * @property MouseEvent evt
71 | * @property StackPane workArea
72 | */
73 | private fun removeHoverTile(evt: MouseEvent, workArea: StackPane) {
74 | if (::hoverTileProperties.isInitialized) {
75 | workArea.children[1].getChildList()!!.remove(hoverTile)
76 | }
77 | evt.consume()
78 | }
79 |
80 | /**
81 | * Reproduce and remove tiles with hover colors as you hover over
82 | * dropped tiles
83 | *
84 | * @property MouseEvent evt
85 | */
86 | fun hoverBehavior(evt: MouseEvent) {
87 | for (child in myTiles.root.children) {
88 | if (child is Tile && child.titleProperty().value.toIntOrNull() == null && ::inFlightTileProperties.isInitialized) {
89 | child.setOnMouseEntered {
90 | val title = child.titleProperty().value
91 |
92 | // TODO: change hashmap to grid output
93 | controller.createGridTilesHashMap[title]?.let {
94 | inFlightTileProperties = it
95 | }
96 |
97 | if (title != "Hi Admin") {
98 | addHoverTile(evt, view.root, child, inFlightTileProperties)
99 | }
100 | }
101 | child.setOnMouseExited {
102 | removeHoverTile(evt, view.root)
103 | }
104 | }
105 | }
106 | }
107 |
108 | /**
109 | * Grabs a tile and its properties to prepare for the animateDrag
110 | * and drop events.
111 | *
112 | * @property MouseEvent evt
113 | */
114 | fun startDrag(evt : MouseEvent) {
115 |
116 | val targetNode = evt.target as Node
117 | val tileTarget = targetNode.findParentOfType(Tile::class)
118 |
119 | val mousePt : Point2D = view.root.sceneToLocal( evt.sceneX, evt.sceneY )
120 | view.moduleBoxItems
121 | .filter {
122 | it.contains(mousePt)
123 | }
124 | .apply {
125 | // select tile from data grid
126 | if (tileTarget is Tile && !myTiles.root.contains(mousePt)) {
127 | val title = tileTarget.titleProperty().value
128 | val color = tileTarget.backgroundColorProperty().value
129 | var titleColor = Color.WHITE
130 |
131 | if (color != Color.TRANSPARENT) {
132 | controller.hashmap[title]?.let {
133 | originalTileProperties = it
134 | }
135 | setProperties()
136 | inFlightTileProperties.title = title
137 | }
138 | else {
139 | inFlightTileProperties = originalTileProperties.copy()
140 | inFlightTileProperties.color = color.toString()
141 | inFlightTileProperties.hoverColor = color.toString()
142 | inFlightTileProperties.image = title
143 | titleColor = Color.TRANSPARENT
144 | isImageTile = true
145 | }
146 | inFlightTileProperties.width = 100.0
147 | inFlightTileProperties.height = 100.0
148 | inFlightTile = controller.moduleTileBuilder(inFlightTileProperties, titleColor)
149 | inFlightTile.isVisible = false
150 | view.root.children[1].add(inFlightTile)
151 | isDragAndDrop = true
152 | }
153 | }
154 |
155 | }
156 |
157 | /**
158 | * Renders a tile the user can drag to the desired grid location
159 | *
160 | * @property MouseEvent evt
161 | */
162 | fun animateDrag(evt : MouseEvent) {
163 |
164 | val mousePt = view.root.sceneToLocal( evt.sceneX, evt.sceneY )
165 | val targetNode = evt.target as Node
166 |
167 | val tileTarget = targetNode.findParentOfType(Tile::class)
168 |
169 | if (tileTarget != null) {
170 | val tileTargetParent = tileTarget.parent
171 | if( view.root.contains(mousePt) && (tileTargetParent !is GridPane) ) {
172 |
173 | // animate a rectangle so that the user can follow
174 | if( !inFlightTile.isVisible ) {
175 | inFlightTile.isVisible = true
176 | }
177 | inFlightTile.toFront()
178 | val widthOffset = inFlightTile.widthProperty().value/2
179 | val heightOffset = inFlightTile.heightProperty().value/2
180 | inFlightTile.relocate( mousePt.x - widthOffset, mousePt.y - heightOffset)
181 | }
182 | }
183 | }
184 |
185 | /**
186 | * Highlight the workArea and hide the draggingTile node
187 | *
188 | * @property MouseEvent evt
189 | */
190 | fun stopDrag(evt: MouseEvent) {
191 | val targetNode = evt.target as Node
192 | val tileTarget = targetNode.findParentOfType(Tile::class)
193 |
194 | if (tileTarget is Tile &&
195 | ::inFlightTileProperties.isInitialized &&
196 | inFlightTile.isVisible ) {
197 | inFlightTile.isVisible = false
198 | }
199 | }
200 |
201 | /**
202 | * Compare selected dragging tile with the location of the drop
203 | * and render the module tile accordingly
204 | *
205 | * @property MouseEvent evt
206 | */
207 | fun drop(evt : MouseEvent, returnView: UIComponent) {
208 |
209 | val mousePt = myTiles.root.sceneToLocal( evt.sceneX, evt.sceneY )
210 | val targetNode = evt.target as Node
211 | val tileTarget = targetNode.findParentOfType(Tile::class)
212 |
213 | val buttonTarget = targetNode.findParentOfType(Button::class)
214 |
215 | if (::inFlightTileProperties.isInitialized && isDragAndDrop) {
216 | if (tileTarget is Tile && myTiles.root.contains(mousePt) &&
217 | inFlightTileProperties.title.toIntOrNull() == null) {
218 | pickGridTile(mousePt.x, mousePt.y, evt)
219 | view.root.children[1].getChildList()!!.remove(inFlightTile)
220 | isDragAndDrop = false
221 | }
222 | inFlightTileProperties.width = 100.0
223 | inFlightTileProperties.height = 100.0
224 | evt.consume()
225 | }
226 |
227 | if (buttonTarget is Button && buttonTarget.textProperty().value == "Return to Workbench") {
228 | workbenchController.returnToWorkbench(returnView)
229 | evt.consume()
230 | }
231 |
232 | }
233 |
234 | /**
235 | * Compare selected dragging tile with the location of the drop
236 | * and render the module tile accordingly
237 | *
238 | * @property MouseEvent evt
239 | * @property Double sceneX
240 | * @property Double sceneY
241 | */
242 | private fun pickGridTile(sceneX: Double, sceneY:Double, evt: MouseEvent) {
243 | val mousePoint= Point2D(sceneX, sceneY)
244 | val mpLocal = myTiles.root.sceneToLocal(mousePoint)
245 |
246 | val rowOffset: Int = ((mpLocal.x - 35)/100).toInt() * 10
247 | val colOffset: Int = ((mpLocal.y - 85)/100).toInt() * 10
248 | val gridColumn: Int = ((mpLocal.x - 35 - rowOffset)/100).toInt()
249 | val gridRow: Int = ((mpLocal.y - 85 - colOffset)/100).toInt()
250 |
251 | // tileExchange is the pair the tiles, one to remove from the grid and
252 | // the other either the dragged tile or the resized version of the dragged tile
253 | val removeTile: Tile = getPickedGridTileInfo(gridRow, gridColumn, evt)
254 | val gridTitleTile = removeTile.titleProperty().value
255 |
256 | if (gridRow < gridInfoModel.item.rows &&
257 | gridColumn < gridInfoModel.item.columns &&
258 | gridTitleTile != "Hi Admin" && isDragAndDrop) {
259 |
260 | myTiles.root.children.remove(removeTile)
261 | myTiles.root.add(dragTile.tile, dragTile.colIndex, dragTile.rowIndex, dragTile.colSpan, dragTile.rowSpan)
262 | isImageTile = false
263 | }
264 |
265 | }
266 |
267 | /**
268 | * Compare selected dragging tile with the location of the drop
269 | * and render the module tile accordingly, assigns the tile to be dropped
270 | * to dragTile and the tile to be removed from the grid
271 | *
272 | * @property Int gridRow
273 | * @property Int gridColumn
274 | */
275 | private fun getPickedGridTileInfo(gridRow: Int, gridColumn: Int, evt: MouseEvent): Tile {
276 | var colIndex = 0
277 | var rowIndex = 0
278 | var rowSpan = 0
279 | var colSpan = 0
280 | lateinit var removeTile: Tile
281 |
282 | val children = myTiles.root.children
283 |
284 | loop@ for (gridTile in children) {
285 | if (gridTile is Tile) {
286 | rowIndex = GridPane.getRowIndex(gridTile)
287 | colIndex = GridPane.getColumnIndex(gridTile)
288 | colSpan = GridPane.getColumnSpan(gridTile)
289 | rowSpan = GridPane.getRowSpan(gridTile)
290 | removeTile = gridTile
291 | isDragAndDrop = false
292 |
293 | // detect for the possibility that the selected gridRow/gridColumn might be in
294 | // within a tile in w span(s) > 1
295 | if (rowSpan > 1 || colSpan > 1) {
296 | for (i in 0..(rowSpan-1)) {
297 | for (j in 0..(colSpan-1)) {
298 | val compareRowIndex = rowIndex + i
299 | val compareColumnIndex = colIndex + j
300 | if (gridRow == compareRowIndex && gridColumn == compareColumnIndex) {
301 | // resize tile
302 | inFlightTileProperties.width = removeTile.widthProperty().value
303 | inFlightTileProperties.height = removeTile.heightProperty().value
304 | isDragAndDrop = true
305 | break@loop
306 | }
307 | }
308 | }
309 | } else {
310 | if (rowIndex == gridRow && colIndex == gridColumn) {
311 | isDragAndDrop = true
312 | break@loop
313 | }
314 | }
315 | }
316 | }
317 |
318 | if (inFlightTile.titleColorProperty().value != c("0xffffffff")) {
319 | val image = inFlightTileProperties.image
320 | val title = removeTile.titleProperty().value
321 | if (controller.createGridTilesHashMap.containsKey(title)) {
322 | controller.createGridTilesHashMap[title]?.let {
323 | originalTileProperties = it
324 | }
325 |
326 | setProperties()
327 | inFlightTileProperties.title = title
328 | inFlightTileProperties.image = image
329 | }
330 | }
331 |
332 | val draggedTile = controller.moduleTileBuilder(inFlightTileProperties)
333 | controller.createGridTilesHashMap.put(inFlightTileProperties.title, inFlightTileProperties.copy())
334 | dragTile = DragTile(draggedTile, colSpan, rowSpan, colIndex, rowIndex)
335 |
336 | return removeTile
337 | }
338 |
339 | fun selectTile(evt: MouseEvent, module: com.example.demo.model.TileBuilder) {
340 | val targetNode = evt.target as Node
341 | val tileTarget = targetNode.findParentOfType(Tile::class)
342 | val mousePt : Point2D = view.root.sceneToLocal( evt.sceneX, evt.sceneY )
343 |
344 | if (myTiles.root.contains(mousePt) && tileTarget!!.graphicProperty().value != null &&
345 | !tileTarget.hasClass(Styles.selectedTile) &&
346 | tileTarget.titleProperty().value.toIntOrNull() == null) {
347 |
348 | myTiles.root.children.forEach {
349 | if (it.hasClass(Styles.selectedTile)) {
350 | it.removeClass(Styles.selectedTile)
351 | }
352 | if (it == tileTarget) {
353 | it.addClass(Styles.selectedTile)
354 | }
355 | }
356 |
357 | val title: String = tileTarget.titleProperty().value
358 |
359 | controller.createGridTilesHashMap[title]?.let {
360 | module.titleProperty.set(it.title)
361 | module.colorProperty.set(it.hoverColor)
362 | module.hoverColorProperty.set(it.hoverColor)
363 | }
364 | }
365 | }
366 |
367 | private fun setProperties() {
368 | inFlightTileProperties = originalTileProperties.copy()
369 | inFlightTileProperties.color = originalTileProperties.color
370 | inFlightTileProperties.hoverColor = originalTileProperties.hoverColor
371 | }
372 | }
--------------------------------------------------------------------------------
/src/main/resources/JSON/GridInfo.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "grid": 1,
4 | "rows": 4,
5 | "columns": 6,
6 | "tiles": [
7 | {
8 | "title": "1",
9 | "colIndex": 2,
10 | "rowIndex": 1,
11 | "colSpan": 1,
12 | "rowSpan": 1,
13 | "width": 100.0,
14 | "height": 100.0
15 | },
16 | {
17 | "title": "2",
18 | "colIndex": 3,
19 | "rowIndex": 1,
20 | "colSpan": 1,
21 | "rowSpan": 1,
22 | "width": 100.0,
23 | "height": 100.0
24 | },
25 | {
26 | "title": "3",
27 | "colIndex": 2,
28 | "rowIndex": 2,
29 | "colSpan": 1,
30 | "rowSpan": 1,
31 | "width": 100.0,
32 | "height": 100.0
33 | },
34 | {
35 | "title": "4",
36 | "colIndex": 3,
37 | "rowIndex": 2,
38 | "colSpan": 1,
39 | "rowSpan": 1,
40 | "width": 100.0,
41 | "height": 100.0
42 | }
43 | ]
44 | },
45 | {
46 | "grid": 2,
47 | "rows": 4,
48 | "columns": 6,
49 | "tiles": [
50 | {
51 | "title": "1",
52 | "colIndex": 0,
53 | "rowIndex": 0,
54 | "colSpan": 1,
55 | "rowSpan": 1,
56 | "width": 100.0,
57 | "height": 100.0
58 | },
59 | {
60 | "title": "2",
61 | "colIndex": 1,
62 | "rowIndex": 0,
63 | "colSpan": 1,
64 | "rowSpan": 1,
65 | "width": 100.0,
66 | "height": 100.0
67 | },
68 | {
69 | "title": "3",
70 | "colIndex": 2,
71 | "rowIndex": 0,
72 | "colSpan": 1,
73 | "rowSpan": 1,
74 | "width": 100.0,
75 | "height": 100.0
76 | },
77 | {
78 | "title": "4",
79 | "colIndex": 3,
80 | "rowIndex": 0,
81 | "colSpan": 1,
82 | "rowSpan": 1,
83 | "width": 100.0,
84 | "height": 100.0
85 | },
86 | {
87 | "title": "5",
88 | "colIndex": 4,
89 | "rowIndex": 0,
90 | "colSpan": 1,
91 | "rowSpan": 1,
92 | "width": 100.0,
93 | "height": 100.0
94 | },
95 | {
96 | "title": "6",
97 | "colIndex": 5,
98 | "rowIndex": 0,
99 | "colSpan": 1,
100 | "rowSpan": 1,
101 | "width": 100.0,
102 | "height": 100.0
103 | },
104 | {
105 | "title": "7",
106 | "colIndex": 0,
107 | "rowIndex": 1,
108 | "colSpan": 1,
109 | "rowSpan": 1,
110 | "width": 100.0,
111 | "height": 100.0
112 | },
113 | {
114 | "title": "8",
115 | "colIndex": 1,
116 | "rowIndex": 1,
117 | "colSpan": 1,
118 | "rowSpan": 1,
119 | "width": 100.0,
120 | "height": 100.0
121 | },
122 | {
123 | "title": "9",
124 | "colIndex": 2,
125 | "rowIndex": 1,
126 | "colSpan": 1,
127 | "rowSpan": 1,
128 | "width": 100.0,
129 | "height": 100.0
130 | },
131 | {
132 | "title": "10",
133 | "colIndex": 3,
134 | "rowIndex": 1,
135 | "colSpan": 1,
136 | "rowSpan": 1,
137 | "width": 100.0,
138 | "height": 100.0
139 | },
140 | {
141 | "title": "11",
142 | "colIndex": 4,
143 | "rowIndex": 1,
144 | "colSpan": 1,
145 | "rowSpan": 1,
146 | "width": 100.0,
147 | "height": 100.0
148 | },
149 | {
150 | "title": "12",
151 | "colIndex": 5,
152 | "rowIndex": 1,
153 | "colSpan": 1,
154 | "rowSpan": 1,
155 | "width": 100.0,
156 | "height": 100.0
157 | },
158 | {
159 | "title": "13",
160 | "colIndex": 0,
161 | "rowIndex": 2,
162 | "colSpan": 1,
163 | "rowSpan": 1,
164 | "width": 100.0,
165 | "height": 100.0
166 | },
167 | {
168 | "title": "14",
169 | "colIndex": 1,
170 | "rowIndex": 2,
171 | "colSpan": 1,
172 | "rowSpan": 1,
173 | "width": 100.0,
174 | "height": 100.0
175 | },
176 | {
177 | "title": "15",
178 | "colIndex": 2,
179 | "rowIndex": 2,
180 | "colSpan": 1,
181 | "rowSpan": 1,
182 | "width": 100.0,
183 | "height": 100.0
184 | },
185 | {
186 | "title": "16",
187 | "colIndex": 3,
188 | "rowIndex": 2,
189 | "colSpan": 1,
190 | "rowSpan": 1,
191 | "width": 100.0,
192 | "height": 100.0
193 | },
194 | {
195 | "title": "17",
196 | "colIndex": 4,
197 | "rowIndex": 2,
198 | "colSpan": 1,
199 | "rowSpan": 1,
200 | "width": 100.0,
201 | "height": 100.0
202 | },
203 | {
204 | "title": "18",
205 | "colIndex": 5,
206 | "rowIndex": 2,
207 | "colSpan": 1,
208 | "rowSpan": 1,
209 | "width": 100.0,
210 | "height": 100.0
211 | },
212 | {
213 | "title": "19",
214 | "colIndex": 0,
215 | "rowIndex": 3,
216 | "colSpan": 1,
217 | "rowSpan": 1,
218 | "width": 100.0,
219 | "height": 100.0
220 | },
221 | {
222 | "title": "20",
223 | "colIndex": 1,
224 | "rowIndex": 3,
225 | "colSpan": 1,
226 | "rowSpan": 1,
227 | "width": 100.0,
228 | "height": 100.0
229 | },
230 | {
231 | "title": "21",
232 | "colIndex": 2,
233 | "rowIndex": 3,
234 | "colSpan": 1,
235 | "rowSpan": 1,
236 | "width": 100.0,
237 | "height": 100.0
238 | },
239 | {
240 | "title": "22",
241 | "colIndex": 3,
242 | "rowIndex": 3,
243 | "colSpan": 1,
244 | "rowSpan": 1,
245 | "width": 100.0,
246 | "height": 100.0
247 | },
248 | {
249 | "title": "23",
250 | "colIndex": 4,
251 | "rowIndex": 3,
252 | "colSpan": 1,
253 | "rowSpan": 1,
254 | "width": 100.0,
255 | "height": 100.0
256 | },
257 | {
258 | "title": "24",
259 | "colIndex": 5,
260 | "rowIndex": 3,
261 | "colSpan": 1,
262 | "rowSpan": 1,
263 | "width": 100.0,
264 | "height": 100.0
265 | }
266 | ]
267 | },
268 | {
269 | "grid": 3,
270 | "rows": 4,
271 | "columns": 6,
272 | "tiles": [
273 | {
274 | "title": "1",
275 | "colIndex": 0,
276 | "rowIndex": 0,
277 | "colSpan": 1,
278 | "rowSpan": 1,
279 | "width": 100.0,
280 | "height": 100.0
281 | },
282 | {
283 | "title": "2",
284 | "colIndex": 1,
285 | "rowIndex": 0,
286 | "colSpan": 1,
287 | "rowSpan": 1,
288 | "width": 100.0,
289 | "height": 100.0
290 | },
291 | {
292 | "title": "3",
293 | "colIndex": 2,
294 | "rowIndex": 0,
295 | "colSpan": 1,
296 | "rowSpan": 1,
297 | "width": 100.0,
298 | "height": 100.0
299 | },
300 | {
301 | "title": "4",
302 | "colIndex": 3,
303 | "rowIndex": 0,
304 | "colSpan": 1,
305 | "rowSpan": 1,
306 | "width": 100.0,
307 | "height": 100.0
308 | },
309 | {
310 | "title": "5",
311 | "colIndex": 4,
312 | "rowIndex": 0,
313 | "colSpan": 1,
314 | "rowSpan": 1,
315 | "width": 100.0,
316 | "height": 100.0
317 | },
318 | {
319 | "title": "6",
320 | "colIndex": 5,
321 | "rowIndex": 0,
322 | "colSpan": 1,
323 | "rowSpan": 1,
324 | "width": 100.0,
325 | "height": 100.0
326 | },
327 | {
328 | "title": "7",
329 | "colIndex": 0,
330 | "rowIndex": 1,
331 | "colSpan": 1,
332 | "rowSpan": 1,
333 | "width": 100.0,
334 | "height": 100.0
335 | },
336 | {
337 | "title": "8",
338 | "colIndex": 0,
339 | "rowIndex": 2,
340 | "colSpan": 1,
341 | "rowSpan": 1,
342 | "width": 100.0,
343 | "height": 100.0
344 | },
345 | {
346 | "title": "9",
347 | "colIndex": 0,
348 | "rowIndex": 3,
349 | "colSpan": 2,
350 | "rowSpan": 1,
351 | "width": 210.0,
352 | "height": 100.0
353 | },
354 | {
355 | "title": "10",
356 | "colIndex": 1,
357 | "rowIndex": 1,
358 | "colSpan": 2,
359 | "rowSpan": 2,
360 | "width": 210.0,
361 | "height": 210.0
362 | },
363 | {
364 | "title": "11",
365 | "colIndex": 3,
366 | "rowIndex": 1,
367 | "colSpan": 2,
368 | "rowSpan": 2,
369 | "width": 210.0,
370 | "height": 210.0
371 | },
372 | {
373 | "title": "12",
374 | "colIndex": 2,
375 | "rowIndex": 3,
376 | "colSpan": 1,
377 | "rowSpan": 1,
378 | "width": 100.0,
379 | "height": 100.0
380 | },
381 | {
382 | "title": "13",
383 | "colIndex": 3,
384 | "rowIndex": 3,
385 | "colSpan": 2,
386 | "rowSpan": 1,
387 | "width": 210.0,
388 | "height": 100.0
389 | },
390 | {
391 | "title": "14",
392 | "colIndex": 5,
393 | "rowIndex": 1,
394 | "colSpan": 1,
395 | "rowSpan": 1,
396 | "width": 100.0,
397 | "height": 100.0
398 | },
399 | {
400 | "title": "15",
401 | "colIndex": 5,
402 | "rowIndex": 2,
403 | "colSpan": 1,
404 | "rowSpan": 1,
405 | "width": 100.0,
406 | "height": 100.0
407 | },
408 | {
409 | "title": "16",
410 | "colIndex": 5,
411 | "rowIndex": 3,
412 | "colSpan": 1,
413 | "rowSpan": 1,
414 | "width": 100.0,
415 | "height": 100.0
416 | }
417 | ]
418 | },
419 | {
420 | "grid": 4,
421 | "rows": 4,
422 | "columns": 6,
423 | "tiles": [
424 | {
425 | "title": "1",
426 | "colIndex": 0,
427 | "rowIndex": 0,
428 | "colSpan": 1,
429 | "rowSpan": 1,
430 | "width": 100.0,
431 | "height": 100.0
432 | },
433 | {
434 | "title": "2",
435 | "colIndex": 1,
436 | "rowIndex": 0,
437 | "colSpan": 1,
438 | "rowSpan": 1,
439 | "width": 100.0,
440 | "height": 100.0
441 | },
442 | {
443 | "title": "3",
444 | "colIndex": 2,
445 | "rowIndex": 0,
446 | "colSpan": 1,
447 | "rowSpan": 1,
448 | "width": 100.0,
449 | "height": 100.0
450 | },
451 | {
452 | "title": "4",
453 | "colIndex": 3,
454 | "rowIndex": 0,
455 | "colSpan": 3,
456 | "rowSpan": 1,
457 | "width": 310.0,
458 | "height": 100.0
459 | },
460 | {
461 | "title": "5",
462 | "colIndex": 0,
463 | "rowIndex": 1,
464 | "colSpan": 2,
465 | "rowSpan": 2,
466 | "width": 210.0,
467 | "height": 210.0
468 | },
469 | {
470 | "title": "6",
471 | "colIndex": 0,
472 | "rowIndex": 3,
473 | "colSpan": 1,
474 | "rowSpan": 1,
475 | "width": 100.0,
476 | "height": 100.0
477 | },
478 | {
479 | "title": "7",
480 | "colIndex": 1,
481 | "rowIndex": 3,
482 | "colSpan": 1,
483 | "rowSpan": 1,
484 | "width": 100.0,
485 | "height": 100.0
486 | },
487 | {
488 | "title": "8",
489 | "colIndex": 2,
490 | "rowIndex": 1,
491 | "colSpan": 1,
492 | "rowSpan": 1,
493 | "width": 100.0,
494 | "height": 100.0
495 | },
496 | {
497 | "title": "9",
498 | "colIndex": 3,
499 | "rowIndex": 1,
500 | "colSpan": 1,
501 | "rowSpan": 1,
502 | "width": 100.0,
503 | "height": 100.0
504 | },
505 | {
506 | "title": "10",
507 | "colIndex": 2,
508 | "rowIndex": 2,
509 | "colSpan": 1,
510 | "rowSpan": 1,
511 | "width": 100.0,
512 | "height": 100.0
513 | },
514 | {
515 | "title": "11",
516 | "colIndex": 3,
517 | "rowIndex": 2,
518 | "colSpan": 1,
519 | "rowSpan": 1,
520 | "width": 100.0,
521 | "height": 100.0
522 | },
523 | {
524 | "title": "12",
525 | "colIndex": 2,
526 | "rowIndex": 3,
527 | "colSpan": 1,
528 | "rowSpan": 1,
529 | "width": 100.0,
530 | "height": 100.0
531 | },
532 | {
533 | "title": "13",
534 | "colIndex": 3,
535 | "rowIndex": 3,
536 | "colSpan": 1,
537 | "rowSpan": 1,
538 | "width": 100.0,
539 | "height": 100.0
540 | },
541 | {
542 | "title": "14",
543 | "colIndex": 4,
544 | "rowIndex": 1,
545 | "colSpan": 2,
546 | "rowSpan": 2,
547 | "width": 210.0,
548 | "height": 210.0
549 | },
550 | {
551 | "title": "15",
552 | "colIndex": 4,
553 | "rowIndex": 3,
554 | "colSpan": 1,
555 | "rowSpan": 1,
556 | "width": 100.0,
557 | "height": 100.0
558 | },
559 | {
560 | "title": "16",
561 | "colIndex": 5,
562 | "rowIndex": 3,
563 | "colSpan": 1,
564 | "rowSpan": 1,
565 | "width": 100.0,
566 | "height": 100.0
567 | }
568 | ]
569 | },
570 | {
571 | "grid": 5,
572 | "rows": 4,
573 | "columns": 6,
574 | "tiles": [
575 | {
576 | "title": "1",
577 | "colIndex": 0,
578 | "rowIndex": 0,
579 | "colSpan": 1,
580 | "rowSpan": 1,
581 | "width": 100.0,
582 | "height": 100.0
583 | },
584 | {
585 | "title": "2",
586 | "colIndex": 1,
587 | "rowIndex": 0,
588 | "colSpan": 1,
589 | "rowSpan": 1,
590 | "width": 100.0,
591 | "height": 100.0
592 | },
593 | {
594 | "title": "3",
595 | "colIndex": 2,
596 | "rowIndex": 0,
597 | "colSpan": 1,
598 | "rowSpan": 1,
599 | "width": 100.0,
600 | "height": 100.0
601 | },
602 | {
603 | "title": "4",
604 | "colIndex": 3,
605 | "rowIndex": 0,
606 | "colSpan": 1,
607 | "rowSpan": 1,
608 | "width": 100.0,
609 | "height": 100.0
610 | },
611 | {
612 | "title": "5",
613 | "colIndex": 4,
614 | "rowIndex": 0,
615 | "colSpan": 1,
616 | "rowSpan": 1,
617 | "width": 100.0,
618 | "height": 100.0
619 | },
620 | {
621 | "title": "6",
622 | "colIndex": 5,
623 | "rowIndex": 0,
624 | "colSpan": 1,
625 | "rowSpan": 1,
626 | "width": 100.0,
627 | "height": 100.0
628 | },
629 | {
630 | "title": "7",
631 | "colIndex": 0,
632 | "rowIndex": 1,
633 | "colSpan": 3,
634 | "rowSpan": 1,
635 | "width": 310.0,
636 | "height": 100.0
637 | },
638 | {
639 | "title": "8",
640 | "colIndex": 0,
641 | "rowIndex": 3,
642 | "colSpan": 1,
643 | "rowSpan": 1,
644 | "width": 100.0,
645 | "height": 100.0
646 | },
647 | {
648 | "title": "9",
649 | "colIndex": 1,
650 | "rowIndex": 3,
651 | "colSpan": 1,
652 | "rowSpan": 1,
653 | "width": 100.0,
654 | "height": 100.0
655 | },
656 | {
657 | "title": "10",
658 | "colIndex": 2,
659 | "rowIndex": 3,
660 | "colSpan": 1,
661 | "rowSpan": 1,
662 | "width": 100.0,
663 | "height": 100.0
664 | },
665 | {
666 | "title": "11",
667 | "colIndex": 3,
668 | "rowIndex": 1,
669 | "colSpan": 2,
670 | "rowSpan": 2,
671 | "width": 210.0,
672 | "height": 210.0
673 | },
674 | {
675 | "title": "12",
676 | "colIndex": 3,
677 | "rowIndex": 3,
678 | "colSpan": 1,
679 | "rowSpan": 1,
680 | "width": 100.0,
681 | "height": 100.0
682 | },
683 | {
684 | "title": "13",
685 | "colIndex": 4,
686 | "rowIndex": 3,
687 | "colSpan": 1,
688 | "rowSpan": 1,
689 | "width": 100.0,
690 | "height": 100.0
691 | },
692 | {
693 | "title": "14",
694 | "colIndex": 5,
695 | "rowIndex": 1,
696 | "colSpan": 1,
697 | "rowSpan": 1,
698 | "width": 100.0,
699 | "height": 100.0
700 | },
701 | {
702 | "title": "15",
703 | "colIndex": 5,
704 | "rowIndex": 2,
705 | "colSpan": 1,
706 | "rowSpan": 1,
707 | "width": 100.0,
708 | "height": 100.0
709 | },
710 | {
711 | "title": "16",
712 | "colIndex": 5,
713 | "rowIndex": 3,
714 | "colSpan": 1,
715 | "rowSpan": 1,
716 | "width": 100.0,
717 | "height": 100.0
718 | },
719 | {
720 | "title": "17",
721 | "colIndex": 0,
722 | "rowIndex": 2,
723 | "colSpan": 3,
724 | "rowSpan": 1,
725 | "width": 310.0,
726 | "height": 100.0
727 | }
728 | ]
729 | },
730 | {
731 | "grid": 6,
732 | "rows": 4,
733 | "columns": 6,
734 | "tiles": [
735 | {
736 | "title": "1",
737 | "colIndex": 0,
738 | "rowIndex": 0,
739 | "colSpan": 1,
740 | "rowSpan": 1,
741 | "width": 100.0,
742 | "height": 100.0
743 | },
744 | {
745 | "title": "2",
746 | "colIndex": 1,
747 | "rowIndex": 0,
748 | "colSpan": 1,
749 | "rowSpan": 1,
750 | "width": 100.0,
751 | "height": 100.0
752 | },
753 | {
754 | "title": "3",
755 | "colIndex": 2,
756 | "rowIndex": 0,
757 | "colSpan": 1,
758 | "rowSpan": 1,
759 | "width": 100.0,
760 | "height": 100.0
761 | },
762 | {
763 | "title": "4",
764 | "colIndex": 0,
765 | "rowIndex": 1,
766 | "colSpan": 1,
767 | "rowSpan": 1,
768 | "width": 100.0,
769 | "height": 100.0
770 | },
771 | {
772 | "title": "5",
773 | "colIndex": 1,
774 | "rowIndex": 1,
775 | "colSpan": 1,
776 | "rowSpan": 1,
777 | "width": 100.0,
778 | "height": 100.0
779 | },
780 | {
781 | "title": "6",
782 | "colIndex": 0,
783 | "rowIndex": 2,
784 | "colSpan": 1,
785 | "rowSpan": 1,
786 | "width": 100.0,
787 | "height": 100.0
788 | },
789 | {
790 | "title": "7",
791 | "colIndex": 1,
792 | "rowIndex": 2,
793 | "colSpan": 1,
794 | "rowSpan": 1,
795 | "width": 100.0,
796 | "height": 100.0
797 | },
798 | {
799 | "title": "8",
800 | "colIndex": 0,
801 | "rowIndex": 3,
802 | "colSpan": 3,
803 | "rowSpan": 1,
804 | "width": 310.0,
805 | "height": 100.0
806 | },
807 | {
808 | "title": "9",
809 | "colIndex": 2,
810 | "rowIndex": 1,
811 | "colSpan": 2,
812 | "rowSpan": 2,
813 | "width": 210.0,
814 | "height": 210.0
815 | },
816 | {
817 | "title": "10",
818 | "colIndex": 3,
819 | "rowIndex": 3,
820 | "colSpan": 1,
821 | "rowSpan": 1,
822 | "width": 100.0,
823 | "height": 100.0
824 | },
825 | {
826 | "title": "11",
827 | "colIndex": 4,
828 | "rowIndex": 1,
829 | "colSpan": 1,
830 | "rowSpan": 1,
831 | "width": 100.0,
832 | "height": 100.0
833 | },
834 | {
835 | "title": "12",
836 | "colIndex": 5,
837 | "rowIndex": 1,
838 | "colSpan": 1,
839 | "rowSpan": 1,
840 | "width": 100.0,
841 | "height": 100.0
842 | },
843 | {
844 | "title": "13",
845 | "colIndex": 4,
846 | "rowIndex": 2,
847 | "colSpan": 2,
848 | "rowSpan": 2,
849 | "width": 210.0,
850 | "height": 210.0
851 | },
852 | {
853 | "title": "14",
854 | "colIndex": 3,
855 | "rowIndex": 0,
856 | "colSpan": 3,
857 | "rowSpan": 1,
858 | "width": 310.0,
859 | "height": 100.0
860 | }
861 | ]
862 | }
863 |
864 | ]
--------------------------------------------------------------------------------