Add a GUI. Use FAKE to manage the build and paket for dependencies.
[sudokuSolver.git] / SudokuSolver / GUI.fs
1 module SudokuSolver.GUI
2
3 open System
4 open System.Threading
5 open System.Collections.ObjectModel
6
7 open Eto
8 open Eto.Forms
9 open Eto.Drawing
10
11 module Solver = Version1
12
13 type DigitBox() as this =
14 inherit Panel()
15 let unselectedBgColor = Colors.LightSkyBlue
16 let selectedBgColor = Colors.Blue
17 let digitChanged = new Event<unit>()
18 let mutable manuallyAssigned = false
19 let mutable value = 0
20 let label =
21 new Label(
22 BackgroundColor = Colors.White,
23 Text = " ",
24 TextAlignment = TextAlignment.Center,
25 VerticalAlignment = VerticalAlignment.Center,
26 Font = new Font("Monospace", 18.f))
27
28 do
29 this.Padding <- Padding(2)
30 this.BackgroundColor <- unselectedBgColor
31 this.Content <- label
32
33 member this.Selected
34 with set (value: bool) =
35 this.BackgroundColor <- if value then selectedBgColor else unselectedBgColor
36
37 member this.ManuallyAssigned
38 with get () : bool = manuallyAssigned
39 and private set (value: bool) =
40 label.BackgroundColor <- if value then Colors.Orange else Colors.White
41 manuallyAssigned <- value
42
43 member this.DigitChanged = digitChanged.Publish
44
45 member this.Value = value
46
47 member this.SetValue(v, manuallyAssigned) =
48 if manuallyAssigned || not this.ManuallyAssigned || v = 0 then
49 let changed = value <> v
50 value <- v
51 label.Text <- if value > 0 && value <= 9 then string value else " "
52 this.ManuallyAssigned <- v <> 0 && manuallyAssigned
53 if manuallyAssigned && changed then
54 digitChanged.Trigger()
55
56 type Grid() =
57 inherit TableLayout()
58
59 type MainForm() as this =
60 inherit Form()
61 do
62 let mutable currentAsync : Async<Solver.Board> option = None
63 let mutable cancellation = new CancellationTokenSource()
64
65 this.Title <- "Sudoku Solver - gburri"
66 this.Size <- Size(400, 400)
67 let digitBoxes = Array2D.init 9 9 (fun _ _ -> new DigitBox())
68
69 let clearComputedDigits () =
70 digitBoxes |> Array2D.iter
71 (fun d ->
72 if not d.ManuallyAssigned then
73 d.SetValue(0, false))
74
75 let computeSolution () =
76 cancellation.Cancel()
77 cancellation.Dispose()
78 cancellation <- new CancellationTokenSource()
79 let board =
80 Solver.Board(
81 digitBoxes
82 |> Array2D.map (fun d -> if d.ManuallyAssigned then d.Value else 0))
83
84 clearComputedDigits ()
85
86 let token = cancellation.Token
87 async {
88 let! result = board.SolveAsync(token)
89 if not token.IsCancellationRequested then
90 Application.Instance.Invoke(
91 fun () ->
92 if result then
93 Array2D.iteri (fun i j v -> digitBoxes.[i, j].SetValue(v, false)) board.Values
94 else
95 clearComputedDigits ()) }
96 |> Async.Start
97
98 let mutable currentDigitBox = digitBoxes.[0,0]
99 digitBoxes.[0,0].Selected <- true
100 this.KeyDown.Add(
101 fun e ->
102 if e.Key = Keys.Backspace || e.Key = Keys.Delete then
103 currentDigitBox.SetValue(0, true)
104 else
105 match Int32.TryParse(e.KeyChar.ToString()) with
106 | (true, digit) when digit >= 0 && digit <= 9 -> currentDigitBox.SetValue(digit, true)
107 | _ -> ())
108
109 let setCurrentDigitBox db =
110 if db <> currentDigitBox then
111 currentDigitBox.Selected <- false
112 db.Selected <- true
113 currentDigitBox <- db
114
115 digitBoxes
116 |> Array2D.iter
117 (fun digitBox ->
118 digitBox.MouseDown.Add(fun _ -> setCurrentDigitBox digitBox)
119 digitBox.DigitChanged.Add (fun _ -> computeSolution ()))
120
121 let gridLayout = new Grid()
122
123 for i = 0 to 8 do
124 // Horizontal separations.
125 if i = 3 || i = 6 then
126 let rowSeparation = new TableRow()
127 gridLayout.Rows.Add(rowSeparation)
128 for j = 0 to 10 do
129 rowSeparation.Cells.Add(new TableCell(new Panel(BackgroundColor = Colors.Black)))
130 let row = new TableRow(ScaleHeight = true)
131 gridLayout.Rows.Add(row)
132 for j = 0 to 8 do
133 // Vertical separations.
134 if j = 3 || j = 6 then
135 row.Cells.Add(new TableCell(new Panel(BackgroundColor = Colors.Black)))
136 row.Cells.Add(new TableCell(digitBoxes.[i, j], true))
137
138 this.Content <- gridLayout
139 computeSolution ()
140
141 let showMainWindow () =
142 use app = new Application()
143 use form = new MainForm()
144 form.Show()
145 app.Run(form) |> ignore