├── 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 | ![alttext](https://github.com/ahinchman1/TornadoFX-DnD-TilesFX/blob/master/readme.png) 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 | ] --------------------------------------------------------------------------------