diff --git a/gui-builder/include/WidgetProperties/EditBoxSliderProperties.hpp b/gui-builder/include/WidgetProperties/EditBoxSliderProperties.hpp new file mode 100644 index 000000000..bb9d1efa6 --- /dev/null +++ b/gui-builder/include/WidgetProperties/EditBoxSliderProperties.hpp @@ -0,0 +1,148 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2024 Bruno Van de Velde (vdv_b@tgui.eu) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +#ifndef TGUI_GUI_BUILDER_EDIT_BOX_SLIDER_PROPERTIES_HPP +#define TGUI_GUI_BUILDER_EDIT_BOX_SLIDER_PROPERTIES_HPP + +#include "WidgetProperties.hpp" + +struct EditBoxSliderProperties : WidgetProperties +{ + void updateProperty(const tgui::Widget::Ptr& widget, const tgui::String& property, const tgui::String& value) const override + { + auto editBoxSlider = widget->cast(); + if (property == "Minimum") + editBoxSlider->setMinimum(value.toFloat()); + else if (property == "Maximum") + editBoxSlider->setMaximum(value.toFloat()); + else if (property == "Value") + editBoxSlider->setValue(value.toFloat()); + else if (property == "Step") + editBoxSlider->setStep(value.toFloat()); + else if (property == "DecimalPlaces") + editBoxSlider->setDecimalPlaces(value.toUInt()); + else if (property == "TextAlignment") + editBoxSlider->setTextAlignment(deserializeTextAlignment(value)); + else + WidgetProperties::updateProperty(widget, property, value); + } + + TGUI_NODISCARD PropertyValueMapPair initProperties(const tgui::Widget::Ptr& widget) const override + { + auto pair = WidgetProperties::initProperties(widget); + auto editBoxSlider = widget->cast(); + pair.first["Minimum"] = {"Float", tgui::String::fromNumber(editBoxSlider->getMinimum())}; + pair.first["Maximum"] = {"Float", tgui::String::fromNumber(editBoxSlider->getMaximum())}; + pair.first["Value"] = {"Float", tgui::String::fromNumber(editBoxSlider->getValue())}; + pair.first["Step"] = {"Float", tgui::String::fromNumber(editBoxSlider->getStep())}; + pair.first["Step"] = {"Float", tgui::String::fromNumber(editBoxSlider->getStep())}; + pair.first["DecimalPlaces"] = {"UInt", tgui::String::fromNumber(editBoxSlider->getDecimalPlaces())}; + pair.first["TextAlignment"] = {"Enum{Left,Center,Right}", serializeTextAlignment(editBoxSlider->getTextAlignment())}; + + const auto editBoxRenderer = editBoxSlider->getEditBoxSharedRenderer(); + pair.second["EditBox.Padding"] = {"Outline", editBoxRenderer->getPadding().toString()}; + pair.second["EditBox.CaretWidth"] = {"Float", tgui::String::fromNumber(editBoxRenderer->getCaretWidth())}; + pair.second["EditBox.TextColor"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getTextColor())}; + pair.second["EditBox.TextColorDisabled"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getTextColorDisabled())}; + pair.second["EditBox.TextColorFocused"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getTextColorFocused())}; + pair.second["EditBox.SelectedTextColor"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getSelectedTextColor())}; + pair.second["EditBox.SelectedTextBackgroundColor"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getSelectedTextBackgroundColor())}; + pair.second["EditBox.DefaultTextColor"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getDefaultTextColor())}; + pair.second["EditBox.BackgroundColor"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBackgroundColor())}; + pair.second["EditBox.BackgroundColorHover"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBackgroundColorHover())}; + pair.second["EditBox.BackgroundColorDisabled"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBackgroundColorDisabled())}; + pair.second["EditBox.BackgroundColorFocused"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBackgroundColorFocused())}; + pair.second["EditBox.CaretColor"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getCaretColor())}; + pair.second["EditBox.CaretColorHover"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getCaretColorHover())}; + pair.second["EditBox.CaretColorFocused"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getCaretColorFocused())}; + pair.second["EditBox.BorderColor"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBorderColor())}; + pair.second["EditBox.BorderColorHover"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBorderColorHover())}; + pair.second["EditBox.BorderColorDisabled"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBorderColorDisabled())}; + pair.second["EditBox.BorderColorFocused"] = {"Color", tgui::Serializer::serialize(editBoxRenderer->getBorderColorFocused())}; + pair.second["EditBox.Texture"] = {"Texture", tgui::Serializer::serialize(editBoxRenderer->getTexture())}; + pair.second["EditBox.TextureHover"] = {"Texture", tgui::Serializer::serialize(editBoxRenderer->getTextureHover())}; + pair.second["EditBox.TextureDisabled"] = {"Texture", tgui::Serializer::serialize(editBoxRenderer->getTextureDisabled())}; + pair.second["EditBox.TextureFocused"] = {"Texture", tgui::Serializer::serialize(editBoxRenderer->getTextureFocused())}; + pair.second["EditBox.TextStyle"] = {"TextStyle", tgui::Serializer::serialize(editBoxRenderer->getTextStyle())}; + pair.second["EditBox.DefaultTextStyle"] = {"TextStyle", tgui::Serializer::serialize(editBoxRenderer->getDefaultTextStyle())}; + + const auto sliderRenderer = editBoxSlider->getSliderSharedRenderer(); + pair.second["Borders"] = {"Outline", tgui::Serializer::serialize(sliderRenderer->getBorders())}; + pair.second["TrackColor"] = {"Color", tgui::Serializer::serialize(sliderRenderer->getTrackColor())}; + pair.second["TrackColorHover"] = {"Color", tgui::Serializer::serialize(sliderRenderer->getTrackColorHover())}; + pair.second["ThumbColor"] = {"Color", tgui::Serializer::serialize(sliderRenderer->getThumbColor())}; + pair.second["ThumbColorHover"] = {"Color", tgui::Serializer::serialize(sliderRenderer->getThumbColorHover())}; + pair.second["BorderColor"] = {"Color", tgui::Serializer::serialize(sliderRenderer->getBorderColor())}; + pair.second["BorderColorHover"] = {"Color", tgui::Serializer::serialize(sliderRenderer->getBorderColorHover())}; + pair.second["TextureTrack"] = {"Texture", tgui::Serializer::serialize(sliderRenderer->getTextureTrack())}; + pair.second["TextureTrackHover"] = {"Texture", tgui::Serializer::serialize(sliderRenderer->getTextureTrackHover())}; + pair.second["TextureThumb"] = {"Texture", tgui::Serializer::serialize(sliderRenderer->getTextureThumb())}; + pair.second["TextureThumbHover"] = {"Texture", tgui::Serializer::serialize(sliderRenderer->getTextureThumbHover())}; + return pair; + } + +private: + + TGUI_NODISCARD static tgui::EditBox::Alignment deserializeTextAlignment(tgui::String value) + { + value = value.trim().toLower(); + if (value == "right") + return tgui::EditBox::Alignment::Right; + else if (value == "center") + return tgui::EditBox::Alignment::Center; + else + return tgui::EditBox::Alignment::Left; + } + + TGUI_NODISCARD static tgui::String serializeTextAlignment(tgui::EditBox::Alignment alignment) + { + if (alignment == tgui::EditBox::Alignment::Center) + return "Center"; + else if (alignment == tgui::EditBox::Alignment::Right) + return "Right"; + else + return "Left"; + } + +private: + + const std::set m_editBoxProperties = { + "Padding", "CaretWidth", "TextColor", "TextColorDisabled", "TextColorFocused", "SelectedTextColor", + "SelectedTextBackgroundColor", "DefaultTextColor", "BackgroundColor", "BackgroundColorHover", + "BackgroundColorDisabled", "BackgroundColorFocused", "CaretColor", "CaretColorHover", "CaretColorFocused", + "BorderColor", "BorderColorHover", "BorderColorDisabled", "BorderColorFocused", "Texture", "TextureHover", + "TextureDisabled", "TextureFocused", "TextStyle", "DefaultTextStyle", "Borders" + }; + + const std::set m_SliderProperties = { + "Borders", "TrackColor", "TrackColorHover", "ThumbColor", "ThumbColorHover", + "BorderColor", "BorderColorHover", "TextureTrack", "TextureTrackHover", "TextureThumb", + "TextureThumbHover" + }; + + +}; + +#endif // TGUI_GUI_BUILDER_EDIT_BOX_SLIDER_PROPERTIES_HPP diff --git a/gui-builder/resources/widget-icons/EditBoxSlider.png b/gui-builder/resources/widget-icons/EditBoxSlider.png new file mode 100644 index 000000000..98efbf4ef Binary files /dev/null and b/gui-builder/resources/widget-icons/EditBoxSlider.png differ diff --git a/gui-builder/src/GuiBuilder.cpp b/gui-builder/src/GuiBuilder.cpp index 5b3cb2c5b..8afdd576e 100644 --- a/gui-builder/src/GuiBuilder.cpp +++ b/gui-builder/src/GuiBuilder.cpp @@ -32,6 +32,7 @@ #include "WidgetProperties/ClickableWidgetProperties.hpp" #include "WidgetProperties/ComboBoxProperties.hpp" #include "WidgetProperties/EditBoxProperties.hpp" +#include "WidgetProperties/EditBoxSliderProperties.hpp" #include "WidgetProperties/GroupProperties.hpp" #include "WidgetProperties/HorizontalLayoutProperties.hpp" #include "WidgetProperties/HorizontalWrapProperties.hpp" @@ -271,6 +272,7 @@ GuiBuilder::GuiBuilder(const tgui::String& programName) : m_widgetProperties["ClickableWidget"] = std::make_unique(); m_widgetProperties["ComboBox"] = std::make_unique(); m_widgetProperties["EditBox"] = std::make_unique(); + m_widgetProperties["EditBoxSlider"] = std::make_unique(); m_widgetProperties["Group"] = std::make_unique(); m_widgetProperties["HorizontalLayout"] = std::make_unique(); m_widgetProperties["HorizontalWrap"] = std::make_unique(); @@ -983,6 +985,7 @@ void GuiBuilder::loadToolbox() {"ClickableWidget", []{ return tgui::ClickableWidget::create({150, 150}); }}, {"ComboBox", []{ return tgui::ComboBox::create(); }}, {"EditBox", []{ return tgui::EditBox::create(); }}, + {"EditBoxSlider", []{ return tgui::EditBoxSlider::create(); }}, {"Group", []{ return tgui::Group::create({150, 150}); }}, {"HorizontalLayout", []{ return tgui::HorizontalLayout::create({150, 150}); }}, {"HorizontalWrap", []{ return tgui::HorizontalWrap::create({150, 150}); }}, diff --git a/include/TGUI/AllWidgets.hpp b/include/TGUI/AllWidgets.hpp index 9a36989c4..907a745e8 100644 --- a/include/TGUI/AllWidgets.hpp +++ b/include/TGUI/AllWidgets.hpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include diff --git a/include/TGUI/Widgets/EditBoxSlider.hpp b/include/TGUI/Widgets/EditBoxSlider.hpp new file mode 100644 index 000000000..8ba5fa885 --- /dev/null +++ b/include/TGUI/Widgets/EditBoxSlider.hpp @@ -0,0 +1,326 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2023 Bruno Van de Velde (vdv_b@tgui.eu) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef TGUI_EDIT_BOX_SLIDER_HPP +#define TGUI_EDIT_BOX_SLIDER_HPP + + +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +TGUI_MODULE_EXPORT namespace tgui +{ + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Edit box slider widget + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + class TGUI_API EditBoxSlider : public SubwidgetContainer + { + public: + + using Ptr = std::shared_ptr; //!< Shared widget pointer + using ConstrPtr = std::shared_ptr; //!< Shared const widget pointer + + static constexpr const char StaticWidgetType[] = "EditBoxSlider"; //!< Type name of the widget + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @internal + /// @brief Constructor + /// @param typeName Type of the widget + /// @param initRenderer Should the renderer be initialized? Should be true unless a derived class initializes it. + /// @see create + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + EditBoxSlider(const char* typeName = StaticWidgetType, bool initRenderer = true); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Copy constructor + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + EditBoxSlider(const EditBoxSlider& copy); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Move constructor + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + EditBoxSlider(EditBoxSlider&& copy) noexcept; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Overload of copy assignment operator + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + EditBoxSlider& operator= (const EditBoxSlider& right); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Overload of move assignment operator + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + EditBoxSlider& operator= (EditBoxSlider&& right) noexcept; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Creates a new edit box slider widget + /// + /// @param min The minimum value of the edit box slider + /// @param max The maximum value of the edit box slider + /// @param value Initial value + /// @param decimal Number of decimal places to display + /// @param step Increment value + /// + /// @return The new edit box slider + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD static EditBoxSlider::Ptr create(float min = 0.0f, float max = 10.0f, float value = 0.0f, unsigned int decimal = 0, float step = 1.0f); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Makes a copy of another edit box slider + /// + /// @param editBox The other edit box slider + /// + /// @return The new edit box slider + /// + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD static EditBoxSlider::Ptr copy(const EditBoxSlider::ConstPtr& editBoxSlider); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer of edit box part of widget + /// @return Temporary pointer to the renderer that may be shared with other widgets using the same renderer + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD EditBoxRenderer* getEditBoxSharedRenderer(); + TGUI_NODISCARD const EditBoxRenderer* getEditBoxSharedRenderer() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer of edit box part of widget + /// @return Temporary pointer to the renderer + /// @warning After calling this function, the widget has its own copy of the renderer and it will no longer be shared. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD EditBoxRenderer* getEditBoxRenderer(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer of slider part of widget + /// @return Temporary pointer to the renderer that may be shared with other widgets using the same renderer + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD SliderRenderer* getSliderSharedRenderer(); + TGUI_NODISCARD const SliderRenderer* getSliderSharedRenderer() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer of slider part of widget + /// @return Temporary pointer to the renderer + /// @warning After calling this function, the widget has its own copy of the renderer and it will no longer be shared. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD SliderRenderer* getSliderRenderer(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the size of the edit box slider + /// + /// @param size The new size of the edit box slider + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSize(const Layout2d& size) override; + using SubwidgetContainer::setSize; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Sets a minimum value + /// + /// @param minimum The new minimum value + /// + /// When the value is too small then it will be changed to this minimum. + /// The default minimum value is 0. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setMinimum(float minimum); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the minimum value + /// + /// @return The current minimum value + /// + /// The default minimum value 0. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD float getMinimum() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Sets a maximum value + /// + /// @param maximum The new maximum value + /// + /// When the value is too big then it will be changed to this maximum. + /// The default maximum value is 10. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setMaximum(float maximum); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the maximum value + /// + /// @return The current maximum value + /// + /// The default maximum value 10. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD float getMaximum() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the current value + /// + /// @param value The new value + /// @return Was the value actually set + /// + /// The value can't be smaller than the minimum or bigger than the maximum. + /// The default value is 0. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool setValue(float value); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the current value + /// + /// @return The current value + /// + /// The default value is 0. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD float getValue() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes how much the value changes on each arrow press + /// @param step The new step size + /// @pre The step size must be a positive value or 0. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setStep(float step); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the number of positions the thumb advances with each move + /// @return The current step size + /// @see setStep + /// + /// The default value is 1.0. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD float getStep() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the number of decimal places to display + /// @param decimalPlaces The new number of decimal places + /// + /// The default value is 0, which means that the value has to be an integer. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setDecimalPlaces(unsigned int decimalPlaces); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the number of decimal places to display + /// @return The current number of decimal places + /// @see setDecimalPlaces + /// + /// The default value is 0, which means that the value has to be an integer. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD unsigned int getDecimalPlaces() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Sets the alignment of the edit box text + /// @param alignment The new text alignment + /// + /// The alignment is an enum with the values {Left, Center, Right} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setTextAlignment(EditBox::Alignment alignment); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the current text alignment of the edit box + /// @return The current text alignment + /// @see setTextAlignment + /// + /// The alignment is an enum with the values {Left, Center, Right} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD EditBox::Alignment getTextAlignment() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + private: + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Helper function that initializes the widget when constructing a new widget or loading one from a file + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void init(); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Checks whether a value lies between the minimum and maximum + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool inRange(const float value) const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Updates the text in the edit box + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setString(const String& str); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + protected: + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Retrieves a signal based on its name + /// + /// @param signalName Name of the signal + /// + /// @return Signal that corresponds to the name + /// + /// @throw Exception when the name does not match any signal + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD Signal& getSignal(String signalName) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Makes a copy of the widget + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD Widget::Ptr clone() const override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Saves the widget as a tree node in order to save it to a file + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TGUI_NODISCARD std::unique_ptr save(SavingRenderersMap& renderers) const override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Loads the widget from a tree of nodes + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void load(const std::unique_ptr& node, const LoadingRenderersMap& renderers) override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + public: + SignalFloat onValueChange = {"ValueChanged"}; //!< Value of the slider changed. Optional parameter: new value + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + protected: + unsigned m_decimalPlaces = 0; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + private: + EditBox::Ptr m_editBox = EditBox::create(); + Slider::Ptr m_slider = Slider::create(); + }; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // TGUI_EDIT_BOX_SLIDER_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 56e1d1893..93bd8b963 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,6 +106,7 @@ set(TGUI_SRC Widgets/ColorPicker.cpp Widgets/ComboBox.cpp Widgets/EditBox.cpp + Widgets/EditBoxSlider.cpp Widgets/FileDialog.cpp Widgets/Group.cpp Widgets/Grid.cpp diff --git a/src/Loading/WidgetFactory.cpp b/src/Loading/WidgetFactory.cpp index f9685e33e..acda26085 100644 --- a/src/Loading/WidgetFactory.cpp +++ b/src/Loading/WidgetFactory.cpp @@ -41,6 +41,7 @@ namespace tgui {"ColorPicker", std::make_shared}, {"ComboBox", std::make_shared}, {"EditBox", std::make_shared}, + {"EditBoxSlider", std::make_shared}, {"FileDialog", std::make_shared}, {"Grid", std::make_shared}, {"Group", std::make_shared}, diff --git a/src/Widgets/EditBoxSlider.cpp b/src/Widgets/EditBoxSlider.cpp new file mode 100644 index 000000000..2e344ad89 --- /dev/null +++ b/src/Widgets/EditBoxSlider.cpp @@ -0,0 +1,366 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2023 Bruno Van de Velde (vdv_b@tgui.eu) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace tgui +{ +#if TGUI_COMPILED_WITH_CPP_VER < 17 + constexpr const char EditBoxSlider::StaticWidgetType[]; +#endif + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxSlider::EditBoxSlider(const char* typeName, bool initRenderer) : + SubwidgetContainer{typeName, initRenderer} + { + m_editBox->setInputValidator(EditBox::Validator::Float); + + m_container->add(m_editBox, "EditBox"); + m_container->add(m_slider, "Slider"); + + init(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxSlider::EditBoxSlider(const EditBoxSlider& other) : + SubwidgetContainer{other}, + onValueChange {other.onValueChange}, + m_decimalPlaces {other.m_decimalPlaces}, + m_editBox {m_container->get(U"EditBox")}, + m_slider {m_container->get(U"Slider")} + { + init(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxSlider::EditBoxSlider(EditBoxSlider&& other) noexcept : + SubwidgetContainer{std::move(other)}, + onValueChange {std::move(other.onValueChange)}, + m_decimalPlaces {std::move(other.m_decimalPlaces)}, + m_editBox {std::move(other.m_editBox)}, + m_slider {std::move(other.m_slider)} + { + init(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxSlider& EditBoxSlider::operator= (const EditBoxSlider& other) + { + if (this != &other) + { + SubwidgetContainer::operator=(other); + onValueChange = other.onValueChange; + m_decimalPlaces = other.m_decimalPlaces; + m_editBox = m_container->get(U"EditBox"); + m_slider = m_container->get(U"Slider"); + + init(); + } + + return *this; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxSlider& EditBoxSlider::operator= (EditBoxSlider&& other) noexcept + { + if (this != &other) + { + onValueChange = std::move(other.onValueChange); + m_decimalPlaces = std::move(other.m_decimalPlaces); + m_editBox = std::move(other.m_editBox); + m_slider = std::move(other.m_slider); + SubwidgetContainer::operator=(std::move(other)); + + init(); + } + + return *this; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxSlider::Ptr EditBoxSlider::create(float min, float max, float value, unsigned int decimal, float step) + { + auto editBoxSlider = std::make_shared(); + + editBoxSlider->setMinimum(min); + editBoxSlider->setMaximum(max); + editBoxSlider->setValue(value); + editBoxSlider->setDecimalPlaces(decimal); + editBoxSlider->setStep(step); + + return editBoxSlider; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxSlider::Ptr EditBoxSlider::copy(const EditBoxSlider::ConstPtr& editBoxSlider) + { + if (editBoxSlider) + return std::static_pointer_cast(editBoxSlider->clone()); + else + return nullptr; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxRenderer* EditBoxSlider::getEditBoxSharedRenderer() + { + return m_editBox->getSharedRenderer(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + const EditBoxRenderer* EditBoxSlider::getEditBoxSharedRenderer() const + { + return m_editBox->getSharedRenderer(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBoxRenderer* EditBoxSlider::getEditBoxRenderer() + { + return m_editBox->getRenderer(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + SliderRenderer* EditBoxSlider::getSliderSharedRenderer() + { + return m_slider->getSharedRenderer(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + const SliderRenderer* EditBoxSlider::getSliderSharedRenderer() const + { + return m_slider->getSharedRenderer(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + SliderRenderer* EditBoxSlider::getSliderRenderer() + { + return m_slider->getRenderer(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::setSize(const Layout2d& size) + { + SubwidgetContainer::setSize(size); + m_editBox->setSize({getSize().x, getSize().y * 0.8f}); + m_slider->setSize({getSize().x, getSize().y * 0.2f}); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::setMinimum(float minimum) + { + m_slider->setMinimum(minimum); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + float EditBoxSlider::getMinimum() const + { + return m_slider->getMinimum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::setMaximum(float maximum) + { + m_slider->setMaximum(maximum); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + float EditBoxSlider::getMaximum() const + { + return m_slider->getMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool EditBoxSlider::setValue(float value) + { + if (m_slider->getValue() != value && inRange(value)) + { + m_slider->setValue(value); + setString(String::fromNumberRounded(value, m_decimalPlaces)); + return true; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + float EditBoxSlider::getValue() const + { + return m_slider->getValue(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::setStep(float step) + { + m_slider->setStep(step); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + float EditBoxSlider::getStep() const + { + return m_slider->getStep(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::setDecimalPlaces(unsigned decimalPlaces) + { + m_decimalPlaces = decimalPlaces; + setString(String::fromNumberRounded(getValue(), m_decimalPlaces)); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + unsigned EditBoxSlider::getDecimalPlaces() const + { + return m_decimalPlaces; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::setTextAlignment(EditBox::Alignment alignment) + { + m_editBox->setAlignment(alignment); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + EditBox::Alignment EditBoxSlider::getTextAlignment() const + { + return m_editBox->getAlignment();; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::unique_ptr EditBoxSlider::save(SavingRenderersMap& renderers) const + { + auto node = SubwidgetContainer::save(renderers); + node->propertyValuePairs[U"DecimalPlaces"] = std::make_unique(String::fromNumber(m_decimalPlaces)); + return node; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::load(const std::unique_ptr& node, const LoadingRenderersMap& renderers) + { + SubwidgetContainer::load(node, renderers); + + if (node->propertyValuePairs[U"DecimalPlaces"]) + setDecimalPlaces(node->propertyValuePairs[U"DecimalPlaces"]->value.toUInt()); + + m_editBox = m_container->get("EditBox"); + m_slider = m_container->get("Slider"); + + if (!m_editBox || !m_slider) + throw Exception{U"Failed to find EditBox and Slider children while loading EditBoxSlider"}; + + init(); + setString(String::fromNumberRounded(m_slider->getValue(), m_decimalPlaces)); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::init() + { + m_slider->setPosition(bindLeft(m_editBox), bindBottom(m_editBox)); + + m_slider->onValueChange.disconnectAll(); + m_slider->onValueChange([this](const float val) { + setString(String::fromNumberRounded(val, m_decimalPlaces)); + onValueChange.emit(this, val); + }); + + m_editBox->onTextChange.disconnectAll(); + m_editBox->onTextChange([this](const String &text) { + m_slider->setValue(text.toFloat()); + }); + + m_editBox->onReturnOrUnfocus.disconnectAll(); + m_editBox->onReturnOrUnfocus([this](const String &text) { + const float val = text.toFloat(); + + if (!inRange(val)) + setString(String::fromNumberRounded(m_slider->getValue(), m_decimalPlaces)); + }); + + const Vector2f editBoxSize = m_editBox->getSize(); + const Vector2f sliderSize = m_slider->getSize(); + SubwidgetContainer::setSize(editBoxSize.x, editBoxSize.y + sliderSize.y); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool EditBoxSlider::inRange(const float value) const + { + return m_slider->getMinimum() <= value && value <= m_slider->getMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void EditBoxSlider::setString(const String& str) + { + m_editBox->setText(str); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Signal& EditBoxSlider::getSignal(String signalName) + { + if (signalName == onValueChange.getName()) + return onValueChange; + else + return Widget::getSignal(std::move(signalName)); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Widget::Ptr EditBoxSlider::clone() const + { + return std::make_shared(*this); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23dcc0580..97d8cf06b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -67,6 +67,7 @@ set(TEST_SOURCES Widgets/ColorPicker.cpp Widgets/ComboBox.cpp Widgets/EditBox.cpp + Widgets/EditBoxSlider.cpp Widgets/FileDialog.cpp Widgets/Group.cpp Widgets/Grid.cpp diff --git a/tests/Widgets/EditBoxSlider.cpp b/tests/Widgets/EditBoxSlider.cpp new file mode 100644 index 000000000..b4417f2f5 --- /dev/null +++ b/tests/Widgets/EditBoxSlider.cpp @@ -0,0 +1,161 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2024 Bruno Van de Velde (vdv_b@tgui.eu) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "Tests.hpp" + +TEST_CASE("[editBoxSlider]") +{ + tgui::EditBoxSlider::Ptr editBoxSlider = tgui::EditBoxSlider::create(0, 10, 5); + + SECTION("Signals") + { + editBoxSlider->onValueChange([](){}); + editBoxSlider->onValueChange([](float){}); + + REQUIRE_NOTHROW(tgui::Widget::Ptr(editBoxSlider)->getSignal("ValueChanged").connect([]{})); + } + + SECTION("WidgetType") + { + REQUIRE(editBoxSlider->getWidgetType() == "EditBoxSlider"); + } + + SECTION("Position and Size") + { + editBoxSlider->setPosition(40, 30); + editBoxSlider->setSize(25, 60); + + REQUIRE(editBoxSlider->getPosition() == tgui::Vector2f(40, 30)); + REQUIRE(editBoxSlider->getSize() == tgui::Vector2f(25, 60)); + REQUIRE(editBoxSlider->getFullSize() == editBoxSlider->getSize()); + REQUIRE(editBoxSlider->getWidgetOffset() == tgui::Vector2f(0, 0)); + } + + SECTION("Minimum") + { + REQUIRE(editBoxSlider->getMinimum() == 0); + + editBoxSlider->setMinimum(2); + REQUIRE(editBoxSlider->getMinimum() == 2); + REQUIRE(editBoxSlider->getValue() == 5); + REQUIRE(editBoxSlider->getMaximum() == 10); + + editBoxSlider->setMinimum(6); + REQUIRE(editBoxSlider->getMinimum() == 6); + REQUIRE(editBoxSlider->getValue() == 6); + REQUIRE(editBoxSlider->getMaximum() == 10); + + editBoxSlider->setMinimum(22); + REQUIRE(editBoxSlider->getMinimum() == 22); + REQUIRE(editBoxSlider->getValue() == 22); + REQUIRE(editBoxSlider->getMaximum() == 22); + } + + SECTION("Maximum") + { + REQUIRE(editBoxSlider->getMaximum() == 10); + + editBoxSlider->setMaximum(17); + REQUIRE(editBoxSlider->getMinimum() == 0); + REQUIRE(editBoxSlider->getValue() == 5); + REQUIRE(editBoxSlider->getMaximum() == 17); + + editBoxSlider->setMaximum(11); + REQUIRE(editBoxSlider->getMinimum() == 0); + REQUIRE(editBoxSlider->getValue() == 5); + REQUIRE(editBoxSlider->getMaximum() == 11); + + editBoxSlider->setMaximum(-2); + REQUIRE(editBoxSlider->getMinimum() == -2); + REQUIRE(editBoxSlider->getValue() == -2); + REQUIRE(editBoxSlider->getMaximum() == -2); + } + + SECTION("Value") + { + REQUIRE(editBoxSlider->getValue() == 5); + REQUIRE(editBoxSlider->setValue(4)); + REQUIRE_FALSE(editBoxSlider->setValue(-2)); + REQUIRE_FALSE(editBoxSlider->setValue(23)); + REQUIRE(editBoxSlider->setValue(8.5)); + } + + SECTION("Step") + { + editBoxSlider->setStep(5); + REQUIRE(editBoxSlider->getStep() == 5); + + editBoxSlider->setMinimum(20.5f); + editBoxSlider->setMaximum(50.5f); + editBoxSlider->setStep(3.0f); + REQUIRE(editBoxSlider->setValue(26.5f)); + REQUIRE(editBoxSlider->getValue() == 26.5f); + + // If a value is set between two steps then it will be rounded to the nearest allowed value + REQUIRE(editBoxSlider->setValue(25.8f)); + REQUIRE(editBoxSlider->getValue() == 26.5f); + } + + SECTION("DecimalPlaces") + { + REQUIRE(editBoxSlider->getDecimalPlaces() == 0); + + editBoxSlider->setDecimalPlaces(3); + REQUIRE(editBoxSlider->getDecimalPlaces() == 3); + } + + SECTION("Events / Signals") + { + SECTION("ValueChanged") + { + editBoxSlider->setPosition(40, 30); + editBoxSlider->setSize(25, 60); + editBoxSlider->setMaximum(20); + editBoxSlider->setMinimum(10); + editBoxSlider->setValue(15); + + unsigned int valueChangedCount = 0; + editBoxSlider->onValueChange(&genericCallback, std::ref(valueChangedCount)); + + editBoxSlider->setValue(10); + REQUIRE(valueChangedCount == 1); + + editBoxSlider->setValue(10); + REQUIRE(valueChangedCount == 1); + + editBoxSlider->setValue(12); + REQUIRE(valueChangedCount == 2); + } + } + + SECTION("Saving and loading from file") + { + editBoxSlider->setMinimum(10); + editBoxSlider->setMaximum(50); + editBoxSlider->setValue(20); + editBoxSlider->setStep(5); + + testSavingWidget("EditBoxSlider", editBoxSlider); + } +}