Skip to content

Commit 889256d

Browse files
committed
Optimization: only rebuild the grid view and its associated cells if the grid changes structurally, otherwise only rebind the cells.
1 parent 78a64e1 commit 889256d

File tree

3 files changed

+112
-84
lines changed

3 files changed

+112
-84
lines changed

desktop/src/main/kotlin/com/github/netomi/sudoku/trainer/view/CellEditView.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ class CellEditView : View()
5959
}
6060
}
6161

62-
private fun rebuildViewFromModel() {
62+
private fun rebuildViewFromModel(oldGrid: Grid?, newGrid: Grid) {
63+
// if the grid size has not changed, we dont have to change anything
64+
if (oldGrid?.gridSize == newGrid.gridSize) return
65+
6366
valuesPane.children.clear()
6467
valuesPane.rowConstraints.clear()
6568
valuesPane.columnConstraints.clear()
@@ -188,7 +191,8 @@ class CellEditView : View()
188191

189192
init {
190193
// rebuild the view when the model has changed
191-
gridProperty.onChange { rebuildViewFromModel() }
194+
gridProperty.addListener(ChangeListener { _, oldGrid: Grid?, newGrid: Grid -> rebuildViewFromModel(oldGrid, newGrid) })
195+
192196
cellProperty.onChange {
193197
valuesPane.children.forEach { it.isDisable = true }
194198
refreshView()

desktop/src/main/kotlin/com/github/netomi/sudoku/trainer/view/CellFragment.kt

Lines changed: 83 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
package com.github.netomi.sudoku.trainer.view
2525

26-
import javafx.beans.property.IntegerProperty
27-
import javafx.beans.property.SimpleIntegerProperty
2826
import javafx.collections.FXCollections
2927
import javafx.collections.ObservableIntegerArray
3028
import javafx.event.EventHandler
@@ -45,8 +43,7 @@ import com.github.netomi.sudoku.solver.LinkType.WEAK
4543
import com.github.netomi.sudoku.trainer.Styles
4644
import com.github.netomi.sudoku.trainer.model.DisplayOptions
4745
import com.github.netomi.sudoku.trainer.pseudoClass
48-
import javafx.beans.property.BooleanProperty
49-
import javafx.beans.property.SimpleBooleanProperty
46+
import javafx.beans.property.*
5047
import javafx.css.PseudoClass
5148
import javafx.geometry.Insets
5249
import javafx.scene.layout.*
@@ -56,8 +53,11 @@ import kotlin.math.sign
5653
/**
5754
* The view to display the state of an individual cell within a sudoku grid.
5855
*/
59-
class CellFragment(private val cell: Cell) : Fragment()
56+
class CellFragment(cellArgument: Cell) : Fragment()
6057
{
58+
private val cellProperty: ObjectProperty<Cell> = SimpleObjectProperty(cellArgument)
59+
var cell: Cell by cellProperty
60+
6161
private val valueProperty: IntegerProperty = SimpleIntegerProperty(0)
6262
private var value by valueProperty
6363

@@ -66,12 +66,77 @@ class CellFragment(private val cell: Cell) : Fragment()
6666
val selectedProperty: BooleanProperty = SimpleBooleanProperty(false)
6767
var selected by selectedProperty
6868

69-
private val candidatesPane: GridPane
70-
private val assignedValueLabel: Label
69+
private var candidatesPane by singleAssign<GridPane>()
70+
private var assignedValueLabel by singleAssign<Label>()
7171
// a simple pane to easily indicate the currently focused cell.
72-
private val selectPane: Pane
72+
private var selectPane by singleAssign<Pane>()
73+
74+
override val root = stackpane {
75+
addClass(Styles.sudokuCell)
76+
77+
selectPane = pane {
78+
useMaxSize = true
79+
id = Styles.cellSelectPane.name
80+
}
81+
82+
setMinSize(30.0, 30.0)
83+
useMaxSize = true
84+
85+
val rows = when (cell.owner.gridSize) {
86+
4 -> 2
87+
6 -> 2
88+
9 -> 3
89+
else -> kotlin.error("unexpected grid size ${cell.owner.gridSize}")
90+
}
91+
val cols = cell.owner.gridSize / rows
92+
93+
val percentageRow = 100.0 / rows
94+
val percentageCol = 100.0 / cols
95+
96+
candidatesPane = gridpane {
97+
var possibleValue = 1
98+
for (i in 0 until rows) {
99+
for (j in 0 until cols) {
100+
label {
101+
useMaxHeight = true
102+
maxWidth = 36.0
103+
104+
id = Styles.cellCandidate.name
105+
text = possibleValue.toString()
106+
107+
gridpaneConstraints {
108+
margin = Insets(3.0)
109+
columnRowIndex(j, i)
110+
}
111+
}
112+
possibleValue++
113+
}
114+
}
73115

74-
override val root = stackpane {}
116+
for (i in 0 until rows) {
117+
val row = RowConstraints(10.0, 20.0, Double.MAX_VALUE)
118+
row.vgrow = Priority.ALWAYS
119+
row.valignment = VPos.CENTER
120+
row.percentHeight = percentageRow
121+
rowConstraints.add(row)
122+
}
123+
124+
for (i in 0 until cols) {
125+
val col = ColumnConstraints(10.0, 20.0, Double.MAX_VALUE)
126+
col.hgrow = Priority.ALWAYS
127+
col.halignment = HPos.CENTER
128+
col.percentWidth = percentageCol
129+
columnConstraints.add(col)
130+
}
131+
}
132+
133+
assignedValueLabel = label {
134+
id = Styles.cellValue.name
135+
isVisible = false
136+
137+
textProperty().bind(valueProperty.asString())
138+
}
139+
}
75140

76141
fun refreshView(conflicts: Array<Conflict>, displayedHint: Hint?) {
77142
assignedValueLabel.apply {
@@ -112,6 +177,12 @@ class CellFragment(private val cell: Cell) : Fragment()
112177
}
113178
}
114179

180+
fun resetView() {
181+
selected = false
182+
value = 0
183+
possibleValuesProperty.clear()
184+
}
185+
115186
private fun processHint(hint: Hint?): Array<CandidateState> {
116187
val result = Array(cell.owner.gridSize + 1) { _ -> CandidateState.NONE }
117188

@@ -314,74 +385,9 @@ class CellFragment(private val cell: Cell) : Fragment()
314385
}
315386

316387
init {
317-
with(root) {
318-
addClass(Styles.sudokuCell)
319-
320-
selectPane = pane {
321-
useMaxSize = true
322-
id = Styles.cellSelectPane.name
323-
}
324-
325-
style += getBorderStyle(cell)
326-
327-
setMinSize(30.0, 30.0)
328-
useMaxSize = true
329-
330-
val rows = when (cell.owner.gridSize) {
331-
4 -> 2
332-
6 -> 2
333-
9 -> 3
334-
else -> kotlin.error("unexpected grid size $cell.owner.gridSize")
335-
}
336-
val cols = cell.owner.gridSize / rows
337-
338-
val percentageRow = 100.0 / rows
339-
val percentageCol = 100.0 / cols
340-
341-
candidatesPane = gridpane {
342-
var possibleValue = 1
343-
for (i in 0 until rows) {
344-
for (j in 0 until cols) {
345-
label {
346-
useMaxHeight = true
347-
maxWidth = 36.0
348-
349-
id = Styles.cellCandidate.name
350-
text = possibleValue.toString()
351-
352-
gridpaneConstraints {
353-
margin = Insets(3.0)
354-
columnRowIndex(j, i)
355-
}
356-
}
357-
possibleValue++
358-
}
359-
}
360-
361-
for (i in 0 until rows) {
362-
val row = RowConstraints(10.0, 20.0, Double.MAX_VALUE)
363-
row.vgrow = Priority.ALWAYS
364-
row.valignment = VPos.CENTER
365-
row.percentHeight = percentageRow
366-
rowConstraints.add(row)
367-
}
368-
369-
for (i in 0 until cols) {
370-
val col = ColumnConstraints(10.0, 20.0, Double.MAX_VALUE)
371-
col.hgrow = Priority.ALWAYS
372-
col.halignment = HPos.CENTER
373-
col.percentWidth = percentageCol
374-
columnConstraints.add(col)
375-
}
376-
}
377-
378-
assignedValueLabel = label {
379-
id = Styles.cellValue.name
380-
isVisible = false
381-
}
382-
}
383-
384-
assignedValueLabel.textProperty().bind(valueProperty.asString())
388+
val updateStyle: (Cell) -> Unit = { root.style = getBorderStyle(it) }
389+
cellProperty.onChange { it?.let(updateStyle) }
390+
updateStyle.invoke(cell)
385391

386392
valueProperty.onChange { newValue ->
387393
candidatesPane.isVisible = newValue == 0

desktop/src/main/kotlin/com/github/netomi/sudoku/trainer/view/GridView.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ class GridView : View()
5252
private val cellFragmentList = ArrayList<CellFragment>()
5353

5454
private val selectedCellFragmentProperty: ObjectProperty<CellFragment?> = SimpleObjectProperty()
55+
private var selectedCellFragment: CellFragment? by selectedCellFragmentProperty
56+
5557
private val selectedCellProperty: ObjectProperty<Cell?> = SimpleObjectProperty()
58+
private var selectedCell: Cell? by selectedCellProperty
5659

5760
override val root =
5861
borderpane {
@@ -76,7 +79,22 @@ class GridView : View()
7679
bottom = cellEditView.root
7780
}
7881

79-
private fun rebuildViewFromModel() {
82+
private fun rebuildViewFromModel(oldGrid: Grid?, newGrid: Grid) {
83+
selectedCellFragment = null
84+
selectedCell = null
85+
86+
if (oldGrid?.type == newGrid.type && cellFragmentList.isNotEmpty()) {
87+
newGrid.cells.forEachIndexed { index, cell ->
88+
cellFragmentList[index].apply {
89+
this.cell = cell
90+
this.resetView()
91+
}
92+
}
93+
newGrid.onUpdate { refreshView() }
94+
refreshView()
95+
return
96+
}
97+
8098
gridPane.children.clear()
8199
gridPane.rowConstraints.clear()
82100
gridPane.columnConstraints.clear()
@@ -94,9 +112,9 @@ class GridView : View()
94112

95113
cellFragment.selectedProperty.onChange { selected ->
96114
if (selected) {
97-
selectedCellFragmentProperty.get()?.selected = false
98-
selectedCellFragmentProperty.set(cellFragment)
99-
selectedCellProperty.set(cell)
115+
selectedCellFragment?.selected = false
116+
selectedCellFragment = cellFragment
117+
selectedCell = cellFragment.cell
100118
}
101119
}
102120
}
@@ -157,7 +175,7 @@ class GridView : View()
157175
}
158176

159177
init {
160-
gridController.gridProperty.onChange { rebuildViewFromModel() }
178+
gridController.gridProperty.addListener(ChangeListener { _, oldGrid: Grid?, newGrid: Grid -> rebuildViewFromModel(oldGrid, newGrid) })
161179

162180
// bind the current model and selected cell to the cell edit view
163181
cellEditView.gridProperty.bind(gridController.gridProperty)

0 commit comments

Comments
 (0)