mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 07:37:46 +00:00
GameOfLife: Add choosable patterns
This commit is contained in:
parent
b808815e57
commit
1965d60aeb
6 changed files with 309 additions and 7 deletions
|
@ -1,22 +1,36 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021, Andres Crucitti <dasc495@gmail.com>
|
* Copyright (c) 2021, Andres Crucitti <dasc495@gmail.com>
|
||||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||||
|
* Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "BoardWidget.h"
|
#include "BoardWidget.h"
|
||||||
|
#include <LibGUI/Menu.h>
|
||||||
#include <LibGUI/Painter.h>
|
#include <LibGUI/Painter.h>
|
||||||
|
|
||||||
BoardWidget::BoardWidget(size_t rows, size_t columns)
|
BoardWidget::BoardWidget(size_t rows, size_t columns)
|
||||||
: m_board(make<Board>(rows, columns))
|
: m_board(make<Board>(rows, columns))
|
||||||
{
|
{
|
||||||
m_timer = add<Core::Timer>();
|
m_timer = add<Core::Timer>();
|
||||||
|
m_pattern_preview_timer = add<Core::Timer>();
|
||||||
m_timer->stop();
|
m_timer->stop();
|
||||||
|
m_pattern_preview_timer->stop();
|
||||||
m_timer->on_timeout = [this] {
|
m_timer->on_timeout = [this] {
|
||||||
run_generation();
|
run_generation();
|
||||||
};
|
};
|
||||||
|
m_pattern_preview_timer->on_timeout = [this] {
|
||||||
|
update();
|
||||||
|
};
|
||||||
m_timer->set_interval(m_running_timer_interval);
|
m_timer->set_interval(m_running_timer_interval);
|
||||||
|
m_pattern_preview_timer->set_interval(m_running_pattern_preview_timer_interval);
|
||||||
|
|
||||||
|
on_pattern_selection = [this](auto* pattern) {
|
||||||
|
m_selected_pattern = pattern;
|
||||||
|
};
|
||||||
|
|
||||||
|
setup_patterns();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoardWidget::run_generation()
|
void BoardWidget::run_generation()
|
||||||
|
@ -128,6 +142,20 @@ void BoardWidget::paint_event(GUI::PaintEvent& event)
|
||||||
fill_color = Color::MidGray;
|
fill_color = Color::MidGray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_selected_pattern != nullptr) {
|
||||||
|
int y_offset = 0;
|
||||||
|
for (auto line : m_selected_pattern->pattern()) {
|
||||||
|
int x_offset = 0;
|
||||||
|
for (auto c : line) {
|
||||||
|
if (c == 'O' && (m_last_cell_hovered.row + y_offset) < m_board->rows()
|
||||||
|
&& (m_last_cell_hovered.column + x_offset) < m_board->columns() && row == (m_last_cell_hovered.row + y_offset) && column == (m_last_cell_hovered.column + x_offset))
|
||||||
|
fill_color = Color::Green;
|
||||||
|
x_offset++;
|
||||||
|
}
|
||||||
|
y_offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
painter.fill_rect(cell_rect, fill_color);
|
painter.fill_rect(cell_rect, fill_color);
|
||||||
if (cell_size > 4) {
|
if (cell_size > 4) {
|
||||||
painter.draw_rect(cell_rect, border_color);
|
painter.draw_rect(cell_rect, border_color);
|
||||||
|
@ -138,12 +166,32 @@ void BoardWidget::paint_event(GUI::PaintEvent& event)
|
||||||
|
|
||||||
void BoardWidget::mousedown_event(GUI::MouseEvent& event)
|
void BoardWidget::mousedown_event(GUI::MouseEvent& event)
|
||||||
{
|
{
|
||||||
|
if (event.button() == GUI::MouseButton::Left) {
|
||||||
set_toggling_cells(true);
|
set_toggling_cells(true);
|
||||||
auto row_and_column = get_row_and_column_for_point(event.x(), event.y());
|
auto row_and_column = get_row_and_column_for_point(event.x(), event.y());
|
||||||
if (!row_and_column.has_value())
|
if (!row_and_column.has_value())
|
||||||
return;
|
return;
|
||||||
auto [row, column] = row_and_column.value();
|
auto [row, column] = row_and_column.value();
|
||||||
|
if (m_selected_pattern == nullptr)
|
||||||
toggle_cell(row, column);
|
toggle_cell(row, column);
|
||||||
|
else
|
||||||
|
place_pattern(row, column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoardWidget::context_menu_event(GUI::ContextMenuEvent& event)
|
||||||
|
{
|
||||||
|
if (!m_context_menu) {
|
||||||
|
m_context_menu = GUI::Menu::construct();
|
||||||
|
|
||||||
|
auto& insert_pattern_menu = m_context_menu->add_submenu("&Insert Pattern");
|
||||||
|
for_each_pattern([&](auto& pattern) {
|
||||||
|
if (pattern.action())
|
||||||
|
insert_pattern_menu.add_action(*pattern.action());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!m_running)
|
||||||
|
m_context_menu->popup(event.screen_position());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoardWidget::mousemove_event(GUI::MouseEvent& event)
|
void BoardWidget::mousemove_event(GUI::MouseEvent& event)
|
||||||
|
@ -156,6 +204,11 @@ void BoardWidget::mousemove_event(GUI::MouseEvent& event)
|
||||||
if (m_last_cell_toggled.row != row || m_last_cell_toggled.column != column)
|
if (m_last_cell_toggled.row != row || m_last_cell_toggled.column != column)
|
||||||
toggle_cell(row, column);
|
toggle_cell(row, column);
|
||||||
}
|
}
|
||||||
|
m_last_cell_hovered = { row, column };
|
||||||
|
if (m_selected_pattern != nullptr) {
|
||||||
|
if (!m_pattern_preview_timer->is_active())
|
||||||
|
m_pattern_preview_timer->start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BoardWidget::mouseup_event(GUI::MouseEvent&)
|
void BoardWidget::mouseup_event(GUI::MouseEvent&)
|
||||||
|
@ -178,3 +231,156 @@ Optional<Board::RowAndColumn> BoardWidget::get_row_and_column_for_point(int x, i
|
||||||
.column = static_cast<size_t>((x - board_offset.width()) / cell_size),
|
.column = static_cast<size_t>((x - board_offset.width()) / cell_size),
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BoardWidget::place_pattern(size_t row, size_t column)
|
||||||
|
{
|
||||||
|
int y_offset = 0;
|
||||||
|
for (auto line : m_selected_pattern->pattern()) {
|
||||||
|
int x_offset = 0;
|
||||||
|
for (auto c : line) {
|
||||||
|
if (c == 'O' && (row + y_offset) < m_board->rows() && (column + x_offset) < m_board->columns())
|
||||||
|
toggle_cell(row + y_offset, column + x_offset);
|
||||||
|
x_offset++;
|
||||||
|
}
|
||||||
|
y_offset++;
|
||||||
|
}
|
||||||
|
m_selected_pattern = nullptr;
|
||||||
|
if (m_pattern_preview_timer->is_active())
|
||||||
|
m_pattern_preview_timer->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoardWidget::setup_patterns()
|
||||||
|
{
|
||||||
|
auto add_pattern = [&](String name, NonnullOwnPtr<Pattern> pattern) {
|
||||||
|
auto action = GUI::Action::create(move(name), [this, pattern = pattern.ptr()](const GUI::Action&) {
|
||||||
|
on_pattern_selection(pattern);
|
||||||
|
});
|
||||||
|
pattern->set_action(action);
|
||||||
|
m_patterns.append(move(pattern));
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> blinker = {
|
||||||
|
"OOO"
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> toad = {
|
||||||
|
".OOO",
|
||||||
|
"OOO."
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> glider = {
|
||||||
|
".O.",
|
||||||
|
"..O",
|
||||||
|
"OOO",
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> lightweight_spaceship = {
|
||||||
|
".OO..",
|
||||||
|
"OOOO.",
|
||||||
|
"OO.OO",
|
||||||
|
"..OO."
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> middleweight_spaceship = {
|
||||||
|
".OOOOO",
|
||||||
|
"O....O",
|
||||||
|
".....O",
|
||||||
|
"O...O.",
|
||||||
|
"..O..."
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> heavyweight_spaceship = {
|
||||||
|
"..OO...",
|
||||||
|
"O....O.",
|
||||||
|
"......O",
|
||||||
|
"O.....O",
|
||||||
|
".OOOOOO"
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> infinite_1 = { "OOOOOOOO.OOOOO...OOO......OOOOOOO.OOOOO" };
|
||||||
|
|
||||||
|
Vector<String> infinite_2 = {
|
||||||
|
"......O.",
|
||||||
|
"....O.OO",
|
||||||
|
"....O.O.",
|
||||||
|
"....O...",
|
||||||
|
"..O.....",
|
||||||
|
"O.O....."
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> infinite_3 = {
|
||||||
|
"OOO.O",
|
||||||
|
"O....",
|
||||||
|
"...OO",
|
||||||
|
".OO.O",
|
||||||
|
"O.O.O"
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> simkin_glider_gun = {
|
||||||
|
"OO.....OO........................",
|
||||||
|
"OO.....OO........................",
|
||||||
|
".................................",
|
||||||
|
"....OO...........................",
|
||||||
|
"....OO...........................",
|
||||||
|
".................................",
|
||||||
|
".................................",
|
||||||
|
".................................",
|
||||||
|
".................................",
|
||||||
|
"......................OO.OO......",
|
||||||
|
".....................O.....O.....",
|
||||||
|
".....................O......O..OO",
|
||||||
|
".....................OOO...O...OO",
|
||||||
|
"..........................O......",
|
||||||
|
".................................",
|
||||||
|
".................................",
|
||||||
|
".................................",
|
||||||
|
"....................OO...........",
|
||||||
|
"....................O............",
|
||||||
|
".....................OOO.........",
|
||||||
|
".......................O........."
|
||||||
|
};
|
||||||
|
Vector<String> gosper_glider_gun = {
|
||||||
|
"........................O...........",
|
||||||
|
"......................O.O...........",
|
||||||
|
"............OO......OO............OO",
|
||||||
|
"...........O...O....OO............OO",
|
||||||
|
"OO........O.....O...OO..............",
|
||||||
|
"OO........O...O.OO....O.O...........",
|
||||||
|
"..........O.....O.......O...........",
|
||||||
|
"...........O...O....................",
|
||||||
|
"............OO......................"
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> r_pentomino = {
|
||||||
|
".OO",
|
||||||
|
"OO.",
|
||||||
|
".O."
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> diehard = {
|
||||||
|
"......O.",
|
||||||
|
"OO......",
|
||||||
|
".O...OOO"
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector<String> acorn = {
|
||||||
|
".O.....",
|
||||||
|
"...O...",
|
||||||
|
"OO..OOO"
|
||||||
|
};
|
||||||
|
|
||||||
|
add_pattern("Blinker", make<Pattern>(move(blinker)));
|
||||||
|
add_pattern("Toad", make<Pattern>(move(toad)));
|
||||||
|
add_pattern("Glider", make<Pattern>(move(glider)));
|
||||||
|
add_pattern("Lightweight Spaceship", make<Pattern>(move(lightweight_spaceship)));
|
||||||
|
add_pattern("Middleweight Spaceship", make<Pattern>(move(middleweight_spaceship)));
|
||||||
|
add_pattern("Heavyweight Spaceship", make<Pattern>(move(heavyweight_spaceship)));
|
||||||
|
add_pattern("Infinite 1", make<Pattern>(move(infinite_1)));
|
||||||
|
add_pattern("Infinite 2", make<Pattern>(move(infinite_2)));
|
||||||
|
add_pattern("Infinite 3", make<Pattern>(move(infinite_3)));
|
||||||
|
add_pattern("R-Pentomino", make<Pattern>(move(r_pentomino)));
|
||||||
|
add_pattern("Diehard", make<Pattern>(move(diehard)));
|
||||||
|
add_pattern("Acorn", make<Pattern>(move(acorn)));
|
||||||
|
add_pattern("Simkin's Glider Gun", make<Pattern>(move(simkin_glider_gun)));
|
||||||
|
add_pattern("Gosper's Glider Gun", make<Pattern>(move(gosper_glider_gun)));
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021, Andres Crucitti <dasc495@gmail.com>
|
* Copyright (c) 2021, Andres Crucitti <dasc495@gmail.com>
|
||||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||||
|
* Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -8,11 +9,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Board.h"
|
#include "Board.h"
|
||||||
|
#include "Pattern.h"
|
||||||
#include <AK/Function.h>
|
#include <AK/Function.h>
|
||||||
#include <AK/NonnullOwnPtr.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
#include <AK/Optional.h>
|
#include <AK/Optional.h>
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
|
#include <LibGUI/Menu.h>
|
||||||
#include <LibGUI/Widget.h>
|
#include <LibGUI/Widget.h>
|
||||||
|
|
||||||
class BoardWidget final : public GUI::Widget {
|
class BoardWidget final : public GUI::Widget {
|
||||||
|
@ -23,6 +26,7 @@ public:
|
||||||
virtual void mousemove_event(GUI::MouseEvent&) override;
|
virtual void mousemove_event(GUI::MouseEvent&) override;
|
||||||
virtual void mouseup_event(GUI::MouseEvent&) override;
|
virtual void mouseup_event(GUI::MouseEvent&) override;
|
||||||
virtual void mousedown_event(GUI::MouseEvent&) override;
|
virtual void mousedown_event(GUI::MouseEvent&) override;
|
||||||
|
virtual void context_menu_event(GUI::ContextMenuEvent&) override;
|
||||||
|
|
||||||
void set_toggling_cells(bool toggling)
|
void set_toggling_cells(bool toggling)
|
||||||
{
|
{
|
||||||
|
@ -46,6 +50,15 @@ public:
|
||||||
bool is_running() const { return m_running; }
|
bool is_running() const { return m_running; }
|
||||||
void set_running(bool r);
|
void set_running(bool r);
|
||||||
|
|
||||||
|
Pattern* selected_pattern() { return m_selected_pattern; }
|
||||||
|
Function<void(Pattern*)> on_pattern_selection;
|
||||||
|
template<typename Callback>
|
||||||
|
void for_each_pattern(Callback callback)
|
||||||
|
{
|
||||||
|
for (auto& pattern : m_patterns)
|
||||||
|
callback(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
void run_generation();
|
void run_generation();
|
||||||
|
|
||||||
int running_timer_interval() const { return m_running_timer_interval; }
|
int running_timer_interval() const { return m_running_timer_interval; }
|
||||||
|
@ -57,14 +70,24 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BoardWidget(size_t rows, size_t columns);
|
BoardWidget(size_t rows, size_t columns);
|
||||||
|
void setup_patterns();
|
||||||
|
void place_pattern(size_t row, size_t column);
|
||||||
|
|
||||||
bool m_toggling_cells { false };
|
bool m_toggling_cells { false };
|
||||||
Board::RowAndColumn m_last_cell_toggled {};
|
Board::RowAndColumn m_last_cell_toggled {};
|
||||||
|
Board::RowAndColumn m_last_cell_hovered {};
|
||||||
|
Pattern* m_selected_pattern { nullptr };
|
||||||
|
NonnullOwnPtrVector<Pattern> m_patterns;
|
||||||
|
|
||||||
NonnullOwnPtr<Board> m_board;
|
NonnullOwnPtr<Board> m_board;
|
||||||
|
|
||||||
bool m_running { false };
|
bool m_running { false };
|
||||||
|
|
||||||
int m_running_timer_interval { 500 };
|
int m_running_timer_interval { 500 };
|
||||||
|
int m_running_pattern_preview_timer_interval { 100 };
|
||||||
|
|
||||||
|
RefPtr<GUI::Menu> m_context_menu;
|
||||||
|
|
||||||
RefPtr<Core::Timer> m_timer;
|
RefPtr<Core::Timer> m_timer;
|
||||||
|
RefPtr<Core::Timer> m_pattern_preview_timer;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
compile_gml(GameOfLife.gml GameOfLifeGML.h game_of_life_gml)
|
compile_gml(GameOfLife.gml GameOfLifeGML.h game_of_life_gml)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
|
main.cpp
|
||||||
Board.cpp
|
Board.cpp
|
||||||
BoardWidget.cpp
|
BoardWidget.cpp
|
||||||
GameOfLifeGML.h
|
GameOfLifeGML.h
|
||||||
main.cpp
|
Pattern.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_app(GameOfLife ICON app-gameoflife)
|
serenity_app(GameOfLife ICON app-gameoflife)
|
||||||
|
|
39
Userland/Games/GameOfLife/Pattern.cpp
Normal file
39
Userland/Games/GameOfLife/Pattern.cpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Pattern.h"
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <AK/StringBuilder.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
|
#include <LibGUI/Action.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
Pattern::Pattern(Vector<String> pattern)
|
||||||
|
{
|
||||||
|
m_pattern = move(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern::~Pattern()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pattern::set_action(GUI::Action* action)
|
||||||
|
{
|
||||||
|
m_action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Pattern::rotate_clockwise()
|
||||||
|
{
|
||||||
|
Vector<String> rotated;
|
||||||
|
for (size_t i = 0; i < m_pattern.first().length(); i++) {
|
||||||
|
StringBuilder builder;
|
||||||
|
for (int j = m_pattern.size() - 1; j >= 0; j--) {
|
||||||
|
builder.append(m_pattern.at(j).substring(i, 1));
|
||||||
|
}
|
||||||
|
rotated.append(builder.to_string());
|
||||||
|
}
|
||||||
|
m_pattern = move(rotated);
|
||||||
|
}
|
26
Userland/Games/GameOfLife/Pattern.h
Normal file
26
Userland/Games/GameOfLife/Pattern.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Ryan Wilson <ryan@rdwilson.xyz>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Board.h"
|
||||||
|
#include <AK/Vector.h>
|
||||||
|
#include <LibGUI/Event.h>
|
||||||
|
#include <LibGUI/Forward.h>
|
||||||
|
|
||||||
|
class Pattern {
|
||||||
|
public:
|
||||||
|
Pattern(Vector<String>);
|
||||||
|
virtual ~Pattern();
|
||||||
|
Vector<String> pattern() { return m_pattern; };
|
||||||
|
GUI::Action* action() { return m_action; }
|
||||||
|
void set_action(GUI::Action*);
|
||||||
|
void rotate_clockwise();
|
||||||
|
|
||||||
|
private:
|
||||||
|
RefPtr<GUI::Action> m_action;
|
||||||
|
Vector<String> m_pattern;
|
||||||
|
};
|
|
@ -15,6 +15,7 @@
|
||||||
#include <LibGUI/SpinBox.h>
|
#include <LibGUI/SpinBox.h>
|
||||||
#include <LibGUI/Statusbar.h>
|
#include <LibGUI/Statusbar.h>
|
||||||
#include <LibGUI/Toolbar.h>
|
#include <LibGUI/Toolbar.h>
|
||||||
|
#include <LibGUI/ToolbarContainer.h>
|
||||||
#include <LibGUI/Window.h>
|
#include <LibGUI/Window.h>
|
||||||
|
|
||||||
const char* click_tip = "Tip: click the board to toggle individual cells, or click+drag to toggle multiple cells";
|
const char* click_tip = "Tip: click the board to toggle individual cells, or click+drag to toggle multiple cells";
|
||||||
|
@ -102,6 +103,12 @@ int main(int argc, char** argv)
|
||||||
});
|
});
|
||||||
main_toolbar.add_action(randomize_cells_action);
|
main_toolbar.add_action(randomize_cells_action);
|
||||||
|
|
||||||
|
auto rotate_pattern_action = GUI::Action::create("&Rotate pattern", { 0, Key_R }, Gfx::Bitmap::load_from_file("/res/icons/16x16/redo.png"), [&](auto&) {
|
||||||
|
if (board_widget.selected_pattern() != nullptr)
|
||||||
|
board_widget.selected_pattern()->rotate_clockwise();
|
||||||
|
});
|
||||||
|
main_toolbar.add_action(rotate_pattern_action);
|
||||||
|
|
||||||
auto menubar = GUI::Menubar::construct();
|
auto menubar = GUI::Menubar::construct();
|
||||||
auto& game_menu = menubar->add_menu("&Game");
|
auto& game_menu = menubar->add_menu("&Game");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue