Custom interfaces¶
The file examples/interface.py showcases the integration of the maze widget into a custom interface.
1from PyQt5.QtCore import QTimer
2from PyQt5.QtWidgets import (
3 QWidget,
4 QHBoxLayout,
5 QFormLayout,
6 QSpinBox,
7 QDoubleSpinBox,
8 QCheckBox,
9 QComboBox,
10)
11
12import amaze
As usual we start by importing the required package. Here, however, we will explicitly qualify every amaze members to better distinguish from class imported from PyQT (which all start with a Q).
15class MainWindow(QWidget):
To make this page understandable, we define a class holding everything together. The main, presented at the bottom will only have to create our custom class and display it.
16 def __init__(self):
17 super().__init__()
18 self.resize(640, 480)
19
20 # Create horizontal layout
21 layout = QHBoxLayout()
22 self.setLayout(layout)
23
24 # Create dedicated widget for rendering mazes
25 self.maze_widget = amaze.MazeWidget(
26 self._maze_data(0, 5, 0, True, amaze.StartLocation.SOUTH_WEST),
27 amaze.Robot(amaze.Robot.BuildData.from_string("D")),
28 dict(robot=False),
29 )
30 layout.addWidget(self.maze_widget)
31
32 # Build content
33 self.widgets, sub_layout = self._create_widgets()
34 layout.addLayout(sub_layout)
First, the constructor delegates the bulk of creating a top-level widget to the
PyQT library.
We, then, create a horizontal layout to lay multiple items next to one another.
In this primitive interface, we place a
MazeWidget on the left while a secondary
layout will hold the configuration widgets.
36 def _create_widgets(self):
37 # Create a secondary vertical layout for inputs
38 sub_layout = QFormLayout()
39
40 widgets = {}
41
42 def _add(cls, name, signal, func=None):
43 sub_layout.addRow(name, _w := cls())
44 if func is not None:
45 func(_w)
46 getattr(_w, signal).connect(self.reset_maze)
47 widgets[name] = _w
48
49 _add(QSpinBox, "Seed", "valueChanged")
50
51 _add(QSpinBox, "Size", "valueChanged", lambda w: w.setRange(5, 20))
52
53 _add(
54 QDoubleSpinBox,
55 "Lures",
56 "valueChanged",
57 lambda w: (w.setRange(0, 100), w.setSuffix("%")),
58 )
59
60 _add(QCheckBox, "Unicursive", "clicked", lambda w: w.setChecked(True))
61
62 _add(
63 QComboBox,
64 "Start",
65 "currentTextChanged",
66 lambda w: w.addItems([s.name for s in amaze.StartLocation]),
67 )
68
69 return widgets, sub_layout
We then create specific widgets to customize the maze thanks to the helper
function _add.
It’s job is to instantiate the widget and add it to the layout.
The QFormLayout is a special case of vertical layout
that places, next to one another, a widget and its string label.
If the newly created widget needs further configuration we call the provided
function to set up things like range or content.
We then connect the widget’s signal to our reset_maze function so that
whenever the user inputs new values, the maze is changed accordingly.
Finally, we store everything for future reference.
For this interface we provide 5 configurable options of various types:
Two
QSpinBoxwill provide integer input in a given rangeOne
QDoubleSpinBoxprovides the same functionality but for float valuesA
QCheckBoxallows binary inputA
QComboBoxlets the user choose from amongst a set of strings
71 def reset_maze(self):
72 self.maze_widget.set_maze(
73 self._maze_data(
74 self.widgets["Seed"].value(),
75 self.widgets["Size"].value(),
76 self.widgets["Lures"].value() / 100,
77 self.widgets["Unicursive"].isChecked(),
78 amaze.StartLocation[self.widgets["Start"].currentText()],
79 )
80 )
The function used to reset the maze is rather trivial as it only fetches values
from the interface to populate a BuildData
dataclass.
However it also demonstrates of the specific widgets we used expose their
values programatically: value for (Double)SpinBox, isChecked for CheckBox and
currentText for ComboBox.
99def main(is_test=False):
100 # Create main QT objects
101 app = amaze.qt_application()
102 window = MainWindow()
103 window.show()
104
105 if is_test:
106 QTimer.singleShot(1000, lambda: window.close())
107
108 # Run
109 app.exec()
Finally, as stated above, the consists only of creating an application and our primitive main window, requesting it to be shown and letting PyQT handle the rest.
As before, the full listing of the example is provided below.
1from PyQt5.QtCore import QTimer
2from PyQt5.QtWidgets import (
3 QWidget,
4 QHBoxLayout,
5 QFormLayout,
6 QSpinBox,
7 QDoubleSpinBox,
8 QCheckBox,
9 QComboBox,
10)
11
12import amaze
13
14
15class MainWindow(QWidget):
16 def __init__(self):
17 super().__init__()
18 self.resize(640, 480)
19
20 # Create horizontal layout
21 layout = QHBoxLayout()
22 self.setLayout(layout)
23
24 # Create dedicated widget for rendering mazes
25 self.maze_widget = amaze.MazeWidget(
26 self._maze_data(0, 5, 0, True, amaze.StartLocation.SOUTH_WEST),
27 amaze.Robot(amaze.Robot.BuildData.from_string("D")),
28 dict(robot=False),
29 )
30 layout.addWidget(self.maze_widget)
31
32 # Build content
33 self.widgets, sub_layout = self._create_widgets()
34 layout.addLayout(sub_layout)
35
36 def _create_widgets(self):
37 # Create a secondary vertical layout for inputs
38 sub_layout = QFormLayout()
39
40 widgets = {}
41
42 def _add(cls, name, signal, func=None):
43 sub_layout.addRow(name, _w := cls())
44 if func is not None:
45 func(_w)
46 getattr(_w, signal).connect(self.reset_maze)
47 widgets[name] = _w
48
49 _add(QSpinBox, "Seed", "valueChanged")
50
51 _add(QSpinBox, "Size", "valueChanged", lambda w: w.setRange(5, 20))
52
53 _add(
54 QDoubleSpinBox,
55 "Lures",
56 "valueChanged",
57 lambda w: (w.setRange(0, 100), w.setSuffix("%")),
58 )
59
60 _add(QCheckBox, "Unicursive", "clicked", lambda w: w.setChecked(True))
61
62 _add(
63 QComboBox,
64 "Start",
65 "currentTextChanged",
66 lambda w: w.addItems([s.name for s in amaze.StartLocation]),
67 )
68
69 return widgets, sub_layout
70
71 def reset_maze(self):
72 self.maze_widget.set_maze(
73 self._maze_data(
74 self.widgets["Seed"].value(),
75 self.widgets["Size"].value(),
76 self.widgets["Lures"].value() / 100,
77 self.widgets["Unicursive"].isChecked(),
78 amaze.StartLocation[self.widgets["Start"].currentText()],
79 )
80 )
81
82 @staticmethod
83 def _maze_data(seed, size, p_lure, easy, start):
84 return amaze.Maze.BuildData(
85 seed=seed,
86 width=size,
87 height=size,
88 unicursive=easy,
89 rotated=True,
90 start=start,
91 clue=[amaze.Sign(value=1)],
92 p_lure=p_lure,
93 lure=[amaze.Sign(value=0.5)],
94 p_trap=0,
95 trap=[],
96 )
97
98
99def main(is_test=False):
100 # Create main QT objects
101 app = amaze.qt_application()
102 window = MainWindow()
103 window.show()
104
105 if is_test:
106 QTimer.singleShot(1000, lambda: window.close())
107
108 # Run
109 app.exec()
110
111
112if __name__ == "__main__":
113 main()