├── .clang-format ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── compile_flags.txt ├── demo ├── demo.cpp ├── sfml.png ├── tahoma.ttf ├── texture-default.png ├── texture-win98.png └── themed-button.png ├── doc ├── button.png ├── checkbox.png ├── optionsbox.png ├── progress-bar.png ├── slider.png └── textbox.png ├── lint.sh ├── main.cpp ├── sfml-widgets.cbp └── src └── Gui ├── Button.cpp ├── Button.hpp ├── CheckBox.cpp ├── CheckBox.hpp ├── ComboBox.hpp ├── ComboBox.inl ├── Enums └── Enums.hpp ├── Gui.hpp ├── Image.cpp ├── Image.hpp ├── Label.cpp ├── Label.hpp ├── Layouts ├── FormLayout.cpp ├── FormLayout.hpp ├── HBoxLayout.cpp ├── HBoxLayout.hpp ├── Layout.cpp ├── Layout.hpp ├── VBoxLayout.cpp └── VBoxLayout.hpp ├── Menu.cpp ├── Menu.hpp ├── OptionsBox.hpp ├── OptionsBox.inl ├── ProgressBar.cpp ├── ProgressBar.hpp ├── Slider.cpp ├── Slider.hpp ├── SpriteButton.cpp ├── SpriteButton.hpp ├── TextBox.cpp ├── TextBox.hpp ├── Theme.cpp ├── Theme.hpp ├── Utils ├── Arrow.cpp ├── Arrow.hpp ├── Box.cpp ├── Box.hpp ├── Cross.cpp ├── Cross.hpp ├── ItemBox.hpp └── ItemBox.inl ├── Widget.cpp └── Widget.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Webkit 3 | AlignAfterOpenBracket: BlockIndent 4 | AlignConsecutiveMacros: true 5 | AlignTrailingComments: true 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortFunctionsOnASingleLine: Inline 8 | AlwaysBreakTemplateDeclarations: Yes 9 | BreakBeforeBraces: Allman 10 | BreakBeforeTernaryOperators: true 11 | BreakConstructorInitializers: AfterColon 12 | ColumnLimit: 120 13 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 14 | Cpp11BracedListStyle: true 15 | MaxEmptyLinesToKeep: 2 16 | PackConstructorInitializers: Never 17 | SortIncludes: false 18 | SpaceBeforeCtorInitializerColon: false 19 | SpaceBeforeInheritanceColon: false 20 | SpaceBeforeRangeBasedForLoopColon: false 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | jobs: 4 | lint: 5 | name: Clang-format 6 | runs-on: ubuntu-22.04 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Install dependencies 10 | run: sudo apt install clang-format 11 | - name: Run clang-format 12 | run: clang-format $(find src -type f) --dry-run -Werror 13 | build: 14 | name: Compile 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | cc: [clang++, g++] 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Install dependencies 23 | run: sudo apt install build-essential libsfml-dev 24 | - name: Build 25 | run: make CC=${{ matrix.cc }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Demo binary 2 | sfml-widgets-demo 3 | 4 | # Lib 5 | *.a 6 | 7 | # Compilation files 8 | *.o 9 | 10 | # Code::Blocks 11 | sfml-widgets.layout 12 | sfml-widgets.depend 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alexandre Bodelot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := sfml-widgets-demo 2 | SRCDIR := src 3 | SRC := $(shell find $(SRCDIR) -name "*.cpp" -type f) 4 | OBJDIR := obj 5 | OBJ := $(SRC:%.cpp=$(OBJDIR)/%.o) 6 | 7 | CC := g++ 8 | CFLAGS := -I$(SRCDIR) -std=c++11 -pedantic -Wall -Wextra -Wshadow -Wwrite-strings -O2 9 | LDFLAGS := -lsfml-graphics -lsfml-window -lsfml-system -lGL 10 | 11 | # Demo 12 | $(TARGET): demo/demo.cpp lib/libsfml-widgets.a 13 | @echo "\033[1;33mlinking exec\033[0m $@" 14 | @$(CC) $< $(CFLAGS) -L./lib -lsfml-widgets $(LDFLAGS) -o $@ 15 | @echo "\033[1;32mDone!\033[0m" 16 | 17 | # Static library 18 | lib/libsfml-widgets.a: $(OBJ) 19 | @mkdir -p lib 20 | @echo "\033[1;33mlinking library\033[0m $@" 21 | @ar crvf $@ $(OBJ) 22 | 23 | # Library objects 24 | $(OBJDIR)/%.o: %.cpp 25 | @echo "\033[1;33mcompiling\033[0m $<" 26 | @mkdir -p $(shell dirname $@) 27 | @$(CC) $(CFLAGS) -c $< -o $@ 28 | 29 | clean: 30 | @echo "\033[1;33mremoving\033[0m $(OBJDIR)" 31 | -@rm -r lib 32 | -@rm -r $(OBJDIR) 33 | 34 | mrproper: clean 35 | @echo "\033[1;33mremoving\033[0m $(TARGET)" 36 | -@rm $(TARGET) 37 | 38 | all: mrproper $(TARGET) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SFML Widgets 2 | ============ 3 | 4 | A simple GUI module for SFML. 5 | 6 | - Spritesheet based: a single image file to customize widget style 7 | - Simple events: set a `std::function` callback on widgets to trigger functions on UI events. 8 | - Layouts: automatically align content without computing positions 9 | 10 | ![workflow](https://github.com/abodelot/sfml-widgets/actions/workflows/ci.yml/badge.svg) 11 | 12 | - Author: Alexandre Bodelot 13 | - License: [MIT License](http://opensource.org/licenses/MIT) (See LICENSE file) 14 | 15 | Run `make` to build the library (`lib/libsfml-widgets.a`) and the demo program. 16 | 17 | You can then run the demo: `./sfml-widgets-demo` 18 | 19 | ## Setup 20 | 21 | 1. Load resources (font, spritesheet) in static class `gui::Theme` 22 | 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. 23 | 3. Create widgets, add theme to the menu and define callbacks on them. NOTE: widgets must be dynamically allocated (`new`). The `gui::Menu` destructor will take care of deallocating widgets. 24 | 25 | Minimal example: 26 | 27 | ```cpp 28 | #include 29 | #include 30 | #include "Gui/Gui.hpp" 31 | 32 | int main() 33 | { 34 | sf::RenderWindow app(sf::VideoMode(800, 600), "SFML Widgets", sf::Style::Close); 35 | 36 | // Declare menu 37 | gui::Menu menu(app); 38 | 39 | gui::Theme::loadFont("demo/tahoma.ttf"); 40 | gui::Theme::loadTexture("demo/texture-default.png"); 41 | 42 | // Create some button widget 43 | gui::Button* button = new gui::Button("My button"); 44 | 45 | // Insert button into menu 46 | menu.add(button); 47 | 48 | // Define a callback 49 | button->setCallback([] { 50 | std::cout << "click!" << std::endl; 51 | }); 52 | 53 | // Start the application loop 54 | while (app.isOpen()) 55 | { 56 | // Process events 57 | sf::Event event; 58 | while (app.pollEvent(event)) 59 | { 60 | // Send events to the menu 61 | menu.onEvent(event); 62 | 63 | if (event.type == sf::Event::Closed) 64 | app.close(); 65 | } 66 | 67 | // Optional: clear window with theme background color 68 | app.clear(gui::Theme::windowBgColor); 69 | 70 | // Render menu 71 | app.draw(menu); 72 | 73 | // Update the window 74 | app.display(); 75 | } 76 | 77 | return 0; 78 | } 79 | ``` 80 | 81 | `demo/demo.cpp` conains a more complex example, featuring all widgets. 82 | 83 | ## Widgets 84 | 85 | ### `gui::Button` 86 | 87 | A simple press button. 88 | 89 | ![button](doc/button.png) 90 | 91 | ### `gui::Checkbox` 92 | 93 | A button with enabled/disabled state. 94 | 95 | ![checkbox](doc/checkbox.png) 96 | 97 | ### `gui::Image` 98 | 99 | Displays an SFML texture. 100 | 101 | It's a simple wrapper around `sf::Texture`, to display a texture as part of the UI. 102 | 103 | ### `gui::Label` 104 | 105 | A static text element. 106 | 107 | It's a simple wrapper around `sf::Text`, to display a text as part of the UI. 108 | 109 | ### `gui::OptionsBox` 110 | 111 | A list of label/value pairs. 112 | 113 | ![optionsbox](doc/optionsbox.png) 114 | 115 | Use templates to define value type. Example: `gui::OptionsBox`. 116 | 117 | Add value with: `optionsBox->addItem("Red", sf::Color::Red)`; 118 | 119 | ### `gui::ProgressBar` 120 | 121 | A simple horizontal or vertical progress bar. 122 | 123 | ![progress-bar](doc/progress-bar.png) 124 | 125 | * `orientation`: `gui::Horizontal` or `gui::Vertical` 126 | * `labelPlacement`: `gui::LabelNone`, or `gui::LabelOver`, or `gui::Outside` 127 | 128 | ### `gui::Slider` 129 | 130 | Provides an horizontal or vertical slider. 131 | 132 | ![slider](doc/slider.png) 133 | 134 | * `orientation`: `gui::Horizontal` or `gui::Vertical` 135 | 136 | ### `gui::TextBox` 137 | 138 | A one-line text editor. 139 | 140 | ![textbox](doc/textbox.png) 141 | 142 | It supports text cursor, and text selection (with mouse or keyboard shortcuts). 143 | 144 | ## Layouts 145 | 146 | Layouts are containers for widgets. They are also widgets themselves, and can be nested! 147 | 148 | ### `gui::Menu` 149 | 150 | The special, unique root layout. It behave like a `VBoxLayout`. 151 | 152 | ### `gui::HBoxLayout` 153 | 154 | Lines up widgets horizontally. 155 | 156 | Use `layout->add(widget)` to append a widget on a new line. 157 | 158 | ### `gui::VBoxLayout` 159 | 160 | Lines up widgets vertically. 161 | 162 | Use `layout->add(widget)` to append a widget on a new column. 163 | 164 | ### `gui::FormLayout` 165 | 166 | Manages forms of input widgets and their associated labels. 167 | 168 | Use `layout->addRow("my label", widget)` to add a new line with label on the left, and widget on the right. 169 | 170 | ## Theming 171 | 172 | To customize the theme, you can: 173 | 174 | - Change the theme values (padding, color, font, etc.) defined the static class `gui::Theme`. 175 | - Use a custom spritesheet image. 176 | -------------------------------------------------------------------------------- /compile_flags.txt: -------------------------------------------------------------------------------- 1 | -I./src 2 | -std=c++11 3 | -Wall 4 | -Wextra 5 | -Wshadow 6 | -Wwrite-strings 7 | -------------------------------------------------------------------------------- /demo/demo.cpp: -------------------------------------------------------------------------------- 1 | #include "Gui/Theme.hpp" 2 | #include "Gui/Gui.hpp" 3 | #include 4 | 5 | 6 | sf::Color hex2color(const std::string& hexcolor) 7 | { 8 | sf::Color color = sf::Color::Black; 9 | if (hexcolor.size() == 7) // #ffffff 10 | { 11 | color.r = strtoul(hexcolor.substr(1, 2).c_str(), NULL, 16); 12 | color.g = strtoul(hexcolor.substr(3, 2).c_str(), NULL, 16); 13 | color.b = strtoul(hexcolor.substr(5, 2).c_str(), NULL, 16); 14 | } 15 | else if (hexcolor.size() == 4) // #fff 16 | { 17 | color.r = strtoul(hexcolor.substr(1, 1).c_str(), NULL, 16) * 17; 18 | color.g = strtoul(hexcolor.substr(2, 1).c_str(), NULL, 16) * 17; 19 | color.b = strtoul(hexcolor.substr(3, 1).c_str(), NULL, 16) * 17; 20 | } 21 | return color; 22 | } 23 | 24 | struct Theme 25 | { 26 | sf::Color backgroundColor; 27 | std::string texturePath; 28 | }; 29 | 30 | int main() 31 | { 32 | Theme defaultTheme = { 33 | hex2color("#dddbde"), 34 | "demo/texture-default.png" 35 | }; 36 | 37 | Theme win98Theme = { 38 | hex2color("#d4d0c8"), 39 | "demo/texture-win98.png" 40 | }; 41 | 42 | // Create the main window 43 | sf::RenderWindow app(sf::VideoMode(800, 600), "SFML Widgets", sf::Style::Close); 44 | app.setFramerateLimit(60); 45 | 46 | gui::Menu menu(app); 47 | menu.setPosition(10, 10); 48 | 49 | gui::Theme::loadFont("demo/tahoma.ttf"); 50 | gui::Theme::loadTexture(defaultTheme.texturePath); 51 | gui::Theme::textSize = 11; 52 | gui::Theme::click.textColor = hex2color("#191B18"); 53 | gui::Theme::click.textColorHover = hex2color("#191B18"); 54 | gui::Theme::click.textColorFocus = hex2color("#000"); 55 | gui::Theme::input.textColor = hex2color("#000"); 56 | gui::Theme::input.textColorHover = hex2color("#000"); 57 | gui::Theme::input.textColorFocus = hex2color("#000"); 58 | gui::Theme::input.textSelectionColor = hex2color("#8791AD"); 59 | gui::Theme::input.textPlaceholderColor = hex2color("#8791AD"); 60 | gui::Theme::PADDING = 2.f; 61 | gui::Theme::windowBgColor = defaultTheme.backgroundColor; 62 | 63 | gui::HBoxLayout* hbox = menu.addHBoxLayout(); 64 | gui::FormLayout* form = hbox->addFormLayout(); 65 | 66 | sf::Text text("Hello world!", gui::Theme::getFont()); 67 | text.setOrigin(text.getLocalBounds().width / 2, text.getLocalBounds().height / 2); 68 | text.setPosition(480, 240); 69 | 70 | // Textbox 71 | gui::TextBox* textbox = new gui::TextBox(); 72 | textbox->setText("Hello world!"); 73 | textbox->setCallback([&]() { 74 | text.setString(textbox->getText()); 75 | text.setOrigin(text.getLocalBounds().width / 2, text.getLocalBounds().height / 2); 76 | }); 77 | textbox->setPlaceholder("Type something!"); 78 | form->addRow("Text", textbox); 79 | 80 | gui::TextBox* textbox2 = new gui::TextBox(); 81 | textbox2->setText("Hello world!"); 82 | textbox2->setMaxLength(5); 83 | form->addRow("Text with limit (5)", textbox2); 84 | 85 | // Slider + ProgressBar for rotation 86 | gui::Slider* sliderRotation = new gui::Slider(); 87 | gui::ProgressBar* pbarRotation1 = new gui::ProgressBar(200.f, gui::Horizontal, gui::LabelNone); 88 | gui::ProgressBar* pbarRotation2 = new gui::ProgressBar(200.f, gui::Horizontal, gui::LabelOver); 89 | gui::ProgressBar* pbarRotation3 = new gui::ProgressBar(200.f, gui::Horizontal, gui::LabelOutside); 90 | 91 | sliderRotation->setStep(1); 92 | sliderRotation->setCallback([&]() { 93 | text.setRotation(sliderRotation->getValue() * 360 / 100.f); 94 | pbarRotation1->setValue(sliderRotation->getValue()); 95 | pbarRotation2->setValue(sliderRotation->getValue()); 96 | pbarRotation3->setValue(sliderRotation->getValue()); 97 | }); 98 | form->addRow("Rotation", sliderRotation); 99 | 100 | // Slider + ProgressBar for scale 101 | gui::Slider* sliderScale = new gui::Slider(); 102 | gui::ProgressBar* pbarScale1 = new gui::ProgressBar(100, gui::Vertical, gui::LabelNone); 103 | gui::ProgressBar* pbarScale2 = new gui::ProgressBar(100, gui::Vertical, gui::LabelOver); 104 | gui::ProgressBar* pbarScale3 = new gui::ProgressBar(100, gui::Vertical, gui::LabelOutside); 105 | sliderScale->setCallback([&]() { 106 | float scale = 1 + sliderScale->getValue() * 2 / 100.f; 107 | text.setScale(scale, scale); 108 | pbarScale1->setValue(sliderScale->getValue()); 109 | pbarScale2->setValue(sliderScale->getValue()); 110 | pbarScale3->setValue(sliderScale->getValue()); 111 | }); 112 | form->addRow("Scale", sliderScale); 113 | 114 | // OptionsBox for color 115 | gui::OptionsBox* opt = new gui::OptionsBox(); 116 | opt->addItem("Red", sf::Color::Red); 117 | opt->addItem("Blue", sf::Color::Blue); 118 | opt->addItem("Green", sf::Color::Green); 119 | opt->addItem("Yellow", sf::Color::Yellow); 120 | opt->addItem("White", sf::Color::White); 121 | opt->setCallback([&]() { 122 | text.setFillColor(opt->getSelectedValue()); 123 | }); 124 | form->addRow("Color", opt); 125 | 126 | // Checbkox 127 | gui::CheckBox* checkboxBold = new gui::CheckBox(); 128 | checkboxBold->setCallback([&]() { 129 | int style = text.getStyle(); 130 | if (checkboxBold->isChecked()) 131 | style |= sf::Text::Bold; 132 | else 133 | style &= ~sf::Text::Bold; 134 | text.setStyle(style); 135 | }); 136 | form->addRow("Bold text", checkboxBold); 137 | 138 | gui::CheckBox* checkboxUnderlined = new gui::CheckBox(); 139 | checkboxUnderlined->setCallback([&]() { 140 | int style = text.getStyle(); 141 | if (checkboxUnderlined->isChecked()) 142 | style |= sf::Text::Underlined; 143 | else 144 | style &= ~sf::Text::Underlined; 145 | text.setStyle(style); 146 | }); 147 | form->addRow("Underlined text", checkboxUnderlined); 148 | 149 | // Progress bar 150 | form->addRow("Progress bar (label = None)", pbarRotation1); 151 | form->addRow("Progress bar (label = Over)", pbarRotation2); 152 | form->addRow("Progress bar (label = Outside)", pbarRotation3); 153 | 154 | // Vertical progress bars 155 | gui::Layout* layoutForVerticalProgressBars = new gui::HBoxLayout(); 156 | layoutForVerticalProgressBars->add(pbarScale1); 157 | layoutForVerticalProgressBars->add(pbarScale2); 158 | layoutForVerticalProgressBars->add(pbarScale3); 159 | form->addRow("Vertical progress bars", layoutForVerticalProgressBars); 160 | 161 | form->addRow("Default button", new gui::Button("button")); 162 | 163 | // Custom button 164 | sf::Texture imgbutton; 165 | imgbutton.loadFromFile("demo/themed-button.png"); 166 | 167 | gui::SpriteButton* customButton = new gui::SpriteButton(imgbutton, "Play"); 168 | customButton->setTextSize(20); 169 | form->addRow("Custom button", customButton); 170 | 171 | gui::VBoxLayout* vbox = hbox->addVBoxLayout(); 172 | vbox->addLabel("This pannel is on the left"); 173 | 174 | gui::OptionsBox* themeBox = new gui::OptionsBox(); 175 | themeBox->addItem("Windows 98", win98Theme); 176 | themeBox->addItem("Default", defaultTheme); 177 | themeBox->setCallback([&]() { 178 | const Theme& theme = themeBox->getSelectedValue(); 179 | gui::Theme::loadTexture(theme.texturePath); 180 | gui::Theme::windowBgColor = theme.backgroundColor; 181 | }); 182 | vbox->add(themeBox); 183 | 184 | // Textbox 185 | gui::HBoxLayout* hbox2 = vbox->addHBoxLayout(); 186 | gui::TextBox* textbox3 = new gui::TextBox(100); 187 | textbox3->setText("My Button"); 188 | textbox3->setPlaceholder("Button label"); 189 | hbox2->add(textbox3); 190 | hbox2->addButton("Create button", [&]() { 191 | vbox->add(new gui::Button(textbox3->getText())); 192 | }); 193 | 194 | // Small progress bar 195 | gui::HBoxLayout* hbox3 = vbox->addHBoxLayout(); 196 | hbox3->addLabel("Small progress bar"); 197 | gui::ProgressBar* pbar = new gui::ProgressBar(40); 198 | hbox3->add(pbar); 199 | 200 | gui::Slider* vslider = new gui::Slider(100, gui::Vertical); 201 | vslider->setCallback([&]() { 202 | pbar->setValue(vslider->getValue()); 203 | }); 204 | hbox->add(vslider); 205 | 206 | menu.addButton("Quit", [&]() { 207 | app.close(); 208 | }); 209 | 210 | sf::Texture texture; 211 | texture.loadFromFile("demo/sfml.png"); 212 | 213 | sf::Sprite sprite(texture); 214 | sprite.setOrigin(texture.getSize().x / 2, texture.getSize().y / 2); 215 | sprite.setPosition(300, 360); 216 | 217 | // Start the application loop 218 | while (app.isOpen()) 219 | { 220 | // Process events 221 | sf::Event event; 222 | while (app.pollEvent(event)) 223 | { 224 | // Send events to menu 225 | menu.onEvent(event); 226 | if (event.type == sf::Event::Closed) 227 | app.close(); 228 | } 229 | 230 | // Clear screen 231 | app.clear(gui::Theme::windowBgColor); 232 | app.draw(menu); 233 | app.draw(text); 234 | // Update the window 235 | app.display(); 236 | } 237 | 238 | return EXIT_SUCCESS; 239 | } 240 | -------------------------------------------------------------------------------- /demo/sfml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/demo/sfml.png -------------------------------------------------------------------------------- /demo/tahoma.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/demo/tahoma.ttf -------------------------------------------------------------------------------- /demo/texture-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/demo/texture-default.png -------------------------------------------------------------------------------- /demo/texture-win98.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/demo/texture-win98.png -------------------------------------------------------------------------------- /demo/themed-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/demo/themed-button.png -------------------------------------------------------------------------------- /doc/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/doc/button.png -------------------------------------------------------------------------------- /doc/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/doc/checkbox.png -------------------------------------------------------------------------------- /doc/optionsbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/doc/optionsbox.png -------------------------------------------------------------------------------- /doc/progress-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/doc/progress-bar.png -------------------------------------------------------------------------------- /doc/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/doc/slider.png -------------------------------------------------------------------------------- /doc/textbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abodelot/sfml-widgets/6e493776c5eb2424cfbc2c48fd725fcca320c99a/doc/textbox.png -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | clang-format $(find src -type f) -i 4 | echo "Done" 5 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Gui/Gui.hpp" 4 | 5 | int main() 6 | { 7 | sf::RenderWindow app(sf::VideoMode(800, 600), "SFML Widgets", sf::Style::Close); 8 | 9 | // Declare menu 10 | gui::Menu menu(app); 11 | 12 | gui::Theme::loadFont("demo/tahoma.ttf"); 13 | gui::Theme::loadTexture("demo/texture-default.png"); 14 | 15 | // Create some button widget 16 | gui::Button* button = new gui::Button("My button"); 17 | 18 | // Insert button into menu 19 | menu.add(button); 20 | 21 | // Define a callback 22 | button->setCallback([] { 23 | std::cout << "click!" << std::endl; 24 | }); 25 | 26 | // Start the application loop 27 | while (app.isOpen()) 28 | { 29 | // Process events 30 | sf::Event event; 31 | while (app.pollEvent(event)) 32 | { 33 | // Send events to the menu 34 | menu.onEvent(event); 35 | 36 | if (event.type == sf::Event::Closed) 37 | app.close(); 38 | } 39 | 40 | // Optional: clear window with theme background color 41 | app.clear(gui::Theme::windowBgColor); 42 | 43 | // Render menu 44 | app.draw(menu); 45 | 46 | // Update the window 47 | app.display(); 48 | } 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /sfml-widgets.cbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 91 | 92 | -------------------------------------------------------------------------------- /src/Gui/Button.cpp: -------------------------------------------------------------------------------- 1 | #include "Button.hpp" 2 | #include "Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | Button::Button(const sf::String& string) 8 | { 9 | m_box.item().setFont(Theme::getFont()); 10 | m_box.item().setCharacterSize(Theme::textSize); 11 | m_box.setSize(Theme::minWidgetWidth, Theme::getBoxHeight()); 12 | setString(string); 13 | setSize(m_box.getSize()); 14 | } 15 | 16 | 17 | void Button::setString(const sf::String& string) 18 | { 19 | m_box.item().setString(string); 20 | 21 | // Recompute widget width 22 | int fittingWidth = m_box.item().getLocalBounds().width + Theme::PADDING * 2 + Theme::borderSize * 2; 23 | int width = std::max(fittingWidth, Theme::minWidgetWidth); 24 | m_box.setSize(width, Theme::getBoxHeight()); 25 | m_box.centerTextHorizontally(m_box.item()); 26 | setSize(m_box.getSize()); 27 | } 28 | 29 | 30 | const sf::String& Button::getString() const 31 | { 32 | return m_box.item().getString(); 33 | } 34 | 35 | 36 | void Button::draw(sf::RenderTarget& target, sf::RenderStates states) const 37 | { 38 | states.transform *= getTransform(); 39 | target.draw(m_box, states); 40 | } 41 | 42 | // Callbacks ------------------------------------------------------------------- 43 | 44 | void Button::onStateChanged(State state) 45 | { 46 | m_box.applyState(state); 47 | } 48 | 49 | 50 | void Button::onMouseMoved(float x, float y) 51 | { 52 | if (getState() == StatePressed) 53 | { 54 | if (containsPoint(sf::Vector2f(x, y))) 55 | m_box.press(); 56 | else 57 | m_box.release(); 58 | } 59 | } 60 | 61 | 62 | void Button::onMousePressed(float x, float y) 63 | { 64 | m_box.press(); 65 | } 66 | 67 | 68 | void Button::onMouseReleased(float x, float y) 69 | { 70 | if (containsPoint({x, y})) 71 | { 72 | triggerCallback(); 73 | } 74 | } 75 | 76 | 77 | void Button::onKeyPressed(const sf::Event::KeyEvent& key) 78 | { 79 | if (key.code == sf::Keyboard::Return) 80 | { 81 | triggerCallback(); 82 | m_box.press(); 83 | } 84 | } 85 | 86 | 87 | void Button::onKeyReleased(const sf::Event::KeyEvent& key) 88 | { 89 | if (key.code == sf::Keyboard::Return) 90 | { 91 | m_box.release(); 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/Gui/Button.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_BUTTON_HPP 2 | #define GUI_BUTTON_HPP 3 | 4 | #include "Widget.hpp" 5 | #include "Utils/ItemBox.hpp" 6 | 7 | namespace gui 8 | { 9 | 10 | /** 11 | * The Button widget is a simple press button. 12 | * The callback is triggered when the button is clicked/activated. 13 | */ 14 | class Button: public Widget 15 | { 16 | public: 17 | Button(const sf::String& string); 18 | 19 | /** 20 | * Set the displayed button label 21 | */ 22 | void setString(const sf::String& string); 23 | 24 | const sf::String& getString() const; 25 | 26 | protected: 27 | // Callbacks 28 | void onStateChanged(State state) override; 29 | void onMouseMoved(float x, float y) override; 30 | void onMousePressed(float x, float y) override; 31 | void onMouseReleased(float x, float y) override; 32 | void onKeyPressed(const sf::Event::KeyEvent& key) override; 33 | void onKeyReleased(const sf::Event::KeyEvent& key) override; 34 | 35 | private: 36 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 37 | 38 | ItemBox m_box; 39 | }; 40 | 41 | } 42 | 43 | #endif // GUI_BUTTON_HPP 44 | -------------------------------------------------------------------------------- /src/Gui/CheckBox.cpp: -------------------------------------------------------------------------------- 1 | #include "CheckBox.hpp" 2 | #include "Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | CheckBox::CheckBox(bool checked): 8 | m_box(Box::Input) 9 | { 10 | int offset = Theme::PADDING + Theme::borderSize; 11 | float box_size = m_cross.getSize().x + offset * 2; 12 | m_box.setSize(box_size, box_size); 13 | m_cross.setPosition(offset, offset); 14 | check(checked); 15 | 16 | setSize(m_box.getSize()); 17 | } 18 | 19 | 20 | bool CheckBox::isChecked() const 21 | { 22 | return m_checked; 23 | } 24 | 25 | 26 | void CheckBox::check(bool checked) 27 | { 28 | m_checked = checked; 29 | } 30 | 31 | 32 | void CheckBox::draw(sf::RenderTarget& target, sf::RenderStates states) const 33 | { 34 | states.transform *= getTransform(); 35 | target.draw(m_box, states); 36 | if (m_checked) 37 | target.draw(m_cross, states); 38 | } 39 | 40 | // callbacks ------------------------------------------------------------------- 41 | 42 | void CheckBox::onStateChanged(State state) 43 | { 44 | m_box.applyState(state); 45 | } 46 | 47 | 48 | void CheckBox::onMouseReleased(float x, float y) 49 | { 50 | if (containsPoint(sf::Vector2f(x, y))) 51 | { 52 | check(!m_checked); 53 | triggerCallback(); 54 | } 55 | } 56 | 57 | 58 | void CheckBox::onKeyPressed(const sf::Event::KeyEvent& key) 59 | { 60 | if (key.code == sf::Keyboard::Space) 61 | { 62 | check(!m_checked); 63 | triggerCallback(); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/Gui/CheckBox.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_CHECKBOX_HPP 2 | #define GUI_CHECKBOX_HPP 3 | 4 | #include "Widget.hpp" 5 | #include "Utils/Box.hpp" 6 | #include "Utils/Cross.hpp" 7 | 8 | namespace gui 9 | { 10 | 11 | /** 12 | * The CheckBox is a widget for enabling/disabling an option. 13 | * The callback is triggered when the checkbox is checked or unchecked. 14 | */ 15 | class CheckBox: public Widget 16 | { 17 | public: 18 | CheckBox(bool checked = false); 19 | 20 | bool isChecked() const; 21 | 22 | void check(bool checked); 23 | 24 | protected: 25 | // Callbacks 26 | void onStateChanged(State state) override; 27 | void onMouseReleased(float x, float y) override; 28 | void onKeyPressed(const sf::Event::KeyEvent& key) override; 29 | 30 | private: 31 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 32 | 33 | Box m_box; 34 | Cross m_cross; 35 | bool m_checked; 36 | }; 37 | 38 | } 39 | 40 | #endif // GUI_CHECKBOX_HPP 41 | -------------------------------------------------------------------------------- /src/Gui/ComboBox.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_COMBOBOX_HPP 2 | #define GUI_COMBOBOX_HPP 3 | 4 | #include "Widget.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | /** 10 | * The ComboBox is a selection widget that displays the current item, and can 11 | * pop up a list of selectable items. 12 | * A ComboBox provides a means of presenting a list of options to the user in a 13 | * way that takes up the minimum amount of screen space. A ComboBox is not 14 | * editable, the user cannot modify items in the list. 15 | * The callback is triggered when selection is changed. 16 | */ 17 | template 18 | class ComboBox: public Widget 19 | { 20 | public: 21 | ComboBox(); 22 | 23 | private: 24 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 25 | }; 26 | 27 | } 28 | 29 | #include "ComboBox.inl" 30 | 31 | #endif // GUI_COMBOBOX_HPP 32 | -------------------------------------------------------------------------------- /src/Gui/ComboBox.inl: -------------------------------------------------------------------------------- 1 | namespace gui 2 | { 3 | 4 | template 5 | ComboBox::ComboBox() 6 | { 7 | } 8 | 9 | 10 | template 11 | void ComboBox::draw(sf::RenderTarget& target, sf::RenderStates states) const 12 | { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Gui/Enums/Enums.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_ENUMS_ENUMS_HPP 2 | #define GUI_ENUMS_ENUMS_HPP 3 | 4 | namespace gui 5 | { 6 | 7 | enum Orientation 8 | { 9 | Horizontal, 10 | Vertical 11 | }; 12 | 13 | } 14 | 15 | #endif // GUI_ENUMS_ENUMS_HPP 16 | -------------------------------------------------------------------------------- /src/Gui/Gui.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_GUI_HPP 2 | #define GUI_GUI_HPP 3 | 4 | #include "Menu.hpp" 5 | #include "Theme.hpp" 6 | 7 | // Enums 8 | #include "Enums/Enums.hpp" 9 | 10 | // Widgets 11 | #include "Button.hpp" 12 | #include "CheckBox.hpp" 13 | #include "ComboBox.hpp" 14 | #include "Image.hpp" 15 | #include "Label.hpp" 16 | #include "OptionsBox.hpp" 17 | #include "ProgressBar.hpp" 18 | #include "Slider.hpp" 19 | #include "SpriteButton.hpp" 20 | #include "TextBox.hpp" 21 | 22 | // Layouts 23 | #include "Layouts/FormLayout.hpp" 24 | #include "Layouts/HBoxLayout.hpp" 25 | #include "Layouts/VBoxLayout.hpp" 26 | 27 | #endif // GUI_GUI_HPP 28 | -------------------------------------------------------------------------------- /src/Gui/Image.cpp: -------------------------------------------------------------------------------- 1 | #include "Image.hpp" 2 | 3 | namespace gui 4 | { 5 | 6 | Image::Image(): 7 | m_texture(nullptr) 8 | { 9 | setSelectable(false); 10 | } 11 | 12 | 13 | Image::Image(const sf::Texture& texture): 14 | m_texture(nullptr) 15 | { 16 | setSelectable(false); 17 | setTexture(texture); 18 | } 19 | 20 | 21 | void Image::setTexture(const sf::Texture& texture) 22 | { 23 | int width = texture.getSize().x; 24 | int height = texture.getSize().y; 25 | m_vertices[0].position = m_vertices[0].texCoords = sf::Vector2f(0, 0); 26 | m_vertices[1].position = m_vertices[1].texCoords = sf::Vector2f(0, height); 27 | m_vertices[2].position = m_vertices[2].texCoords = sf::Vector2f(width, height); 28 | m_vertices[3].position = m_vertices[3].texCoords = sf::Vector2f(width, 0); 29 | m_texture = &texture; 30 | 31 | // Set widget dimensions 32 | setSize(width, height); 33 | } 34 | 35 | 36 | void Image::setColor(const sf::Color& color) 37 | { 38 | for (int i = 0; i < 4; ++i) 39 | m_vertices[i].color = color; 40 | } 41 | 42 | 43 | void Image::draw(sf::RenderTarget& target, sf::RenderStates states) const 44 | { 45 | if (m_texture != nullptr) 46 | { 47 | states.transform *= getTransform(); 48 | states.texture = m_texture; 49 | target.draw(m_vertices, 4, sf::Quads, states); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/Gui/Image.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_IMAGE_HPP 2 | #define GUI_IMAGE_HPP 3 | 4 | #include "Widget.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | /** 10 | * Widget for displaying a picture 11 | * Passive widget: cannot be focused or trigger event 12 | */ 13 | class Image: public Widget 14 | { 15 | public: 16 | Image(); 17 | Image(const sf::Texture& texture); 18 | 19 | void setTexture(const sf::Texture& texture); 20 | 21 | void setColor(const sf::Color& color); 22 | 23 | private: 24 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 25 | 26 | sf::Vertex m_vertices[4]; 27 | const sf::Texture* m_texture; 28 | }; 29 | 30 | } 31 | 32 | #endif // GUI_IMAGE_HPP 33 | -------------------------------------------------------------------------------- /src/Gui/Label.cpp: -------------------------------------------------------------------------------- 1 | #include "Label.hpp" 2 | #include "Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | Label::Label(const sf::String& string) 8 | { 9 | m_text.setFont(Theme::getFont()); 10 | m_text.setPosition(Theme::PADDING, Theme::PADDING); 11 | m_text.setFillColor(Theme::click.textColor); 12 | m_text.setCharacterSize(Theme::textSize); 13 | setSelectable(false); 14 | setText(string); 15 | } 16 | 17 | 18 | void Label::setText(const sf::String& string) 19 | { 20 | m_text.setString(string); 21 | updateGeometry(); 22 | } 23 | 24 | 25 | const sf::String& Label::getText() const 26 | { 27 | return m_text.getString(); 28 | } 29 | 30 | 31 | void Label::setFillColor(const sf::Color& color) 32 | { 33 | m_text.setFillColor(color); 34 | } 35 | 36 | 37 | const sf::Color& Label::getFillColor() const 38 | { 39 | return m_text.getFillColor(); 40 | } 41 | 42 | 43 | void Label::setTextSize(size_t size) 44 | { 45 | m_text.setCharacterSize(size); 46 | updateGeometry(); 47 | } 48 | 49 | 50 | size_t Label::getTextSize() const 51 | { 52 | return m_text.getCharacterSize(); 53 | } 54 | 55 | 56 | void Label::draw(sf::RenderTarget& target, sf::RenderStates states) const 57 | { 58 | states.transform *= getTransform(); 59 | target.draw(m_text, states); 60 | } 61 | 62 | 63 | void Label::updateGeometry() 64 | { 65 | Widget::setSize( 66 | m_text.getLocalBounds().width + Theme::PADDING * 2, m_text.getLocalBounds().height + Theme::PADDING * 2 67 | ); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Gui/Label.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_LABEL_HPP 2 | #define GUI_LABEL_HPP 3 | 4 | #include "Widget.hpp" 5 | 6 | 7 | namespace gui 8 | { 9 | 10 | /** 11 | * Widget for displaying a simple text 12 | * Passive widget: cannot be focused or trigger event 13 | */ 14 | class Label: public Widget 15 | { 16 | public: 17 | Label(const sf::String& string = ""); 18 | 19 | /** 20 | * Label's text 21 | */ 22 | void setText(const sf::String& string); 23 | const sf::String& getText() const; 24 | 25 | /** 26 | * Label's color 27 | */ 28 | void setFillColor(const sf::Color& color); 29 | const sf::Color& getFillColor() const; 30 | 31 | void setTextSize(size_t size); 32 | size_t getTextSize() const; 33 | 34 | private: 35 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 36 | 37 | void updateGeometry(); 38 | 39 | sf::Text m_text; 40 | }; 41 | 42 | } 43 | 44 | #endif // GUI_LABEL_HPP 45 | -------------------------------------------------------------------------------- /src/Gui/Layouts/FormLayout.cpp: -------------------------------------------------------------------------------- 1 | #include "FormLayout.hpp" 2 | #include "../Theme.hpp" 3 | #include "../Label.hpp" 4 | 5 | namespace gui 6 | { 7 | 8 | FormLayout::FormLayout(): 9 | m_labelWidth(0.f) 10 | { 11 | } 12 | 13 | 14 | Widget* FormLayout::addRow(const sf::String& str, Widget* widget) 15 | { 16 | gui::Label* label = new gui::Label(str); 17 | if (label->getSize().x > m_labelWidth) 18 | { 19 | m_labelWidth = label->getSize().x; 20 | } 21 | Layout::add(label); 22 | Layout::add(widget); 23 | return widget; 24 | } 25 | 26 | 27 | void FormLayout::recomputeGeometry() 28 | { 29 | size_t i = 0; 30 | sf::Vector2f pos; 31 | sf::Vector2f size; 32 | for (Widget* widget = getFirstWidget(); widget != nullptr; widget = widget->m_next) 33 | { 34 | if (i % 2 == 0) 35 | { 36 | // Left-side: label 37 | widget->setPosition(0, pos.y); 38 | if (widget->getSize().x > m_labelWidth) 39 | m_labelWidth = widget->getSize().x; 40 | } 41 | else 42 | { 43 | // Right-side: widget 44 | widget->setPosition(m_labelWidth + Theme::MARGIN, pos.y); 45 | // Row height is at least Theme::getBoxHeight() 46 | pos.y += std::max(widget->getSize().y, Theme::getBoxHeight()) + Theme::MARGIN; 47 | if (widget->getSize().x > size.x) 48 | size.x = widget->getSize().x; 49 | } 50 | ++i; 51 | } 52 | size.x += m_labelWidth + Theme::MARGIN; 53 | size.y = pos.y - Theme::MARGIN; 54 | Widget::setSize(size); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Gui/Layouts/FormLayout.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_FORMLAYOUT_HPP 2 | #define GUI_FORMLAYOUT_HPP 3 | 4 | #include "Layout.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | /** 10 | * Horizontally stacked layout with a label before each widget 11 | */ 12 | class FormLayout: public Layout 13 | { 14 | public: 15 | FormLayout(); 16 | 17 | /** 18 | * Add a new label/widget row in the form 19 | * @param label: label displayed before the widget 20 | * @param widget: widget to be added 21 | */ 22 | Widget* addRow(const sf::String& label, Widget* widget); 23 | 24 | private: 25 | void recomputeGeometry() override; 26 | 27 | float m_labelWidth; 28 | }; 29 | 30 | } 31 | 32 | #endif // GUI_FORMLAYOUT_HPP 33 | -------------------------------------------------------------------------------- /src/Gui/Layouts/HBoxLayout.cpp: -------------------------------------------------------------------------------- 1 | #include "HBoxLayout.hpp" 2 | #include "../Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | void HBoxLayout::recomputeGeometry() 8 | { 9 | sf::Vector2f pos; 10 | sf::Vector2f size; 11 | for (Widget* w = getFirstWidget(); w != nullptr; w = w->m_next) 12 | { 13 | w->setPosition(pos); 14 | pos.x += w->getSize().x + Theme::MARGIN; 15 | 16 | // Layout's height is the tallest widget's height 17 | if (w->getSize().y > size.y) 18 | size.y = w->getSize().y; 19 | } 20 | size.x = pos.x - Theme::MARGIN; 21 | Widget::setSize(size); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Gui/Layouts/HBoxLayout.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_HBOXLAYOUT_HPP 2 | #define GUI_HBOXLAYOUT_HPP 3 | 4 | #include "Layout.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | /** 10 | * Horizontally stacked layout 11 | */ 12 | class HBoxLayout: public Layout 13 | { 14 | public: 15 | private: 16 | void recomputeGeometry() override; 17 | }; 18 | 19 | } 20 | 21 | #endif // GUI_HBOXLAYOUT_HPP 22 | -------------------------------------------------------------------------------- /src/Gui/Layouts/Layout.cpp: -------------------------------------------------------------------------------- 1 | #include "Layout.hpp" 2 | #include "FormLayout.hpp" 3 | #include "HBoxLayout.hpp" 4 | #include "VBoxLayout.hpp" 5 | #include "../Button.hpp" 6 | #include "../Label.hpp" 7 | 8 | namespace gui 9 | { 10 | 11 | Layout::Layout(): 12 | m_first(nullptr), 13 | m_last(nullptr), 14 | m_hover(nullptr), 15 | m_focus(nullptr) 16 | { 17 | } 18 | 19 | 20 | Layout::~Layout() 21 | { 22 | // Deallocate all widgets 23 | const Widget* widget = m_first; 24 | while (widget != nullptr) 25 | { 26 | const Widget* next = widget->m_next; 27 | delete widget; 28 | widget = next; 29 | } 30 | } 31 | 32 | 33 | Widget* Layout::add(Widget* widget) 34 | { 35 | widget->setParent(this); 36 | 37 | if (m_first == nullptr) 38 | { 39 | m_first = m_last = widget; 40 | } 41 | else 42 | { 43 | m_last->m_next = widget; 44 | widget->m_previous = m_last; 45 | m_last = widget; 46 | } 47 | recomputeGeometry(); 48 | return widget; 49 | } 50 | 51 | 52 | Button* Layout::addButton(const sf::String& string, std::function callback) 53 | { 54 | Button* button = new Button(string); 55 | button->setCallback(callback); 56 | add(button); 57 | return button; 58 | } 59 | 60 | 61 | Label* Layout::addLabel(const sf::String& string) 62 | { 63 | Label* label = new Label(string); 64 | add(label); 65 | return label; 66 | } 67 | 68 | 69 | FormLayout* Layout::addFormLayout() 70 | { 71 | FormLayout* form = new FormLayout(); 72 | add(form); 73 | return form; 74 | } 75 | 76 | 77 | HBoxLayout* Layout::addHBoxLayout() 78 | { 79 | HBoxLayout* hbox = new HBoxLayout(); 80 | add(hbox); 81 | return hbox; 82 | } 83 | 84 | 85 | VBoxLayout* Layout::addVBoxLayout() 86 | { 87 | VBoxLayout* vbox = new VBoxLayout(); 88 | add(vbox); 89 | return vbox; 90 | } 91 | 92 | 93 | Widget* Layout::getFirstWidget() 94 | { 95 | return m_first; 96 | } 97 | 98 | 99 | // Callbacks ------------------------------------------------------------------- 100 | 101 | void Layout::onStateChanged(State state) 102 | { 103 | if (state == StateDefault) 104 | { 105 | if (m_focus != nullptr) 106 | { 107 | m_focus->setState(StateDefault); 108 | m_focus = nullptr; 109 | } 110 | } 111 | } 112 | 113 | 114 | void Layout::onMouseMoved(float x, float y) 115 | { 116 | // Pressed widgets still receive mouse move events even when not hovered if mouse is pressed 117 | // Example: moving a slider's handle 118 | if (m_focus != nullptr && sf::Mouse::isButtonPressed(sf::Mouse::Left)) 119 | { 120 | m_focus->onMouseMoved(x - m_focus->getPosition().x, y - m_focus->getPosition().y); 121 | if (!m_focus->containsPoint({x, y})) 122 | { 123 | m_hover = nullptr; 124 | } 125 | } 126 | else 127 | { 128 | // Loop over widgets 129 | for (Widget* widget = m_first; widget != nullptr; widget = widget->m_next) 130 | { 131 | // Convert mouse position to widget coordinates system 132 | sf::Vector2f mouse = sf::Vector2f(x, y) - widget->getPosition(); 133 | if (widget->containsPoint(mouse)) 134 | { 135 | if (m_hover != widget) 136 | { 137 | // A new widget is hovered 138 | if (m_hover != nullptr) 139 | { 140 | m_hover->setState(StateDefault); 141 | m_hover->onMouseLeave(); 142 | } 143 | 144 | m_hover = widget; 145 | // Don't send Hovered state if widget is already focused 146 | if (m_hover != m_focus) 147 | { 148 | widget->setState(StateHovered); 149 | } 150 | widget->onMouseEnter(); 151 | } 152 | else 153 | { 154 | // Hovered widget was already hovered 155 | widget->onMouseMoved(mouse.x, mouse.y); 156 | } 157 | return; 158 | } 159 | } 160 | // No widget hovered, remove hovered state 161 | if (m_hover != nullptr) 162 | { 163 | m_hover->onMouseMoved(x, y); 164 | m_hover->setState(m_focus == m_hover ? StateFocused : StateDefault); 165 | m_hover->onMouseLeave(); 166 | m_hover = nullptr; 167 | } 168 | } 169 | } 170 | 171 | 172 | void Layout::onMousePressed(float x, float y) 173 | { 174 | if (m_hover != nullptr) 175 | { 176 | // Clicked widget takes focus 177 | if (m_focus != m_hover) 178 | { 179 | focusWidget(m_hover); 180 | } 181 | // Send event to widget 182 | sf::Vector2f mouse = sf::Vector2f(x, y) - m_hover->getPosition(); 183 | m_hover->onMousePressed(mouse.x, mouse.y); 184 | } 185 | // User didn't click on a widget, remove focus state 186 | else if (m_focus != nullptr) 187 | { 188 | m_focus->setState(StateDefault); 189 | m_focus = nullptr; 190 | } 191 | } 192 | 193 | 194 | void Layout::onMouseReleased(float x, float y) 195 | { 196 | if (m_focus != nullptr) 197 | { 198 | // Send event to the focused widget 199 | sf::Vector2f mouse = sf::Vector2f(x, y) - m_focus->getPosition(); 200 | m_focus->onMouseReleased(mouse.x, mouse.y); 201 | m_focus->setState(StateFocused); 202 | } 203 | } 204 | 205 | 206 | void Layout::onMouseWheelMoved(int delta) 207 | { 208 | if (m_focus != nullptr) 209 | { 210 | m_focus->onMouseWheelMoved(delta); 211 | } 212 | } 213 | 214 | 215 | void Layout::onKeyPressed(const sf::Event::KeyEvent& key) 216 | { 217 | if (key.code == Theme::nextWidgetKey) 218 | { 219 | if (!focusNextWidget()) 220 | // Try to focus first widget if possible 221 | focusNextWidget(); 222 | } 223 | else if (key.code == Theme::previousWidgetKey) 224 | { 225 | if (!focusPreviousWidget()) 226 | focusPreviousWidget(); 227 | } 228 | else if (m_focus != nullptr) 229 | { 230 | m_focus->onKeyPressed(key); 231 | } 232 | } 233 | 234 | 235 | void Layout::onKeyReleased(const sf::Event::KeyEvent& key) 236 | { 237 | if (m_focus != nullptr) 238 | { 239 | m_focus->onKeyReleased(key); 240 | } 241 | } 242 | 243 | 244 | void Layout::onTextEntered(sf::Uint32 unicode) 245 | { 246 | if (m_focus != nullptr) 247 | { 248 | m_focus->onTextEntered(unicode); 249 | } 250 | } 251 | 252 | 253 | void Layout::draw(sf::RenderTarget& target, sf::RenderStates states) const 254 | { 255 | states.transform *= getTransform(); 256 | for (const Widget* widget = m_first; widget != nullptr; widget = widget->m_next) 257 | { 258 | target.draw(*widget, states); 259 | } 260 | } 261 | 262 | 263 | bool Layout::focusWidget(Widget* widget) 264 | { 265 | if (widget != nullptr) 266 | { 267 | // If another widget was already focused, remove focus 268 | if (m_focus != nullptr && m_focus != widget) 269 | { 270 | m_focus->setState(StateDefault); 271 | m_focus = nullptr; 272 | } 273 | // Apply focus to widget 274 | if (widget->isSelectable()) 275 | { 276 | m_focus = widget; 277 | widget->setState(StateFocused); 278 | return true; 279 | } 280 | } 281 | return false; 282 | } 283 | 284 | 285 | bool Layout::focusPreviousWidget() 286 | { 287 | // If a sublayout is already focused 288 | if (m_focus != nullptr && m_focus->toLayout() != nullptr) 289 | { 290 | if (m_focus->toLayout()->focusPreviousWidget()) 291 | return true; 292 | } 293 | 294 | Widget* start = m_focus != nullptr ? m_focus->m_previous : m_last; 295 | for (Widget* widget = start; widget != nullptr; widget = widget->m_previous) 296 | { 297 | Layout* container = widget->toLayout(); 298 | if (container != nullptr) 299 | { 300 | if (container->focusPreviousWidget()) 301 | { 302 | focusWidget(container); 303 | return true; 304 | } 305 | } 306 | else if (focusWidget(widget)) 307 | { 308 | return true; 309 | } 310 | } 311 | 312 | if (m_focus != nullptr) 313 | m_focus->setState(StateDefault); 314 | m_focus = nullptr; 315 | return false; 316 | } 317 | 318 | 319 | bool Layout::focusNextWidget() 320 | { 321 | // If a sublayout is already focused 322 | if (m_focus != nullptr && m_focus->toLayout() != nullptr) 323 | { 324 | if (m_focus->toLayout()->focusNextWidget()) 325 | return true; 326 | } 327 | 328 | Widget* start = m_focus != nullptr ? m_focus->m_next : m_first; 329 | for (Widget* widget = start; widget != nullptr; widget = widget->m_next) 330 | { 331 | Layout* container = widget->toLayout(); 332 | if (container != nullptr) 333 | { 334 | if (container->focusNextWidget()) 335 | { 336 | focusWidget(container); 337 | return true; 338 | } 339 | } 340 | else if (focusWidget(widget)) 341 | { 342 | return true; 343 | } 344 | } 345 | 346 | if (m_focus != nullptr) 347 | m_focus->setState(StateDefault); 348 | m_focus = nullptr; 349 | return false; 350 | } 351 | 352 | } 353 | -------------------------------------------------------------------------------- /src/Gui/Layouts/Layout.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_LAYOUT_HPP 2 | #define GUI_LAYOUT_HPP 3 | 4 | #include "../Widget.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | class Widget; 10 | class Label; 11 | class Button; 12 | class FormLayout; 13 | class HBoxLayout; 14 | class VBoxLayout; 15 | 16 | /** 17 | * Base class for layouts. Layouts are special widgets which act as containers 18 | * See FormLayout, HBoxLayout and VBoxLayout 19 | */ 20 | class Layout: public Widget 21 | { 22 | public: 23 | Layout(); 24 | ~Layout(); 25 | 26 | /** 27 | * Add a new widget in the container 28 | * The container will take care of widget deallocation 29 | * @return added widget 30 | */ 31 | Widget* add(Widget* widget); 32 | 33 | /// Helpers 34 | Button* addButton(const sf::String& string, std::function callback); 35 | Label* addLabel(const sf::String& string); 36 | FormLayout* addFormLayout(); 37 | HBoxLayout* addHBoxLayout(); 38 | VBoxLayout* addVBoxLayout(); 39 | 40 | protected: 41 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 42 | 43 | // Callbacks --------------------------------------------------------------- 44 | void onStateChanged(State state) override; 45 | void onMouseMoved(float x, float y) override; 46 | void onMousePressed(float x, float y) override; 47 | void onMouseReleased(float x, float y) override; 48 | void onMouseWheelMoved(int delta) override; 49 | void onKeyPressed(const sf::Event::KeyEvent& key) override; 50 | void onKeyReleased(const sf::Event::KeyEvent& key) override; 51 | void onTextEntered(sf::Uint32 unicode) override; 52 | 53 | inline Layout* toLayout() override { return this; } 54 | bool focusNextWidget(); 55 | bool focusPreviousWidget(); 56 | 57 | Widget* getFirstWidget(); 58 | 59 | private: 60 | /** 61 | * Give the focus to a widget, if applicable 62 | * @param state: new state of the widget if it took focus 63 | * @return true if widget took the focus, otherwise false 64 | */ 65 | bool focusWidget(Widget* widget); 66 | 67 | Widget* m_first; 68 | Widget* m_last; 69 | Widget* m_hover; 70 | Widget* m_focus; 71 | }; 72 | 73 | } 74 | 75 | #endif // GUI_LAYOUT_HPP 76 | -------------------------------------------------------------------------------- /src/Gui/Layouts/VBoxLayout.cpp: -------------------------------------------------------------------------------- 1 | #include "VBoxLayout.hpp" 2 | #include "../Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | void VBoxLayout::recomputeGeometry() 8 | { 9 | sf::Vector2f pos; 10 | sf::Vector2f size; 11 | for (Widget* w = getFirstWidget(); w != nullptr; w = w->m_next) 12 | { 13 | w->setPosition(pos); 14 | pos.y += w->getSize().y + Theme::MARGIN; 15 | 16 | // Layout's width is the wider widget's width 17 | if (w->getSize().x > size.x) 18 | size.x = w->getSize().x; 19 | } 20 | size.y = pos.y - Theme::MARGIN; 21 | Widget::setSize(size); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Gui/Layouts/VBoxLayout.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_VBOXLAYOUT_HPP 2 | #define GUI_VBOXLAYOUT_HPP 3 | 4 | #include "Layout.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | /** 10 | * Vertically stacked layout 11 | */ 12 | class VBoxLayout: public Layout 13 | { 14 | private: 15 | void recomputeGeometry() override; 16 | }; 17 | 18 | } 19 | 20 | #endif // GUI_VBOXLAYOUT_HPP 21 | -------------------------------------------------------------------------------- /src/Gui/Menu.cpp: -------------------------------------------------------------------------------- 1 | #include "Menu.hpp" 2 | #include "Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | Menu::Menu(sf::RenderWindow& window): 8 | m_window(window), 9 | m_cursorType(sf::Cursor::Arrow) 10 | { 11 | } 12 | 13 | 14 | void Menu::onEvent(const sf::Event& event) 15 | { 16 | switch (event.type) 17 | { 18 | case sf::Event::MouseMoved: 19 | { 20 | sf::Vector2f mouse = convertMousePosition(event.mouseMove.x, event.mouseMove.y); 21 | onMouseMoved(mouse.x, mouse.y); 22 | break; 23 | } 24 | 25 | case sf::Event::MouseButtonPressed: 26 | if (event.mouseButton.button == sf::Mouse::Left) 27 | { 28 | sf::Vector2f mouse = convertMousePosition(event.mouseButton.x, event.mouseButton.y); 29 | onMousePressed(mouse.x, mouse.y); 30 | } 31 | break; 32 | 33 | case sf::Event::MouseButtonReleased: 34 | if (event.mouseButton.button == sf::Mouse::Left) 35 | { 36 | sf::Vector2f mouse = convertMousePosition(event.mouseButton.x, event.mouseButton.y); 37 | onMouseReleased(mouse.x, mouse.y); 38 | } 39 | break; 40 | 41 | case sf::Event::MouseWheelMoved: 42 | onMouseWheelMoved(event.mouseWheel.delta); 43 | break; 44 | 45 | case sf::Event::KeyPressed: 46 | onKeyPressed(event.key); 47 | break; 48 | 49 | case sf::Event::KeyReleased: 50 | onKeyReleased(event.key); 51 | break; 52 | 53 | case sf::Event::TextEntered: 54 | onTextEntered(event.text.unicode); 55 | break; 56 | 57 | default: 58 | break; 59 | } 60 | } 61 | 62 | 63 | sf::Vector2f Menu::convertMousePosition(int x, int y) const 64 | { 65 | sf::Vector2f mouse = m_window.mapPixelToCoords(sf::Vector2i(x, y)); 66 | mouse -= getPosition(); 67 | return mouse; 68 | } 69 | 70 | 71 | void Menu::setMouseCursor(sf::Cursor::Type cursorType) 72 | { 73 | if (cursorType != m_cursorType) 74 | { 75 | gui::Theme::cursor.loadFromSystem(cursorType); 76 | m_window.setMouseCursor(gui::Theme::cursor); 77 | m_cursorType = cursorType; 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/Gui/Menu.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_MENU_HPP 2 | #define GUI_MENU_HPP 3 | 4 | #include "Layouts/VBoxLayout.hpp" 5 | #include 6 | 7 | namespace gui 8 | { 9 | 10 | /** 11 | * Entry point for the GUI. 12 | * A Menu is a VBoxLayout with a general onEvent handler 13 | */ 14 | class Menu: public gui::VBoxLayout 15 | { 16 | public: 17 | Menu(sf::RenderWindow& window); 18 | 19 | /** 20 | * Handle an SFML event and send it to widgets 21 | */ 22 | void onEvent(const sf::Event& event); 23 | 24 | private: 25 | /** 26 | * Get mouse cursor relative position 27 | * @param x: absolute x position from the event 28 | * @param y: absolute y position from the event 29 | * @return relative mouse position 30 | */ 31 | sf::Vector2f convertMousePosition(int x, int y) const; 32 | 33 | /** 34 | * Update the cursor type on the RenderWindow 35 | */ 36 | void setMouseCursor(sf::Cursor::Type cursorType) override; 37 | 38 | sf::RenderWindow& m_window; 39 | sf::Cursor::Type m_cursorType; 40 | }; 41 | 42 | } 43 | 44 | #endif // GUI_MENU_HPP 45 | -------------------------------------------------------------------------------- /src/Gui/OptionsBox.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_OPTIONSBOX_HPP 2 | #define GUI_OPTIONSBOX_HPP 3 | 4 | #include "Widget.hpp" 5 | #include "Utils/Arrow.hpp" 6 | #include "Utils/ItemBox.hpp" 7 | 8 | namespace gui 9 | { 10 | 11 | /** 12 | * Widget for selecting one item from a list. 13 | * Callback is triggered when selection is changed. 14 | */ 15 | template 16 | class OptionsBox: public Widget 17 | { 18 | public: 19 | OptionsBox(); 20 | 21 | /** 22 | * Append a new item in the list 23 | * @param label: displayed label when selected 24 | * @param value: value associated 25 | */ 26 | void addItem(const sf::String& label, const T& value); 27 | 28 | /** 29 | * Make an item the current one 30 | * @param item_index: position of the item in the list 31 | */ 32 | void selectItem(size_t item_index); 33 | 34 | /** 35 | * Get the value of the selected item 36 | * @return associated value 37 | */ 38 | const T& getSelectedValue() const; 39 | 40 | /** 41 | * Get the index of the selected item 42 | */ 43 | size_t getSelectedIndex() const; 44 | 45 | /** 46 | * Select next item in the list 47 | */ 48 | void selectNext(); 49 | 50 | /** 51 | * Select previous item in the list 52 | */ 53 | void selectPrevious(); 54 | 55 | protected: 56 | // Callbacks 57 | void onStateChanged(State state) override; 58 | void onMouseMoved(float x, float y) override; 59 | void onMousePressed(float x, float y) override; 60 | void onMouseReleased(float x, float y) override; 61 | void onKeyPressed(const sf::Event::KeyEvent& key) override; 62 | void onKeyReleased(const sf::Event::KeyEvent& key) override; 63 | 64 | private: 65 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 66 | 67 | void updateArrow(ItemBox& arrow, float x, float y); 68 | 69 | struct Item 70 | { 71 | Item(const sf::String& string, const T& value); 72 | 73 | sf::String label; 74 | T value; 75 | }; 76 | 77 | typedef std::vector ItemVector; 78 | ItemVector m_items; 79 | size_t m_currentIndex; 80 | 81 | // Visual components 82 | ItemBox m_box; 83 | ItemBox m_arrowLeft; 84 | ItemBox m_arrowRight; 85 | }; 86 | 87 | } 88 | 89 | #include "OptionsBox.inl" 90 | 91 | #endif // GUI_OPTIONSBOX_HPP 92 | -------------------------------------------------------------------------------- /src/Gui/OptionsBox.inl: -------------------------------------------------------------------------------- 1 | #include "Theme.hpp" 2 | 3 | namespace gui 4 | { 5 | 6 | template 7 | OptionsBox::OptionsBox(): 8 | m_currentIndex(-1), 9 | m_box(Box::Input), 10 | m_arrowLeft(Arrow(Arrow::Left)), 11 | m_arrowRight(Arrow(Arrow::Right)) 12 | { 13 | // Build visual components 14 | m_box.item().setFont(Theme::getFont()); 15 | m_box.item().setCharacterSize(Theme::textSize); 16 | m_box.setSize(Theme::minWidgetWidth, Theme::getBoxHeight()); 17 | 18 | // Pack left arrow 19 | m_arrowLeft.setSize(Theme::getBoxHeight(), Theme::getBoxHeight()); 20 | m_arrowLeft.centerItem(m_arrowLeft.item()); 21 | 22 | // Pack right arrow 23 | m_arrowRight.setSize(Theme::getBoxHeight(), Theme::getBoxHeight()); 24 | m_arrowRight.setPosition(m_box.getSize().x - Theme::getBoxHeight(), 0); 25 | m_arrowRight.centerItem(m_arrowRight.item()); 26 | 27 | // Widget local bounds 28 | setSize(m_box.getSize()); 29 | } 30 | 31 | 32 | template 33 | void OptionsBox::addItem(const sf::String& label, const T& value) 34 | { 35 | m_items.push_back(Item(label, value)); 36 | 37 | m_box.item().setString(label); 38 | int width = m_box.item().getLocalBounds().width + Theme::getBoxHeight() * 2 + Theme::PADDING * 2; 39 | // Check if box needs to be resized 40 | if (width > getSize().x) 41 | { 42 | m_box.setSize(width, Theme::getBoxHeight()); 43 | m_arrowRight.setPosition(width - Theme::getBoxHeight(), 0); 44 | m_arrowRight.centerItem(m_arrowRight.item()); 45 | setSize(m_box.getSize()); 46 | } 47 | 48 | selectItem(m_items.size() - 1); 49 | } 50 | 51 | 52 | template 53 | void OptionsBox::selectItem(size_t item_index) 54 | { 55 | if (item_index < m_items.size()) 56 | { 57 | m_currentIndex = item_index; 58 | m_box.item().setString(m_items[item_index].label); 59 | m_box.centerTextHorizontally(m_box.item()); 60 | } 61 | } 62 | 63 | 64 | template 65 | const T& OptionsBox::getSelectedValue() const 66 | { 67 | return m_items[m_currentIndex].value; 68 | } 69 | 70 | 71 | template 72 | size_t OptionsBox::getSelectedIndex() const 73 | { 74 | return m_currentIndex; 75 | } 76 | 77 | 78 | template 79 | void OptionsBox::selectNext() 80 | { 81 | if (m_items.size() > 1) 82 | { 83 | // Get next item index 84 | selectItem(m_currentIndex == (m_items.size() - 1) ? 0 : m_currentIndex + 1); 85 | triggerCallback(); 86 | } 87 | } 88 | 89 | 90 | template 91 | void OptionsBox::selectPrevious() 92 | { 93 | if (m_items.size() > 1) 94 | { 95 | // Get previous item index 96 | selectItem(m_currentIndex == 0 ? m_items.size() - 1 : m_currentIndex - 1); 97 | triggerCallback(); 98 | } 99 | } 100 | 101 | 102 | template 103 | void OptionsBox::draw(sf::RenderTarget& target, sf::RenderStates states) const 104 | { 105 | states.transform *= getTransform(); 106 | target.draw(m_box, states); 107 | target.draw(m_arrowLeft, states); 108 | target.draw(m_arrowRight, states); 109 | } 110 | 111 | 112 | template 113 | void OptionsBox::updateArrow(ItemBox& arrow, float x, float y) 114 | { 115 | if (arrow.containsPoint(x, y)) 116 | { 117 | if (getState() == StatePressed) 118 | arrow.press(); 119 | else 120 | arrow.applyState(StateHovered); 121 | } 122 | else 123 | { 124 | arrow.applyState(isFocused() ? StateFocused : StateDefault); 125 | } 126 | } 127 | 128 | 129 | // callbacks ------------------------------------------------------------------- 130 | 131 | template 132 | void OptionsBox::onStateChanged(State state) 133 | { 134 | // Hovered state is handled in the onMouseMoved callback 135 | if (state == StateDefault || state == StateFocused) 136 | { 137 | m_arrowLeft.applyState(state); 138 | m_arrowRight.applyState(state); 139 | m_box.applyState(state); 140 | } 141 | } 142 | 143 | 144 | template 145 | void OptionsBox::onMouseMoved(float x, float y) 146 | { 147 | updateArrow(m_arrowLeft, x, y); 148 | updateArrow(m_arrowRight, x, y); 149 | } 150 | 151 | 152 | template 153 | void OptionsBox::onMousePressed(float x, float y) 154 | { 155 | if (m_arrowLeft.containsPoint(x, y)) 156 | m_arrowLeft.press(); 157 | 158 | else if (m_arrowRight.containsPoint(x, y)) 159 | m_arrowRight.press(); 160 | } 161 | 162 | 163 | template 164 | void OptionsBox::onMouseReleased(float x, float y) 165 | { 166 | if (m_arrowLeft.containsPoint(x, y)) 167 | { 168 | selectPrevious(); 169 | m_arrowLeft.release(); 170 | } 171 | else if (m_arrowRight.containsPoint(x, y)) 172 | { 173 | selectNext(); 174 | m_arrowRight.release(); 175 | } 176 | } 177 | 178 | 179 | template 180 | void OptionsBox::onKeyPressed(const sf::Event::KeyEvent& key) 181 | { 182 | if (key.code == sf::Keyboard::Left) 183 | { 184 | selectPrevious(); 185 | m_arrowLeft.press(); 186 | } 187 | else if (key.code == sf::Keyboard::Right) 188 | { 189 | selectNext(); 190 | m_arrowRight.press(); 191 | } 192 | } 193 | 194 | 195 | template 196 | void OptionsBox::onKeyReleased(const sf::Event::KeyEvent& key) 197 | { 198 | if (key.code == sf::Keyboard::Left) 199 | { 200 | m_arrowLeft.release(); 201 | } 202 | else if (key.code == sf::Keyboard::Right) 203 | { 204 | m_arrowRight.release(); 205 | } 206 | } 207 | 208 | 209 | template 210 | OptionsBox::Item::Item(const sf::String& string, const T& val): 211 | label(string), 212 | value(val) 213 | { 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/Gui/ProgressBar.cpp: -------------------------------------------------------------------------------- 1 | #include "ProgressBar.hpp" 2 | #include "Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | ProgressBar::ProgressBar(float length, Orientation orientation, LabelPlacement labelPlacement): 8 | m_box(Box::Input), 9 | m_orientation(orientation), 10 | m_labelPlacement(labelPlacement), 11 | m_value(0.f) 12 | { 13 | if (orientation == Horizontal) 14 | { 15 | m_box.setSize(length, Theme::getBoxHeight()); 16 | } 17 | else 18 | { 19 | m_box.setSize(Theme::getBoxHeight(), length); 20 | if (m_labelPlacement == LabelOver) 21 | m_label.setRotation(90.f); 22 | } 23 | 24 | m_label.setString("100%"); 25 | m_label.setFont(Theme::getFont()); 26 | m_label.setFillColor(Theme::input.textColor); 27 | m_label.setCharacterSize(Theme::textSize); 28 | 29 | // Build bar 30 | const float x1 = Theme::PADDING; 31 | const float y1 = Theme::PADDING; 32 | const float x2 = (orientation == Horizontal ? length : Theme::getBoxHeight()) - Theme::PADDING; 33 | const float y2 = (orientation == Horizontal ? Theme::getBoxHeight() : length) - Theme::PADDING; 34 | m_bar[0].position = {x1, y1}; 35 | m_bar[1].position = {x2, y1}; 36 | m_bar[2].position = {x2, y2}; 37 | m_bar[3].position = {x1, y2}; 38 | 39 | const sf::IntRect& rect = Theme::getProgressBarTextureRect(); 40 | m_bar[0].texCoords = sf::Vector2f(rect.left, rect.top); 41 | m_bar[1].texCoords = sf::Vector2f(rect.left + rect.width, rect.top); 42 | m_bar[2].texCoords = sf::Vector2f(rect.left + rect.width, rect.top + rect.height); 43 | m_bar[3].texCoords = sf::Vector2f(rect.left, rect.top + rect.height); 44 | 45 | float labelWidth = m_label.getLocalBounds().width; 46 | float labelHeight = m_label.getLocalBounds().height; 47 | if (m_labelPlacement == LabelOutside) 48 | { 49 | if (orientation == Horizontal) 50 | { 51 | // Place label on the right of the bar 52 | m_label.setPosition(length + Theme::PADDING, Theme::PADDING); 53 | setSize(length + Theme::PADDING + labelWidth, m_box.getSize().y); 54 | } 55 | else 56 | { 57 | // Place label below the bar 58 | setSize(m_box.getSize().x, length + Theme::PADDING + labelHeight); 59 | } 60 | } 61 | else 62 | { 63 | setSize(m_box.getSize()); 64 | } 65 | 66 | setValue(m_value); 67 | setSelectable(false); 68 | } 69 | 70 | 71 | void ProgressBar::setValue(float value) 72 | { 73 | m_label.setString(std::to_string((int)value) + "%"); 74 | if (m_orientation == Horizontal) 75 | { 76 | float x = Theme::PADDING + (m_box.getSize().x - Theme::PADDING * 2) * value / 100; 77 | m_bar[1].position.x = m_bar[2].position.x = x; 78 | if (m_labelPlacement == LabelOver) 79 | { 80 | m_box.centerTextHorizontally(m_label); 81 | } 82 | } 83 | else 84 | { 85 | float fullHeight = m_box.getSize().y - Theme::PADDING * 2; 86 | float y = fullHeight * value / 100; 87 | m_bar[0].position.y = m_bar[1].position.y = (fullHeight - y) + Theme::PADDING; 88 | if (m_labelPlacement == LabelOver) 89 | { 90 | m_box.centerTextVertically(m_label); 91 | } 92 | else if (m_labelPlacement == LabelOutside) 93 | { 94 | // Re-center label horizontally (text width can change) 95 | float labelX = (m_box.getSize().x - m_label.getLocalBounds().width) / 2; 96 | m_label.setPosition(labelX, m_box.getSize().y + Theme::PADDING); 97 | } 98 | } 99 | 100 | m_value = value; 101 | } 102 | 103 | 104 | float ProgressBar::getValue() const 105 | { 106 | return m_value; 107 | } 108 | 109 | 110 | void ProgressBar::draw(sf::RenderTarget& target, sf::RenderStates states) const 111 | { 112 | states.transform *= getTransform(); 113 | target.draw(m_box, states); 114 | states.texture = &Theme::getTexture(); 115 | target.draw(m_bar, 4, sf::Quads, states); 116 | if (m_labelPlacement != LabelNone) 117 | target.draw(m_label, states); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/Gui/ProgressBar.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_PROGRESS_BAR_HPP 2 | #define GUI_PROGRESS_BAR_HPP 3 | 4 | #include "Widget.hpp" 5 | #include "Utils/Box.hpp" 6 | #include "Enums/Enums.hpp" 7 | 8 | namespace gui 9 | { 10 | 11 | enum LabelPlacement 12 | { 13 | LabelNone, // Do no display the label 14 | LabelOver, // Display the label over the progress bar 15 | LabelOutside // Display the label outside the progress bar 16 | }; 17 | 18 | /** 19 | * This widget provides a horizontal progress bar. 20 | * Passive widget: cannot be focused or trigger event 21 | */ 22 | class ProgressBar: public Widget 23 | { 24 | public: 25 | /** 26 | * @param length: bar length bar in pixels (Horizontal or Vertical, according to orientation) 27 | * @param orientation: orientation of the progress bar (Horizontal or Vertical) 28 | * @param labelPlacement: where to place the label (XXX%) 29 | */ 30 | ProgressBar(float length = 200.f, Orientation orientation = Horizontal, LabelPlacement labelPlacement = LabelOver); 31 | 32 | /// [0..100] 33 | void setValue(float value); 34 | float getValue() const; 35 | 36 | private: 37 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 38 | 39 | Box m_box; 40 | Orientation m_orientation; 41 | sf::Vertex m_bar[4]; 42 | sf::Text m_label; 43 | LabelPlacement m_labelPlacement; 44 | float m_value; 45 | }; 46 | 47 | } 48 | 49 | #endif // GUI_PROGRESS_BAR_HPP 50 | -------------------------------------------------------------------------------- /src/Gui/Slider.cpp: -------------------------------------------------------------------------------- 1 | #include "Slider.hpp" 2 | #include "Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | Slider::Slider(float length, Orientation orientation): 8 | m_orientation(orientation), 9 | m_step(10), 10 | m_value(0), 11 | m_box(Box::Input) 12 | { 13 | int handleHeight = Theme::getBoxHeight(); 14 | int handleWidth = handleHeight / 2; 15 | int boxHeight = Theme::borderSize * 3; 16 | int boxOffset = (handleHeight - boxHeight) / 2; 17 | 18 | if (orientation == Horizontal) 19 | { 20 | m_box.setSize(length, boxHeight); 21 | m_box.setPosition(0, boxOffset); 22 | m_handle.setSize(handleWidth, handleHeight); 23 | 24 | setSize(length, handleHeight); 25 | 26 | for (int i = 0; i < 4; ++i) 27 | { 28 | m_progression[i].color = Theme::windowBgColor; 29 | m_progression[i].position.x = m_box.getPosition().x + Theme::borderSize; 30 | m_progression[i].position.y = m_box.getPosition().y + Theme::borderSize; 31 | } 32 | m_progression[2].position.y += m_box.getSize().y - Theme::borderSize * 2; 33 | m_progression[3].position.y += m_box.getSize().y - Theme::borderSize * 2; 34 | } 35 | else 36 | { 37 | m_box.setSize(boxHeight, length); 38 | m_box.setPosition(boxOffset, 0); 39 | m_handle.setSize(handleHeight, handleWidth); 40 | 41 | setSize(handleHeight, length); 42 | 43 | for (int i = 0; i < 4; ++i) 44 | { 45 | m_progression[i].color = Theme::windowBgColor; 46 | m_progression[i].position.x = m_box.getPosition().x + Theme::borderSize; 47 | m_progression[i].position.y = m_box.getSize().y - Theme::borderSize; 48 | } 49 | m_progression[1].position.x += m_box.getSize().x - Theme::borderSize * 2; 50 | m_progression[2].position.x += m_box.getSize().x - Theme::borderSize * 2; 51 | } 52 | updateHandlePosition(); 53 | } 54 | 55 | 56 | int Slider::getStep() const 57 | { 58 | return m_step; 59 | } 60 | 61 | 62 | void Slider::setStep(int step) 63 | { 64 | if (step > 0 && step < 100) 65 | m_step = step; 66 | } 67 | 68 | 69 | int Slider::getValue() const 70 | { 71 | return m_value; 72 | } 73 | 74 | 75 | void Slider::setValue(int value) 76 | { 77 | // Ensure value is in bounds 78 | if (value < 0) 79 | value = 0; 80 | else if (value > 100) 81 | value = 100; 82 | else 83 | { 84 | // Round value to the closest step multiple 85 | int temp = value + m_step / 2; 86 | value = temp - temp % m_step; 87 | } 88 | 89 | // If value has changed 90 | if (value != m_value) 91 | { 92 | m_value = value; 93 | triggerCallback(); 94 | // Move the handle on the slider 95 | updateHandlePosition(); 96 | } 97 | } 98 | 99 | 100 | void Slider::updateHandlePosition() 101 | { 102 | if (m_orientation == Horizontal) 103 | { 104 | int max = getSize().x - m_handle.getSize().x - Theme::borderSize * 2; 105 | int x = max * m_value / 100 + Theme::borderSize; 106 | m_handle.setPosition(x, 0); 107 | m_progression[1].position.x = x; 108 | m_progression[2].position.x = x; 109 | } 110 | else 111 | { 112 | int max = getSize().y - m_handle.getSize().y - Theme::borderSize * 2; 113 | int reverse_value = 100 - m_value; 114 | int y = max * reverse_value / 100 + Theme::borderSize; 115 | m_handle.setPosition(0, y); 116 | m_progression[0].position.y = y; 117 | m_progression[1].position.y = y; 118 | } 119 | } 120 | 121 | 122 | void Slider::draw(sf::RenderTarget& target, sf::RenderStates states) const 123 | { 124 | states.transform *= getTransform(); 125 | target.draw(m_box, states); 126 | target.draw(m_progression, 4, sf::Quads, states); 127 | target.draw(m_handle, states); 128 | } 129 | 130 | 131 | // callbacks ------------------------------------------------------------------ 132 | 133 | void Slider::onKeyPressed(const sf::Event::KeyEvent& key) 134 | { 135 | switch (key.code) 136 | { 137 | case sf::Keyboard::Left: 138 | setValue(m_value - m_step); 139 | break; 140 | case sf::Keyboard::Right: 141 | setValue(m_value + m_step); 142 | break; 143 | case sf::Keyboard::Home: 144 | setValue(0); 145 | break; 146 | case sf::Keyboard::End: 147 | setValue(100); 148 | break; 149 | default: 150 | break; 151 | } 152 | } 153 | 154 | 155 | void Slider::onMousePressed(float x, float y) 156 | { 157 | if (m_orientation == Horizontal) 158 | setValue(100 * x / getSize().x); 159 | else 160 | setValue(100 - (100 * (y) / getSize().y)); 161 | 162 | m_handle.press(); 163 | } 164 | 165 | 166 | void Slider::onMouseMoved(float x, float y) 167 | { 168 | if (getState() == StateFocused) 169 | { 170 | if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) 171 | { 172 | if (m_orientation == Horizontal) 173 | setValue(100 * x / getSize().x); 174 | else 175 | setValue(100 - (100 * y / getSize().y)); 176 | } 177 | } 178 | else if (m_handle.containsPoint(x, y)) 179 | { 180 | m_handle.applyState(StateHovered); 181 | } 182 | else 183 | { 184 | m_handle.applyState(StateDefault); 185 | } 186 | } 187 | 188 | 189 | void Slider::onMouseReleased(float, float) 190 | { 191 | m_handle.release(); 192 | } 193 | 194 | 195 | void Slider::onMouseWheelMoved(int delta) 196 | { 197 | setValue(m_value + (delta > 0 ? m_step : -m_step)); 198 | } 199 | 200 | 201 | void Slider::onStateChanged(State state) 202 | { 203 | if (state == StateFocused || state == StateDefault) 204 | { 205 | m_handle.applyState(state); 206 | } 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/Gui/Slider.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_SLIDER_HPP 2 | #define GUI_SLIDER_HPP 3 | 4 | #include "Widget.hpp" 5 | #include "Utils/Box.hpp" 6 | #include "Enums/Enums.hpp" 7 | 8 | namespace gui 9 | { 10 | 11 | /** 12 | * This widget provides a vertical or horizontal slider. 13 | * The slider is the classic widget for controlling a bounded value. 14 | * The callback is triggered when 'Return' key is pressed. 15 | */ 16 | class Slider: public Widget 17 | { 18 | public: 19 | Slider(float length = 200, Orientation orientation = Horizontal); 20 | 21 | int getStep() const; 22 | 23 | /** 24 | * Define the amount of units to change the slider when adjusting by drag and drop 25 | */ 26 | void setStep(int step); 27 | 28 | int getValue() const; 29 | 30 | void setValue(int value); 31 | 32 | protected: 33 | // Callbacks 34 | void onKeyPressed(const sf::Event::KeyEvent& key) override; 35 | void onMousePressed(float x, float y) override; 36 | void onMouseMoved(float x, float y) override; 37 | void onMouseReleased(float x, float y) override; 38 | void onMouseWheelMoved(int delta) override; 39 | void onStateChanged(State state) override; 40 | 41 | private: 42 | void updateHandlePosition(); 43 | 44 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 45 | 46 | Orientation m_orientation; 47 | int m_step; 48 | int m_value; 49 | Box m_box; 50 | sf::Vertex m_progression[4]; 51 | Box m_handle; 52 | }; 53 | 54 | } 55 | 56 | #endif // GUI_SLIDER_HPP 57 | -------------------------------------------------------------------------------- /src/Gui/SpriteButton.cpp: -------------------------------------------------------------------------------- 1 | #include "SpriteButton.hpp" 2 | #include "Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | SpriteButton::SpriteButton(const sf::Texture& texture, const sf::String& string): 8 | Widget(), 9 | m_pressed(false) 10 | { 11 | size_t width = texture.getSize().x; 12 | size_t height = texture.getSize().y / 3; // default, hover, focus 13 | 14 | m_background.setTexture(texture); 15 | m_background.setTextureRect(sf::IntRect(0, 0, width, height)); 16 | 17 | setSize(width, height); 18 | 19 | m_text.setFont(Theme::getFont()); 20 | m_text.setCharacterSize(Theme::textSize); 21 | 22 | setString(string); 23 | } 24 | 25 | 26 | void SpriteButton::setString(const sf::String& string) 27 | { 28 | m_text.setString(string); 29 | centerText(m_text); 30 | } 31 | 32 | 33 | const sf::String& SpriteButton::getString() const 34 | { 35 | return m_text.getString(); 36 | } 37 | 38 | 39 | void SpriteButton::setFont(const sf::Font& font) 40 | { 41 | m_text.setFont(font); 42 | centerText(m_text); 43 | } 44 | 45 | 46 | const sf::Font& SpriteButton::getFont() const 47 | { 48 | return *m_text.getFont(); 49 | } 50 | 51 | 52 | void SpriteButton::setTextSize(size_t size) 53 | { 54 | m_text.setCharacterSize(size); 55 | centerText(m_text); 56 | } 57 | 58 | 59 | void SpriteButton::draw(sf::RenderTarget& target, sf::RenderStates states) const 60 | { 61 | states.transform *= getTransform(); 62 | target.draw(m_background, states); 63 | target.draw(m_text, states); 64 | } 65 | 66 | 67 | // Callbacks ------------------------------------------------------------------- 68 | 69 | void SpriteButton::onStateChanged(State state) 70 | { 71 | sf::Vector2f size = getSize(); 72 | switch (state) 73 | { 74 | case StateDefault: 75 | m_background.setTextureRect(sf::IntRect(0, 0, size.x, size.y)); 76 | break; 77 | case StateHovered: 78 | m_background.setTextureRect(sf::IntRect(0, size.y, size.x, size.y)); 79 | break; 80 | case StatePressed: 81 | case StateFocused: 82 | m_background.setTextureRect(sf::IntRect(0, size.y * 2, size.x, size.y)); 83 | break; 84 | } 85 | } 86 | 87 | 88 | void SpriteButton::onMouseMoved(float x, float y) 89 | { 90 | if (isFocused()) 91 | { 92 | if (containsPoint({x, y}) && sf::Mouse::isButtonPressed(sf::Mouse::Left)) 93 | press(); 94 | else 95 | release(); 96 | } 97 | } 98 | 99 | 100 | void SpriteButton::onMousePressed(float, float) 101 | { 102 | press(); 103 | } 104 | 105 | 106 | void SpriteButton::onMouseReleased(float x, float y) 107 | { 108 | 109 | release(); 110 | if (containsPoint({x, y})) 111 | { 112 | triggerCallback(); 113 | } 114 | } 115 | 116 | 117 | void SpriteButton::onKeyPressed(const sf::Event::KeyEvent& key) 118 | { 119 | if (key.code == sf::Keyboard::Return) 120 | { 121 | triggerCallback(); 122 | press(); 123 | } 124 | } 125 | 126 | 127 | void SpriteButton::onKeyReleased(const sf::Event::KeyEvent& key) 128 | { 129 | if (key.code == sf::Keyboard::Return) 130 | release(); 131 | } 132 | 133 | 134 | void SpriteButton::press() 135 | { 136 | if (!m_pressed) 137 | { 138 | m_pressed = true; 139 | m_text.move(0, 1); 140 | } 141 | } 142 | 143 | 144 | void SpriteButton::release() 145 | { 146 | if (m_pressed) 147 | { 148 | m_pressed = false; 149 | m_text.move(0, -1); 150 | } 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/Gui/SpriteButton.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_SPRITEBUTTON_HPP 2 | #define GUI_SPRITEBUTTON_HPP 3 | 4 | #include "Widget.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | /** 10 | * Push button linked to a sprite sheet 11 | */ 12 | class SpriteButton: public Widget 13 | { 14 | public: 15 | SpriteButton(const sf::Texture& texture, const sf::String& label = ""); 16 | 17 | void setString(const sf::String& string); 18 | const sf::String& getString() const; 19 | 20 | void setFont(const sf::Font& font); 21 | const sf::Font& getFont() const; 22 | 23 | void setTextSize(size_t size); 24 | 25 | void setTexture(const sf::Texture& texture); 26 | 27 | void onStateChanged(State state) override; 28 | void onMouseMoved(float x, float y) override; 29 | void onMousePressed(float x, float y) override; 30 | void onMouseReleased(float x, float y) override; 31 | void onKeyPressed(const sf::Event::KeyEvent& key) override; 32 | void onKeyReleased(const sf::Event::KeyEvent& key) override; 33 | 34 | private: 35 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 36 | void press(); 37 | void release(); 38 | 39 | sf::Text m_text; 40 | sf::Sprite m_background; 41 | bool m_pressed; 42 | }; 43 | 44 | } 45 | 46 | #endif // GUI_SPRITEBUTTON_HPP 47 | -------------------------------------------------------------------------------- /src/Gui/TextBox.cpp: -------------------------------------------------------------------------------- 1 | #include "TextBox.hpp" 2 | #include "Theme.hpp" 3 | 4 | #include 5 | 6 | #define BLINK_PERIOD 1.f 7 | 8 | namespace gui 9 | { 10 | 11 | TextBox::TextBox(float width): 12 | m_box(Box::Input), 13 | m_cursorPos(0), 14 | m_maxLength(256), 15 | m_selectionFirst(0), 16 | m_selectionLast(0) 17 | { 18 | m_box.setSize(width, Theme::getBoxHeight()); 19 | 20 | int offset = Theme::borderSize + Theme::PADDING; 21 | m_text.setFont(Theme::getFont()); 22 | m_text.setPosition(offset, offset); 23 | m_text.setFillColor(Theme::input.textColor); 24 | m_text.setCharacterSize(Theme::textSize); 25 | 26 | m_placeholder.setFont(Theme::getFont()); 27 | m_placeholder.setPosition(offset, offset); 28 | m_placeholder.setFillColor(Theme::input.textPlaceholderColor); 29 | m_placeholder.setCharacterSize(Theme::textSize); 30 | 31 | // Build cursor 32 | m_cursor.setPosition(offset, offset); 33 | m_cursor.setSize(sf::Vector2f(1.f, Theme::getLineSpacing())); 34 | m_cursor.setFillColor(Theme::input.textColor); 35 | setCursor(0); 36 | 37 | setSize(m_box.getSize()); 38 | } 39 | 40 | 41 | void TextBox::setText(const sf::String& string) 42 | { 43 | // Trim current text if needed 44 | if (string.getSize() > m_maxLength) 45 | { 46 | m_text.setString(string.substring(0, m_maxLength)); 47 | } 48 | else 49 | { 50 | m_text.setString(string); 51 | } 52 | setCursor(getText().getSize()); 53 | } 54 | 55 | 56 | const sf::String& TextBox::getText() const 57 | { 58 | return m_text.getString(); 59 | } 60 | 61 | 62 | void TextBox::setMaxLength(size_t maxLength) 63 | { 64 | m_maxLength = maxLength; 65 | // Trim current text if needed 66 | if (m_text.getString().getSize() > m_maxLength) 67 | { 68 | m_text.setString(m_text.getString().substring(0, m_maxLength)); 69 | setCursor(m_maxLength); 70 | } 71 | } 72 | 73 | 74 | void TextBox::setCursor(size_t index) 75 | { 76 | if (index <= m_text.getString().getSize()) 77 | { 78 | m_cursorPos = index; 79 | m_selectionFirst = index; 80 | m_selectionLast = index; 81 | 82 | float padding = Theme::borderSize + Theme::PADDING; 83 | m_cursor.setPosition(m_text.findCharacterPos(index).x, padding); 84 | m_cursorTimer.restart(); 85 | 86 | if (m_cursor.getPosition().x > getSize().x - padding) 87 | { 88 | // Shift text on left 89 | float diff = m_cursor.getPosition().x - getSize().x + padding; 90 | m_text.move(-diff, 0); 91 | m_cursor.move(-diff, 0); 92 | } 93 | else if (m_cursor.getPosition().x < padding) 94 | { 95 | // Shift text on right 96 | float diff = padding - m_cursor.getPosition().x; 97 | m_text.move(diff, 0); 98 | m_cursor.move(diff, 0); 99 | } 100 | 101 | float textWidth = m_text.getLocalBounds().width; 102 | if (m_text.getPosition().x < padding && m_text.getPosition().x + textWidth < getSize().x - padding) 103 | { 104 | float diff = (getSize().x - padding) - (m_text.getPosition().x + textWidth); 105 | m_text.move(diff, 0); 106 | m_cursor.move(diff, 0); 107 | // If text is smaller than the textbox, force align on left 108 | if (textWidth < (getSize().x - padding * 2)) 109 | { 110 | diff = padding - m_text.getPosition().x; 111 | m_text.move(diff, 0); 112 | m_cursor.move(diff, 0); 113 | } 114 | } 115 | } 116 | } 117 | 118 | 119 | size_t TextBox::getCursor() const 120 | { 121 | return m_cursorPos; 122 | } 123 | 124 | 125 | void TextBox::onKeyPressed(const sf::Event::KeyEvent& key) 126 | { 127 | switch (key.code) 128 | { 129 | case sf::Keyboard::Left: 130 | if (key.shift) 131 | { 132 | if (m_cursorPos == m_selectionLast) 133 | { 134 | // Extend selection to left 135 | if (m_selectionFirst > 0) 136 | setSelectedText(m_selectionFirst - 1, m_selectionLast); 137 | } 138 | else 139 | { 140 | // Shrink selection to right 141 | setSelectedText(m_selectionFirst, m_selectionLast - 1); 142 | } 143 | } 144 | else if (m_selectedText.isEmpty()) 145 | { 146 | // Move cursor to the left 147 | setCursor(m_cursorPos - 1); 148 | } 149 | else 150 | { 151 | // Clear selection, move cursor to the left 152 | setCursor(m_selectionFirst); 153 | clearSelectedText(); 154 | } 155 | break; 156 | 157 | case sf::Keyboard::Right: 158 | if (key.shift) 159 | { 160 | if (m_cursorPos == m_selectionFirst) 161 | { 162 | // Extend selection to right 163 | if (m_selectionLast < m_text.getString().getSize()) 164 | setSelectedText(m_selectionFirst, m_selectionLast + 1); 165 | } 166 | else 167 | { 168 | // Shrink selection to left 169 | setSelectedText(m_selectionFirst + 1, m_selectionLast); 170 | } 171 | } 172 | else if (m_selectedText.isEmpty()) 173 | { 174 | // Move cursor to the right 175 | setCursor(m_cursorPos + 1); 176 | } 177 | else 178 | { 179 | // Clear selection, move cursor to the right 180 | setCursor(m_selectionLast); 181 | clearSelectedText(); 182 | } 183 | break; 184 | 185 | case sf::Keyboard::BackSpace: 186 | if (!m_selectedText.isEmpty()) 187 | { 188 | deleteSelectedText(); 189 | break; 190 | } 191 | // Erase character before cursor 192 | if (m_cursorPos > 0) 193 | { 194 | sf::String string = m_text.getString(); 195 | string.erase(m_cursorPos - 1); 196 | m_text.setString(string); 197 | 198 | setCursor(m_cursorPos - 1); 199 | } 200 | break; 201 | 202 | case sf::Keyboard::Delete: 203 | if (!m_selectedText.isEmpty()) 204 | { 205 | deleteSelectedText(); 206 | break; 207 | } 208 | // Erase character after cursor 209 | if (m_cursorPos < m_text.getString().getSize()) 210 | { 211 | sf::String string = m_text.getString(); 212 | string.erase(m_cursorPos); 213 | m_text.setString(string); 214 | 215 | setCursor(m_cursorPos); 216 | } 217 | break; 218 | 219 | case sf::Keyboard::Home: 220 | if (key.shift) 221 | { 222 | // Shift+Home: select from start to cursor 223 | setSelectedText(0, m_cursorPos); 224 | } 225 | else 226 | { 227 | setCursor(0); 228 | } 229 | break; 230 | 231 | case sf::Keyboard::End: 232 | if (key.shift) 233 | { 234 | // Shift+End: select from cursor to end 235 | setSelectedText(m_cursorPos, m_text.getString().getSize()); 236 | } 237 | else 238 | { 239 | setCursor(m_text.getString().getSize()); 240 | } 241 | break; 242 | 243 | case sf::Keyboard::Return: 244 | triggerCallback(); 245 | break; 246 | 247 | // Ctrl+A: select all 248 | case sf::Keyboard::A: 249 | if (key.control) 250 | { 251 | setSelectedText(0, m_text.getString().getSize()); 252 | } 253 | break; 254 | 255 | // Ctrl+V: paste clipboard 256 | case sf::Keyboard::V: 257 | if (key.control) 258 | { 259 | // Delete selected text and write clipboard string over it. 260 | deleteSelectedText(); 261 | sf::String string = m_text.getString(); 262 | sf::String clipboardString = sf::Clipboard::getString(); 263 | // Trim clipboard content if needed 264 | if ((string.getSize() + clipboardString.getSize()) > m_maxLength) 265 | { 266 | clipboardString = clipboardString.substring(0, m_maxLength - string.getSize()); 267 | } 268 | // Insert string at cursor position 269 | string.insert(m_cursorPos, clipboardString); 270 | m_text.setString(string); 271 | setCursor(m_cursorPos + clipboardString.getSize()); 272 | } 273 | break; 274 | 275 | // Ctrl+C: copy selected text to clipboard 276 | case sf::Keyboard::C: 277 | if (key.control) 278 | { 279 | if (!m_selectedText.isEmpty()) 280 | { 281 | sf::Clipboard::setString(m_selectedText); 282 | } 283 | } 284 | break; 285 | 286 | // Ctrl+X: cut selected text to clipboard 287 | case sf::Keyboard::X: 288 | if (key.control) 289 | { 290 | if (!m_selectedText.isEmpty()) 291 | { 292 | sf::Clipboard::setString(m_selectedText); 293 | deleteSelectedText(); 294 | } 295 | } 296 | break; 297 | 298 | default: 299 | break; 300 | } 301 | } 302 | 303 | 304 | void TextBox::onMouseEnter() 305 | { 306 | setMouseCursor(sf::Cursor::Text); 307 | } 308 | 309 | 310 | void TextBox::onMouseLeave() 311 | { 312 | setMouseCursor(sf::Cursor::Arrow); 313 | } 314 | 315 | 316 | void TextBox::onMousePressed(float x, float) 317 | { 318 | for (int i = m_text.getString().getSize(); i >= 0; --i) 319 | { 320 | // Place cursor after the character under the mouse 321 | sf::Vector2f glyphPos = m_text.findCharacterPos(i); 322 | if (glyphPos.x <= x) 323 | { 324 | setCursor(i); 325 | break; 326 | } 327 | } 328 | } 329 | 330 | 331 | void TextBox::onMouseReleased(float x, float) 332 | { 333 | for (int i = m_text.getString().getSize(); i >= 0; --i) 334 | { 335 | // Place cursor after the character under the mouse 336 | sf::Vector2f glyphPos = m_text.findCharacterPos(i); 337 | if (glyphPos.x <= x) 338 | { 339 | setSelectedText(m_cursorPos, i); 340 | break; 341 | } 342 | } 343 | } 344 | 345 | 346 | void TextBox::onMouseMoved(float x, float) 347 | { 348 | if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) 349 | { 350 | for (int i = m_text.getString().getSize(); i >= 0; --i) 351 | { 352 | // Place cursor after the character under the mouse 353 | sf::Vector2f glyphPos = m_text.findCharacterPos(i); 354 | if (glyphPos.x <= x) 355 | { 356 | setSelectedText(m_cursorPos, i); 357 | break; 358 | } 359 | } 360 | } 361 | } 362 | 363 | 364 | void TextBox::onTextEntered(sf::Uint32 unicode) 365 | { 366 | if (unicode > 30 && (unicode < 127 || unicode > 159)) 367 | { 368 | // Delete selected text when a new input is received 369 | deleteSelectedText(); 370 | sf::String string = m_text.getString(); 371 | if (string.getSize() < m_maxLength) 372 | { 373 | // Insert character in string at cursor position 374 | string.insert(m_cursorPos, unicode); 375 | m_text.setString(string); 376 | setCursor(m_cursorPos + 1); 377 | } 378 | } 379 | } 380 | 381 | 382 | void TextBox::onStateChanged(State state) 383 | { 384 | m_box.applyState(state); 385 | 386 | // Discard selection when focus is lost 387 | if (state != State::StateFocused) 388 | { 389 | clearSelectedText(); 390 | } 391 | } 392 | 393 | 394 | void TextBox::draw(sf::RenderTarget& target, sf::RenderStates states) const 395 | { 396 | states.transform *= getTransform(); 397 | target.draw(m_box, states); 398 | 399 | // Crop the text with GL Scissor 400 | glEnable(GL_SCISSOR_TEST); 401 | 402 | sf::Vector2f pos = getAbsolutePosition(); 403 | glScissor(pos.x + Theme::borderSize, target.getSize().y - (pos.y + getSize().y), getSize().x, getSize().y); 404 | 405 | if (m_text.getString().isEmpty()) 406 | { 407 | target.draw(m_placeholder, states); 408 | } 409 | else 410 | { 411 | // Draw selection indicator 412 | if (!m_selectedText.isEmpty()) 413 | { 414 | sf::RectangleShape selRect; 415 | const sf::Vector2f& startPos = m_text.findCharacterPos(m_selectionFirst); 416 | selRect.setPosition(startPos); 417 | selRect.setSize({m_text.findCharacterPos(m_selectionLast).x - startPos.x, m_cursor.getSize().y}); 418 | selRect.setFillColor(Theme::input.textSelectionColor); 419 | target.draw(selRect, states); 420 | } 421 | target.draw(m_text, states); 422 | } 423 | 424 | glDisable(GL_SCISSOR_TEST); 425 | 426 | // Show cursor if focused and no selection 427 | if (isFocused() && m_selectedText.isEmpty()) 428 | { 429 | // Make it blink 430 | float timer = m_cursorTimer.getElapsedTime().asSeconds(); 431 | if (timer >= BLINK_PERIOD) 432 | m_cursorTimer.restart(); 433 | 434 | // Updating in the drawing method, deal with it 435 | sf::Color color = Theme::input.textColor; 436 | color.a = 255 - (255 * timer / BLINK_PERIOD); 437 | m_cursor.setFillColor(color); 438 | 439 | target.draw(m_cursor, states); 440 | } 441 | } 442 | 443 | 444 | void TextBox::setSelectedText(size_t from, size_t to) 445 | { 446 | if (from != to) 447 | { 448 | size_t selectionLast = std::max(from, to); 449 | size_t selectionFirst = std::min(from, to); 450 | if (selectionFirst != m_selectionFirst || selectionLast != m_selectionLast) 451 | { 452 | m_selectionFirst = selectionFirst; 453 | m_selectionLast = selectionLast; 454 | m_selectedText = m_text.getString().substring(m_selectionFirst, m_selectionLast - m_selectionFirst); 455 | } 456 | } 457 | else 458 | { 459 | clearSelectedText(); 460 | } 461 | } 462 | 463 | 464 | void TextBox::clearSelectedText() 465 | { 466 | m_selectionFirst = m_selectionLast = m_cursorPos; 467 | m_selectedText.clear(); 468 | } 469 | 470 | 471 | const sf::String& TextBox::getSelectedText() const 472 | { 473 | return m_selectedText; 474 | } 475 | 476 | 477 | void TextBox::deleteSelectedText() 478 | { 479 | // Delete if any selected text 480 | if (!m_selectedText.isEmpty()) 481 | { 482 | sf::String str = m_text.getString(); 483 | str.erase(m_selectionFirst, m_selectionLast - m_selectionFirst); 484 | setCursor(m_selectionFirst); 485 | clearSelectedText(); 486 | m_text.setString(str); 487 | } 488 | } 489 | 490 | 491 | void TextBox::setPlaceholder(const sf::String& placeholder) 492 | { 493 | m_placeholder.setString(placeholder); 494 | } 495 | 496 | 497 | const sf::String& TextBox::getPlaceholder() const 498 | { 499 | return m_placeholder.getString(); 500 | } 501 | 502 | } 503 | -------------------------------------------------------------------------------- /src/Gui/TextBox.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_TEXTBOX_HPP 2 | #define GUI_TEXTBOX_HPP 3 | 4 | #include "Widget.hpp" 5 | #include "Utils/Box.hpp" 6 | 7 | namespace gui 8 | { 9 | 10 | /** 11 | * The TextBox widget is a one-line text editor. 12 | * It allows the user to enter and edit a single line of plain text. 13 | */ 14 | class TextBox: public Widget 15 | { 16 | public: 17 | TextBox(float width = 200.f); 18 | 19 | /** 20 | * Define textbox content 21 | */ 22 | void setText(const sf::String& string); 23 | 24 | /** 25 | * Get textbox content 26 | */ 27 | const sf::String& getText() const; 28 | 29 | /** 30 | * Define max length of textbox content (default is 256 characters) 31 | */ 32 | void setMaxLength(size_t maxLength); 33 | 34 | /** 35 | * Set the cursor position 36 | */ 37 | void setCursor(size_t index); 38 | 39 | /** 40 | * Get the cursor position 41 | */ 42 | size_t getCursor() const; 43 | 44 | /** 45 | * Set selected text 46 | */ 47 | void setSelectedText(size_t from, size_t to); 48 | 49 | /** 50 | * Get selected text 51 | */ 52 | const sf::String& getSelectedText() const; 53 | 54 | /** 55 | * Cancel the text selection range, if any 56 | */ 57 | void clearSelectedText(); 58 | 59 | /** 60 | * Set placeholder text 61 | */ 62 | void setPlaceholder(const sf::String& placeholder); 63 | 64 | /** 65 | * Get placeholder text 66 | */ 67 | const sf::String& getPlaceholder() const; 68 | 69 | protected: 70 | // Callbacks 71 | void onKeyPressed(const sf::Event::KeyEvent& key) override; 72 | void onMouseEnter() override; 73 | void onMouseLeave() override; 74 | void onMousePressed(float x, float y) override; 75 | void onMouseReleased(float x, float y) override; 76 | void onMouseMoved(float x, float y) override; 77 | void onTextEntered(sf::Uint32 unicode) override; 78 | void onStateChanged(State state) override; 79 | 80 | private: 81 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 82 | 83 | /** 84 | * Delete selected text if any 85 | */ 86 | void deleteSelectedText(); 87 | 88 | sf::Text m_text; 89 | sf::Text m_placeholder; 90 | Box m_box; 91 | mutable sf::RectangleShape m_cursor; 92 | mutable sf::Clock m_cursorTimer; 93 | size_t m_cursorPos; 94 | size_t m_maxLength; 95 | size_t m_selectionFirst; 96 | size_t m_selectionLast; 97 | sf::String m_selectedText; 98 | }; 99 | 100 | } 101 | 102 | #endif // GUI_TEXTBOX_HPP 103 | -------------------------------------------------------------------------------- /src/Gui/Theme.cpp: -------------------------------------------------------------------------------- 1 | #include "Theme.hpp" 2 | 3 | namespace gui 4 | { 5 | 6 | static sf::Cursor& getDefaultCursor() 7 | { 8 | static sf::Cursor cursor; 9 | cursor.loadFromSystem(sf::Cursor::Arrow); 10 | return cursor; 11 | } 12 | 13 | size_t Theme::textSize = 12; 14 | Theme::Style Theme::click; 15 | Theme::Style Theme::input; 16 | sf::Color Theme::windowBgColor; 17 | int Theme::borderSize = 1.f; 18 | int Theme::minWidgetWidth = 86; 19 | float Theme::PADDING = 1.f; 20 | float Theme::MARGIN = 7.f; 21 | 22 | sf::Keyboard::Key Theme::nextWidgetKey = sf::Keyboard::Down; 23 | sf::Keyboard::Key Theme::previousWidgetKey = sf::Keyboard::Up; 24 | 25 | sf::Font Theme::m_font; 26 | sf::Texture Theme::m_texture; 27 | sf::IntRect Theme::m_subrects[_TEXTURE_ID_COUNT]; 28 | 29 | sf::Cursor& Theme::cursor = getDefaultCursor(); 30 | 31 | bool Theme::loadFont(const std::string& filename) 32 | { 33 | return m_font.loadFromFile(filename); 34 | } 35 | 36 | 37 | bool Theme::loadTexture(const std::string& filename) 38 | { 39 | if (m_texture.loadFromFile(filename)) 40 | { 41 | sf::IntRect subrect; 42 | subrect.width = m_texture.getSize().x; 43 | subrect.height = m_texture.getSize().y / _TEXTURE_ID_COUNT; 44 | 45 | for (int i = 0; i < _TEXTURE_ID_COUNT; ++i) 46 | { 47 | m_subrects[i] = subrect; 48 | subrect.top += subrect.height; 49 | } 50 | 51 | borderSize = subrect.width / 3; 52 | return true; 53 | } 54 | return false; 55 | } 56 | 57 | 58 | const sf::Font& Theme::getFont() 59 | { 60 | return m_font; 61 | } 62 | 63 | 64 | const sf::Texture& Theme::getTexture() 65 | { 66 | return m_texture; 67 | } 68 | 69 | 70 | const sf::IntRect& Theme::getTextureRect(Box::Type type, State state) 71 | { 72 | TextureID id(BOX_DEFAULT); 73 | switch (state) 74 | { 75 | case StateDefault: 76 | id = type == Box::Click ? BOX_DEFAULT : BOX_INPUT_DEFAULT; 77 | break; 78 | case StateHovered: 79 | id = type == Box::Click ? BOX_HOVERED : BOX_INPUT_DEFAULT; 80 | break; 81 | case StatePressed: 82 | id = type == Box::Click ? BOX_PRESSED : BOX_INPUT_FOCUSED; 83 | break; 84 | case StateFocused: 85 | id = type == Box::Click ? BOX_FOCUSED : BOX_INPUT_FOCUSED; 86 | break; 87 | } 88 | return m_subrects[id]; 89 | } 90 | 91 | 92 | const sf::IntRect& Theme::getCrossTextureRect() 93 | { 94 | return m_subrects[CROSS]; 95 | } 96 | 97 | 98 | const sf::IntRect& Theme::getArrowTextureRect() 99 | { 100 | return m_subrects[ARROW]; 101 | } 102 | 103 | 104 | const sf::IntRect& Theme::getProgressBarTextureRect() 105 | { 106 | return m_subrects[PROGRESS_BAR]; 107 | } 108 | 109 | 110 | float Theme::getBoxHeight() 111 | { 112 | return getLineSpacing() + borderSize * 2 + PADDING * 2; 113 | } 114 | 115 | 116 | int Theme::getLineSpacing() 117 | { 118 | return m_font.getLineSpacing(textSize); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/Gui/Theme.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_THEME_HPP 2 | #define GUI_THEME_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Widget.hpp" 10 | #include "Utils/Box.hpp" 11 | 12 | namespace gui 13 | { 14 | 15 | class Theme 16 | { 17 | public: 18 | /** 19 | * Load the GUI global font 20 | */ 21 | static bool loadFont(const std::string& path); 22 | static const sf::Font& getFont(); 23 | 24 | /** 25 | * Load the GUI spritesheet 26 | */ 27 | static bool loadTexture(const std::string& path); 28 | static const sf::Texture& getTexture(); 29 | 30 | static const sf::IntRect& getTextureRect(Box::Type type, State state); 31 | 32 | static const sf::IntRect& getCrossTextureRect(); 33 | 34 | static const sf::IntRect& getArrowTextureRect(); 35 | 36 | static const sf::IntRect& getProgressBarTextureRect(); 37 | 38 | /** 39 | * Widget height based on text size 40 | */ 41 | static float getBoxHeight(); 42 | 43 | /** 44 | * Height of a line of text 45 | */ 46 | static int getLineSpacing(); 47 | 48 | static size_t textSize; 49 | struct Style 50 | { 51 | sf::Color textColor; 52 | sf::Color textColorHover; 53 | sf::Color textColorFocus; 54 | sf::Color textSelectionColor; 55 | sf::Color textPlaceholderColor; 56 | }; 57 | 58 | static Style click; 59 | static Style input; 60 | 61 | static sf::Color windowBgColor; 62 | static int borderSize; 63 | static int minWidgetWidth; 64 | 65 | static float PADDING; // Spacing inside widget 66 | static float MARGIN; // Spacing between widgets 67 | static sf::Keyboard::Key previousWidgetKey; 68 | static sf::Keyboard::Key nextWidgetKey; 69 | 70 | // Auto-initialized to default cursor 71 | static sf::Cursor& cursor; 72 | 73 | private: 74 | enum TextureID 75 | { 76 | BOX_DEFAULT, 77 | BOX_HOVERED, 78 | BOX_PRESSED, 79 | BOX_FOCUSED, 80 | BOX_INPUT_DEFAULT, 81 | BOX_INPUT_FOCUSED, 82 | CROSS, 83 | ARROW, 84 | PROGRESS_BAR, 85 | _TEXTURE_ID_COUNT 86 | }; 87 | 88 | static sf::Font m_font; 89 | static sf::Texture m_texture; 90 | static sf::IntRect m_subrects[_TEXTURE_ID_COUNT]; 91 | }; 92 | 93 | } 94 | 95 | #endif // GUI_THEME_HPP 96 | -------------------------------------------------------------------------------- /src/Gui/Utils/Arrow.cpp: -------------------------------------------------------------------------------- 1 | #include "Arrow.hpp" 2 | #include "../Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | Arrow::Arrow(Direction direction): 8 | m_direction(direction) 9 | { 10 | const sf::IntRect& rect = Theme::getArrowTextureRect(); 11 | m_vertices[0].texCoords = sf::Vector2f(rect.left, rect.top); 12 | m_vertices[1].texCoords = sf::Vector2f(rect.left + rect.width, rect.top); 13 | m_vertices[2].texCoords = sf::Vector2f(rect.left + rect.width, rect.top + rect.height); 14 | m_vertices[3].texCoords = sf::Vector2f(rect.left, rect.top + rect.height); 15 | 16 | updateGeometry(0, 0, direction); 17 | } 18 | 19 | 20 | void Arrow::setFillColor(const sf::Color& color) 21 | { 22 | for (int i = 0; i < 4; ++i) 23 | m_vertices[i].color = color; 24 | } 25 | 26 | 27 | void Arrow::setPosition(float x, float y) 28 | { 29 | updateGeometry(x, y, m_direction); 30 | } 31 | 32 | 33 | void Arrow::move(float dx, float dy) 34 | { 35 | for (int i = 0; i < 4; ++i) 36 | { 37 | m_vertices[i].position.x += dx; 38 | m_vertices[i].position.y += dy; 39 | } 40 | } 41 | 42 | 43 | sf::Vector2f Arrow::getSize() const 44 | { 45 | const sf::IntRect& rect = Theme::getArrowTextureRect(); 46 | return sf::Vector2f(rect.width, rect.height); 47 | } 48 | 49 | 50 | void Arrow::draw(sf::RenderTarget& target, sf::RenderStates states) const 51 | { 52 | states.texture = &Theme::getTexture(); 53 | target.draw(m_vertices, 4, sf::Quads, states); 54 | } 55 | 56 | 57 | void Arrow::updateGeometry(float x, float y, Direction direction) 58 | { 59 | const sf::IntRect& rect = Theme::getArrowTextureRect(); 60 | switch (direction) 61 | { 62 | case Top: 63 | m_vertices[0].position = sf::Vector2f(x, y); 64 | m_vertices[1].position = sf::Vector2f(x + rect.width, y); 65 | m_vertices[2].position = sf::Vector2f(x + rect.width, y + rect.height); 66 | m_vertices[3].position = sf::Vector2f(x, y + rect.height); 67 | break; 68 | case Bottom: 69 | m_vertices[0].position = sf::Vector2f(x + rect.width, y + rect.height); 70 | m_vertices[1].position = sf::Vector2f(x, y + rect.height); 71 | m_vertices[2].position = sf::Vector2f(x, y); 72 | m_vertices[3].position = sf::Vector2f(x + rect.width, y); 73 | break; 74 | case Left: 75 | m_vertices[0].position = sf::Vector2f(x, y + rect.width); 76 | m_vertices[1].position = sf::Vector2f(x, y); 77 | m_vertices[2].position = sf::Vector2f(x + rect.height, y); 78 | m_vertices[3].position = sf::Vector2f(x + rect.height, y + rect.width); 79 | break; 80 | case Right: 81 | m_vertices[0].position = sf::Vector2f(x + rect.height, y); 82 | m_vertices[1].position = sf::Vector2f(x + rect.height, y + rect.width); 83 | m_vertices[2].position = sf::Vector2f(x, y + rect.width); 84 | m_vertices[3].position = sf::Vector2f(x, y); 85 | break; 86 | } 87 | m_direction = direction; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/Gui/Utils/Arrow.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_ARROW_HPP 2 | #define GUI_ARROW_HPP 3 | 4 | #include 5 | 6 | namespace gui 7 | { 8 | 9 | class Arrow: public sf::Drawable 10 | { 11 | public: 12 | enum Direction 13 | { 14 | Left, 15 | Right, 16 | Top, 17 | Bottom 18 | }; 19 | 20 | Arrow(Direction direction); 21 | 22 | void setFillColor(const sf::Color& color); 23 | 24 | void move(float dx, float dy); 25 | 26 | void setPosition(float x, float y); 27 | 28 | sf::Vector2f getSize() const; 29 | 30 | private: 31 | void draw(sf::RenderTarget& target, sf::RenderStates states) const; 32 | 33 | void updateGeometry(float x, float y, Direction direction); 34 | 35 | sf::Vertex m_vertices[4]; 36 | Direction m_direction; 37 | }; 38 | 39 | } 40 | 41 | #endif // GUI_ARROW_HPP 42 | -------------------------------------------------------------------------------- /src/Gui/Utils/Box.cpp: -------------------------------------------------------------------------------- 1 | #include "Box.hpp" 2 | #include "../Theme.hpp" 3 | 4 | 5 | namespace gui 6 | { 7 | 8 | Box::Box(Type type): 9 | m_type(type), 10 | m_state(StatePressed) 11 | { 12 | applyState(StateDefault); 13 | } 14 | 15 | // Geometry -------------------------------------------------------------------- 16 | 17 | const sf::Vector2f& Box::getPosition() const 18 | { 19 | return m_vertices[TOP_LEFT].position; 20 | } 21 | 22 | 23 | void Box::setPosition(float x, float y) 24 | { 25 | sf::Vector2f diff = sf::Vector2f(x, y) - getPosition(); 26 | for (size_t i = 0; i < VERTEX_COUNT; ++i) 27 | m_vertices[i].position += diff; 28 | } 29 | 30 | 31 | void Box::setSliceTextureCoords(Slice slice, float x, float y) 32 | { 33 | int index = slice * 4; 34 | m_vertices[index].texCoords = sf::Vector2f(x, y); 35 | m_vertices[++index].texCoords = sf::Vector2f(x + Theme::borderSize, y); 36 | m_vertices[++index].texCoords = sf::Vector2f(x + Theme::borderSize, y + Theme::borderSize); 37 | m_vertices[++index].texCoords = sf::Vector2f(x, y + Theme::borderSize); 38 | } 39 | 40 | 41 | void Box::setSliceGeometry(Slice slice, float x1, float y1, float x2, float y2) 42 | { 43 | int index = slice * 4; 44 | m_vertices[index].position = sf::Vector2f(x1, y1); 45 | m_vertices[++index].position = sf::Vector2f(x2, y1); 46 | m_vertices[++index].position = sf::Vector2f(x2, y2); 47 | m_vertices[++index].position = sf::Vector2f(x1, y2); 48 | } 49 | 50 | 51 | void Box::setSize(float width, float height) 52 | { 53 | if (width <= 0 || height <= 0) 54 | return; 55 | 56 | // Move/resize each of the 9 slices 57 | // 0--x1--x2--x3 58 | // | | | | 59 | // y1--+---+---+ 60 | // | | | | 61 | // y2--+---+---+ 62 | // | | | | 63 | // y3--+---+---+ 64 | float x1 = Theme::borderSize; 65 | float x2 = width - Theme::borderSize; 66 | float x3 = width; 67 | float y1 = Theme::borderSize; 68 | float y2 = height - Theme::borderSize; 69 | float y3 = height; 70 | setSliceGeometry(TOP_LEFT, 0, 0, x1, y1); 71 | setSliceGeometry(TOP, x1, 0, x2, y1); 72 | setSliceGeometry(TOP_RIGHT, x2, 0, x3, y1); 73 | setSliceGeometry(LEFT, 0, y1, x1, y2); 74 | setSliceGeometry(MIDDLE, x1, y1, x2, y2); 75 | setSliceGeometry(RIGHT, x2, y1, x3, y2); 76 | setSliceGeometry(BOTTOM_LEFT, 0, y2, x1, y3); 77 | setSliceGeometry(BOTTOM, x1, y2, x2, y3); 78 | setSliceGeometry(BOTTOM_RIGHT, x2, y2, x3, y3); 79 | } 80 | 81 | 82 | sf::Vector2f Box::getSize() const 83 | { 84 | // Bottom right corner - top left corner 85 | return m_vertices[BOTTOM_RIGHT * 4 + 2].position - getPosition(); 86 | } 87 | 88 | 89 | void Box::press() 90 | { 91 | applyState(StatePressed); 92 | } 93 | 94 | 95 | void Box::release() 96 | { 97 | applyState(StateDefault); 98 | } 99 | 100 | 101 | bool Box::containsPoint(float x, float y) const 102 | { 103 | return x > m_vertices[0].position.x && x < m_vertices[BOTTOM_RIGHT * 4 + 2].position.x 104 | && y > m_vertices[0].position.y && y < m_vertices[BOTTOM_RIGHT * 4 + 2].position.y; 105 | } 106 | 107 | // Visual properties ----------------------------------------------------------- 108 | 109 | void Box::applyState(State state) 110 | { 111 | if (state == m_state || (state == StateHovered && m_state == StateFocused)) 112 | return; 113 | 114 | const sf::IntRect& subrect = Theme::getTextureRect(m_type, state); 115 | float x = subrect.left; 116 | float y = subrect.top; 117 | float width = Theme::borderSize; 118 | float height = Theme::borderSize; 119 | 120 | setSliceTextureCoords(TOP_LEFT, x, y); 121 | setSliceTextureCoords(TOP, x + width, y); 122 | setSliceTextureCoords(TOP_RIGHT, x + width * 2, y); 123 | setSliceTextureCoords(LEFT, x, y + height); 124 | setSliceTextureCoords(MIDDLE, x + width, y + height); 125 | setSliceTextureCoords(RIGHT, x + width * 2, y + height); 126 | setSliceTextureCoords(BOTTOM_LEFT, x, y + height * 2); 127 | setSliceTextureCoords(BOTTOM, x + width, y + height * 2); 128 | setSliceTextureCoords(BOTTOM_RIGHT, x + width * 2, y + height * 2); 129 | 130 | if (m_state == StatePressed) 131 | { 132 | onRelease(); 133 | } 134 | else if (state == StatePressed) 135 | { 136 | onPress(); 137 | } 138 | m_state = state; 139 | } 140 | 141 | 142 | void Box::draw(sf::RenderTarget& target, sf::RenderStates states) const 143 | { 144 | states.texture = &Theme::getTexture(); 145 | target.draw(m_vertices, VERTEX_COUNT, sf::Quads, states); 146 | } 147 | 148 | 149 | void Box::centerTextHorizontally(sf::Text& text) 150 | { 151 | sf::Vector2f size = getSize(); 152 | sf::FloatRect textSize = text.getLocalBounds(); 153 | int x = getPosition().x + (size.x - textSize.width) / 2; 154 | text.setPosition(x, Theme::borderSize + Theme::PADDING); 155 | } 156 | 157 | 158 | void Box::centerTextVertically(sf::Text& text) 159 | { 160 | sf::Vector2f size = getSize(); 161 | sf::FloatRect textSize = text.getLocalBounds(); 162 | int y = getPosition().y + (size.y - textSize.width) / 2; 163 | text.setPosition(Theme::getBoxHeight() - Theme::PADDING, y); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/Gui/Utils/Box.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_BOX_HPP 2 | #define GUI_BOX_HPP 3 | 4 | #include 5 | #include "../Widget.hpp" 6 | 7 | namespace gui 8 | { 9 | 10 | /** 11 | * Utility class used by widgets for holding visual components 12 | */ 13 | class Box: public sf::Drawable 14 | { 15 | public: 16 | enum Type 17 | { 18 | Input, 19 | Click 20 | }; 21 | 22 | Box(Type type = Click); 23 | 24 | /** 25 | * Get box position 26 | */ 27 | const sf::Vector2f& getPosition() const; 28 | 29 | void setPosition(float x, float y); 30 | 31 | /** 32 | * Set the box dimensions 33 | */ 34 | void setSize(float width, float height); 35 | 36 | /** 37 | * Get box dimensions 38 | */ 39 | sf::Vector2f getSize() const; 40 | 41 | void press(); 42 | 43 | void release(); 44 | 45 | /** 46 | * @return true if point is inside the box limits 47 | */ 48 | bool containsPoint(float x, float y) const; 49 | 50 | void applyState(State state); 51 | 52 | template 53 | void centerItem(T& item) 54 | { 55 | sf::Vector2f size = getSize(); 56 | sf::Vector2f itemSize = item.getSize(); 57 | // Center item 58 | item.setPosition( 59 | int(getPosition().x + (size.x - itemSize.x) / 2), int(getPosition().y + (size.y - itemSize.y) / 2) 60 | ); 61 | } 62 | 63 | void centerTextHorizontally(sf::Text& item); 64 | 65 | void centerTextVertically(sf::Text& item); 66 | 67 | protected: 68 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 69 | 70 | virtual void onPress() {}; 71 | virtual void onRelease() {}; 72 | 73 | Type m_type; 74 | 75 | private: 76 | enum Slice 77 | { 78 | TOP_LEFT, 79 | TOP, 80 | TOP_RIGHT, 81 | LEFT, 82 | MIDDLE, 83 | RIGHT, 84 | BOTTOM_LEFT, 85 | BOTTOM, 86 | BOTTOM_RIGHT 87 | }; 88 | 89 | /** 90 | * Set the texture coords for one of the 9 slices 91 | */ 92 | void setSliceTextureCoords(Slice slice, float x, float y); 93 | 94 | /** 95 | * Set the geometry for one of the 9 slices 96 | */ 97 | void setSliceGeometry(Slice slice, float x1, float y1, float x2, float y2); 98 | 99 | State m_state; 100 | 101 | // The box is a 9-slices plane, 4 vertices per slice 102 | static constexpr size_t VERTEX_COUNT = 9 * 4; 103 | sf::Vertex m_vertices[VERTEX_COUNT]; 104 | }; 105 | 106 | } 107 | 108 | #endif // GUI_BOX_HPP 109 | -------------------------------------------------------------------------------- /src/Gui/Utils/Cross.cpp: -------------------------------------------------------------------------------- 1 | #include "Cross.hpp" 2 | #include "../Theme.hpp" 3 | 4 | namespace gui 5 | { 6 | 7 | Cross::Cross() 8 | { 9 | const sf::IntRect& rect = Theme::getCrossTextureRect(); 10 | m_vertices[0].texCoords = sf::Vector2f(rect.left, rect.top); 11 | m_vertices[1].texCoords = sf::Vector2f(rect.left + rect.width, rect.top); 12 | m_vertices[2].texCoords = sf::Vector2f(rect.left + rect.width, rect.top + rect.height); 13 | m_vertices[3].texCoords = sf::Vector2f(rect.left, rect.top + rect.height); 14 | 15 | updateGeometry(0, 0); 16 | } 17 | 18 | 19 | void Cross::setPosition(float x, float y) 20 | { 21 | updateGeometry(x, y); 22 | } 23 | 24 | 25 | void Cross::move(float dx, float dy) 26 | { 27 | for (int i = 0; i < 4; ++i) 28 | { 29 | m_vertices[i].position.x += dx; 30 | m_vertices[i].position.y += dy; 31 | } 32 | } 33 | 34 | 35 | void Cross::setSize(float) { } 36 | 37 | 38 | sf::Vector2f Cross::getSize() const 39 | { 40 | const sf::IntRect& rect = Theme::getCrossTextureRect(); 41 | return sf::Vector2f(rect.width, rect.height); 42 | } 43 | 44 | 45 | void Cross::setColor(const sf::Color& color) 46 | { 47 | for (int i = 0; i < 4; ++i) 48 | m_vertices[i].color = color; 49 | } 50 | 51 | 52 | void Cross::draw(sf::RenderTarget& target, sf::RenderStates states) const 53 | { 54 | states.texture = &Theme::getTexture(); 55 | target.draw(m_vertices, 4, sf::Quads, states); 56 | } 57 | 58 | 59 | void Cross::updateGeometry(float x, float y) 60 | { 61 | const sf::IntRect& rect = Theme::getCrossTextureRect(); 62 | m_vertices[0].position = sf::Vector2f(x, y); 63 | m_vertices[1].position = sf::Vector2f(x + rect.width, y); 64 | m_vertices[2].position = sf::Vector2f(x + rect.width, y + rect.height); 65 | m_vertices[3].position = sf::Vector2f(x, y + rect.height); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/Gui/Utils/Cross.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_CROSS_HPP 2 | #define GUI_CROSS_HPP 3 | 4 | #include 5 | 6 | namespace gui 7 | { 8 | 9 | class Cross: public sf::Drawable 10 | { 11 | public: 12 | Cross(); 13 | 14 | void setPosition(float x, float y); 15 | void move(float dx, float dy); 16 | 17 | void setSize(float size); 18 | sf::Vector2f getSize() const; 19 | void setColor(const sf::Color& color); 20 | 21 | private: 22 | void draw(sf::RenderTarget& target, sf::RenderStates states) const; 23 | 24 | void updateGeometry(float x, float y); 25 | 26 | sf::Vertex m_vertices[4]; 27 | }; 28 | 29 | } 30 | 31 | #endif // GUI_CROSS_HPP 32 | -------------------------------------------------------------------------------- /src/Gui/Utils/ItemBox.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_ITEMBOX_HPP 2 | #define GUI_ITEMBOX_HPP 3 | 4 | #include "Box.hpp" 5 | 6 | namespace gui 7 | { 8 | 9 | /** 10 | * Utility class used by widgets for holding visual components 11 | */ 12 | template 13 | class ItemBox: public Box 14 | { 15 | public: 16 | ItemBox(Box::Type type = Box::Click); 17 | ItemBox(const T& item, Box::Type type = Box::Click); 18 | 19 | void applyState(State state); 20 | 21 | inline T& item() { return m_item; } 22 | inline const T& item() const { return m_item; } 23 | 24 | private: 25 | void draw(sf::RenderTarget& target, sf::RenderStates states) const override; 26 | void onPress() override; 27 | void onRelease() override; 28 | 29 | T m_item; 30 | }; 31 | 32 | } 33 | 34 | #include "ItemBox.inl" 35 | 36 | #endif // GUI_ITEMBOX_HPP 37 | -------------------------------------------------------------------------------- /src/Gui/Utils/ItemBox.inl: -------------------------------------------------------------------------------- 1 | #include "../Theme.hpp" 2 | 3 | namespace gui 4 | { 5 | 6 | template 7 | ItemBox::ItemBox(Box::Type type): 8 | Box(type) 9 | { 10 | applyState(StateDefault); 11 | } 12 | 13 | template 14 | ItemBox::ItemBox(const T& item, Box::Type type): 15 | Box(type), 16 | m_item(item) 17 | { 18 | applyState(StateDefault); 19 | } 20 | 21 | template 22 | void ItemBox::applyState(State state) 23 | { 24 | Box::applyState(state); 25 | switch (state) 26 | { 27 | case StateDefault: 28 | m_item.setFillColor(m_type == Click ? Theme::click.textColor : Theme::input.textColor); 29 | break; 30 | case StateHovered: 31 | m_item.setFillColor(m_type == Click ? Theme::click.textColorHover : Theme::input.textColorHover); 32 | break; 33 | case StatePressed: 34 | case StateFocused: 35 | m_item.setFillColor(m_type == Click ? Theme::click.textColorFocus : Theme::input.textColorFocus); 36 | break; 37 | } 38 | } 39 | 40 | template 41 | void ItemBox::draw(sf::RenderTarget& target, sf::RenderStates states) const 42 | { 43 | Box::draw(target, states); 44 | target.draw(m_item, states); 45 | } 46 | 47 | template 48 | void ItemBox::onPress() 49 | { 50 | m_item.move(0.f, 1.f); 51 | } 52 | 53 | template 54 | void ItemBox::onRelease() 55 | { 56 | m_item.move(0.f, -1.f); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Gui/Widget.cpp: -------------------------------------------------------------------------------- 1 | #include "Widget.hpp" 2 | #include "Menu.hpp" 3 | #include "Layouts/Layout.hpp" 4 | #include 5 | 6 | namespace gui 7 | { 8 | 9 | Widget::Widget(): 10 | m_parent(nullptr), 11 | m_previous(nullptr), 12 | m_next(nullptr), 13 | m_state(StateDefault), 14 | m_selectable(true) 15 | { 16 | } 17 | 18 | 19 | void Widget::setPosition(const sf::Vector2f& pos) 20 | { 21 | m_position = pos; 22 | m_transform = sf::Transform( 23 | 1, 0, (int)pos.x, // translate x 24 | 0, 1, (int)pos.y, // translate y 25 | 0, 0, 1 26 | ); 27 | } 28 | 29 | 30 | void Widget::setPosition(float x, float y) 31 | { 32 | setPosition(sf::Vector2f(x, y)); 33 | } 34 | 35 | 36 | const sf::Vector2f& Widget::getPosition() const 37 | { 38 | return m_position; 39 | } 40 | 41 | 42 | sf::Vector2f Widget::getAbsolutePosition() const 43 | { 44 | sf::Vector2f position = m_position; 45 | for (Widget* parent = m_parent; parent != nullptr; parent = parent->m_parent) 46 | { 47 | position.x += parent->m_position.x; 48 | position.y += parent->m_position.y; 49 | } 50 | return position; 51 | } 52 | 53 | 54 | void Widget::setSize(const sf::Vector2f& size) 55 | { 56 | m_size = size; 57 | if (m_parent != nullptr) 58 | { 59 | Widget* parent = m_parent; 60 | parent->recomputeGeometry(); 61 | } 62 | } 63 | 64 | 65 | void Widget::setSize(float width, float height) 66 | { 67 | setSize(sf::Vector2f(width, height)); 68 | } 69 | 70 | 71 | const sf::Vector2f& Widget::getSize() const 72 | { 73 | return m_size; 74 | } 75 | 76 | 77 | bool Widget::containsPoint(const sf::Vector2f& point) const 78 | { 79 | return point.x > 0.f && point.x < m_size.x && point.y > 0.f && point.y < m_size.y; 80 | } 81 | 82 | 83 | bool Widget::isSelectable() const 84 | { 85 | return m_selectable; 86 | } 87 | 88 | 89 | bool Widget::isFocused() const 90 | { 91 | return m_state == StateFocused || m_state == StatePressed; 92 | } 93 | 94 | 95 | void Widget::setSelectable(bool selectable) 96 | { 97 | m_selectable = selectable; 98 | } 99 | 100 | 101 | void Widget::setCallback(std::function callback) 102 | { 103 | m_callback = callback; 104 | } 105 | 106 | 107 | void Widget::triggerCallback() 108 | { 109 | if (m_callback) 110 | { 111 | m_callback(); 112 | } 113 | } 114 | 115 | 116 | void Widget::setParent(Layout* parent) 117 | { 118 | m_parent = parent; 119 | } 120 | 121 | 122 | void Widget::setState(State state) 123 | { 124 | m_state = state; 125 | onStateChanged(state); 126 | } 127 | 128 | 129 | State Widget::getState() const 130 | { 131 | return m_state; 132 | } 133 | 134 | 135 | const sf::Transform& Widget::getTransform() const 136 | { 137 | return m_transform; 138 | } 139 | 140 | 141 | void Widget::setMouseCursor(sf::Cursor::Type cursor) 142 | { 143 | // Propagate the mouse cursor to the parent, until it reaches the top-level Menu widget 144 | // (Menu is the only widget that can change the mouse cursor on the RenderWindow) 145 | if (m_parent) 146 | m_parent->setMouseCursor(cursor); 147 | } 148 | 149 | 150 | void Widget::centerText(sf::Text& text) 151 | { 152 | sf::FloatRect r = text.getLocalBounds(); 153 | text.setOrigin(r.left + std::round(r.width / 2.f), r.top + std::round(r.height / 2.f)); 154 | text.setPosition(m_size.x / 2, m_size.y / 2); 155 | } 156 | 157 | // callbacks ------------------------------------------------------------------- 158 | 159 | void Widget::onStateChanged(State) { } 160 | void Widget::onMouseEnter() { } 161 | void Widget::onMouseLeave() { } 162 | void Widget::onMouseMoved(float, float) { } 163 | void Widget::onMousePressed(float, float) { } 164 | void Widget::onMouseReleased(float, float) { } 165 | void Widget::onMouseWheelMoved(int) { } 166 | void Widget::onKeyPressed(const sf::Event::KeyEvent&) { } 167 | void Widget::onKeyReleased(const sf::Event::KeyEvent&) { } 168 | void Widget::onTextEntered(sf::Uint32) { } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/Gui/Widget.hpp: -------------------------------------------------------------------------------- 1 | #ifndef GUI_WIDGET_HPP 2 | #define GUI_WIDGET_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gui 9 | { 10 | 11 | enum State 12 | { 13 | StateDefault, 14 | StateHovered, 15 | StatePressed, 16 | StateFocused 17 | }; 18 | 19 | class Layout; 20 | 21 | /** 22 | * Abstract base class for gui widgets 23 | */ 24 | class Widget: public sf::Drawable 25 | { 26 | public: 27 | Widget(); 28 | 29 | /** 30 | * Widget's position 31 | */ 32 | void setPosition(const sf::Vector2f& pos); 33 | void setPosition(float x, float y); 34 | const sf::Vector2f& getPosition() const; 35 | 36 | sf::Vector2f getAbsolutePosition() const; 37 | 38 | /** 39 | * Get widget's dimensions 40 | */ 41 | const sf::Vector2f& getSize() const; 42 | 43 | /** 44 | * Check if a point is inside the widget 45 | */ 46 | bool containsPoint(const sf::Vector2f& point) const; 47 | 48 | /** 49 | * Check if the widget can be selected and trigger events 50 | */ 51 | bool isSelectable() const; 52 | 53 | /** 54 | * Check if the widget is currently focused 55 | */ 56 | bool isFocused() const; 57 | 58 | /** 59 | * Set a function to be called when this widget is triggered 60 | */ 61 | void setCallback(std::function callback); 62 | 63 | protected: 64 | // Callbacks 65 | virtual void onStateChanged(State state); 66 | virtual void onMouseEnter(); 67 | virtual void onMouseLeave(); 68 | virtual void onMouseMoved(float x, float y); 69 | virtual void onMousePressed(float x, float y); 70 | virtual void onMouseReleased(float x, float y); 71 | virtual void onMouseWheelMoved(int delta); 72 | virtual void onKeyPressed(const sf::Event::KeyEvent& key); 73 | virtual void onKeyReleased(const sf::Event::KeyEvent& key); 74 | virtual void onTextEntered(sf::Uint32 unicode); 75 | 76 | void setSize(const sf::Vector2f& size); 77 | void setSize(float widget, float height); 78 | 79 | friend class Layout; 80 | friend class FormLayout; 81 | friend class HBoxLayout; 82 | friend class VBoxLayout; 83 | 84 | void setSelectable(bool selectable); 85 | 86 | /** 87 | * Notify parent that the widget has been triggered by user input 88 | */ 89 | void triggerCallback(); 90 | 91 | void setState(State state); 92 | State getState() const; 93 | 94 | /** 95 | * Set the widget's container (parent) 96 | */ 97 | void setParent(Layout* parent); 98 | Layout* getParent() { return m_parent; } 99 | 100 | /** 101 | * Get the widget typed as a Layout if applicable 102 | * Used to check if the widget is a container (Layout and its subclasses) 103 | */ 104 | virtual Layout* toLayout() { return nullptr; } 105 | 106 | void centerText(sf::Text& text); 107 | 108 | virtual void recomputeGeometry() {}; 109 | 110 | const sf::Transform& getTransform() const; 111 | 112 | virtual void setMouseCursor(sf::Cursor::Type cursor); 113 | 114 | private: 115 | Layout* m_parent; 116 | Widget* m_previous; 117 | Widget* m_next; 118 | 119 | State m_state; 120 | sf::Vector2f m_position; 121 | sf::Vector2f m_size; 122 | bool m_selectable; 123 | 124 | std::function m_callback; 125 | 126 | sf::Transform m_transform; 127 | }; 128 | 129 | } 130 | 131 | #endif // GUI_WIDGET_HPP 132 | --------------------------------------------------------------------------------