diff --git a/.gitignore b/.gitignore index c73eaa7..783d8ec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,52 @@ sfml-widgets-demo # Lib +*.lai +*.la *.a +*.lib + +# Compiled Dynamic libraries +*.so +*.dylib +*.dl # Compilation files +*.slo +*.lo *.o +*.obj + +# Executables +*.exe +*.out +*.app +*.zip +*.rar +/cmake-build-*/ +**/.DS_Store # Code::Blocks sfml-widgets.layout sfml-widgets.depend + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ + +# Visual Studio +.vs/ +.vscode/ + +# Clion +*.iml +.idea/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0589549 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "master" + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d8646e9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +project(sfml-widgets VERSION 1.1.0) + +# ############################################################################## +# Option Setting +option(ENABLE_DEMO "build demo program.(Default:ON)" ON) +set(DEMO_EXE "demo_widgets") + +# ############################################################################## +# HINT: Tell CMake which directory you installed SFML libs +############################################################################### +##### (WINDOWS) +# set(SFML_HOME "C:/SFML/SFML-2.6.1") +##### (MACOS) +# set(SFML_HOME "/Library/Frameworks/SFML.framework/") + +set(ENABLE_DEMO TRUE) + +# FIND SFML precompiled libs & headers +############################################## +# set(SFML_DIR ${SFML_HOME}/lib/cmake/SFML) +find_package(SFML 2.5 REQUIRED COMPONENTS "graphics" "window" "system") +find_package(OpenGL REQUIRED) + +# ############################################################################## +# Configure general build settings +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED True) +if(NOT APPLE) + set(SFML_STATIC_LIBRARIES TRUE) +endif() + +# ############################################################################## +# Configure build for SFML Widgets library +file(GLOB_RECURSE SFML_WIDGETS_SRC src/Gui/*.cpp) +add_library(sfml-widgets ${SFML_WIDGETS_SRC}) +target_link_libraries(sfml-widgets PUBLIC sfml-graphics sfml-window sfml-system) +target_include_directories(sfml-widgets PUBLIC ${CMAKE_SOURCE_DIR}/src/) + +# show warnings (depending on C++ compiler) +if(MSVC) + target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /W4) +else() + target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC -Wall -Wextra) +endif() + +# ############################################################################## +# Configure build for Sample program +############################ START OF DEMO ############################### + +if(ENABLE_DEMO) + file(GLOB DEMO_SRC demo/*.cpp demo/*.mm) + + if(WIN32) + add_executable(${DEMO_EXE} WIN32 ${DEMO_SRC} ${CMAKE_SOURCE_DIR}/demo/resources/win-icon.rc) + target_link_libraries(${DEMO_EXE} PRIVATE sfml-widgets sfml-main OpenGL::GL sfml-graphics + sfml-window sfml-system) + + elseif(APPLE) + include(${CMAKE_SOURCE_DIR}/apple_cmake/MacBundle.cmake) + find_library(FOUNDATION_FRAMEWORK Foundation) + target_link_libraries(${DEMO_EXE} PRIVATE sfml-widgets OpenGL::GL sfml-graphics sfml-window + sfml-system ${FOUNDATION_FRAMEWORK}) + else() + add_executable(${DEMO_EXE} ${DEMO_SRC}) + target_link_libraries(${DEMO_EXE} PRIVATE sfml-widgets OpenGL::GL sfml-graphics sfml-window + sfml-system) + endif(WIN32) + + + # INCLUDE SFML Headers + #################################################### + # include_directories(${SFML_HOME}/include) + + # COPY RESOURCES TO BUILD FOLDER (Windows & Linux) + ##################################################### + if(NOT APPLE) + add_custom_command(TARGET ${DEMO_EXE} + POST_BUILD COMMAND + ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/demo/resources + $/resources) + endif() +endif(ENABLE_DEMO) +############################ END OF DEMO PROGRAM ############################### + + diff --git a/Makefile b/Makefile deleted file mode 100644 index d2bf1fa..0000000 --- a/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -TARGET := sfml-widgets-demo -SRCDIR := src -SRC := $(shell find $(SRCDIR) -name "*.cpp" -type f) -OBJDIR := obj -OBJ := $(SRC:%.cpp=$(OBJDIR)/%.o) - -CC := g++ -CFLAGS := -I$(SRCDIR) -std=c++11 -pedantic -Wall -Wextra -Wshadow -Wwrite-strings -O2 -LDFLAGS := -lsfml-graphics -lsfml-window -lsfml-system -lGL - -# Demo -$(TARGET): demo/demo.cpp lib/libsfml-widgets.a - @echo "\033[1;33mlinking exec\033[0m $@" - @$(CC) $< $(CFLAGS) -L./lib -lsfml-widgets $(LDFLAGS) -o $@ - @echo "\033[1;32mDone!\033[0m" - -# Static library -lib/libsfml-widgets.a: $(OBJ) - @mkdir -p lib - @echo "\033[1;33mlinking library\033[0m $@" - @ar crvf $@ $(OBJ) - -# Library objects -$(OBJDIR)/%.o: %.cpp - @echo "\033[1;33mcompiling\033[0m $<" - @mkdir -p $(shell dirname $@) - @$(CC) $(CFLAGS) -c $< -o $@ - -clean: - @echo "\033[1;33mremoving\033[0m $(OBJDIR)" - -@rm -r lib - -@rm -r $(OBJDIR) - -mrproper: clean - @echo "\033[1;33mremoving\033[0m $(TARGET)" - -@rm $(TARGET) - -all: mrproper $(TARGET) diff --git a/README.md b/README.md index e74f1d8..e8a4037 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,47 @@ A simple GUI module for SFML. - Author: Alexandre Bodelot - License: [MIT License](http://opensource.org/licenses/MIT) (See LICENSE file) -Run `make` to build the library (`lib/libsfml-widgets.a`) and the demo program. +## Requirements: +- [Download SFML 2.6.x](https://www.sfml-dev.org/download.php) for your platform +- Latest [CMake Installer](https://cmake.org/download/) - version 3.20 or higher + +### Windows: +- Visual Studio 2017 or later (select complete "**Desktop C++ Development**" workload) +- CMake for Visual Studio (download using Visual Studio installer). + +### MacOS +- XCode latest with MacOS SDK. +- Follow [official guide](https://www.sfml-dev.org/tutorials/2.6/start-osx.php) on installing SFML on MacOS +- Extra Apple Developer Tools. After Xcode installs, run this in Terminal: + ```bash + sudo xcode-select --install + ``` +- After CMake GUI installs on your Mac, make sure to add its CLI to PATH: + ```bash + sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install + ``` + +### Linux Desktop +- Use your package manager (`apt-get` or `yum`) to download SFML dev dependencies listed [in official docs](https://www.sfml-dev.org/tutorials/2.6/compile-with-cmake.php#installing-dependencies) + +## Building locally + +- On Windows, open this project in Visual Studio 2022 or later, choose "Release" or "Debug" from top toolbar. Then click "Build" > "Build All". +- If using CMake GUI (all platforms), set "Source Folder" to this project root. Then set "Build Folder" to new _relative_ folder `/build` or `/out`. See image below. Then click "Configure", choose **Unix Makefiles**, then click Generate. Finally, open build folder, then run `make all` in Terminal. + + ![cmake_screenshot](doc/cmake_gui.png) + +- Alternatively, you can use the CMake CLI on your Terminal: + +```bash +mkdir build +cd build +cmake . . +cmake --build . --config Release --target all +``` -You can then run the demo: `./sfml-widgets-demo` -## Setup +## How to use the library 1. Load resources (font, spritesheet) in static class `gui::Theme` 2. Use `gui::Menu` to create a new sfml-widgets menu. It needs to be connected to your SFML render window, which is given to the constructor. @@ -32,12 +68,15 @@ Minimal example: int main() { sf::RenderWindow app(sf::VideoMode(800, 600), "SFML Widgets", sf::Style::Close); + + //Set FPS limit + //app.setFramerateLimit(60); // Declare menu gui::Menu menu(app); - gui::Theme::loadFont("demo/tahoma.ttf"); - gui::Theme::loadTexture("demo/texture-default.png"); + gui::Theme::loadFont("resources/tahoma.ttf"); + gui::Theme::loadTexture("resources/texture-default.png"); // Create some button widget gui::Button* button = new gui::Button("My button"); @@ -74,12 +113,14 @@ int main() app.display(); } - return 0; + return EXIT_SUCCESS; } ``` `demo/demo.cpp` conains a more complex example, featuring all widgets. +![demo_screenshot](doc/demo_screenshot.png) + ## Widgets ### `gui::Button` diff --git a/apple_cmake/MacBundle.cmake b/apple_cmake/MacBundle.cmake new file mode 100644 index 0000000..d840735 --- /dev/null +++ b/apple_cmake/MacBundle.cmake @@ -0,0 +1,30 @@ +# app icon +set(MACOSX_BUNDLE_ICON_FILE "SfmlWidgets.icns") +set(application_icon "${CMAKE_SOURCE_DIR}/demo/resources/${MACOSX_BUNDLE_ICON_FILE}") +set_source_files_properties(${application_icon} + PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + +# images and fonts +file(GLOB_RECURSE my_images "${CMAKE_SOURCE_DIR}/demo/resources/*") +foreach(FILE ${my_images}) + file(RELATIVE_PATH NEW_FILE "${CMAKE_SOURCE_DIR}/" ${FILE}) + get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) + set_source_files_properties(${FILE} PROPERTIES MACOSX_PACKAGE_LOCATION + "Resources/${NEW_FILE_PATH}") +endforeach() + +add_executable(${DEMO_EXE} MACOSX_BUNDLE + ${DEMO_SRC} "${my_images}") + +set_target_properties( + ${DEMO_EXE} + PROPERTIES BUNDLE TRUE + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" + XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" + XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks" + MACOSX_BUNDLE_BUNDLE_NAME "${DEMO_EXE}" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.alexandre.bodelot.${DEMO_EXE}" + MACOSX_BUNDLE_COPYRIGHT "(c) 2021, Alexandre Bodelot" + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION} + RESOURCE "${my_images}") diff --git a/apple_cmake/README.md b/apple_cmake/README.md new file mode 100644 index 0000000..33dc38d --- /dev/null +++ b/apple_cmake/README.md @@ -0,0 +1,4 @@ +## What's this file for? + +This contains specific CMake instructions to build MacOS GUI `.app` Bundle for the demo. It embeds the icon to the app bundle. It also copies all assets/resources into the Bundle at build time. It additionally adds an important `Info.plist` file which contains metadata about the application. + diff --git a/demo/ResourcePath.cpp b/demo/ResourcePath.cpp new file mode 100644 index 0000000..80e64cf --- /dev/null +++ b/demo/ResourcePath.cpp @@ -0,0 +1,11 @@ +// for Windows and Linux OS. Helper to load assets + +#ifndef __APPLE__ +#include "ResourcePath.hpp" + +std::string chk::getResourcePath(const std::string& relativePath) +{ + return "resources/" + relativePath; +} + +#endif \ No newline at end of file diff --git a/demo/ResourcePath.hpp b/demo/ResourcePath.hpp new file mode 100644 index 0000000..1adea93 --- /dev/null +++ b/demo/ResourcePath.hpp @@ -0,0 +1,16 @@ +#pragma once +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + +namespace chk +{ +/** + * \brief Get absolute path of file specified + * \param relativePath file relative to `Resources/` directory + * \return The absolute file path + */ +std::string getResourcePath(const std::string &relativePath); +} // namespace chk \ No newline at end of file diff --git a/demo/ResourcePath.mm b/demo/ResourcePath.mm new file mode 100644 index 0000000..f910118 --- /dev/null +++ b/demo/ResourcePath.mm @@ -0,0 +1,16 @@ +#include "ResourcePath.hpp" + +#ifdef __APPLE__ +#import +std::string chk::getResourcePath(const std::string& relativePath) { + NSBundle* mainBundle = [NSBundle mainBundle]; + NSString* resourcePath = [mainBundle pathForResource:@(relativePath.c_str()) ofType:nil]; + + if (resourcePath == nil) { + std::cerr << "Error: File '" << relativePath << "' not found." << std::endl; + return ""; + } + + return [resourcePath UTF8String]; +} +#endif \ No newline at end of file diff --git a/demo/demo.cpp b/demo/demo.cpp index 3073e63..ea95438 100644 --- a/demo/demo.cpp +++ b/demo/demo.cpp @@ -1,6 +1,7 @@ #include "Gui/Theme.hpp" #include "Gui/Gui.hpp" #include +#include "ResourcePath.hpp" sf::Color hex2color(const std::string& hexcolor) @@ -29,15 +30,9 @@ struct Theme int main() { - Theme defaultTheme = { - hex2color("#dddbde"), - "demo/texture-default.png" - }; + Theme defaultTheme = {hex2color("#dddbde"), chk::getResourcePath("texture-default.png")}; - Theme win98Theme = { - hex2color("#d4d0c8"), - "demo/texture-win98.png" - }; + Theme win98Theme = {hex2color("#d4d0c8"), chk::getResourcePath("texture-win98.png")}; // Create the main window sf::RenderWindow app(sf::VideoMode(800, 600), "SFML Widgets", sf::Style::Close); @@ -46,10 +41,10 @@ int main() gui::Menu menu(app); menu.setPosition(10, 10); - gui::Theme::loadFont("demo/tahoma.ttf"); + gui::Theme::loadFont(chk::getResourcePath("tahoma.ttf")); gui::Theme::loadTexture(defaultTheme.texturePath); gui::Theme::textSize = 11; - gui::Theme::click.textColor = hex2color("#191B18"); + gui::Theme::click.textColor = hex2color("#191B18"); gui::Theme::click.textColorHover = hex2color("#191B18"); gui::Theme::click.textColorFocus = hex2color("#000"); gui::Theme::input.textColor = hex2color("#000"); @@ -70,10 +65,13 @@ int main() // Textbox gui::TextBox* textbox = new gui::TextBox(); textbox->setText("Hello world!"); - textbox->setCallback([&]() { - text.setString(textbox->getText()); - text.setOrigin(text.getLocalBounds().width / 2, text.getLocalBounds().height / 2); - }); + textbox->setCallback( + [&]() + { + text.setString(textbox->getText()); + text.setOrigin(text.getLocalBounds().width / 2, text.getLocalBounds().height / 2); + } + ); textbox->setPlaceholder("Type something!"); form->addRow("Text", textbox); @@ -89,12 +87,15 @@ int main() gui::ProgressBar* pbarRotation3 = new gui::ProgressBar(200.f, gui::Horizontal, gui::LabelOutside); sliderRotation->setStep(1); - sliderRotation->setCallback([&]() { - text.setRotation(sliderRotation->getValue() * 360 / 100.f); - pbarRotation1->setValue(sliderRotation->getValue()); - pbarRotation2->setValue(sliderRotation->getValue()); - pbarRotation3->setValue(sliderRotation->getValue()); - }); + sliderRotation->setCallback( + [&]() + { + text.setRotation(sliderRotation->getValue() * 360 / 100.f); + pbarRotation1->setValue(sliderRotation->getValue()); + pbarRotation2->setValue(sliderRotation->getValue()); + pbarRotation3->setValue(sliderRotation->getValue()); + } + ); form->addRow("Rotation", sliderRotation); // Slider + ProgressBar for scale @@ -102,13 +103,16 @@ int main() gui::ProgressBar* pbarScale1 = new gui::ProgressBar(100, gui::Vertical, gui::LabelNone); gui::ProgressBar* pbarScale2 = new gui::ProgressBar(100, gui::Vertical, gui::LabelOver); gui::ProgressBar* pbarScale3 = new gui::ProgressBar(100, gui::Vertical, gui::LabelOutside); - sliderScale->setCallback([&]() { - float scale = 1 + sliderScale->getValue() * 2 / 100.f; - text.setScale(scale, scale); - pbarScale1->setValue(sliderScale->getValue()); - pbarScale2->setValue(sliderScale->getValue()); - pbarScale3->setValue(sliderScale->getValue()); - }); + sliderScale->setCallback( + [&]() + { + float scale = 1 + sliderScale->getValue() * 2 / 100.f; + text.setScale(scale, scale); + pbarScale1->setValue(sliderScale->getValue()); + pbarScale2->setValue(sliderScale->getValue()); + pbarScale3->setValue(sliderScale->getValue()); + } + ); form->addRow("Scale", sliderScale); // OptionsBox for color @@ -118,32 +122,36 @@ int main() opt->addItem("Green", sf::Color::Green); opt->addItem("Yellow", sf::Color::Yellow); opt->addItem("White", sf::Color::White); - opt->setCallback([&]() { - text.setFillColor(opt->getSelectedValue()); - }); + opt->setCallback([&]() { text.setFillColor(opt->getSelectedValue()); }); form->addRow("Color", opt); // Checbkox gui::CheckBox* checkboxBold = new gui::CheckBox(); - checkboxBold->setCallback([&]() { - int style = text.getStyle(); - if (checkboxBold->isChecked()) - style |= sf::Text::Bold; - else - style &= ~sf::Text::Bold; - text.setStyle(style); - }); + checkboxBold->setCallback( + [&]() + { + int style = text.getStyle(); + if (checkboxBold->isChecked()) + style |= sf::Text::Bold; + else + style &= ~sf::Text::Bold; + text.setStyle(style); + } + ); form->addRow("Bold text", checkboxBold); gui::CheckBox* checkboxUnderlined = new gui::CheckBox(); - checkboxUnderlined->setCallback([&]() { - int style = text.getStyle(); - if (checkboxUnderlined->isChecked()) - style |= sf::Text::Underlined; - else - style &= ~sf::Text::Underlined; - text.setStyle(style); - }); + checkboxUnderlined->setCallback( + [&]() + { + int style = text.getStyle(); + if (checkboxUnderlined->isChecked()) + style |= sf::Text::Underlined; + else + style &= ~sf::Text::Underlined; + text.setStyle(style); + } + ); form->addRow("Underlined text", checkboxUnderlined); // Progress bar @@ -162,7 +170,7 @@ int main() // Custom button sf::Texture imgbutton; - imgbutton.loadFromFile("demo/themed-button.png"); + imgbutton.loadFromFile(chk::getResourcePath("themed-button.png")); gui::SpriteButton* customButton = new gui::SpriteButton(imgbutton, "Play"); customButton->setTextSize(20); @@ -174,11 +182,14 @@ int main() gui::OptionsBox* themeBox = new gui::OptionsBox(); themeBox->addItem("Windows 98", win98Theme); themeBox->addItem("Default", defaultTheme); - themeBox->setCallback([&]() { - const Theme& theme = themeBox->getSelectedValue(); - gui::Theme::loadTexture(theme.texturePath); - gui::Theme::windowBgColor = theme.backgroundColor; - }); + themeBox->setCallback( + [&]() + { + const Theme& theme = themeBox->getSelectedValue(); + gui::Theme::loadTexture(theme.texturePath); + gui::Theme::windowBgColor = theme.backgroundColor; + } + ); vbox->add(themeBox); // Textbox @@ -187,9 +198,7 @@ int main() textbox3->setText("My Button"); textbox3->setPlaceholder("Button label"); hbox2->add(textbox3); - hbox2->addButton("Create button", [&]() { - vbox->add(new gui::Button(textbox3->getText())); - }); + hbox2->addButton("Create button", [&]() { vbox->add(new gui::Button(textbox3->getText())); }); // Small progress bar gui::HBoxLayout* hbox3 = vbox->addHBoxLayout(); @@ -198,17 +207,13 @@ int main() hbox3->add(pbar); gui::Slider* vslider = new gui::Slider(100, gui::Vertical); - vslider->setCallback([&]() { - pbar->setValue(vslider->getValue()); - }); + vslider->setCallback([&]() { pbar->setValue(vslider->getValue()); }); hbox->add(vslider); - menu.addButton("Quit", [&]() { - app.close(); - }); + menu.addButton("Quit", [&]() { app.close(); }); sf::Texture texture; - texture.loadFromFile("demo/sfml.png"); + texture.loadFromFile(chk::getResourcePath("sfml.png")); sf::Sprite sprite(texture); sprite.setOrigin(texture.getSize().x / 2, texture.getSize().y / 2); diff --git a/demo/resources/SfmlWidgets.icns b/demo/resources/SfmlWidgets.icns new file mode 100644 index 0000000..52008d4 Binary files /dev/null and b/demo/resources/SfmlWidgets.icns differ diff --git a/demo/sfml.png b/demo/resources/sfml.png similarity index 100% rename from demo/sfml.png rename to demo/resources/sfml.png diff --git a/demo/tahoma.ttf b/demo/resources/tahoma.ttf similarity index 100% rename from demo/tahoma.ttf rename to demo/resources/tahoma.ttf diff --git a/demo/texture-default.png b/demo/resources/texture-default.png similarity index 100% rename from demo/texture-default.png rename to demo/resources/texture-default.png diff --git a/demo/texture-win98.png b/demo/resources/texture-win98.png similarity index 100% rename from demo/texture-win98.png rename to demo/resources/texture-win98.png diff --git a/demo/themed-button.png b/demo/resources/themed-button.png similarity index 100% rename from demo/themed-button.png rename to demo/resources/themed-button.png diff --git a/demo/resources/win-icon.ico b/demo/resources/win-icon.ico new file mode 100644 index 0000000..dce6b2e Binary files /dev/null and b/demo/resources/win-icon.ico differ diff --git a/demo/resources/win-icon.rc b/demo/resources/win-icon.rc new file mode 100644 index 0000000..b4044ff --- /dev/null +++ b/demo/resources/win-icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "win-icon.ico" \ No newline at end of file diff --git a/doc/cmake_gui.png b/doc/cmake_gui.png new file mode 100644 index 0000000..5d59c2b Binary files /dev/null and b/doc/cmake_gui.png differ diff --git a/doc/demo_screenshot.png b/doc/demo_screenshot.png new file mode 100644 index 0000000..9acd5ed Binary files /dev/null and b/doc/demo_screenshot.png differ