Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code & Build Cleanup & Enhancements #18

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#
# Copyright (c) 2024 Sidhin S Thomas. All rights reserved
#

cmake_minimum_required(VERSION 3.20)
project(gamemenu)
project(Menu)

set(VERSION_MAJOR 2)
set(VERSION_MAJOR 3)
set(VERSION_MINOR 0)
set(VERSION_PATCH 0)

set(CMAKE_CXX_STANDARD 20)

option(BUILD_SHARED_LIBS "Build shared libraries" OFF)

if (NOT DEFINED LINK_TYPE)
message("Library link type not specified, using default - STATIC")
set(LINK_TYPE STATIC)
@@ -22,38 +28,38 @@ FetchContent_Declare(SFML

FetchContent_MakeAvailable(SFML)


set(SOURCES
"public/game_menu/game_menu.h"
"src/game_menu_impl.cpp"
"public/game_menu/Menu.h"
"src/MenuImpl.h"
"src/MenuImpl.cpp"
)

add_library(gamemenu ${LINK_TYPE} ${SOURCES})
target_link_libraries(gamemenu PUBLIC sfml-graphics)
add_library(Menu ${LINK_TYPE} ${SOURCES})
target_link_libraries(Menu PUBLIC sfml-graphics)
target_include_directories(
gamemenu
Menu
PUBLIC
"public"
"include"
)

set(GMENU_HEADERS
"public/game_menu/game_menu.h"
"public/game_menu/Menu.h"
)

set_target_properties(gamemenu PROPERTIES
set_target_properties(Menu PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}
MACOSX_FRAMEWORK_IDENTIFIER in.sidhin.gamemenu
MACOSX_FRAMEWORK_IDENTIFIER in.sidhin.Menu
MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}
MACOSX_FRAMEWORK_BUNDLE_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}
PUBLIC_HEADER "${GMENU_HEADERS}")

if(WIN32)
add_custom_command(
TARGET CMakeSFMLProject
TARGET Menu
COMMENT "Copy OpenAL DLL"
PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${SFML_SOURCE_DIR}/extlibs/bin/$<IF:$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>,x64,x86>/openal32.dll $<TARGET_FILE_DIR:CMakeSFMLProject>
PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${SFML_SOURCE_DIR}/extlibs/bin/$<IF:$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>,x64,x86>/openal32.dll $<TARGET_FILE_DIR:Menu>
VERBATIM)
endif()

@@ -63,7 +69,7 @@ if(BUILD_WITH_EXAMPLE)
add_executable(example
"examples/sample_menu.cpp"
)
target_link_libraries(example PRIVATE gamemenu)
target_link_libraries(example PRIVATE Menu)
add_custom_target(copy_example_font
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/examples/sansation.ttf ${CMAKE_BINARY_DIR}
)
164 changes: 111 additions & 53 deletions examples/sample_menu.cpp
Original file line number Diff line number Diff line change
@@ -1,71 +1,129 @@
#include "game_menu/game_menu.h"
/*
Copyright (c) 2024 Sidhin S Thomas. All rights reserved
*/

#include <SFML/Graphics.hpp>
#include <memory>
#include <vector>

int main() {
sf::RenderWindow w( sf::VideoMode( 800, 600 ), "Sample Title", sf::Style::Close);
#include <SFML/Graphics.hpp>

#include "game_menu/Menu.h"

int main()
{
// Creating the SFML RenderWindow object for displaying the example.
sf::RenderWindow window(sf::VideoMode(800, 600), "Sample Menu Example", sf::Style::Close);

// Loading the font from the example font file.
sf::Font font;
font.loadFromFile( "sansation.ttf" );
game_menu::Style style {
.TitleFont = &font,
.ItemFont = &font,
.TitleFontSize = 36,
.ItemFontSize = 24,
.MenuTitleScaleFactor = 1,
.MenuItemScaleFactor = 1.5,
.colorScheme = {
.titleColor = 0xFFFFFF,
.itemColor = 0xFFFFFF,
.selectedColor = 0xFF22F1
},
.PaddingTitle = {
.top = 100,
.left = 0,
},
.PaddingItems = {
.top = 40,
},
.TitleAlign = game_menu::Align::Center,
.ItemAlign = game_menu::Align::Center
};
font.loadFromFile("sansation.ttf");

bool is_exit_requested = false;
// Variable to be captured by action functions below for the example.
auto isExitRequested = false;

std::vector<game_menu::MenuItem> items {
{ "New Game", [](sf::RenderTarget &target) {}},
{ "Load Game", [](sf::RenderTarget &target) {}},
{ "Leaderboard", [](sf::RenderTarget &target) {}},
{ "Settings", [](sf::RenderTarget &target) {}},
{ "Exit", [&is_exit_requested](sf::RenderTarget &target) {is_exit_requested = true;}}
};
// This configuration defines the layout and operation of a menu.
Menu::MenuConfig menuConfig;

game_menu::MenuConfig config {
.title = "My Game",
.items = items,
.style = style
// The items list should have all selectable menu items in the list. Along with each string include a
// function pointer to the action you want performed when the menu item is selected. In the example
// below we have added 3 menu items with generic names. The third item "Exit" has an example exit method.
menuConfig.Items =
{
// Text and function pointers.
{ "Menu Item 1", [](sf::RenderTarget& target) {} },
{ "Menu Item 2", [](sf::RenderTarget& target) {} },
{ "Exit", [&](sf::RenderTarget& target) { isExitRequested = true; } },
};

// The ItemStyle member defines the style to apply to all items in the menu.

// Alignment Defines how the text of menu items is aligned. Center is the default.
menuConfig.ItemStyle.Alignment = Menu::Align::Center;
// Color to use for menu items that are not selected.
menuConfig.ItemStyle.Color = sf::Color::Blue;

// Font to use for menu item text.
menuConfig.ItemStyle.Font = font;

// Size of the menu item font in points.
menuConfig.ItemStyle.FontSize = 24;

// Padding for menu item text in pixels.
menuConfig.ItemStyle.Padding.Bottom = 10.0f;
menuConfig.ItemStyle.Padding.Left = 10.0f;
menuConfig.ItemStyle.Padding.Right = 10.0f;
menuConfig.ItemStyle.Padding.Top = 10.0f;

// The color to use when a menu item is selected.
menuConfig.SelectedItemColor = sf::Color::Cyan;

// The TitleStyle member defines the style to apply to the title.

// Gives the alignment of the title.
menuConfig.TitleStyle.Alignment = Menu::Align::Center;

// Color to use for the title text.
menuConfig.TitleStyle.Color = sf::Color::Green;

// Font to use for title text.
menuConfig.TitleStyle.Font = font;

// Size of the title font in points.
menuConfig.TitleStyle.FontSize = 36;

// Padding for title text in pixels.
menuConfig.TitleStyle.Padding.Bottom = 20.0f;
menuConfig.TitleStyle.Padding.Left = 20.0f;
menuConfig.TitleStyle.Padding.Right = 20.0f;
menuConfig.TitleStyle.Padding.Top = 20.0f;

// Test to use as the title of the menu.
menuConfig.TitleText = "My Menu Title";

auto menu_ptr = create_menu_context(w, config);
std::unique_ptr<game_menu::MENU, decltype(&menu_destroy_context)> menu(menu_ptr, &menu_destroy_context);
while (w.isOpen()) {
if (is_exit_requested) {
w.close();
// Scaling factor to apply to the whole menu.
menuConfig.ScalingFactor = 1.0;

// Use the BuildMenu factory function in the IMenu header. This gives a fully built menu object.
auto menu = Menu::BuildMenu(window.getSize(), menuConfig);

// This while loop ill control the menu operation.
while (window.isOpen())
{
// The isExitRequested was passed into the "Exit" menu action by reference.
if (isExitRequested)
{
// When the isExitRequested goes true the user pressed enter on the "Exit" action.
window.close();
break;
}
sf::Event event;
while (w.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
is_exit_requested = true;

// Poll the events from the window.
sf::Event windowEvent;
while (window.pollEvent(windowEvent))
{
// Handle the closed event here, but pass all other events to the menu since it's displayed.
if (windowEvent.type == sf::Event::Closed)
{
// Just handle this as if the "Exit" entry had been seleced.
isExitRequested = true;
}
else
{
// Pass the event to the menu
menu->HandleEvent(windowEvent, window);
}
menu_handle_event(menu.get(), event);
}
w.clear();
menu_render(menu.get());
w.display();

// Clear the window.
window.clear();

// Draw the menu on the window.
menu->Draw(window);

// Tell the window to display.
window.display();
}

// Exit from the example.
return 0;
}

180 changes: 180 additions & 0 deletions public/game_menu/Menu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
Copyright (c) 2024 Sidhin S Thomas. All rights reserved
*/

#pragma once

#include <cstdint>
#include <functional>
#include <string>

#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics.hpp>

namespace Menu
{
/// <summary>
/// Interface for a Menu object.
/// </summary>
class IMenu
{
public:
/// <summary>
/// Destructor.
/// </summary>
virtual ~IMenu();

/// <summary>
/// Draws the built menu on the provided render target.
/// </summary>
/// <param name="window">Target to draw on.</param>
virtual void Draw(sf::RenderTarget& window) = 0;

/// <summary>
/// Handles a window event applied to the menu.
/// </summary>
/// <param name="event">Received event.</param>
/// <param name="window">Render target to be passed along for actions tied to menu items.</param>
virtual void HandleEvent(const sf::Event& event, sf::RenderTarget& window) = 0;
};

/// <summary>
/// Defines alignment options to be applied to containers.
/// </summary>
enum class Align
{
/// <summary>
/// Aligns containers to the left of the window.
/// </summary>
Left = 0,
/// <summary>
/// Aligns containers to the center of the window.
/// </summary>
Center = 1,
/// <summary>
/// Aligns containers to the right of the window.
/// </summary>
Right = 2
};

/// <summary>
/// Definition of a text menu item.
/// </summary>
struct MenuItem
{
/// <summary>
/// Text to appear on the menu item.
/// </summary>
std::string Name;

/// <summary>
/// Action function to be called when the menu item is selected.
/// </summary>
std::function<void(sf::RenderTarget&)> Action;
};

/// <summary>
/// Container to store padding values.
/// </summary>
struct Padding
{
/// <summary>
/// Padding to apply to the bottom of the container.
/// Units in pixels.
/// </summary>
float Bottom = 0.0f;

/// <summary>
/// Padding to apply to the left of the container.
/// Units in pixels.
/// </summary>
float Left = 0.0f;

/// <summary>
/// Padding to apply to the right of the container.
/// Units in pixels.
/// </summary>
float Right = 0.0f;

/// <summary>
/// Padding to apply to the top of the container.
/// Units in pixels.
/// </summary>
float Top = 0.0f;
};

/// <summary>
/// Style definition of a menu item.
/// </summary>
struct Style
{
/// <summary>
/// Alignment of the menu item text.
/// </summary>
Align Alignment = Align::Center;

/// <summary>
/// Standard color of menu item text.
/// </summary>
sf::Color Color;

/// <summary>
/// Font to use for the menu item text.
/// </summary>
sf::Font Font;

/// <summary>
/// Font size to use for menu item text.
/// </summary>
std::uint32_t FontSize = 8;

/// <summary>
/// Padding to use for menu item text.
/// </summary>
Padding Padding;
};

/// <summary>
/// Configuration container of the menu.
/// </summary>
struct MenuConfig
{
/// <summary>
/// List of the menu items to be in the menu.
/// </summary>
std::vector<MenuItem> Items;

/// <summary>
/// Style to apply to all Items.
/// </summary>
Style ItemStyle;

/// <summary>
/// Color to apply to items when they are actively selected.
/// </summary>
sf::Color SelectedItemColor;

/// <summary>
/// Style to apply to title.
/// </summary>
Style TitleStyle;

/// <summary>
/// Text to use on the title.
/// </summary>
std::string TitleText;

/// <summary>
/// Scaling factor to apply to the menu.
/// </summary>
float ScalingFactor = 1.0;
};

/// <summary>
/// Factory function to build a Menu interface reference.
/// </summary>
/// <param name="windowSize">Overall size of the window.</param>
/// <param name="menuConfig">Configuration of the menu.</param>
/// <returns>Menu instance.</returns>
std::shared_ptr<IMenu> BuildMenu(const sf::Vector2u& windowSize, const MenuConfig& menuConfig);
}
70 changes: 0 additions & 70 deletions public/game_menu/game_menu.h

This file was deleted.

121 changes: 121 additions & 0 deletions src/MenuImpl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Copyright (c) 2024 Sidhin S Thomas. All rights reserved
*/

#include <iostream>
#include <string>

#include <SFML/Graphics/Color.hpp>
#include <SFML/Window/Event.hpp>

#include "game_menu/Menu.h"
#include "MenuImpl.h"

namespace Menu
{
sf::Text BuildText(const std::string& text, const Style& textStyle, const sf::Vector2u& windowSize, const float& scalingFactor, const float& yHeight);

IMenu::~IMenu() {}

MenuImpl::MenuImpl(const MenuConfig& config, const sf::Vector2u& windowSize)
: _selectedColor(config.SelectedItemColor)
, _unselectedColor(config.ItemStyle.Color)
{
_title = BuildText(config.TitleText, config.TitleStyle, windowSize, config.ScalingFactor, 0.0);

auto y = (config.TitleStyle.Padding.Top * config.ScalingFactor) + _title.getGlobalBounds().height + (config.TitleStyle.Padding.Bottom * config.ScalingFactor);
for (auto& item : config.Items)
{
auto text = BuildText(item.Name, config.ItemStyle, windowSize, config.ScalingFactor, y);
_items.push_back(std::make_pair(text, item.Action));

y += (config.ItemStyle.Padding.Top * config.ScalingFactor) + text.getGlobalBounds().height + (config.ItemStyle.Padding.Bottom * config.ScalingFactor);
}
}

MenuImpl::~MenuImpl() {}

void MenuImpl::HandleEvent(const sf::Event& event, sf::RenderTarget& window)
{
if (event.type == sf::Event::KeyPressed)
{
const auto itemCount = _items.size();
switch (event.key.code)
{
case sf::Keyboard::Up:
_currentlySelectedItem = (_currentlySelectedItem + itemCount - 1) % itemCount;
break;
case sf::Keyboard::Down:
_currentlySelectedItem = (_currentlySelectedItem + 1) % itemCount;
break;
case sf::Keyboard::Return:
_items[_currentlySelectedItem].second(window);
break;
}
}
else if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Left)
{
sf::Vector2f mouseClickPosition(event.mouseButton.x, event.mouseButton.y);
for (size_t i = 0; i < _items.size(); ++i)
{
if (_items[i].first.getGlobalBounds().contains(mouseClickPosition))
{
_currentlySelectedItem = i;
_items[_currentlySelectedItem].second(window);
}
}
}
else if (event.type == sf::Event::MouseMoved)
{
sf::Vector2f mousePosition(event.mouseMove.x, event.mouseMove.y);
for (size_t i = 0; i < _items.size(); ++i)
{
if (_items[i].first.getGlobalBounds().contains(mousePosition))
{
_currentlySelectedItem = i;
}
}
}
}

void MenuImpl::Draw(sf::RenderTarget& window)
{
window.draw(_title);
for (size_t i = 0; i < _items.size(); ++i)
{
auto color = i == _currentlySelectedItem ? _selectedColor : _unselectedColor;
_items[i].first.setColor(color);
window.draw(_items[i].first);
}
}

sf::Text BuildText(const std::string& text, const Style& textStyle, const sf::Vector2u& windowSize, const float& scalingFactor, const float& yHeight)
{
sf::Text sfText(text, textStyle.Font, (std::uint32_t)(textStyle.FontSize * scalingFactor));
sfText.setColor(textStyle.Color);

auto x = textStyle.Padding.Left * scalingFactor;
if (textStyle.Alignment == Align::Center)
{
// The x position of the text is the mid point of the window, minus half the width of the text box including padding. Then the padding for the left
// must be added back for the proper left indentation.
const auto totalTextLength = (textStyle.Padding.Left * scalingFactor) + sfText.getGlobalBounds().width + (textStyle.Padding.Right * scalingFactor);
x = (windowSize.x / 2.0) - (totalTextLength / 2.0) + (textStyle.Padding.Left * scalingFactor);
}
else if (textStyle.Alignment == Align::Right)
{
// The x position of the text is the right border of the window minus the full width of the text with the padding.
x = windowSize.x - (textStyle.Padding.Right * scalingFactor) - sfText.getGlobalBounds().width - (textStyle.Padding.Left * scalingFactor);
}

const auto y = yHeight + (textStyle.Padding.Top * scalingFactor);
sfText.setPosition(x, y);

return sfText;
}

std::shared_ptr<IMenu> BuildMenu(const sf::Vector2u& windowSize, const MenuConfig& menuConfig)
{
return std::make_shared<MenuImpl>(menuConfig, windowSize);
}
}
34 changes: 34 additions & 0 deletions src/MenuImpl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Copyright (c) 2024 Sidhin S Thomas. All rights reserved
*/

#pragma once

#include <vector>

#include <SFML/Graphics.hpp>
#include <SFML/Graphics/RenderTarget.hpp>

#include "game_menu/Menu.h"

namespace Menu
{
class MenuImpl : public IMenu
{
public:
MenuImpl(const MenuConfig& config, const sf::Vector2u& windowSize);
virtual ~MenuImpl();

void Draw(sf::RenderTarget& window) override;
void HandleEvent(const sf::Event& event, sf::RenderTarget& window) override;

private:
const sf::Color _selectedColor;
const sf::Color _unselectedColor;

sf::Text _title;
std::vector<std::pair<sf::Text, std::function<void(sf::RenderTarget&)>>> _items;

size_t _currentlySelectedItem = 0;
};
}
144 changes: 0 additions & 144 deletions src/game_menu_impl.cpp

This file was deleted.

53 changes: 0 additions & 53 deletions src/game_menu_impl.h

This file was deleted.