In the previous posts, we discussed the outermost code to alternate between screens and the main menu buttons:
-- enumeration with the possible menu choices type Menu = <Story=(), Editor=(), ...> task menu_button: [pos:Point, lbl:String] -> () 👈 (this post) -- receives a position and label to show task main_menu: () -> Menu { par { await spawn menu_button [[-125,35], "Story"] 3️⃣ return Menu.Story } with { await spawn menu_button [[ 125,35], "Editor"] 3️⃣ return Menu.Editor } with { ... -- other options } } -- spawns the game code spawn { 1️⃣ -- the outer loop loop { -- main menu var opt = await spawn main_menu () 2️⃣ -- chosen screen var lbl = ifs { opt ? Story { "Story" } opt ? Editor { "Editor" } ... -- other options } await spawn menu_button [[0,0], lbl] -- loops back to menu after screen terminates } } -- enters the SDL engine loop call pico_loop ()
In this post, we complete the structured main menu with the menu_button
implementation.
The outermost code (1️⃣) spawns the main_menu
task (2️⃣), which spawns several
menu_button
tasks in parallel (3️⃣).
In the code below, each button receives a position and label to show, and is
responsible for redrawing (1️⃣) and terminating itself on a mouse click (2️⃣):
task menu_button: [pos:Point, tit:String] -> () { var size: Size output Get.Size.Image [/size, "data/images/menuitem.png"] spawn { every evt?Draw { 1️⃣ output Draw.Image [arg.pos, "data/images/menuitem.png"] output Set.Font ["data/fonts/film-cryptic/Filmcryptic.ttf",45] output Draw.Text [arg.pos, arg.tit] } } await and [ 2️⃣ evt?Mouse?Button?Down isPointVsRect [evt!Mouse!Button!Down.pos, [arg.pos,size]] ] }
The menu_button
task spawns a dedicated task to redraw itself on each
occurrence of evt?Draw
(1️⃣), which is an engine event to signal that the
screen must be updated.
While the redrawing task executes in the background, the menu_button
also
waits for an evt?Mouse?Button?Down
event (2️⃣).
If the click occurs inside the button, the menu_button
task awakes and
terminates.
Its termination awakes the main_menu
task, which in turn returns the chosen
screen to the outermost code.
The relevant structured mechanism in this code is how deep nested tasks, such
as menu_button
, can react to engine events directly (1️⃣ and 2️⃣), bypassing the
task hierarchy entirely.
This self-dispatching mechanism is one of the control-flow patterns in the
previous post:
The original implementation in C++ needs to dispatch the events explicitly
through its containers hierarchy, which is split in multiple files.
Starting at the engine, the draw
method is dispatched through the path
ScreenManager
-> GUIScreen
-> GroupComponent
-> SurfaceButton
, which
ultimately draws the button on the screen:
// screen_manager.cpp:
// https://github.com/Pingus/pingus/blob/master/src/engine/screen/screen_manager.cpp#L200
void ScreenManager::update (...) {
...
get_current_screen()->draw(...);
...
}
// gui_screen.cpp
// https://github.com/Pingus/pingus/blob/master/src/engine/screen/gui_screen.cpp#L40
void GUIScreen::draw (...) {
...
gui_manager->draw(...);
...
}
// group_component.cpp
// https://github.com/Pingus/pingus/blob/master/src/engine/gui/group_component.cpp#L47
void GroupComponent::draw (...) {
...
for (Components::iterator i...) {
...
(*i)->draw(...);
}
...
}
// surface_button.cpp
// https://github.com/Pingus/pingus/blob/master/src/engine/gui/surface_button.cpp#L52
void SurfaceButton::draw (...) {
...
gc.draw(...);
...
}
Understanding the method dispatch requires examining at least 4 files, not
including the class hierarchy.
For instance, we started examining the PingusMenu
implementing the main menu,
which extends the GUIScreen
in the dispatching path.
This all only for redrawing, as the update
dispatch goes through a similar
hierarchy.
An important detail is that self dispatching in Ceu relies on two properties of its synchronous execution model:
Comment on @_fsantanna.