diff --git a/changelog.txt b/changelog.txt index c0f05d34..25a759ca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,7 @@ TGUI 0.8.3 (TBD) ----------------- +- ListView widget added - TextBox can now have a horizontal scrollbar - Label can now have a vertical scrollbar - Default scrollbar width wasn't always taken from texture size in widgets containing scrollbars diff --git a/include/TGUI/Renderers/ListViewRenderer.hpp b/include/TGUI/Renderers/ListViewRenderer.hpp new file mode 100644 index 00000000..a174ba9c --- /dev/null +++ b/include/TGUI/Renderers/ListViewRenderer.hpp @@ -0,0 +1,309 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2018 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_LIST_VIEW_RENDERER_HPP +#define TGUI_LIST_VIEW_RENDERER_HPP + + +#include + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace tgui +{ + class TGUI_API ListViewRenderer : public WidgetRenderer + { + public: + + using WidgetRenderer::WidgetRenderer; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the size of the borders + /// + /// @param borders Size of the borders + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setBorders(const Borders& borders); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the size of the borders + /// + /// @return border size + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Borders getBorders() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the padding of the list box + /// + /// @param padding The padding width and height + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setPadding(const Padding& padding); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the size of the padding + /// + /// @return padding size + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Padding getPadding() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the background color of the list box + /// + /// @param backgroundColor The new background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setBackgroundColor(Color backgroundColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the background color + /// + /// @return Background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getBackgroundColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the background color used for the item below the mouse + /// + /// @param backgroundColor The new hover background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setBackgroundColorHover(Color backgroundColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the background color used for the item below the mouse + /// + /// @return Background color of hovered item + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getBackgroundColorHover() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the background color of the selected item + /// + /// @param backgroundColor The new selected item background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSelectedBackgroundColor(Color backgroundColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the background color of the selected item + /// + /// @return Selected item background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getSelectedBackgroundColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the background color used for the selected item when the mouse hovers over it + /// + /// @param backgroundColor The new selected hover background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSelectedBackgroundColorHover(Color backgroundColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the background color used for the selected item when the mouse hovers over it + /// + /// @return Background color of selected item in hover state + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getSelectedBackgroundColorHover() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the color of the text + /// + /// @param textColor The new text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setTextColor(Color textColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the color of the text + /// + /// @return Text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getTextColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the color of the text of the item below the mouse + /// + /// @param textColor The new hover text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setTextColorHover(Color textColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the color of the text of the item below the mouse + /// + /// @return Hover text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getTextColorHover() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the color of the text from the selected item + /// + /// @param textColor The new selected text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSelectedTextColor(Color textColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the color of the text from the selected item + /// + /// @return Selected text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getSelectedTextColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the color of the text of the selected item when it is below the mouse + /// + /// @param textColor The new hover text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSelectedTextColorHover(Color textColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the color of the text of the selected item when it is below the mouse + /// + /// @return Hover text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getSelectedTextColorHover() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the background color of the header + /// + /// @param backgroundColor The new header background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setHeaderBackgroundColor(Color backgroundColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the color of the background color of the header + /// + /// @return Header background color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getHeaderBackgroundColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the text color of the header captions + /// + /// @param textColor The new header text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setHeaderTextColor(Color textColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the text color of the header captions + /// + /// @return Header text color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getHeaderTextColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the color of the borders + /// + /// @param borderColor The new border color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setBorderColor(Color borderColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the color of the borders + /// + /// @return Border color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getBorderColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the color of the separators + /// + /// @param separatorColor The new separator color + /// + /// The border color will be used when no separator color is set. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSeparatorColor(Color separatorColor); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the color of the separators + /// + /// @return Separator color + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Color getSeparatorColor() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Sets the renderer data of the scrollbar + /// + /// @param scrollbarRendererData Data about how the scrollbar should look + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setScrollbar(std::shared_ptr scrollbarRendererData); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer data of the scrollbar + /// + /// @return Data about how the scrollbar looks + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::shared_ptr getScrollbar() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Sets the wanted width of the scrollbar + /// + /// @param scrollbarWidth Requested scrollbar width or 0 to use the default width (texture size if using textures) + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setScrollbarWidth(float scrollbarWidth); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the wanted width of the scrollbar + /// + /// @return Requested scrollbar width or 0 if no width was set (texture width or default value will be used) + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + float getScrollbarWidth() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + }; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // TGUI_LIST_VIEW_RENDERER_HPP diff --git a/include/TGUI/TGUI.hpp b/include/TGUI/TGUI.hpp index 2784c533..4d8d15eb 100644 --- a/include/TGUI/TGUI.hpp +++ b/include/TGUI/TGUI.hpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include diff --git a/include/TGUI/Widgets/ListView.hpp b/include/TGUI/Widgets/ListView.hpp new file mode 100644 index 00000000..d11a969f --- /dev/null +++ b/include/TGUI/Widgets/ListView.hpp @@ -0,0 +1,657 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2018 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_LIST_VIEW_HPP +#define TGUI_LIST_VIEW_HPP + + +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace tgui +{ + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief List view widget + /// @warning This widget is new and API stability is not yet guaranteed. Functions and their behavior may still change in newer patch releases, based on feedback. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + class TGUI_API ListView : public Widget + { + public: + + typedef std::shared_ptr Ptr; ///< Shared widget pointer + typedef std::shared_ptr ConstPtr; ///< Shared constant widget pointer + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief The text alignment for all texts within a column + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + enum class ColumnAlignment + { + Left, ///< Place the text on the left side (default) + Center, ///< Center the text inside the column + Right ///< Place the text on the right side (e.g. for numbers) + }; + + struct Item + { + std::vector texts; + }; + + struct Column + { + float width; + float designWidth; + Text text; + ColumnAlignment alignment; + }; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Default constructor + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ListView(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Creates a new list view widget + /// @return The new list view + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + static ListView::Ptr create(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Makes a copy of another list view + /// + /// @param listView The other list view + /// + /// @return The new list view + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + static ListView::Ptr copy(ListView::ConstPtr listView); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer, which gives access to functions that determine how the widget is displayed + /// @return Temporary pointer to the renderer that may be shared with other widgets using the same renderer + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ListViewRenderer* getSharedRenderer(); + const ListViewRenderer* getSharedRenderer() const; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the renderer, which gives access to functions that determine how the widget is displayed + /// @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. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ListViewRenderer* getRenderer(); + const ListViewRenderer* getRenderer() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the size of the list view + /// + /// @param size The new size of the list view + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSize(const Layout2d& size) override; + using Widget::setSize; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Adds a column + /// + /// @param text The caption of the new column + /// @param width Width of the column. Set width to 0 to make it depend on the width of the column caption. + /// @param alignment The text alignment for all texts in the column + /// + /// @return Index of the added column + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::size_t addColumn(const sf::String& text, float width = 0, ColumnAlignment alignment = ColumnAlignment::Left); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the width of a column + /// + /// @param index Index of the column to change + /// @param width Width of the column. Set width to 0 to make it depend on the width of the column caption. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setColumnWidth(std::size_t index, float width); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Removes all columns + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void removeAllColumns(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the amount of columns in the list view + /// + /// @return Number of columns + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::size_t getColumnCount() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the height of the header row + /// + /// @param height Height of the header or 0 to make the header size depend on the row height + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setHeaderHeight(float height); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the height of the header row + /// + /// @param Current height of the header + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + float getHeaderHeight() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes whether the header is shown + /// + /// @param showHeader Whether the header containing the column names shoud be visible + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setHeaderVisible(bool showHeader); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns whether the header is shown + /// + /// @return Whether the header containing the column names is visible + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool getHeaderVisible() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the text alignment within a column + /// + /// @param columnIndex Index of the column to change + /// @param alignment The text alignment for all texts in the column + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setColumnAlignment(unsigned int columnIndex, ColumnAlignment alignment); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the current text alignment within a column + /// + /// @param columnIndex Index of the column to inspect + /// + /// @return Text alignment for all texts in the column + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ColumnAlignment getColumnAlignment(unsigned int columnIndex) const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Adds an item to the list + /// + /// @param text The caption of the item you want to add + /// + /// @return Index of the item that was just added + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::size_t addItem(const sf::String& text); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Adds an item with values for multiple columns to the list + /// + /// @param item Texts for each column + /// + /// @return Index of the item that was just added + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::size_t addItem(const std::vector& item); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Removes the item from the list view + /// + /// @param index Index of the item in the list view + /// + /// @return True when the item was removed, false when the index was too high + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool removeItem(std::size_t index); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Removes all items from the list + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void removeAllItems(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Selects an item in the list view + /// + /// @param index Index of the item in the list view + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSelectedItem(std::size_t index); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Deselects the selected item + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void deselectItem(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Gets the index of the selected item + /// + /// @return The index of the selected item, or -1 when no item was selected + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + int getSelectedItemIndex() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the amount of items in the list view + /// + /// @return Number of items inside the list view + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::size_t getItemCount() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Retrieves an item in the list + /// + /// @param index The index of the item + /// + /// @return Text of the item or an empty string when the index was higher than the amount of items + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + sf::String getItem(std::size_t index) const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Retrieves the values of all columns for an item in the list + /// + /// @param index The index of the item + /// + /// @return Texts of the item for each column or an list of empty strings when the index was too high + /// + /// The returned list has the same length as the amount of columns. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::vector getItemRow(std::size_t index) const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns a list of the texts in the first column for all items in the list view + /// + /// @return Texts of the first column of items + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::vector getItems() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns a list of all column values for all items in the list view + /// + /// @return Texts of the items and their subitems + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::vector> getItemRows() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the height of the items in the list view + /// + /// @param itemHeight The size of a single item in the list + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setItemHeight(unsigned int itemHeight); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the height of the items in the list view + /// + /// @return The item height + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + unsigned int getItemHeight() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the text size of the items + /// + /// @param textSize The character size of the text + /// + /// This will not change the height that each item has. By default (or when passing 0 to this function) the text will + /// be auto-sized to nicely fit inside this item height. + /// + /// @see setItemHeight + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setTextSize(unsigned int textSize); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the text size of the items + /// + /// @return The text size + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + unsigned int getTextSize() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the text size of the header caption + /// + /// @param textSize The character size of the header text + /// + /// By default, header text size is the same as the text size of the items. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setHeaderTextSize(unsigned int textSize); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the text size of the header caption + /// + /// @return The header text size + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + unsigned int getHeaderTextSize() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes the width of the column separator + /// + /// @param width Width of the line separating the columns + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setSeparatorWidth(unsigned int width); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns the width of the column separator + /// + /// @return Width of the line separating the columns + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + unsigned int getSeparatorWidth() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes whether the list view scrolls to the bottom when a new item is added + /// + /// @param autoScroll Should list view scroll to the bottom when new items are added? + /// + /// Auto scrolling is enabled by default. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setAutoScroll(bool autoScroll); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns whether the list view scrolls to the bottom when a new item is added + /// + /// @return Does the list view scroll to the bottom when new items are added + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool getAutoScroll() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes when the vertical scrollbar should be displayed + /// @param policy The policy for displaying the vertical scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setVerticalScrollbarPolicy(Scrollbar::Policy policy); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns when the vertical scrollbar should be displayed + /// @return The policy for displaying the vertical scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Scrollbar::Policy getVerticalScrollbarPolicy() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Changes when the horizontal scrollbar should be displayed + /// @param policy The policy for displaying the horizontal scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setHorizontalScrollbarPolicy(Scrollbar::Policy policy); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns when the horizontal scrollbar should be displayed + /// @return The policy for displaying the horizontal scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Scrollbar::Policy getHorizontalScrollbarPolicy() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Returns whether the mouse position (which is relative to the parent widget) lies on top of the widget + /// @return Is the mouse on top of the widget? + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool mouseOnWidget(Vector2f pos) const override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @internal + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void leftMousePressed(Vector2f pos) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @internal + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void leftMouseReleased(Vector2f pos) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @internal + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void mouseMoved(Vector2f pos) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @internal + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + bool mouseWheelScrolled(float delta, Vector2f pos) override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @internal + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void mouseNoLongerOnWidget() override; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @internal + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void mouseNoLongerDown() override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Draw the widget to a render target + /// + /// @param target Render target to draw to + /// @param states Current render states + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void draw(sf::RenderTarget& target, sf::RenderStates states) const override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + 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 + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Signal& getSignal(std::string signalName) override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Function called when one of the properties of the renderer is changed + /// + /// @param property Lowercase name of the property that was changed + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void rendererChanged(const std::string& property) override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// @brief Saves the widget as a tree node in order to save it to a file + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + 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; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Returns the size without the borders + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Vector2f getInnerSize() const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Create a Text object from the given caption, using the preset color, font, text size and opacity + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Text createText(const sf::String& caption); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Changes the color of all Text objects in an item + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void setItemColor(std::size_t index, const Color& color); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Calculate the width of the column based on its caption when no column width was provided + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + float calculateAutoColumnWidth(const Text& text); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Update the colors of the selected and hovered items + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateSelectedAndhoveredItemColors(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Update the color of all the items + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateItemColors(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Update on which item the mouse is standing + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateHoveredItem(int item); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Update which item is selected + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateSelectedItem(int item); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Recalculate the size and viewport size of the scrollbars + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateScrollbars(); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Recalculate the maximum value for the vertical scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateVerticalScrollbarMaximum(); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Recalculate the maximum value for the horizontal scrollbar + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void updateHorizontalScrollbarMaximum(); + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Draw the header text for a single column + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void drawHeaderText(sf::RenderTarget& target, sf::RenderStates states, float columnWidth, float headerHeight, std::size_t column) const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Draw the texts in a single column + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void drawColumn(sf::RenderTarget& target, sf::RenderStates states, std::size_t firstItem, std::size_t lastItem, std::size_t column, float columnWidth) const; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // This function is called every frame with the time passed since the last frame. + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void update(sf::Time elapsedTime) override; + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Makes a copy of the widget + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + Widget::Ptr clone() const override + { + return std::make_shared(*this); + } + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + public: + + SignalInt onItemSelect = {"ItemSelected"}; ///< An item was selected in the list view. Optional parameter: selected item index (-1 when deselecting) + SignalInt onDoubleClick = {"DoubleClicked"}; ///< An item was double clicked. Optional parameter: selected item index + + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + protected: + + std::vector m_columns; + std::vector m_items; + + int m_selectedItem = -1; + int m_hoveredItem = -1; + + float m_requestedHeaderHeight = 0; + unsigned int m_itemHeight = 0; + unsigned int m_requestedTextSize = 0; + unsigned int m_textSize = 0; + unsigned int m_headerTextSize = 0; + unsigned int m_separatorWidth = 1; + bool m_headerVisible = true; + + CopiedSharedPtr m_horizontalScrollbar; + CopiedSharedPtr m_verticalScrollbar; + Scrollbar::Policy m_verticalScrollbarPolicy = Scrollbar::Policy::Automatic; + Scrollbar::Policy m_horizontalScrollbarPolicy = Scrollbar::Policy::Automatic; + + bool m_possibleDoubleClick = false; // Will be set to true after the first click, but gets reset to false when the second click does not occur soon after + bool m_autoScroll = true; // Should the list view scroll to the bottom when a new item is added? + + // Cached renderer properties + Borders m_bordersCached; + Borders m_paddingCached; + Color m_borderColorCached; + Color m_separatorColorCached; + Color m_headerTextColorCached; + Color m_headerBackgroundColorCached; + Color m_backgroundColorCached; + Color m_backgroundColorHoverCached; + Color m_selectedBackgroundColorCached; + Color m_selectedBackgroundColorHoverCached; + Color m_textColorCached; + Color m_textColorHoverCached; + Color m_selectedTextColorCached; + Color m_selectedTextColorHoverCached; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + }; + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#endif // TGUI_LIST_VIEW_HPP diff --git a/src/TGUI/CMakeLists.txt b/src/TGUI/CMakeLists.txt index cddbcb9d..c22f3f2c 100644 --- a/src/TGUI/CMakeLists.txt +++ b/src/TGUI/CMakeLists.txt @@ -34,6 +34,7 @@ set(TGUI_SRC Renderers/KnobRenderer.cpp Renderers/LabelRenderer.cpp Renderers/ListBoxRenderer.cpp + Renderers/ListViewRenderer.cpp Renderers/MenuBarRenderer.cpp Renderers/MessageBoxRenderer.cpp Renderers/PanelRenderer.cpp @@ -67,6 +68,7 @@ set(TGUI_SRC Widgets/Knob.cpp Widgets/Label.cpp Widgets/ListBox.cpp + Widgets/ListView.cpp Widgets/MenuBar.cpp Widgets/MessageBox.cpp Widgets/Panel.cpp diff --git a/src/TGUI/Loading/Theme.cpp b/src/TGUI/Loading/Theme.cpp index 380214b0..60707481 100644 --- a/src/TGUI/Loading/Theme.cpp +++ b/src/TGUI/Loading/Theme.cpp @@ -136,6 +136,19 @@ namespace tgui {"backgroundcolorhover", Color::White}, {"selectedbackgroundcolor", Color{0, 110, 255}}, {"selectedbackgroundcolorhover", Color{30, 150, 255}}})}, + {"listview", RendererData::create({{"borders", Borders{1}}, + {"padding", Padding{0}}, + {"bordercolor", Color::Black}, + {"separatorcolor", Color(200, 200, 200)}, + {"headertextcolor", Color::Black}, + {"headerbackgroundcolor", Color(230, 230, 230)}, + {"textcolor", Color{60, 60, 60}}, + {"textcolorhover", Color::Black}, + {"selectedtextcolor", Color::White}, + {"backgroundcolor", Color{245, 245, 245}}, + {"backgroundcolorhover", Color::White}, + {"selectedbackgroundcolor", Color{0, 110, 255}}, + {"selectedbackgroundcolorhover", Color{30, 150, 255}}})}, {"menubar", RendererData::create({{"textcolor", Color{60, 60, 60}}, {"selectedtextcolor", Color::White}, {"backgroundcolor", Color::White}, diff --git a/src/TGUI/Renderers/ListViewRenderer.cpp b/src/TGUI/Renderers/ListViewRenderer.cpp new file mode 100644 index 00000000..f908e7ee --- /dev/null +++ b/src/TGUI/Renderers/ListViewRenderer.cpp @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2018 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 +#include + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace tgui +{ + TGUI_RENDERER_PROPERTY_OUTLINE(ListViewRenderer, Borders) + TGUI_RENDERER_PROPERTY_OUTLINE(ListViewRenderer, Padding) + + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, BackgroundColor, Color::White) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, BackgroundColorHover, {}) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, SelectedBackgroundColor, Color(0, 110, 255)) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, SelectedBackgroundColorHover, {}) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, TextColor, Color::Black) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, TextColorHover, {}) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, SelectedTextColor, Color::White) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, SelectedTextColorHover, {}) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, HeaderBackgroundColor, Color(230, 230, 230)) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, HeaderTextColor, {}) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, BorderColor, Color::Black) + TGUI_RENDERER_PROPERTY_COLOR(ListViewRenderer, SeparatorColor, Color(200, 200, 200)) + + TGUI_RENDERER_PROPERTY_RENDERER(ListViewRenderer, Scrollbar, "scrollbar") + TGUI_RENDERER_PROPERTY_NUMBER(ListViewRenderer, ScrollbarWidth, 0) +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/TGUI/Widgets/ListView.cpp b/src/TGUI/Widgets/ListView.cpp new file mode 100644 index 00000000..bae330a4 --- /dev/null +++ b/src/TGUI/Widgets/ListView.cpp @@ -0,0 +1,1404 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2018 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 +#include + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace tgui +{ + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ListView::ListView() + { + m_type = "ListView"; + m_draggableWidget = true; + + m_horizontalScrollbar->setSize(m_horizontalScrollbar->getSize().y, m_horizontalScrollbar->getSize().x); + + m_renderer = aurora::makeCopied(); + setRenderer(Theme::getDefault()->getRendererNoThrow(m_type)); + + setTextSize(getGlobalTextSize()); + setItemHeight(static_cast(Text::getLineHeight(m_fontCached, m_textSize) * 1.25f)); + setSize({m_itemHeight * 12, + getHeaderHeight() + (m_itemHeight * 6) + m_paddingCached.getTop() + m_paddingCached.getBottom() + m_bordersCached.getTop() + m_bordersCached.getBottom()}); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ListView::Ptr ListView::create() + { + return std::make_shared(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ListView::Ptr ListView::copy(ListView::ConstPtr listView) + { + if (listView) + return std::static_pointer_cast(listView->clone()); + else + return nullptr; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ListViewRenderer* ListView::getSharedRenderer() + { + return aurora::downcast(Widget::getSharedRenderer()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + const ListViewRenderer* ListView::getSharedRenderer() const + { + return aurora::downcast(Widget::getSharedRenderer()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ListViewRenderer* ListView::getRenderer() + { + return aurora::downcast(Widget::getRenderer()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + const ListViewRenderer* ListView::getRenderer() const + { + return aurora::downcast(Widget::getRenderer()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setSize(const Layout2d& size) + { + Widget::setSize(size); + + m_bordersCached.updateParentSize(getSize()); + m_paddingCached.updateParentSize(getSize()); + + m_verticalScrollbar->setPosition(getSize().x - m_bordersCached.getRight() - m_verticalScrollbar->getSize().x, m_bordersCached.getTop()); + m_horizontalScrollbar->setPosition(m_bordersCached.getLeft(), getSize().y - m_bordersCached.getBottom() - m_horizontalScrollbar->getSize().y); + updateScrollbars(); + + setPosition(m_position); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::size_t ListView::addColumn(const sf::String& text, float width, ColumnAlignment alignment) + { + Column column; + column.text = createText(text); + column.text.setCharacterSize(getHeaderTextSize()); + column.alignment = alignment; + column.designWidth = width; + if (width) + column.width = width; + else + column.width = calculateAutoColumnWidth(column.text); + + m_columns.push_back(std::move(column)); + updateHorizontalScrollbarMaximum(); + + return m_columns.size()-1; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setColumnWidth(std::size_t index, float width) + { + if (index >= m_columns.size()) + return; + + m_columns[index].designWidth = width; + if (width) + m_columns[index].width = width; + else + m_columns[index].width = calculateAutoColumnWidth(m_columns[index].text); + + updateHorizontalScrollbarMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::removeAllColumns() + { + m_columns.clear(); + updateHorizontalScrollbarMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::size_t ListView::getColumnCount() const + { + return m_columns.size(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setHeaderHeight(float height) + { + m_requestedHeaderHeight = height; + setSize(getSize()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + float ListView::getHeaderHeight() const + { + if (m_requestedHeaderHeight > 0) + return m_requestedHeaderHeight; + else + return m_itemHeight * 1.25f; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setHeaderVisible(bool showHeader) + { + m_headerVisible = showHeader; + updateHorizontalScrollbarMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool ListView::getHeaderVisible() const + { + return m_headerVisible; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setColumnAlignment(unsigned int columnIndex, ColumnAlignment alignment) + { + if (columnIndex < m_columns.size()) + m_columns[columnIndex].alignment = alignment; + else + { + TGUI_PRINT_WARNING("setColumnAlignment called with invalid columnIndex."); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + ListView::ColumnAlignment ListView::getColumnAlignment(unsigned int columnIndex) const + { + if (columnIndex < m_columns.size()) + return m_columns[columnIndex].alignment; + else + { + TGUI_PRINT_WARNING("getColumnAlignment called with invalid columnIndex."); + return ColumnAlignment::Left; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::size_t ListView::addItem(const sf::String& text) + { + Item item; + item.texts.push_back(createText(text)); + m_items.push_back(std::move(item)); + + updateVerticalScrollbarMaximum(); + + // Scroll down when auto-scrolling is enabled + if (m_autoScroll && (m_verticalScrollbar->getViewportSize() < m_verticalScrollbar->getMaximum())) + m_verticalScrollbar->setValue(m_verticalScrollbar->getMaximum() - m_verticalScrollbar->getViewportSize()); + + return m_items.size()-1; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::size_t ListView::addItem(const std::vector& itemTexts) + { + Item item; + item.texts.reserve(itemTexts.size()); + for (const auto& text : itemTexts) + item.texts.push_back(createText(text)); + + m_items.push_back(std::move(item)); + + updateVerticalScrollbarMaximum(); + + // Scroll down when auto-scrolling is enabled + if (m_autoScroll && (m_verticalScrollbar->getViewportSize() < m_verticalScrollbar->getMaximum())) + m_verticalScrollbar->setValue(m_verticalScrollbar->getMaximum() - m_verticalScrollbar->getViewportSize()); + + return m_items.size()-1; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool ListView::removeItem(std::size_t index) + { + // Update the hovered item + if (m_hoveredItem >= 0) + { + if (m_hoveredItem == static_cast(index)) + updateHoveredItem(-1); + else if (m_selectedItem > static_cast(index)) + { + // Don't call updateSelectedItem here, there should not be no callback and the item hasn't been erased yet so it would point to the wrong place + m_selectedItem = m_selectedItem - 1; + } + } + + // Update the selected item + if (m_selectedItem >= 0) + { + if (m_selectedItem == static_cast(index)) + updateSelectedItem(-1); + else if (m_selectedItem > static_cast(index)) + { + // Don't call updateSelectedItem here, there should not be no callback and the item hasn't been erased yet so it would point to the wrong place + m_selectedItem = m_selectedItem - 1; + } + } + + if (index >= m_items.size()) + return false; + + m_items.erase(m_items.begin() + index); + + updateVerticalScrollbarMaximum(); + return true; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::removeAllItems() + { + updateSelectedItem(-1); + updateHoveredItem(-1); + + m_items.clear(); + + updateVerticalScrollbarMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setSelectedItem(std::size_t index) + { + if (index >= m_items.size()) + { + updateSelectedItem(-1); + return; + } + + updateSelectedItem(index); + + // Move the scrollbar + if (m_selectedItem * getItemHeight() < m_verticalScrollbar->getValue()) + m_verticalScrollbar->setValue(m_selectedItem * getItemHeight()); + else if ((m_selectedItem + 1) * getItemHeight() > m_verticalScrollbar->getValue() + m_verticalScrollbar->getViewportSize()) + m_verticalScrollbar->setValue((m_selectedItem + 1) * getItemHeight() - m_verticalScrollbar->getViewportSize()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::deselectItem() + { + updateSelectedItem(-1); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + int ListView::getSelectedItemIndex() const + { + return m_selectedItem; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::size_t ListView::getItemCount() const + { + return m_items.size(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + sf::String ListView::getItem(std::size_t index) const + { + if (index >= m_items.size()) + return ""; + + if (m_items[index].texts.empty()) + return ""; + + return m_items[index].texts[0].getString(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::vector ListView::getItemRow(std::size_t index) const + { + std::vector row; + if (index < m_items.size()) + { + for (const auto& text : m_items[index].texts) + row.push_back(text.getString()); + } + + row.resize(m_columns.size()); + return row; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::vector ListView::getItems() const + { + std::vector items; + + for (const auto& item : m_items) + { + if (item.texts.empty()) + items.push_back(""); + else + items.push_back(item.texts[0].getString()); + } + + return items; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::vector> ListView::getItemRows() const + { + std::vector> rows; + + for (const auto& item : m_items) + { + std::vector row; + for (const auto& text : item.texts) + row.push_back(text.getString()); + + row.resize(m_columns.size()); + rows.push_back(std::move(row)); + } + + return rows; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setItemHeight(unsigned int itemHeight) + { + // Set the new heights + m_itemHeight = itemHeight; + if (m_requestedTextSize == 0) + { + m_textSize = Text::findBestTextSize(m_fontCached, itemHeight * 0.8f); + for (auto& item : m_items) + { + for (auto& text : item.texts) + text.setCharacterSize(m_textSize); + } + } + + m_verticalScrollbar->setScrollAmount(m_itemHeight); + updateVerticalScrollbarMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + unsigned int ListView::getItemHeight() const + { + return m_itemHeight; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setTextSize(unsigned int textSize) + { + m_requestedTextSize = textSize; + + if (textSize) + m_textSize = textSize; + else + m_textSize = Text::findBestTextSize(m_fontCached, m_itemHeight * 0.8f); + + for (auto& item : m_items) + { + for (auto& text : item.texts) + text.setCharacterSize(m_textSize); + } + + const unsigned int headerTextSize = getHeaderTextSize(); + for (Column& column : m_columns) + column.text.setCharacterSize(headerTextSize); + + m_horizontalScrollbar->setScrollAmount(m_textSize); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + unsigned int ListView::getTextSize() const + { + return m_textSize; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setHeaderTextSize(unsigned int textSize) + { + m_headerTextSize = textSize; + + const unsigned int headerTextSize = getHeaderTextSize(); + for (Column& column : m_columns) + { + column.text.setCharacterSize(headerTextSize); + + if (column.designWidth == 0) + column.width = calculateAutoColumnWidth(column.text); + } + + updateHorizontalScrollbarMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + unsigned int ListView::getHeaderTextSize() const + { + if (m_headerTextSize) + return m_headerTextSize; + else + return m_textSize; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setSeparatorWidth(unsigned int width) + { + m_separatorWidth = width; + updateHorizontalScrollbarMaximum(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + unsigned int ListView::getSeparatorWidth() const + { + return m_separatorWidth; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setAutoScroll(bool autoScroll) + { + m_autoScroll = autoScroll; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool ListView::getAutoScroll() const + { + return m_autoScroll; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setVerticalScrollbarPolicy(Scrollbar::Policy policy) + { + m_verticalScrollbarPolicy = policy; + + if (policy == Scrollbar::Policy::Always) + { + m_verticalScrollbar->setVisible(true); + m_verticalScrollbar->setAutoHide(false); + } + else if (policy == Scrollbar::Policy::Never) + { + m_verticalScrollbar->setVisible(false); + } + else // Scrollbar::Policy::Automatic + { + m_verticalScrollbar->setVisible(true); + m_verticalScrollbar->setAutoHide(true); + } + + updateScrollbars(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Scrollbar::Policy ListView::getVerticalScrollbarPolicy() const + { + return m_verticalScrollbarPolicy; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setHorizontalScrollbarPolicy(Scrollbar::Policy policy) + { + m_horizontalScrollbarPolicy = policy; + + if (policy == Scrollbar::Policy::Always) + { + m_horizontalScrollbar->setVisible(true); + m_horizontalScrollbar->setAutoHide(false); + } + else if (policy == Scrollbar::Policy::Never) + { + m_horizontalScrollbar->setVisible(false); + } + else // Scrollbar::Policy::Automatic + { + m_horizontalScrollbar->setVisible(true); + m_horizontalScrollbar->setAutoHide(true); + } + + updateScrollbars(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Scrollbar::Policy ListView::getHorizontalScrollbarPolicy() const + { + return m_horizontalScrollbarPolicy; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool ListView::mouseOnWidget(Vector2f pos) const + { + return FloatRect{getPosition().x, getPosition().y, getSize().x, getSize().y}.contains(pos); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::leftMousePressed(Vector2f pos) + { + pos -= getPosition(); + + m_mouseDown = true; + + const float headerHeight = (m_headerVisible && !m_columns.empty()) ? getHeaderHeight() : 0.f; + + if (m_verticalScrollbar->mouseOnWidget(pos)) + { + m_verticalScrollbar->leftMousePressed(pos); + } + else if (m_horizontalScrollbar->isShown() && m_horizontalScrollbar->mouseOnWidget(pos)) + { + m_horizontalScrollbar->leftMousePressed(pos); + } + else if (FloatRect{m_bordersCached.getLeft() + m_paddingCached.getLeft(), m_bordersCached.getTop() + m_paddingCached.getTop() + headerHeight, + getInnerSize().x - m_paddingCached.getLeft() - m_paddingCached.getRight(), getInnerSize().y - m_paddingCached.getTop() - m_paddingCached.getBottom()}.contains(pos)) + { + pos.y -= (m_bordersCached.getTop() + m_paddingCached.getTop() + headerHeight); + + int hoveredItem = static_cast(((pos.y - (m_itemHeight - (m_verticalScrollbar->getValue() % m_itemHeight))) / m_itemHeight) + (m_verticalScrollbar->getValue() / m_itemHeight) + 1); + if (hoveredItem < static_cast(m_items.size())) + updateHoveredItem(hoveredItem); + else + updateHoveredItem(-1); + + if (m_selectedItem != m_hoveredItem) + { + m_possibleDoubleClick = false; + updateSelectedItem(m_hoveredItem); + } + + // Check if you double-clicked + if (m_possibleDoubleClick) + { + m_possibleDoubleClick = false; + if (m_selectedItem >= 0) + onDoubleClick.emit(this, m_selectedItem); + } + else // This is the first click + { + m_animationTimeElapsed = {}; + m_possibleDoubleClick = true; + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::leftMouseReleased(Vector2f pos) + { + if (m_verticalScrollbar->isShown() && m_verticalScrollbar->isMouseDown()) + m_verticalScrollbar->leftMouseReleased(pos - getPosition()); + + if (m_horizontalScrollbar->isShown() && m_horizontalScrollbar->isMouseDown()) + m_horizontalScrollbar->leftMouseReleased(pos - getPosition()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::mouseMoved(Vector2f pos) + { + pos -= getPosition(); + + if (!m_mouseHover) + mouseEnteredWidget(); + + updateHoveredItem(-1); + + // Check if the mouse event should go to the scrollbar + if ((m_verticalScrollbar->isMouseDown() && m_verticalScrollbar->isMouseDownOnThumb()) || m_verticalScrollbar->mouseOnWidget(pos)) + { + m_verticalScrollbar->mouseMoved(pos); + } + else if ((m_horizontalScrollbar->isMouseDown() && m_horizontalScrollbar->isMouseDownOnThumb()) || m_horizontalScrollbar->mouseOnWidget(pos)) + { + m_horizontalScrollbar->mouseMoved(pos); + } + else // Mouse not on scrollbar or dragging the scrollbar thumb + { + m_verticalScrollbar->mouseNoLongerOnWidget(); + m_horizontalScrollbar->mouseNoLongerOnWidget(); + + // Find out on which item the mouse is hovered + const float headerHeight = (m_headerVisible && !m_columns.empty()) ? getHeaderHeight() : 0.f; + if (FloatRect{m_bordersCached.getLeft() + m_paddingCached.getLeft(), + m_bordersCached.getTop() + m_paddingCached.getTop() + headerHeight, + getInnerSize().x - m_paddingCached.getLeft() - m_paddingCached.getRight(), + getInnerSize().y - m_paddingCached.getTop() - m_paddingCached.getBottom()}.contains(pos)) + { + pos.y -= (m_bordersCached.getTop() + m_paddingCached.getTop() + headerHeight); + + int hoveredItem = static_cast(((pos.y - (m_itemHeight - (m_verticalScrollbar->getValue() % m_itemHeight))) / m_itemHeight) + (m_verticalScrollbar->getValue() / m_itemHeight) + 1); + if (hoveredItem < static_cast(m_items.size())) + updateHoveredItem(hoveredItem); + else + updateHoveredItem(-1); + + // If the mouse is held down then select the item below the mouse + if (m_mouseDown && !m_verticalScrollbar->isMouseDown()) + { + if (m_selectedItem != m_hoveredItem) + { + m_possibleDoubleClick = false; + + updateSelectedItem(m_hoveredItem); + } + } + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + bool ListView::mouseWheelScrolled(float delta, Vector2f pos) + { + if (m_horizontalScrollbar->isShown() + && (!m_verticalScrollbar->isShown() + || m_horizontalScrollbar->mouseOnWidget(pos - getPosition()) + || sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) + || sf::Keyboard::isKeyPressed(sf::Keyboard::RShift))) + { + m_horizontalScrollbar->mouseWheelScrolled(delta, pos - getPosition()); + mouseMoved(pos); // Update on which item the mouse is hovered + return true; + } + else if (m_verticalScrollbar->isShown()) + { + m_verticalScrollbar->mouseWheelScrolled(delta, pos - getPosition()); + mouseMoved(pos); // Update on which item the mouse is hovered + return true; + } + + return false; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::mouseNoLongerOnWidget() + { + Widget::mouseNoLongerOnWidget(); + m_verticalScrollbar->mouseNoLongerOnWidget(); + m_horizontalScrollbar->mouseNoLongerOnWidget(); + + updateHoveredItem(-1); + + m_possibleDoubleClick = false; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::mouseNoLongerDown() + { + Widget::mouseNoLongerDown(); + m_verticalScrollbar->mouseNoLongerDown(); + m_horizontalScrollbar->mouseNoLongerDown(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Signal& ListView::getSignal(std::string signalName) + { + if (signalName == toLower(onItemSelect.getName())) + return onItemSelect; + else if (signalName == toLower(onDoubleClick.getName())) + return onDoubleClick; + else + return Widget::getSignal(std::move(signalName)); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::rendererChanged(const std::string& property) + { + if (property == "borders") + { + m_bordersCached = getSharedRenderer()->getBorders(); + setSize(m_size); + } + else if (property == "padding") + { + m_paddingCached = getSharedRenderer()->getPadding(); + setSize(m_size); + } + else if (property == "textcolor") + { + m_textColorCached = getSharedRenderer()->getTextColor(); + updateItemColors(); + } + else if (property == "textcolorhover") + { + m_textColorHoverCached = getSharedRenderer()->getTextColorHover(); + updateItemColors(); + } + else if (property == "selectedtextcolor") + { + m_selectedTextColorCached = getSharedRenderer()->getSelectedTextColor(); + updateItemColors(); + } + else if (property == "selectedtextcolorhover") + { + m_selectedTextColorHoverCached = getSharedRenderer()->getSelectedTextColorHover(); + updateItemColors(); + } + else if (property == "scrollbar") + { + m_verticalScrollbar->setRenderer(getSharedRenderer()->getScrollbar()); + + // If no scrollbar width was set then we may need to use the one from the texture + if (!getSharedRenderer()->getScrollbarWidth()) + { + m_verticalScrollbar->setSize({m_verticalScrollbar->getDefaultWidth(), m_verticalScrollbar->getSize().y}); + setSize(m_size); + } + } + else if (property == "scrollbarwidth") + { + const float width = getSharedRenderer()->getScrollbarWidth() ? getSharedRenderer()->getScrollbarWidth() : m_verticalScrollbar->getDefaultWidth(); + m_verticalScrollbar->setSize({width, m_verticalScrollbar->getSize().y}); + setSize(m_size); + } + else if (property == "bordercolor") + { + m_borderColorCached = getSharedRenderer()->getBorderColor(); + } + else if (property == "separatorcolor") + { + m_separatorColorCached = getSharedRenderer()->getSeparatorColor(); + } + else if (property == "headertextcolor") + { + m_headerTextColorCached = getSharedRenderer()->getHeaderTextColor(); + } + else if (property == "headerbackgroundcolor") + { + m_headerBackgroundColorCached = getSharedRenderer()->getHeaderBackgroundColor(); + } + else if (property == "backgroundcolor") + { + m_backgroundColorCached = getSharedRenderer()->getBackgroundColor(); + } + else if (property == "backgroundcolorhover") + { + m_backgroundColorHoverCached = getSharedRenderer()->getBackgroundColorHover(); + } + else if (property == "selectedbackgroundcolor") + { + m_selectedBackgroundColorCached = getSharedRenderer()->getSelectedBackgroundColor(); + } + else if (property == "selectedbackgroundcolorhover") + { + m_selectedBackgroundColorHoverCached = getSharedRenderer()->getSelectedBackgroundColorHover(); + } + else if ((property == "opacity") || (property == "opacitydisabled")) + { + Widget::rendererChanged(property); + + m_verticalScrollbar->setInheritedOpacity(m_opacityCached); + m_horizontalScrollbar->setInheritedOpacity(m_opacityCached); + + for (auto& column : m_columns) + column.text.setOpacity(m_opacityCached); + + for (auto& item : m_items) + { + for (auto& text : item.texts) + text.setOpacity(m_opacityCached); + } + } + else if (property == "font") + { + Widget::rendererChanged(property); + + for (auto& column : m_columns) + column.text.setFont(m_fontCached); + + for (auto& item : m_items) + { + for (auto& text : item.texts) + text.setFont(m_fontCached); + } + + // Recalculate the text size with the new font + if (m_requestedTextSize == 0) + { + m_textSize = Text::findBestTextSize(m_fontCached, m_itemHeight * 0.8f); + for (auto& item : m_items) + { + for (auto& text : item.texts) + text.setCharacterSize(m_textSize); + } + + if (!m_headerTextSize) + { + for (auto& column : m_columns) + column.text.setCharacterSize(m_textSize); + } + } + + // Recalculate the width of the columns if they depended on the header text + for (auto& column : m_columns) + { + if (column.designWidth == 0) + column.width = calculateAutoColumnWidth(column.text); + } + updateHorizontalScrollbarMaximum(); + } + else + Widget::rendererChanged(property); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + std::unique_ptr ListView::save(SavingRenderersMap& renderers) const + { + auto node = Widget::save(renderers); + + for (const auto& column : m_columns) + { + auto columnNode = std::make_unique(); + columnNode->name = "Column"; + + columnNode->propertyValuePairs["Text"] = std::make_unique(Serializer::serialize(column.text.getString())); + if (column.designWidth) + columnNode->propertyValuePairs["Width"] = std::make_unique(to_string(column.designWidth)); + + if (column.alignment == ColumnAlignment::Center) + columnNode->propertyValuePairs["Alignment"] = std::make_unique("Center"); + else if (column.alignment == ColumnAlignment::Right) + columnNode->propertyValuePairs["Alignment"] = std::make_unique("Right"); + } + + for (const auto& item : m_items) + { + auto itemNode = std::make_unique(); + itemNode->name = "Item"; + + if (!item.texts.empty()) + { + std::string textsList = "[" + Serializer::serialize(item.texts[0].getString()); + for (std::size_t i = 1; i < item.texts.size(); ++i) + textsList += ", " + Serializer::serialize(item.texts[i].getString()); + textsList += "]"; + + node->propertyValuePairs["Texts"] = std::make_unique(textsList); + } + } + + if (!m_autoScroll) + node->propertyValuePairs["AutoScroll"] = std::make_unique("false"); + + if (m_headerTextSize) + node->propertyValuePairs["HeaderTextSize"] = std::make_unique(to_string(m_headerTextSize)); + + if (m_selectedItem >= 0) + node->propertyValuePairs["SelectedItemIndex"] = std::make_unique(to_string(m_selectedItem)); + + node->propertyValuePairs["HeaderVisible"] = std::make_unique(Serializer::serialize(m_headerVisible)); + node->propertyValuePairs["HeaderHeight"] = std::make_unique(to_string(m_requestedHeaderHeight)); + node->propertyValuePairs["SeparatorWidth"] = std::make_unique(to_string(m_separatorWidth)); + node->propertyValuePairs["TextSize"] = std::make_unique(to_string(m_textSize)); + node->propertyValuePairs["ItemHeight"] = std::make_unique(to_string(m_itemHeight)); + return node; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::load(const std::unique_ptr& node, const LoadingRenderersMap& renderers) + { + Widget::load(node, renderers); + + for (const auto& childNode : node->children) + { + if (toLower(childNode->name) != "column") + continue; + + sf::String text; + float width = 0; + ColumnAlignment alignment = ColumnAlignment::Left; + + if (childNode->propertyValuePairs["text"]) + text = Deserializer::deserialize(ObjectConverter::Type::String, childNode->propertyValuePairs["text"]->value).getString(); + if (childNode->propertyValuePairs["width"]) + width = static_cast(Deserializer::deserialize(ObjectConverter::Type::Number, childNode->propertyValuePairs["width"]->value).getNumber()); + + if (childNode->propertyValuePairs["alignment"]) + { + std::string alignmentString = toLower(Deserializer::deserialize(ObjectConverter::Type::String, childNode->propertyValuePairs["alignment"]->value).getString()); + if (alignmentString == "right") + alignment = ColumnAlignment::Right; + else if (alignmentString == "center") + alignment = ColumnAlignment::Center; + else if (alignmentString != "left") + throw Exception{"Failed to parse Alignment property, found unknown value."}; + } + + addColumn(text, width, alignment); + } + + for (const auto& childNode : node->children) + { + if (toLower(childNode->name) != "item") + continue; + + if (!node->propertyValuePairs["items"]) + throw Exception{"Failed to parse 'Item' property, no Texts property found"}; + if (!node->propertyValuePairs["texts"]->listNode) + throw Exception{"Failed to parse 'Texts' property inside the 'Item' property, expected a list as value"}; + + for (const auto& item : childNode->propertyValuePairs["texts"]->valueList) + addItem(Deserializer::deserialize(ObjectConverter::Type::String, item).getString()); + } + + if (node->propertyValuePairs["autoscroll"]) + setAutoScroll(Deserializer::deserialize(ObjectConverter::Type::Bool, node->propertyValuePairs["autoscroll"]->value).getBool()); + if (node->propertyValuePairs["headervisible"]) + setHeaderVisible(Deserializer::deserialize(ObjectConverter::Type::Bool, node->propertyValuePairs["headervisible"]->value).getBool()); + if (node->propertyValuePairs["headerheight"]) + setHeaderHeight(tgui::stoi(node->propertyValuePairs["headerheight"]->value)); + if (node->propertyValuePairs["separatorwidth"]) + setSeparatorWidth(tgui::stoi(node->propertyValuePairs["separatorwidth"]->value)); + if (node->propertyValuePairs["textsize"]) + setTextSize(tgui::stoi(node->propertyValuePairs["textsize"]->value)); + if (node->propertyValuePairs["itemheight"]) + setItemHeight(tgui::stoi(node->propertyValuePairs["itemheight"]->value)); + if (node->propertyValuePairs["selecteditemindex"]) + setSelectedItem(tgui::stoi(node->propertyValuePairs["selecteditemindex"]->value)); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Vector2f ListView::getInnerSize() const + { + return {getSize().x - m_bordersCached.getLeft() - m_bordersCached.getRight(), getSize().y - m_bordersCached.getTop() - m_bordersCached.getBottom()}; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + Text ListView::createText(const sf::String& caption) + { + Text text; + text.setFont(m_fontCached); + text.setColor(m_textColorCached); + text.setOpacity(m_opacityCached); + text.setCharacterSize(m_textSize); + text.setString(caption); + return text; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::setItemColor(std::size_t index, const Color& color) + { + for (auto& text : m_items[index].texts) + text.setColor(color); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + float ListView::calculateAutoColumnWidth(const Text& text) + { + return text.getSize().x + (2.f * text.getExtraHorizontalOffset()); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::updateSelectedAndhoveredItemColors() + { + if (m_selectedItem >= 0) + { + if ((m_selectedItem == m_hoveredItem) && m_selectedTextColorHoverCached.isSet()) + setItemColor(m_selectedItem, m_selectedTextColorHoverCached); + else if (m_selectedTextColorCached.isSet()) + setItemColor(m_selectedItem, m_selectedTextColorCached); + } + + if ((m_hoveredItem >= 0) && (m_selectedItem != m_hoveredItem)) + { + if (m_textColorHoverCached.isSet()) + setItemColor(m_hoveredItem, m_textColorHoverCached); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::updateItemColors() + { + for (std::size_t i = 0; i < m_items.size(); ++i) + setItemColor(i, m_textColorCached); + + updateSelectedAndhoveredItemColors(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::updateHoveredItem(int item) + { + if (m_hoveredItem == item) + return; + + if (m_hoveredItem >= 0) + { + if ((m_selectedItem == m_hoveredItem) && m_selectedTextColorCached.isSet()) + setItemColor(m_hoveredItem, m_selectedTextColorCached); + else + setItemColor(m_hoveredItem, m_textColorCached); + } + + m_hoveredItem = item; + + updateSelectedAndhoveredItemColors(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::updateSelectedItem(int item) + { + if (m_selectedItem == item) + return; + + if (m_selectedItem >= 0) + { + if ((m_selectedItem == m_hoveredItem) && m_textColorHoverCached.isSet()) + setItemColor(m_selectedItem, m_textColorHoverCached); + else + setItemColor(m_selectedItem, m_textColorCached); + } + + m_selectedItem = item; + onItemSelect.emit(this, m_selectedItem); + + updateSelectedAndhoveredItemColors(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::updateScrollbars() + { + const float headerHeight = (m_headerVisible && !m_columns.empty()) ? getHeaderHeight() : 0.f; + const Vector2f innerSize = {std::max(0.f, getInnerSize().x - m_paddingCached.getLeft() - m_paddingCached.getRight()), + std::max(0.f, getInnerSize().y - m_paddingCached.getTop() - m_paddingCached.getBottom() - headerHeight)}; + + if (m_verticalScrollbarPolicy != Scrollbar::Policy::Never) + { + if (m_horizontalScrollbar->isShown()) + { + m_verticalScrollbar->setSize({m_verticalScrollbar->getSize().x, std::max(0.f, getInnerSize().y) - m_horizontalScrollbar->getSize().y}); + m_verticalScrollbar->setViewportSize(static_cast(innerSize.y - m_horizontalScrollbar->getSize().y)); + } + else + { + m_verticalScrollbar->setSize({m_verticalScrollbar->getSize().x, std::max(0.f, getInnerSize().y)}); + m_verticalScrollbar->setViewportSize(static_cast(innerSize.y)); + } + } + + if (m_horizontalScrollbarPolicy != Scrollbar::Policy::Never) + { + if (m_verticalScrollbar->isShown()) + { + m_horizontalScrollbar->setSize({getInnerSize().x - m_verticalScrollbar->getSize().x, m_horizontalScrollbar->getSize().y}); + m_horizontalScrollbar->setViewportSize(static_cast(innerSize.x - m_verticalScrollbar->getSize().x)); + } + else + { + m_horizontalScrollbar->setSize({getInnerSize().x, m_horizontalScrollbar->getSize().y}); + m_horizontalScrollbar->setViewportSize(static_cast(innerSize.x)); + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::updateVerticalScrollbarMaximum() + { + m_verticalScrollbar->setMaximum(static_cast(m_items.size() * m_itemHeight)); + updateScrollbars(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::updateHorizontalScrollbarMaximum() + { + if (!m_headerVisible || m_columns.empty()) + m_horizontalScrollbar->setMaximum(0u); + else if (m_columns.size() == 1) + m_horizontalScrollbar->setMaximum(static_cast(m_columns[0].width)); + else + { + float width = 0; + for (const auto& column : m_columns) + width += column.width + m_separatorWidth; + + m_horizontalScrollbar->setMaximum(static_cast(width)); + } + + updateScrollbars(); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::drawHeaderText(sf::RenderTarget& target, sf::RenderStates states, float columnWidth, float headerHeight, std::size_t column) const + { + if (column >= m_columns.size()) + return; + + const unsigned int headerTextSize = getHeaderTextSize(); + const float textPadding = Text::getExtraHorizontalOffset(m_fontCached, headerTextSize); + + const Clipping clipping{target, states, {textPadding, 0}, {columnWidth - (2 * textPadding), headerHeight}}; + + float translateX; + if ((m_columns[column].alignment == ColumnAlignment::Left) || (column >= m_columns.size())) + translateX = textPadding; + else if (m_columns[column].alignment == ColumnAlignment::Center) + translateX = (columnWidth - m_columns[column].text.getSize().x) / 2.f; + else // if (m_columns[column].alignment == ColumnAlignment::Right) + translateX = columnWidth - textPadding - m_columns[column].text.getSize().x; + + states.transform.translate({translateX, (headerHeight - Text::getLineHeight(m_fontCached, headerTextSize)) / 2.0f}); + m_columns[column].text.draw(target, states); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::drawColumn(sf::RenderTarget& target, sf::RenderStates states, std::size_t firstItem, std::size_t lastItem, std::size_t column, float columnWidth) const + { + if (firstItem == lastItem) + return; + + const float headerHeight = (m_headerVisible && !m_columns.empty()) ? getHeaderHeight() : 0.f; + const float textPadding = Text::getExtraHorizontalOffset(m_fontCached, m_textSize); + const float columnHeight = getInnerSize().y - m_paddingCached.getTop() - m_paddingCached.getBottom() + - headerHeight - (m_horizontalScrollbar->isShown() ? m_horizontalScrollbar->getSize().y : 0); + const Clipping clipping{target, states, {textPadding, 0}, {columnWidth - (2 * textPadding), columnHeight}}; + + states.transform.translate({0, -static_cast(m_verticalScrollbar->getValue())}); + states.transform.translate({0, (m_itemHeight * firstItem) + (m_itemHeight - Text::getLineHeight(m_fontCached, m_textSize)) / 2.0f}); + + for (std::size_t i = firstItem; i < lastItem; ++i) + { + if (column >= m_items[i].texts.size()) + { + states.transform.translate({0, static_cast(m_itemHeight)}); + continue; + } + + float translateX; + if ((m_columns[column].alignment == ColumnAlignment::Left) || (column >= m_columns.size())) + translateX = textPadding; + else if (m_columns[column].alignment == ColumnAlignment::Center) + translateX = (columnWidth - m_items[i].texts[column].getSize().x) / 2.f; + else // if (m_columns[column].alignment == ColumnAlignment::Right) + translateX = columnWidth - textPadding - m_items[i].texts[column].getSize().x; + + states.transform.translate({translateX, 0}); + m_items[i].texts[column].draw(target, states); + states.transform.translate({-translateX, static_cast(m_itemHeight)}); + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::update(sf::Time elapsedTime) + { + Widget::update(elapsedTime); + + if (m_animationTimeElapsed >= sf::milliseconds(getDoubleClickTime())) + { + m_animationTimeElapsed = {}; + m_possibleDoubleClick = false; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + void ListView::draw(sf::RenderTarget& target, sf::RenderStates states) const + { + states.transform.translate(getPosition()); + const sf::RenderStates statesForScrollbar = states; + + // Draw the borders + if (m_bordersCached != Borders{0}) + { + drawBorders(target, states, m_bordersCached, getSize(), m_borderColorCached); + states.transform.translate(m_bordersCached.getOffset()); + } + + // Draw the background + drawRectangleShape(target, states, getInnerSize(), m_backgroundColorCached); + + float availableWidth = getInnerSize().x - m_paddingCached.getLeft() - m_paddingCached.getRight(); + if (m_verticalScrollbar->isShown()) + availableWidth -= m_verticalScrollbar->getSize().x; + + // Find out which items are visible + std::size_t firstItem = 0; + std::size_t lastItem = m_items.size(); + if (m_verticalScrollbar->getViewportSize() < m_verticalScrollbar->getMaximum()) + { + firstItem = m_verticalScrollbar->getValue() / m_itemHeight; + lastItem = (m_verticalScrollbar->getValue() + m_verticalScrollbar->getViewportSize()) / m_itemHeight; + + // Show another item when the scrollbar is standing between two items + if ((m_verticalScrollbar->getValue() + m_verticalScrollbar->getViewportSize()) % m_itemHeight != 0) + ++lastItem; + } + + states.transform.translate({m_paddingCached.getLeft(), m_paddingCached.getTop()}); + + // Draw the scrollbars + if (m_verticalScrollbar->isVisible()) + m_verticalScrollbar->draw(target, statesForScrollbar); + if (m_horizontalScrollbar->isVisible()) + m_horizontalScrollbar->draw(target, statesForScrollbar); + + const float headerHeight = (m_headerVisible && !m_columns.empty()) ? getHeaderHeight() : 0.f; + const float innerHeight = getInnerSize().y - m_paddingCached.getTop() - m_paddingCached.getBottom() + - (m_horizontalScrollbar->isShown() ? m_horizontalScrollbar->getSize().y : 0); + + // Draw the background of the selected and hovered items + if ((m_selectedItem >= 0) || (m_hoveredItem >= 0)) + { + states.transform.translate({0, headerHeight}); + const Clipping clipping{target, states, {}, {availableWidth, innerHeight - headerHeight}}; + + // Draw the background of the selected item + if (m_selectedItem >= 0) + { + states.transform.translate({0, m_selectedItem * static_cast(m_itemHeight) - m_verticalScrollbar->getValue()}); + + if ((m_selectedItem == m_hoveredItem) && m_selectedBackgroundColorHoverCached.isSet()) + drawRectangleShape(target, states, {availableWidth, static_cast(m_itemHeight)}, m_selectedBackgroundColorHoverCached); + else + drawRectangleShape(target, states, {availableWidth, static_cast(m_itemHeight)}, m_selectedBackgroundColorCached); + + states.transform.translate({0, -m_selectedItem * static_cast(m_itemHeight) + m_verticalScrollbar->getValue()}); + } + + // Draw the background of the item on which the mouse is standing + if ((m_hoveredItem >= 0) && (m_hoveredItem != m_selectedItem) && m_backgroundColorHoverCached.isSet()) + { + states.transform.translate({0, m_hoveredItem * static_cast(m_itemHeight) - m_verticalScrollbar->getValue()}); + drawRectangleShape(target, states, {availableWidth, static_cast(m_itemHeight)}, m_backgroundColorHoverCached); + states.transform.translate({0, -m_hoveredItem * static_cast(m_itemHeight) + m_verticalScrollbar->getValue()}); + } + + states.transform.translate({0, -headerHeight}); + } + + // Draw the header background + if (m_headerVisible && !m_columns.empty()) + { + if (m_headerBackgroundColorCached.isSet()) + drawRectangleShape(target, states, {availableWidth, headerHeight}, m_headerBackgroundColorCached); + } + + const Clipping clipping{target, states, {}, {availableWidth, innerHeight}}; + if (m_horizontalScrollbar->isShown()) + states.transform.translate({-static_cast(m_horizontalScrollbar->getValue()), 0}); + + // Draw the header texts + if (m_headerVisible && !m_columns.empty()) + { + if (m_columns.empty()) + drawHeaderText(target, states, getInnerSize().x - m_paddingCached.getLeft() - m_paddingCached.getRight(), headerHeight, 0); + else + { + sf::RenderStates headerStates = states; + + for (std::size_t col = 0; col < m_columns.size(); ++col) + { + drawHeaderText(target, headerStates, m_columns[col].width, headerHeight, col); + headerStates.transform.translate({m_columns[col].width + m_separatorWidth, 0}); + } + } + + states.transform.translate({0, headerHeight}); + } + + // Draw the items and the separation lines + if (m_columns.empty()) + drawColumn(target, states, firstItem, lastItem, 0, getInnerSize().x - m_paddingCached.getLeft() - m_paddingCached.getRight()); + else + { + for (std::size_t col = 0; col < m_columns.size(); ++col) + { + drawColumn(target, states, firstItem, lastItem, col, m_columns[col].width); + states.transform.translate({m_columns[col].width, 0}); + + if (m_separatorWidth) + { + if (headerHeight) + states.transform.translate({0, -headerHeight}); + + const Color& separatorColor = m_separatorColorCached.isSet() ? m_separatorColorCached : m_borderColorCached; + drawRectangleShape(target, states, {static_cast(m_separatorWidth), innerHeight}, separatorColor); + states.transform.translate({static_cast(m_separatorWidth), headerHeight}); + } + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/TGUI/Widgets/TextBox.cpp b/src/TGUI/Widgets/TextBox.cpp index 46a6b232..9432a402 100644 --- a/src/TGUI/Widgets/TextBox.cpp +++ b/src/TGUI/Widgets/TextBox.cpp @@ -520,10 +520,7 @@ namespace tgui if (m_horizontalScrollbar->isShown()) { if (m_horizontalScrollbar->isMouseDown()) - { m_horizontalScrollbar->leftMouseReleased(pos - getPosition()); - recalculateVisibleLines(); - } } } @@ -550,7 +547,6 @@ namespace tgui else if (m_horizontalScrollbar->isShown() && ((m_horizontalScrollbar->isMouseDown() && m_horizontalScrollbar->isMouseDownOnThumb()) || m_horizontalScrollbar->mouseOnWidget(pos))) { m_horizontalScrollbar->mouseMoved(pos); - recalculateVisibleLines(); } // If the mouse is held down then you are selecting text diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c172c604..b0c041c6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,7 @@ set(TEST_SOURCES Widgets/Knob.cpp Widgets/Label.cpp Widgets/ListBox.cpp + Widgets/ListView.cpp Widgets/MenuBar.cpp Widgets/MessageBox.cpp Widgets/Panel.cpp diff --git a/tests/Widgets/ListView.cpp b/tests/Widgets/ListView.cpp new file mode 100644 index 00000000..e53a2f97 --- /dev/null +++ b/tests/Widgets/ListView.cpp @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// TGUI - Texus' Graphical User Interface +// Copyright (C) 2012-2018 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" +#include + +TEST_CASE("[ListView]") +{ + tgui::ListView::Ptr listView = tgui::ListView::create(); + listView->getRenderer()->setFont("resources/DejaVuSans.ttf"); + + SECTION("Signals") + { + REQUIRE_NOTHROW(listView->connect("ItemSelected", [](){})); + REQUIRE_NOTHROW(listView->connect("ItemSelected", [](int){})); + REQUIRE_NOTHROW(listView->connect("ItemSelected", [](tgui::Widget::Ptr, std::string){})); + REQUIRE_NOTHROW(listView->connect("ItemSelected", [](tgui::Widget::Ptr, std::string, int){})); + + REQUIRE_NOTHROW(listView->connect("DoubleClicked", [](){})); + REQUIRE_NOTHROW(listView->connect("DoubleClicked", [](int){})); + REQUIRE_NOTHROW(listView->connect("DoubleClicked", [](tgui::Widget::Ptr, std::string){})); + REQUIRE_NOTHROW(listView->connect("DoubleClicked", [](tgui::Widget::Ptr, std::string, int){})); + } + + SECTION("WidgetType") + { + REQUIRE(listView->getWidgetType() == "ListView"); + } + + SECTION("Position and Size") + { + listView->setPosition(40, 30); + listView->setSize(150, 100); + listView->getRenderer()->setBorders(2); + + REQUIRE(listView->getPosition() == sf::Vector2f(40, 30)); + REQUIRE(listView->getSize() == sf::Vector2f(150, 100)); + REQUIRE(listView->getFullSize() == listView->getSize()); + REQUIRE(listView->getWidgetOffset() == sf::Vector2f(0, 0)); + } + + // TODO +} diff --git a/themes/Black.txt b/themes/Black.txt index dd4071f7..fa92b5b2 100644 --- a/themes/Black.txt +++ b/themes/Black.txt @@ -89,6 +89,18 @@ ListBox { Scrollbar = &Scrollbar; } +ListView { + TextColor = rgb(190, 190, 190); + TextColorHover = rgb(250, 250, 250); + BackgroundColor = rgb( 80, 80, 80); + BackgroundColorHover = rgb(100, 100, 100); + SelectedBackgroundColor = rgb( 10, 110, 255); + SelectedBackgroundColorHover = rgb(30, 150, 255); + SelectedTextColor = White; + Padding = (3, 3, 3, 3); + Scrollbar = &Scrollbar; +} + MenuBar { TextureBackground = "Black.png" Part(115, 179, 8, 6) Middle(2, 2, 4, 2); TextureItemBackground = "Black.png" Part(115, 181, 8, 4) Middle(2, 0, 4, 2);