From 372d8cb49d5522a1e35d50c26f49bc12aec83393 Mon Sep 17 00:00:00 2001 From: Zax Zaxu Date: Wed, 25 Nov 2015 22:53:05 +0100 Subject: [PATCH 1/2] ListBox widget added. --- CMakeLists.txt | 2 + examples/CMakeLists.txt | 1 + examples/ListBox.cpp | 91 ++++++++++++++ include/SFGUI/ListBox.hpp | 60 ++++++++++ include/SFGUI/Widgets.hpp | 1 + src/SFGUI/ListBox.cpp | 243 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 398 insertions(+) create mode 100644 examples/ListBox.cpp create mode 100644 include/SFGUI/ListBox.hpp create mode 100644 src/SFGUI/ListBox.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8936030c..3d1fdf28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ set( "${INCLUDE_PATH}/SFGUI/Frame.hpp" "${INCLUDE_PATH}/SFGUI/Image.hpp" "${INCLUDE_PATH}/SFGUI/Label.hpp" + "${INCLUDE_PATH}/SFGUI/ListBox.hpp" "${INCLUDE_PATH}/SFGUI/Misc.hpp" "${INCLUDE_PATH}/SFGUI/Notebook.hpp" "${INCLUDE_PATH}/SFGUI/Object.hpp" @@ -135,6 +136,7 @@ set( "${SOURCE_PATH}/SFGUI/GLLoader.hpp" "${SOURCE_PATH}/SFGUI/Image.cpp" "${SOURCE_PATH}/SFGUI/Label.cpp" + "${SOURCE_PATH}/SFGUI/ListBox.cpp" "${SOURCE_PATH}/SFGUI/Misc.cpp" "${SOURCE_PATH}/SFGUI/Notebook.cpp" "${SOURCE_PATH}/SFGUI/Object.cpp" diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0a81d307..e8f0d547 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -38,6 +38,7 @@ build_example( "ProgressBar" "ProgressBar.cpp" ) build_example( "SpinButton" "SpinButton.cpp" ) build_example( "Canvas" "Canvas.cpp" ) build_example( "CustomWidget" "CustomWidget.cpp" ) +build_example( "ListBox" "ListBox.cpp" ) build_example( "SFGUI-Test" "Test.cpp" ) # Copy data directory to build cache directory to be able to run examples from diff --git a/examples/ListBox.cpp b/examples/ListBox.cpp new file mode 100644 index 00000000..aec1de5f --- /dev/null +++ b/examples/ListBox.cpp @@ -0,0 +1,91 @@ +// Always include the necessary header files. +// Including SFGUI/Widgets.hpp includes everything +// you can possibly need automatically. +#include +#include + +#include + +int main() { + sfg::SFGUI sfgui; + sf::RenderWindow window(sf::VideoMode(640, 480), "ListBox Example"); + window.setVerticalSyncEnabled(true); + window.setFramerateLimit(30); + + sfg::Desktop desktop; + + auto sfg_window = sfg::Window::Create(); + sfg_window->SetTitle( "ListBoxExample" ); + + auto box_outer = sfg::Box::Create( sfg::Box::Orientation::HORIZONTAL, 15.0f ); + auto box_inner1 = sfg::Box::Create( sfg::Box::Orientation::VERTICAL, 5.0f ); + auto box_inner2 = sfg::Box::Create( sfg::Box::Orientation::VERTICAL, 5.0f ); + + auto label1 = sfg::Label::Create("I'm single-select list."); + auto label2 = sfg::Label::Create("I'm multi-select list."); + + auto input1 = sfg::Entry::Create(""); + auto input2 = sfg::Entry::Create(""); + + auto list1 = sfg::ListBox::Create(); + list1->AppendItem( "Item1" ); + list1->AppendItem( "Item2" ); + list1->AppendItem( "Item3" ); + list1->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( [list1, input1](){ if(list1->GetSelectedItemsCount()) input1->SetText(list1->GetSelectedItemText()); else input1->SetText(""); } ) ); + + auto list2 = sfg::ListBox::Create(); + list2->AppendItem( "Item1" ); + list2->AppendItem( "Item2" ); + list2->AppendItem( "Item3" ); + list2->multiselect = true; + list2->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( [list2, input2](){ + std::string str = ""; + for(unsigned i=0; iGetSelectedItemsCount(); i++) + { + str += list2->GetSelectedItemText(i); + str += ' '; + } + input2->SetText(str); + } ) ); + + box_outer->Pack(box_inner1); + box_outer->Pack(box_inner2); + + box_inner1->Pack(label1); + box_inner1->Pack(list1); + box_inner1->Pack(input1); + + box_inner2->Pack(label2); + box_inner2->Pack(list2); + box_inner2->Pack(input2); + + sfg_window->Add( box_outer ); + desktop.Add( sfg_window ); + + sfg_window->SetPosition(sf::Vector2f(window.getSize().x/2-sfg_window->GetRequisition().x/2, window.getSize().y/2-sfg_window->GetRequisition().y/2)); + + sf::Event event; + sf::Clock clock; + + window.resetGLStates(); + + while (window.isOpen()) + { + while (window.pollEvent(event)) + { + desktop.HandleEvent( event ); + switch(event.type) + { + case sf::Event::Closed: + window.close(); + break; + } + } + desktop.Update( clock.restart().asSeconds() ); + window.clear(); + sfgui.Display( window ); + window.display(); + } + + return 0; +} diff --git a/include/SFGUI/ListBox.hpp b/include/SFGUI/ListBox.hpp new file mode 100644 index 00000000..8ab55c0b --- /dev/null +++ b/include/SFGUI/ListBox.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include +#include +#include + +namespace sfg { + +class SFGUI_API ListBox : public Widget { + public: + typedef std::shared_ptr Ptr; //!< Shared pointer. + typedef std::shared_ptr PtrConst; //!< Shared pointer. + + /** Create button. + * @return ListBox. + */ + static Ptr Create( ); + + const std::string& GetName() const override; + + unsigned GetSelectedItemsCount(); + unsigned GetSelectedItemIndex(unsigned n=0); + const std::string& GetSelectedItemText(unsigned n=0); + + void AppendItem(std::string str); + void PrependItem(std::string str); + void RemoveItem(unsigned index); + void Clear(); + + // Signals. + static Signal::SignalID OnSelect; //!< Fired when an entry is selected. + + bool resize_automatically = true; + bool multiselect = false; + + protected: + /** Ctor. + */ + ListBox() = default; + + std::unique_ptr InvalidateImpl() const override; + sf::Vector2f CalculateRequisition() override; + + private: + void HandleMouseEnter( int x, int y ) override; + void HandleMouseLeave( int x, int y ) override; + void HandleMouseMoveEvent( int x, int y ) override; + void HandleMouseButtonEvent( sf::Mouse::Button button, bool press, int x, int y ) override; + + std::vector m_entries; + int selection_begin = -1, selection_end = -1; + int hovered_element = -1; bool pressed_on_widget = false; + std::vector selection_odds; + + bool widget_hover = false; +}; + +} diff --git a/include/SFGUI/Widgets.hpp b/include/SFGUI/Widgets.hpp index 45774997..ebc07f25 100644 --- a/include/SFGUI/Widgets.hpp +++ b/include/SFGUI/Widgets.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/src/SFGUI/ListBox.cpp b/src/SFGUI/ListBox.cpp new file mode 100644 index 00000000..8e487792 --- /dev/null +++ b/src/SFGUI/ListBox.cpp @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include +#include + +float h = 0; +float padding; +int odds_last_sel = -1; +std::vector sel; + +namespace sfg { + +Signal::SignalID ListBox::OnSelect = 0; + +ListBox::Ptr ListBox::Create( ) { + auto ptr = Ptr( new ListBox ); + return ptr; +} + +std::unique_ptr ListBox::InvalidateImpl() const { + + auto background_color = sfg::Context::Get().GetEngine().GetProperty( "BackgroundColor", shared_from_this() ); + const auto& font_name = sfg::Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ); + auto font_size = sfg::Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ); + padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); + + auto inverted_color = sf::Color::White - background_color; + inverted_color.a = 255; + + sf::Color med_color(background_color.r/2+inverted_color.r/2, background_color.g/2+inverted_color.g/2, background_color.b/2+inverted_color.b/2, 255); + + const auto& font = sfg::Context::Get().GetEngine().GetResourceManager().GetFont( font_name ); + + std::unique_ptr queue( new sfg::RenderQueue ); + + queue->Add( + sfg::Renderer::Get().CreatePane( + sf::Vector2f( 0.f, 0.f ), + sf::Vector2f( GetAllocation().width, GetAllocation().height ), + 1.f, + background_color, + sf::Color(0, 0, 0, 128), + 5.f + ) + ); + + h = sfg::Context::Get().GetEngine().GetFontLineHeight( *font, font_size ); + + sel.clear(); + for(unsigned i=0; i=selection_begin && i<=selection_end) || (i>=selection_end && i<=selection_begin)) selected = true; + + if(selection_odds[i]) selected = !selected; + + if(selected) sel.push_back(i); + + text.setPosition( + padding, + y + padding + ); + + queue->Add( + sfg::Renderer::Get().CreatePane( + sf::Vector2f( 0.f, y ), + sf::Vector2f( GetAllocation().width, h+padding*2 ), + 1.f, + (selected?sf::Color::Blue:(hover?med_color:background_color)), + sf::Color(0, 0, 0, 128), + 5.f + ) + ); + + queue->Add( sfg::Renderer::Get().CreateText( text ) ); + } + + return queue; +} + +sf::Vector2f ListBox::CalculateRequisition() { + padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); + float spacing( Context::Get().GetEngine().GetProperty( "Spacing", shared_from_this() ) ); + const std::string& font_name( Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ) ); + unsigned int font_size( Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ) ); + const sf::Font& font( *Context::Get().GetEngine().GetResourceManager().GetFont( font_name ) ); + + h = sfg::Context::Get().GetEngine().GetFontLineHeight( font, font_size ); + sf::Vector2f ret(100, 100); + + for(unsigned i=0; i ret.x) ret.x = requisition.x; + } + + float lines_h = (h+2 * padding-1) * m_entries.size(); + if(lines_h>ret.y) ret.y = lines_h; + + return ret; +} + +const std::string& ListBox::GetName() const { + static const std::string name( "ListBox" ); + return name; +} + +unsigned ListBox::GetSelectedItemsCount() +{ + return sel.size(); +} + +unsigned ListBox::GetSelectedItemIndex(unsigned i) +{ + if(i>sel.size()) return (unsigned)(-1); + return sel[i]; +} + +const std::string& ListBox::GetSelectedItemText(unsigned i) +{ + if(i>sel.size()) return 0; + return m_entries[sel[i]]; +} + +void ListBox::AppendItem(std::string str) +{ + m_entries.push_back(str); + selection_odds.push_back(false); + if(resize_automatically) + { + RequestResize(); + Invalidate(); + } +} + +void ListBox::PrependItem(std::string str) +{ + m_entries.insert(m_entries.begin(),str); + selection_odds.insert(selection_odds.begin(), false); +} + +void ListBox::RemoveItem(unsigned index) +{ + m_entries.erase(m_entries.begin()+index); + selection_odds.erase(selection_odds.begin()+index); +} + +void ListBox::Clear() +{ + m_entries.clear(); + selection_odds.clear(); +} + +void ListBox::HandleMouseEnter( int /*x*/, int /*y*/ ) { + widget_hover = true; +} + +void ListBox::HandleMouseLeave( int /*x*/, int /*y*/ ) { + widget_hover = false; + pressed_on_widget = false; + hovered_element = -1; + Invalidate(); +} + +void ListBox::HandleMouseMoveEvent( int x, int y ) { + if( ( x == std::numeric_limits::min() ) || ( y == std::numeric_limits::min() ) ) { + pressed_on_widget = false; return; + } + + if(widget_hover) + { + int hov_el_cpy = hovered_element; + hovered_element = (h?((y-padding+1)/(h+padding*2)-1):0); + if(pressed_on_widget && hov_el_cpy!=hovered_element) + { + if(multiselect){ selection_end=hovered_element; } + else { selection_begin=selection_end=hovered_element; } + } + Invalidate(); + + } +} + +void ListBox::HandleMouseButtonEvent( sf::Mouse::Button button, bool press, int /*x*/, int /*y*/ ) { + if(press && button==sf::Mouse::Left && hovered_element != -1) + { + pressed_on_widget = true; + bool shift = sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) && !sf::Keyboard::isKeyPressed(sf::Keyboard::LControl); + bool control = sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) && !sf::Keyboard::isKeyPressed(sf::Keyboard::LShift); + if(multiselect && shift && selection_begin!=-1) + { + if(odds_last_sel==-1) + { + selection_end = hovered_element; + int beg = std::min(selection_begin, selection_end); + int end = std::max(selection_begin, selection_end); + for (std::vector::iterator it = selection_odds.begin()+beg; it != selection_odds.begin()+end; ++it) + *it = false; + } + else + { + selection_begin = odds_last_sel; + odds_last_sel = -1; + selection_end = hovered_element; + for (std::vector::iterator it = selection_odds.begin(); it != selection_odds.end(); ++it) + *it = false; + } + + } + else if(multiselect && control && selection_begin!=-1) + { + selection_odds[hovered_element] = !selection_odds[hovered_element]; + odds_last_sel = hovered_element; + } + else + { + selection_begin = selection_end = hovered_element; + for (std::vector::iterator it = selection_odds.begin(); it != selection_odds.end(); ++it) + *it = false; + } + Invalidate(); + } + if(pressed_on_widget && !press && button==sf::Mouse::Left && hovered_element != -1) + { + pressed_on_widget = false; + GetSignals().Emit( OnSelect ); + } +} + + +} From 57f63f08597f07ea5b55d200bedcd9f4dc78e000 Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Mon, 22 Feb 2016 00:44:45 +0100 Subject: [PATCH 2/2] Rewrite ListBox widget --- CMakeLists.txt | 3 +- examples/ListBox.cpp | 203 ++++---- include/SFGUI/Engine.hpp | 7 + include/SFGUI/Engines/BREW.hpp | 1 + include/SFGUI/ListBox.hpp | 140 ++++-- src/SFGUI/Engines/BREW.cpp | 6 + src/SFGUI/Engines/BREW/ListBox.cpp | 89 ++++ src/SFGUI/ListBox.cpp | 754 ++++++++++++++++++++--------- 8 files changed, 872 insertions(+), 331 deletions(-) create mode 100644 src/SFGUI/Engines/BREW/ListBox.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d1fdf28..f6bb8967 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,7 @@ set( "${SOURCE_PATH}/SFGUI/Engines/BREW/Frame.cpp" "${SOURCE_PATH}/SFGUI/Engines/BREW/Image.cpp" "${SOURCE_PATH}/SFGUI/Engines/BREW/Label.cpp" + "${SOURCE_PATH}/SFGUI/Engines/BREW/ListBox.cpp" "${SOURCE_PATH}/SFGUI/Engines/BREW/Notebook.cpp" "${SOURCE_PATH}/SFGUI/Engines/BREW/ProgressBar.cpp" "${SOURCE_PATH}/SFGUI/Engines/BREW/Scale.cpp" @@ -269,7 +270,7 @@ elseif( APPLE ) elseif( "${CMAKE_SYSTEM_NAME}" MATCHES "Linux" ) target_link_libraries( sfgui ${SFML_LIBRARIES} ${SFML_DEPENDENCIES} ${OPENGL_gl_LIBRARY} ${X11_LIBRARIES} ) set( SHARE_PATH "${CMAKE_INSTALL_PREFIX}/share/SFGUI" ) - + if( LIB_SUFFIX ) set( LIB_PATH "lib${LIB_SUFFIX}" ) else() diff --git a/examples/ListBox.cpp b/examples/ListBox.cpp index aec1de5f..4dd46dda 100644 --- a/examples/ListBox.cpp +++ b/examples/ListBox.cpp @@ -2,90 +2,129 @@ // Including SFGUI/Widgets.hpp includes everything // you can possibly need automatically. #include -#include - +#include + #include int main() { - sfg::SFGUI sfgui; - sf::RenderWindow window(sf::VideoMode(640, 480), "ListBox Example"); - window.setVerticalSyncEnabled(true); - window.setFramerateLimit(30); - - sfg::Desktop desktop; - - auto sfg_window = sfg::Window::Create(); - sfg_window->SetTitle( "ListBoxExample" ); - - auto box_outer = sfg::Box::Create( sfg::Box::Orientation::HORIZONTAL, 15.0f ); - auto box_inner1 = sfg::Box::Create( sfg::Box::Orientation::VERTICAL, 5.0f ); - auto box_inner2 = sfg::Box::Create( sfg::Box::Orientation::VERTICAL, 5.0f ); - - auto label1 = sfg::Label::Create("I'm single-select list."); - auto label2 = sfg::Label::Create("I'm multi-select list."); - - auto input1 = sfg::Entry::Create(""); - auto input2 = sfg::Entry::Create(""); - - auto list1 = sfg::ListBox::Create(); - list1->AppendItem( "Item1" ); - list1->AppendItem( "Item2" ); - list1->AppendItem( "Item3" ); - list1->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( [list1, input1](){ if(list1->GetSelectedItemsCount()) input1->SetText(list1->GetSelectedItemText()); else input1->SetText(""); } ) ); - - auto list2 = sfg::ListBox::Create(); - list2->AppendItem( "Item1" ); - list2->AppendItem( "Item2" ); - list2->AppendItem( "Item3" ); - list2->multiselect = true; - list2->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( [list2, input2](){ - std::string str = ""; - for(unsigned i=0; iGetSelectedItemsCount(); i++) - { - str += list2->GetSelectedItemText(i); - str += ' '; - } - input2->SetText(str); - } ) ); - - box_outer->Pack(box_inner1); - box_outer->Pack(box_inner2); - - box_inner1->Pack(label1); - box_inner1->Pack(list1); - box_inner1->Pack(input1); - - box_inner2->Pack(label2); - box_inner2->Pack(list2); - box_inner2->Pack(input2); - - sfg_window->Add( box_outer ); - desktop.Add( sfg_window ); - - sfg_window->SetPosition(sf::Vector2f(window.getSize().x/2-sfg_window->GetRequisition().x/2, window.getSize().y/2-sfg_window->GetRequisition().y/2)); - - sf::Event event; - sf::Clock clock; - - window.resetGLStates(); - - while (window.isOpen()) - { - while (window.pollEvent(event)) - { - desktop.HandleEvent( event ); - switch(event.type) - { - case sf::Event::Closed: - window.close(); - break; - } - } - desktop.Update( clock.restart().asSeconds() ); - window.clear(); - sfgui.Display( window ); - window.display(); - } - + sfg::SFGUI sfgui; + sf::RenderWindow window(sf::VideoMode(800, 600), "ListBox Example"); + window.setVerticalSyncEnabled(true); + window.setFramerateLimit(30); + + sfg::Desktop desktop; + + auto window1 = sfg::Window::Create(); + window1->SetTitle( "ListBox with ItemTextPolicy::RESIZE_LISTBOX" ); + + auto box1 = sfg::Box::Create( sfg::Box::Orientation::VERTICAL ); + box1->SetSpacing( 5.f ); + box1->PackEnd( sfg::Label::Create( "The minimum width\nof this ListBox\ncorresponds to the largest\nitem's text width." ), false, true ); + + auto listbox1 = sfg::ListBox::Create(); + listbox1->AppendItem( "This is the first item" ); + listbox1->AppendItem( "Second item" ); + listbox1->AppendItem( "Third item which is a bit large" ); + listbox1->AppendItem( "Fourth item" ); + listbox1->AppendItem( "Fifth item" ); + listbox1->AppendItem( "Sixth item" ); + listbox1->AppendItem( "Last one !" ); + box1->PackEnd( listbox1 ); + + window1->Add( box1 ); + + auto window2 = sfg::Window::Create(); + window2->SetTitle( "ListBox with ItemTextPolicy::SHRINK" ); + + auto box2 = sfg::Box::Create( sfg::Box::Orientation::VERTICAL ); + box2->SetSpacing( 5.f ); + auto label2 = sfg::Label::Create( "The items' texts\nare shrinked if the\nListBox is not big\nenough." ); + box2->PackEnd( label2, false, true ); + + auto listbox2 = sfg::ListBox::Create(); + listbox2->AppendItem( "This is the first item (long text)" ); + listbox2->AppendItem( "Second item" ); + listbox2->AppendItem( "Third item which is very long !" ); + listbox2->AppendItem( "Fourth item" ); + listbox2->AppendItem( "Fifth item" ); + listbox2->AppendItem( "Sixth item, again it's too large !" ); + listbox2->AppendItem( "Last one !" ); + listbox2->SetItemTextPolicy( sfg::ListBox::ItemTextPolicy::SHRINK ); + box2->PackEnd( listbox2 ); + + window2->Add( box2 ); + + auto window3 = sfg::Window::Create(); + window3->SetTitle( "ListBox with ItemTextPolicy::SHRINK" ); + + auto box3 = sfg::Box::Create( sfg::Box::Orientation::VERTICAL ); + box3->SetSpacing( 5.f ); + auto label3 = sfg::Label::Create( "You can select multiple\nitems in this ListBox." ); + box3->PackEnd( label3, false, true ); + + auto listbox3 = sfg::ListBox::Create(); + listbox3->AppendItem( "First item" ); + listbox3->AppendItem( "Second item" ); + listbox3->AppendItem( "Third item" ); + listbox3->AppendItem( "Fourth item" ); + listbox3->AppendItem( "Fifth item" ); + listbox3->AppendItem( "Sixth item" ); + listbox3->AppendItem( "Last one !" ); + listbox3->SetSelectionMode( sfg::ListBox::SelectionMode::MULTI_SELECTION ); + listbox3->SetSelection( {1, 3, 4, 5} ); + box3->PackEnd( listbox3 ); + + window3->Add( box3 ); + + desktop.Add( window1 ); + desktop.Add( window2 ); + desktop.Add( window3 ); + + sf::Vector2f windowSize( static_cast( window.getSize().x ), static_cast( window.getSize().y ) ); + + window2->SetPosition(sf::Vector2f(windowSize.x/2.f - window2->GetRequisition().x/2.f, windowSize.y/2.f - window2->GetRequisition().y/2.f)); + window3->SetPosition(sf::Vector2f(windowSize.x - window3->GetRequisition().x - 100.f, windowSize.y - window3->GetRequisition().y - 100.f)); + + sf::Event event; + sf::Clock clock; + + window.resetGLStates(); + + int i = 0; + + while (window.isOpen()) + { + while (window.pollEvent(event)) + { + desktop.HandleEvent( event ); + switch(event.type) + { + case sf::Event::Closed: + window.close(); + break; + case sf::Event::KeyPressed: + if( event.key.code == sf::Keyboard::R ) { + listbox3->RemoveItem(2); + } else if( event.key.code == sf::Keyboard::I ) { + listbox3->InsertItem(3, "Inserted item #" + std::to_string(i)); + ++i; + } else if( event.key.code == sf::Keyboard::A) { + listbox3->AppendItem("Appended item #" + std::to_string(i)); + ++i; + } else if( event.key.code == sf::Keyboard::P) { + listbox3->PrependItem("Prepended item #" + std::to_string(i)); + ++i; + } + break; + default: + break; + } + } + desktop.Update( clock.restart().asSeconds() ); + window.clear(); + sfgui.Display( window ); + window.display(); + } + return 0; } diff --git a/include/SFGUI/Engine.hpp b/include/SFGUI/Engine.hpp index 00f5b64a..f8032859 100644 --- a/include/SFGUI/Engine.hpp +++ b/include/SFGUI/Engine.hpp @@ -40,6 +40,7 @@ class Notebook; class Spinner; class ComboBox; class SpinButton; +class ListBox; class Selector; class RenderQueue; @@ -164,6 +165,12 @@ class SFGUI_API Engine { */ virtual std::unique_ptr CreateSpinButtonDrawable( std::shared_ptr spinbutton ) const = 0; + /** Create drawable for listbox widgets. + * @param listbox Widget. + * @return New drawable object (unmanaged memory!). + */ + virtual std::unique_ptr CreateListBoxDrawable( std::shared_ptr listbox ) const = 0; + /** Get maximum line height. * @param font Font. * @param font_size Font size. diff --git a/include/SFGUI/Engines/BREW.hpp b/include/SFGUI/Engines/BREW.hpp index 34a88232..0d5ef5b2 100644 --- a/include/SFGUI/Engines/BREW.hpp +++ b/include/SFGUI/Engines/BREW.hpp @@ -43,6 +43,7 @@ class SFGUI_API BREW : public Engine { std::unique_ptr CreateSpinnerDrawable( std::shared_ptr spinner ) const override; std::unique_ptr CreateComboBoxDrawable( std::shared_ptr combo_box ) const override; std::unique_ptr CreateSpinButtonDrawable( std::shared_ptr spinbutton ) const override; + std::unique_ptr CreateListBoxDrawable( std::shared_ptr listbox ) const override; private: static std::unique_ptr CreateBorder( const sf::FloatRect& rect, float border_width, const sf::Color& light_color, const sf::Color& dark_color ); diff --git a/include/SFGUI/ListBox.hpp b/include/SFGUI/ListBox.hpp index 8ab55c0b..36b135ac 100644 --- a/include/SFGUI/ListBox.hpp +++ b/include/SFGUI/ListBox.hpp @@ -1,60 +1,136 @@ #pragma once -#include +#include +#include #include -#include + +#include +#include +#include #include -namespace sfg { - -class SFGUI_API ListBox : public Widget { +namespace sfg { + +class SFGUI_API ListBox : public Container { public: typedef std::shared_ptr Ptr; //!< Shared pointer. typedef std::shared_ptr PtrConst; //!< Shared pointer. + typedef int IndexType; + + static const IndexType NONE; + + enum class SelectionMode : char { + NO_SELECTION, + SINGLE_SELECTION, + MULTI_SELECTION, + DEFAULT = SINGLE_SELECTION + }; - /** Create button. + enum class ScrollbarPolicy : char { + VERTICAL_ALWAYS, + VERTICAL_AUTOMATIC, + VERTICAL_NEVER, + DEFAULT = VERTICAL_AUTOMATIC + }; + + enum class ItemTextPolicy : char { + RESIZE_LISTBOX, + SHRINK, + DEFAULT = RESIZE_LISTBOX + }; + + /** Create listbox. * @return ListBox. */ static Ptr Create( ); - const std::string& GetName() const override; - - unsigned GetSelectedItemsCount(); - unsigned GetSelectedItemIndex(unsigned n=0); - const std::string& GetSelectedItemText(unsigned n=0); - - void AppendItem(std::string str); - void PrependItem(std::string str); - void RemoveItem(unsigned index); + const std::string& GetName() const override; + + void AppendItem( const sf::String& str ); + void InsertItem( IndexType index, const sf::String& str ); + void PrependItem( const sf::String& str ); + void ChangeItem( IndexType index, const sf::String& str ); + void RemoveItem( IndexType index ); void Clear(); - - // Signals. - static Signal::SignalID OnSelect; //!< Fired when an entry is selected. - - bool resize_automatically = true; - bool multiselect = false; + + IndexType GetItemsCount() const; + const sf::String& GetItemText( IndexType index ) const; + const sf::String& GetDisplayedItemText( IndexType index ) const; + + IndexType GetHighlightedItem() const; + + void SetSelection( IndexType index ); + void SetSelection( std::initializer_list indices ); + void AppendToSelection( IndexType index ); + void RemoveFromSelection( IndexType index ); + void ClearSelection(); + + bool IsItemSelected( IndexType index ) const; + IndexType GetSelectedItemsCount() const; + IndexType GetSelectedItemIndex( IndexType index = 0 ) const; + const sf::String& GetSelectedItemText( IndexType index = 0 ) const; + + IndexType GetFirstDisplayedItemIndex() const; + IndexType GetDisplayedItemsCount() const; + IndexType GetMaxDisplayedItemsCount() const; + + SelectionMode GetSelectionMode() const; + void SetSelectionMode( SelectionMode mode ); + + ScrollbarPolicy GetScrollbarPolicy() const; + void SetScrollbarPolicy( ScrollbarPolicy policy ); + + ItemTextPolicy GetItemTextPolicy() const; + void SetItemTextPolicy( ItemTextPolicy policy ); + + // Signals. + static Signal::SignalID OnSelect; //!< Fired when an entry is selected. protected: /** Ctor. */ - ListBox() = default; + ListBox(); std::unique_ptr InvalidateImpl() const override; sf::Vector2f CalculateRequisition() override; - private: + private: void HandleMouseEnter( int x, int y ) override; - void HandleMouseLeave( int x, int y ) override; + void HandleMouseLeave( int x, int y ) override; void HandleMouseMoveEvent( int x, int y ) override; - void HandleMouseButtonEvent( sf::Mouse::Button button, bool press, int x, int y ) override; - - std::vector m_entries; - int selection_begin = -1, selection_end = -1; - int hovered_element = -1; bool pressed_on_widget = false; - std::vector selection_odds; - - bool widget_hover = false; + void HandleMouseButtonEvent( sf::Mouse::Button button, bool press, int x, int y ) override; + void HandleSizeChange() override; + bool HandleAdd( Widget::Ptr ) override; + void HandleRemove( Widget::Ptr ) override; + + IndexType GetItemAt( float y ) const; + + bool IsScrollbarVisible() const; + + void UpdateDisplayedItems(); + void UpdateScrollbarAdjustment(); + void UpdateScrollbarAllocation(); + + void UpdateDisplayedItemsText(); + + void OnScrollbarChanged(); + + std::vector m_items; + + SelectionMode m_selection_mode; + std::set m_selected_items; + + IndexType m_highlighted_item; + + IndexType m_first_displayed_item; + IndexType m_max_displayed_items_count; + + Scrollbar::Ptr m_vertical_scrollbar; + ScrollbarPolicy m_scrollbar_policy; + + ItemTextPolicy m_item_text_policy; + std::vector m_displayed_items_texts; }; } diff --git a/src/SFGUI/Engines/BREW.cpp b/src/SFGUI/Engines/BREW.cpp index a221c1ed..3838b755 100644 --- a/src/SFGUI/Engines/BREW.cpp +++ b/src/SFGUI/Engines/BREW.cpp @@ -153,6 +153,12 @@ void BREW::ResetProperties() { SetProperty( "SpinButton", "StepperSpeed", 10.f ); SetProperty( "SpinButton", "StepperRepeatDelay", 500 ); + // ListBox-specific. + SetProperty( "ListBox", "BackgroundColor", sf::Color( 0x5e, 0x5e, 0x5e ) ); + SetProperty( "ListBox", "Color", sf::Color::White ); + SetProperty( "ListBox", "HighlightedColor", sf::Color( 0x65, 0x67, 0x62 ) ); + SetProperty( "ListBox", "SelectedColor", sf::Color( 0x5a, 0x6a, 0x50 ) ); + // (Re)Enable automatic widget refreshing after we are done setting all these properties. SetAutoRefresh( true ); } diff --git a/src/SFGUI/Engines/BREW/ListBox.cpp b/src/SFGUI/Engines/BREW/ListBox.cpp new file mode 100644 index 00000000..c6753001 --- /dev/null +++ b/src/SFGUI/Engines/BREW/ListBox.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include + +#include + +namespace sfg { +namespace eng { + +std::unique_ptr BREW::CreateListBoxDrawable( std::shared_ptr listbox ) const { + auto border_color = GetProperty( "BorderColor", listbox ); + auto background_color = GetProperty( "BackgroundColor", listbox ); + auto highlighted_color = GetProperty( "HighlightedColor", listbox ); + auto selected_color = GetProperty( "SelectedColor", listbox ); + auto text_color = GetProperty( "Color", listbox ); + auto text_padding = GetProperty( "Padding", listbox ); + auto border_width = GetProperty( "BorderWidth", listbox ); + auto border_color_shift = GetProperty( "BorderColorShift", listbox ); + const auto& font_name = GetProperty( "FontName", listbox ); + const auto& font = GetResourceManager().GetFont( font_name ); + auto font_size = GetProperty( "FontSize", listbox ); + + std::unique_ptr queue( new RenderQueue ); + + // Pane. + queue->Add( + Renderer::Get().CreatePane( + sf::Vector2f( 0.f, 0.f ), + sf::Vector2f( listbox->GetAllocation().width, listbox->GetAllocation().height ), + border_width, + background_color, + border_color, + -border_color_shift + ) + ); + + // Items. + sf::Vector2f itemPosition = sf::Vector2f( border_width + text_padding, border_width + text_padding ); + for( ListBox::IndexType i = listbox->GetFirstDisplayedItemIndex(); + i < std::min(listbox->GetFirstDisplayedItemIndex() + listbox->GetMaxDisplayedItemsCount(), listbox->GetItemsCount()); + ++i ) { + auto& itemText = listbox->GetDisplayedItemText( i ); + + auto metrics = GetTextStringMetrics( itemText, *font, font_size ); + metrics.y = GetFontLineHeight( *font, font_size ); + + sf::Text text( itemText, *font, font_size ); + text.setPosition(itemPosition); + text.setColor(text_color); + + if( listbox->IsItemSelected( i ) ) { + queue->Add( + Renderer::Get().CreateRect( + sf::FloatRect( + itemPosition.x - text_padding, + itemPosition.y - text_padding, + listbox->GetAllocation().width - 2 * border_width, + std::min(metrics.y + text_padding * 2, listbox->GetAllocation().height - (itemPosition.y - text_padding) - border_width) + // Avoid the selection box to go further than the widget's height when the last displayed item padding space is not fully displayed. + ), + selected_color + ) + ); + } else if( i == listbox->GetHighlightedItem() ) { + queue->Add( + Renderer::Get().CreateRect( + sf::FloatRect( + itemPosition.x - text_padding, + itemPosition.y - text_padding, + listbox->GetAllocation().width - 2 * border_width, + std::min(metrics.y + text_padding * 2, listbox->GetAllocation().height - (itemPosition.y - text_padding) - border_width) + // Avoid the highlight box to go further than the widget's height when the last displayed item padding space is not fully displayed. + ), + highlighted_color + ) + ); + } + + queue->Add( Renderer::Get().CreateText(text) ); + + itemPosition += sf::Vector2f( 0.f, metrics.y + text_padding * 2 ); + } + + return queue; +} + +} +} diff --git a/src/SFGUI/ListBox.cpp b/src/SFGUI/ListBox.cpp index 8e487792..c4a52ea9 100644 --- a/src/SFGUI/ListBox.cpp +++ b/src/SFGUI/ListBox.cpp @@ -1,243 +1,565 @@ #include #include #include -#include -#include +#include +#include + +#include + #include - -float h = 0; -float padding; -int odds_last_sel = -1; -std::vector sel; - -namespace sfg { - + +namespace sfg { + +const ListBox::IndexType ListBox::NONE = -1; +static const sf::String EMPTY = ""; + Signal::SignalID ListBox::OnSelect = 0; ListBox::Ptr ListBox::Create( ) { auto ptr = Ptr( new ListBox ); + static_cast( ptr )->Add( ptr->m_vertical_scrollbar ); return ptr; } -std::unique_ptr ListBox::InvalidateImpl() const { - - auto background_color = sfg::Context::Get().GetEngine().GetProperty( "BackgroundColor", shared_from_this() ); - const auto& font_name = sfg::Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ); - auto font_size = sfg::Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ); - padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); - - auto inverted_color = sf::Color::White - background_color; - inverted_color.a = 255; - - sf::Color med_color(background_color.r/2+inverted_color.r/2, background_color.g/2+inverted_color.g/2, background_color.b/2+inverted_color.b/2, 255); - - const auto& font = sfg::Context::Get().GetEngine().GetResourceManager().GetFont( font_name ); - - std::unique_ptr queue( new sfg::RenderQueue ); - - queue->Add( - sfg::Renderer::Get().CreatePane( - sf::Vector2f( 0.f, 0.f ), - sf::Vector2f( GetAllocation().width, GetAllocation().height ), - 1.f, - background_color, - sf::Color(0, 0, 0, 128), - 5.f - ) - ); - - h = sfg::Context::Get().GetEngine().GetFontLineHeight( *font, font_size ); - - sel.clear(); - for(unsigned i=0; i=selection_begin && i<=selection_end) || (i>=selection_end && i<=selection_begin)) selected = true; - - if(selection_odds[i]) selected = !selected; - - if(selected) sel.push_back(i); - - text.setPosition( - padding, - y + padding - ); - - queue->Add( - sfg::Renderer::Get().CreatePane( - sf::Vector2f( 0.f, y ), - sf::Vector2f( GetAllocation().width, h+padding*2 ), - 1.f, - (selected?sf::Color::Blue:(hover?med_color:background_color)), - sf::Color(0, 0, 0, 128), - 5.f - ) - ); - - queue->Add( sfg::Renderer::Get().CreateText( text ) ); - } - - return queue; +ListBox::ListBox() : + Container(), + m_items(), + m_selection_mode(SelectionMode::DEFAULT), + m_selected_items(), + m_highlighted_item(NONE), + m_first_displayed_item(0), + m_max_displayed_items_count(0), + m_vertical_scrollbar(nullptr), + m_scrollbar_policy(ScrollbarPolicy::DEFAULT), + m_item_text_policy(ItemTextPolicy::DEFAULT), + m_displayed_items_texts() +{ + m_vertical_scrollbar = Scrollbar::Create(Scrollbar::Orientation::VERTICAL); + m_vertical_scrollbar->GetAdjustment()->GetSignal(sfg::Adjustment::OnChange).Connect(std::bind(&ListBox::OnScrollbarChanged, this)); +} + +std::unique_ptr ListBox::InvalidateImpl() const { + return Context::Get().GetEngine().CreateListBoxDrawable( std::dynamic_pointer_cast( shared_from_this() ) ); } sf::Vector2f ListBox::CalculateRequisition() { - padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); - float spacing( Context::Get().GetEngine().GetProperty( "Spacing", shared_from_this() ) ); - const std::string& font_name( Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ) ); - unsigned int font_size( Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ) ); - const sf::Font& font( *Context::Get().GetEngine().GetResourceManager().GetFont( font_name ) ); - - h = sfg::Context::Get().GetEngine().GetFontLineHeight( font, font_size ); - sf::Vector2f ret(100, 100); - - for(unsigned i=0; i ret.x) ret.x = requisition.x; - } - - float lines_h = (h+2 * padding-1) * m_entries.size(); - if(lines_h>ret.y) ret.y = lines_h; - - return ret; + auto text_padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); + auto border_width = Context::Get().GetEngine().GetProperty( "BorderWidth", shared_from_this() ); + const auto& font_name = Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ); + const auto& font = Context::Get().GetEngine().GetResourceManager().GetFont( font_name ); + auto font_size = Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ); + auto dots_width = Context::Get().GetEngine().GetTextStringMetrics("...", *font, font_size).x; + + // Calculate the max width of items + float items_max_width = 0.f; + for( const sf::String& item : m_items ) { + items_max_width = std::max(items_max_width, Context::Get().GetEngine().GetTextStringMetrics(item, *font, font_size).x); + } + + return sf::Vector2f( + border_width * 2 + text_padding * 2 + + ( m_item_text_policy == ItemTextPolicy::RESIZE_LISTBOX ? items_max_width : dots_width ) + + ( IsScrollbarVisible() ? m_vertical_scrollbar->GetRequisition().x : 0 ), + std::max(m_vertical_scrollbar->GetRequisition().y, 50.f) + ); } const std::string& ListBox::GetName() const { static const std::string name( "ListBox" ); return name; -} - -unsigned ListBox::GetSelectedItemsCount() -{ - return sel.size(); -} - -unsigned ListBox::GetSelectedItemIndex(unsigned i) -{ - if(i>sel.size()) return (unsigned)(-1); - return sel[i]; -} - -const std::string& ListBox::GetSelectedItemText(unsigned i) -{ - if(i>sel.size()) return 0; - return m_entries[sel[i]]; -} - -void ListBox::AppendItem(std::string str) -{ - m_entries.push_back(str); - selection_odds.push_back(false); - if(resize_automatically) - { - RequestResize(); - Invalidate(); - } -} - -void ListBox::PrependItem(std::string str) -{ - m_entries.insert(m_entries.begin(),str); - selection_odds.insert(selection_odds.begin(), false); -} - -void ListBox::RemoveItem(unsigned index) -{ - m_entries.erase(m_entries.begin()+index); - selection_odds.erase(selection_odds.begin()+index); -} - -void ListBox::Clear() -{ - m_entries.clear(); - selection_odds.clear(); -} - +} + +void ListBox::AppendItem( const sf::String& str ) { + m_items.push_back( str ); + + UpdateDisplayedItems(); + RequestResize(); + HandleSizeChange(); + Invalidate(); +} + +void ListBox::InsertItem( IndexType index, const sf::String& str ) { + m_items.insert( m_items.begin() + index, str ); + + // Update next selected indexes (decrement them). + std::set new_selected_items; + std::transform( + m_selected_items.cbegin(), + m_selected_items.cend(), + std::inserter(new_selected_items, new_selected_items.end()), + [&](IndexType i){ + if( i < index ) + return i; + else + return ++i; + } + ); + m_selected_items = std::move(new_selected_items); + + UpdateDisplayedItems(); + RequestResize(); + HandleSizeChange(); + Invalidate(); +} + +void ListBox::PrependItem( const sf::String& str ) { + m_items.insert( m_items.begin(), str ); + + // Update selected items indexes. + std::set new_selected_items; + std::transform( + m_selected_items.cbegin(), + m_selected_items.cend(), + std::inserter(new_selected_items, new_selected_items.end()), + [&](IndexType i){ + return ++i; + } + ); + m_selected_items = std::move(new_selected_items); + + UpdateDisplayedItems(); + RequestResize(); + HandleSizeChange(); + Invalidate(); +} + +void ListBox::ChangeItem( IndexType index, const sf::String& str ) { + if( index >= static_cast( m_items.size() ) || index < 0 ) { + return; + } + + m_items[ static_cast( index ) ] = str; + + UpdateDisplayedItems(); + Invalidate(); +} + +void ListBox::RemoveItem( IndexType index ) { + if( index >= static_cast( m_items.size() ) || index < 0 ) { + return; + } + + m_items.erase( m_items.begin() + index ); + + // Remove it from the selected indexes. + m_selected_items.erase( index ); + + // Update next selected indexes (decrement them). + std::set new_selected_items; + std::transform( + m_selected_items.cbegin(), + m_selected_items.cend(), + std::inserter(new_selected_items, new_selected_items.end()), + [&](IndexType i){ + if( i > index ) + return --i; + else + return i; + } + ); + m_selected_items = std::move(new_selected_items); + + UpdateDisplayedItems(); + RequestResize(); + HandleSizeChange(); + Invalidate(); +} + +void ListBox::Clear() { + m_items.clear(); + m_selected_items.clear(); + + UpdateDisplayedItems(); + RequestResize(); + HandleSizeChange(); + Invalidate(); +} + +ListBox::IndexType ListBox::GetItemsCount() const { + return static_cast( m_items.size() ); +} + +const sf::String& ListBox::GetItemText( IndexType index ) const { + if( index >= static_cast( m_items.size() ) || index < 0 ) { + return EMPTY; + } + + return m_items[ static_cast( index )]; +} + +const sf::String& ListBox::GetDisplayedItemText( IndexType index ) const { + if( index >= static_cast( m_items.size() ) || index < 0 ) { + return EMPTY; + } + + if(m_item_text_policy == ItemTextPolicy::RESIZE_LISTBOX) { + return GetItemText( index ); + } else { + return m_displayed_items_texts[ static_cast( index ) ]; + } +} + +ListBox::IndexType ListBox::GetHighlightedItem() const { + return m_highlighted_item; +} + +void ListBox::SetSelection( IndexType index ) { + if( m_selection_mode == SelectionMode::NO_SELECTION ) { + return; + } + + m_selected_items.clear(); + if( index != NONE ) { + m_selected_items.insert( index ); + } + + Invalidate(); +} + +void ListBox::SetSelection( std::initializer_list indices ) { + if( m_selection_mode == SelectionMode::MULTI_SELECTION ) { + m_selected_items = std::set( indices.begin(), indices.end() ); + + Invalidate(); + } else if( m_selection_mode == SelectionMode::SINGLE_SELECTION ) { + if( indices.size() > 0) { + SetSelection( *( indices.begin() ) ); + } else { + SetSelection( NONE ); + } + + Invalidate(); + } +} + +void ListBox::AppendToSelection( IndexType index ) { + if( m_selection_mode == SelectionMode::NO_SELECTION ) { + return; + } + + if( m_selected_items.size() == 0 || m_selection_mode == SelectionMode::MULTI_SELECTION ) { + m_selected_items.insert( index ); + } + + Invalidate(); +} + +void ListBox::RemoveFromSelection( IndexType index ) { + m_selected_items.erase( index ); + + Invalidate(); +} + +void ListBox::ClearSelection() { + m_selected_items.clear(); + + Invalidate(); +} + +bool ListBox::IsItemSelected(IndexType index) const { + return m_selected_items.find( index ) != m_selected_items.end(); +} + +ListBox::IndexType ListBox::GetSelectedItemsCount() const { + return static_cast( m_selected_items.size() ); +} + +ListBox::IndexType ListBox::GetSelectedItemIndex( IndexType index ) const { + if( index >= static_cast( m_selected_items.size() ) || index < 0 ) { + return NONE; + } + + auto it = m_selected_items.cbegin(); + std::advance( it, index ); + return *it; +} + +const sf::String& ListBox::GetSelectedItemText( IndexType index ) const { + return m_items[ static_cast( GetSelectedItemIndex( index ) ) ]; +} + +ListBox::IndexType ListBox::GetFirstDisplayedItemIndex() const { + return m_first_displayed_item; +} + +ListBox::IndexType ListBox::GetDisplayedItemsCount() const { + return std::min(m_max_displayed_items_count, static_cast( m_items.size() ) - m_first_displayed_item); +} + +ListBox::IndexType ListBox::GetMaxDisplayedItemsCount() const { + return m_max_displayed_items_count; +} + +ListBox::SelectionMode ListBox::GetSelectionMode() const { + return m_selection_mode; +} + +void ListBox::SetSelectionMode( SelectionMode mode ) { + m_selection_mode = mode; + + if( m_selection_mode == SelectionMode::NO_SELECTION ) { + m_selected_items.clear(); + Invalidate(); + } else if( m_selection_mode == SelectionMode::SINGLE_SELECTION && m_selected_items.size() > 1 ) { + auto it = m_selected_items.begin(); + std::advance( it, 1 ); + m_selected_items.erase( it, m_selected_items.end() ); + } +} + +ListBox::ScrollbarPolicy ListBox::GetScrollbarPolicy() const { + return m_scrollbar_policy; +} + +void ListBox::SetScrollbarPolicy( ScrollbarPolicy policy ) { + m_scrollbar_policy = policy; + + RequestResize(); + Invalidate(); +} + +ListBox::ItemTextPolicy ListBox::GetItemTextPolicy() const { + return m_item_text_policy; +} + +void ListBox::SetItemTextPolicy( ItemTextPolicy policy ) { + m_item_text_policy = policy; + + RequestResize(); + Invalidate(); +} + void ListBox::HandleMouseEnter( int /*x*/, int /*y*/ ) { - widget_hover = true; + if( !HasFocus() ) { + SetState( State::PRELIGHT ); + } } void ListBox::HandleMouseLeave( int /*x*/, int /*y*/ ) { - widget_hover = false; - pressed_on_widget = false; - hovered_element = -1; - Invalidate(); -} - + if( !HasFocus() ) { + SetState( State::NORMAL ); + } +} + void ListBox::HandleMouseMoveEvent( int x, int y ) { - if( ( x == std::numeric_limits::min() ) || ( y == std::numeric_limits::min() ) ) { - pressed_on_widget = false; return; - } - - if(widget_hover) - { - int hov_el_cpy = hovered_element; - hovered_element = (h?((y-padding+1)/(h+padding*2)-1):0); - if(pressed_on_widget && hov_el_cpy!=hovered_element) - { - if(multiselect){ selection_end=hovered_element; } - else { selection_begin=selection_end=hovered_element; } - } - Invalidate(); - - } -} - -void ListBox::HandleMouseButtonEvent( sf::Mouse::Button button, bool press, int /*x*/, int /*y*/ ) { - if(press && button==sf::Mouse::Left && hovered_element != -1) - { - pressed_on_widget = true; - bool shift = sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) && !sf::Keyboard::isKeyPressed(sf::Keyboard::LControl); - bool control = sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) && !sf::Keyboard::isKeyPressed(sf::Keyboard::LShift); - if(multiselect && shift && selection_begin!=-1) - { - if(odds_last_sel==-1) - { - selection_end = hovered_element; - int beg = std::min(selection_begin, selection_end); - int end = std::max(selection_begin, selection_end); - for (std::vector::iterator it = selection_odds.begin()+beg; it != selection_odds.begin()+end; ++it) - *it = false; - } - else - { - selection_begin = odds_last_sel; - odds_last_sel = -1; - selection_end = hovered_element; - for (std::vector::iterator it = selection_odds.begin(); it != selection_odds.end(); ++it) - *it = false; - } - - } - else if(multiselect && control && selection_begin!=-1) - { - selection_odds[hovered_element] = !selection_odds[hovered_element]; - odds_last_sel = hovered_element; - } - else - { - selection_begin = selection_end = hovered_element; - for (std::vector::iterator it = selection_odds.begin(); it != selection_odds.end(); ++it) - *it = false; - } - Invalidate(); - } - if(pressed_on_widget && !press && button==sf::Mouse::Left && hovered_element != -1) - { - pressed_on_widget = false; - GetSignals().Emit( OnSelect ); - } -} + if( ( x == std::numeric_limits::min() ) || ( y == std::numeric_limits::min() ) ) { + return; + } + + float realX = static_cast( x ) - GetAllocation().left; + float realY = static_cast( y ) - GetAllocation().top; + + if( IsMouseInWidget() && ( !IsScrollbarVisible() || ( IsScrollbarVisible() && realX < m_vertical_scrollbar->GetAllocation().left ) ) ) { + //Highlight the item under the mouse + IndexType hovered_item_index = GetItemAt( realY ); + m_highlighted_item = hovered_item_index; + + Invalidate(); + } else if( m_highlighted_item != NONE ) { + m_highlighted_item = NONE; + + Invalidate(); + } +} + +void ListBox::HandleMouseButtonEvent( sf::Mouse::Button button, bool press, int x, int y ) { + if( ( x == std::numeric_limits::min() ) || ( y == std::numeric_limits::min() ) ) { + return; + } + + float realX = static_cast( x ) - GetAllocation().left; + float realY = static_cast( y ) - GetAllocation().top; + + if( IsMouseInWidget() ) { + if( button == sf::Mouse::Left && press ) { + if( !IsScrollbarVisible() || ( IsScrollbarVisible() && realX < m_vertical_scrollbar->GetAllocation().left ) ) { + if( m_selection_mode != SelectionMode::NO_SELECTION ) { + // Find out and select which item has been clicked. + IndexType clicked_item_index = GetItemAt( realY ); + + if( clicked_item_index != NONE ) { // Only change the selection if the user has clicked on an item. + // Determine whether the clicked item is new to the selection. + bool selection_changed = false; + if( m_selection_mode == SelectionMode::SINGLE_SELECTION || ( !sf::Keyboard::isKeyPressed( sf::Keyboard::LControl ) && !sf::Keyboard::isKeyPressed( sf::Keyboard::RControl ) ) ) { + // In SINGLE_SELECTION mode or when Ctrl is not pressed, if the clicked item was not in the selected items list, the selection has changed. + selection_changed = std::find( m_selected_items.begin(), m_selected_items.end(), clicked_item_index ) == m_selected_items.end(); + + // Clear the selection and add the item to the selection. + m_selected_items.clear(); + m_selected_items.insert( clicked_item_index ); + } else { + // In MULTI_SELECTION and when Ctrl is pressed, the selection has changed (as if the clicked item is already selected, it is removed from the selection). + selection_changed = true; + + // Add or remove the clicked item, depending on if it was in the selection or not. + if(m_selected_items.find( clicked_item_index ) == m_selected_items.end() ) + m_selected_items.insert( clicked_item_index ); + else + m_selected_items.erase( clicked_item_index ); + } + + if( selection_changed ) // Only emit the OnSelect signal if the selection changed. + GetSignals().Emit( OnSelect ); + } + } + + GrabFocus(); + Invalidate(); + } + } + } +} +ListBox::IndexType ListBox::GetItemAt( float y ) const { + auto text_padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); + auto border_width = Context::Get().GetEngine().GetProperty( "BorderWidth", shared_from_this() ); + const auto& font_name = Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ); + const auto& font = Context::Get().GetEngine().GetResourceManager().GetFont( font_name ); + auto font_size = Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ); + auto line_height = Context::Get().GetEngine().GetFontLineHeight( *font, font_size ); + + IndexType item_index = 0; + while(y > border_width + static_cast( item_index ) * ( line_height + text_padding * 2 ) ) { + ++item_index; + } + + if( item_index == 0 ) + return 0 + m_first_displayed_item; + else if( item_index > GetDisplayedItemsCount() ) + return NONE; + else + return item_index - 1 + m_first_displayed_item; +} + +bool ListBox::IsScrollbarVisible() const { + return ( m_scrollbar_policy == ScrollbarPolicy::VERTICAL_ALWAYS || ( m_scrollbar_policy == ScrollbarPolicy::VERTICAL_AUTOMATIC && GetMaxDisplayedItemsCount() < GetItemsCount() ) ); +} + +void ListBox::HandleSizeChange() { + UpdateDisplayedItems(); + UpdateScrollbarAdjustment(); + UpdateScrollbarAllocation(); + + UpdateDisplayedItemsText(); + + Invalidate(); +} + +bool ListBox::HandleAdd( Widget::Ptr widget ) { + // The user can't add widgets to the ListBox. + + if( widget == m_vertical_scrollbar && GetChildren().size() == 0 ) { // The scrollbar is an exception (added while creating the widget). + Container::HandleAdd( widget ); + return true; + } + + #if defined( SFGUI_DEBUG ) + std::cerr << "SFGUI warning: No widgets can be added to a ListBox.\n"; + #endif + + return false; +} + +void ListBox::HandleRemove( Widget::Ptr /*widget*/ ){ + std::cerr << "SFGUI warning: No widgets can be removed from a ListBox.\n"; +} + +void ListBox::UpdateDisplayedItems() { + auto text_padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); + auto border_width = Context::Get().GetEngine().GetProperty( "BorderWidth", shared_from_this() ); + const auto& font_name = Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ); + const auto& font = Context::Get().GetEngine().GetResourceManager().GetFont( font_name ); + auto font_size = Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ); + auto line_height = Context::Get().GetEngine().GetFontLineHeight( *font, font_size ); + + // Update the displayed items count. + float items_height = 0.f; + m_max_displayed_items_count = 0; + while(items_height < GetAllocation().height - border_width * 2.f + text_padding ) { + items_height += line_height + text_padding * 2.f; + ++m_max_displayed_items_count; + } + if(m_max_displayed_items_count > 0) + --m_max_displayed_items_count; + + // If there aren't enough items from m_first_displayed_item to + // m_first_displayed_item + m_max_displayed_items_count, decrement + // m_first_displayed_item. + if(m_first_displayed_item + m_max_displayed_items_count > static_cast( m_items.size() ) ) { + m_first_displayed_item = std::max( static_cast( m_items.size() ) - m_max_displayed_items_count, static_cast( 0 ) ); + } +} + +void ListBox::UpdateScrollbarAdjustment() { + m_vertical_scrollbar->GetAdjustment()->Configure( + static_cast( m_first_displayed_item ), + 0.f, + static_cast( m_items.size() ), + 1.f, + static_cast( m_max_displayed_items_count ), + static_cast( m_max_displayed_items_count ) + ); +} + +void ListBox::UpdateScrollbarAllocation() { + auto border_width = Context::Get().GetEngine().GetProperty( "BorderWidth", shared_from_this() ); + m_vertical_scrollbar->SetAllocation( sf::FloatRect( + GetAllocation().width - border_width - m_vertical_scrollbar->GetRequisition().x, + border_width, + m_vertical_scrollbar->GetRequisition().x, + GetAllocation().height - border_width * 2.f + ) ); + + m_vertical_scrollbar->RequestResize(); + m_vertical_scrollbar->Invalidate(); + m_vertical_scrollbar->Show( IsScrollbarVisible() ); + + Invalidate(); +} + +void ListBox::UpdateDisplayedItemsText() { + m_displayed_items_texts.clear(); + if(m_item_text_policy == ItemTextPolicy::RESIZE_LISTBOX) + return; + + auto text_padding = Context::Get().GetEngine().GetProperty( "Padding", shared_from_this() ); + auto border_width = Context::Get().GetEngine().GetProperty( "BorderWidth", shared_from_this() ); + const auto& font_name = Context::Get().GetEngine().GetProperty( "FontName", shared_from_this() ); + const auto& font = Context::Get().GetEngine().GetResourceManager().GetFont( font_name ); + auto font_size = Context::Get().GetEngine().GetProperty( "FontSize", shared_from_this() ); + auto dots_width = Context::Get().GetEngine().GetTextStringMetrics("...", *font, font_size).x; + + float max_width = GetAllocation().width - border_width * 2 - text_padding * 2 - ( IsScrollbarVisible() ? m_vertical_scrollbar->GetAllocation().width : 0 ); + + for(auto& itemText : m_items) { + if( Context::Get().GetEngine().GetTextStringMetrics( itemText, *font, font_size ).x < max_width ) { + // The item's text is fully displayable in the listbox's width. + m_displayed_items_texts.push_back( itemText ); + } else { + // We need to shrink the text so that it will fit inside the listbox (and don't forget some width for "..."). + sf::String displayableText; + + while( displayableText.getSize() < itemText.getSize() && + Context::Get().GetEngine().GetTextStringMetrics( displayableText, *font, font_size ).x <= max_width - dots_width ) { + displayableText += itemText[ displayableText.getSize() ]; + } + + if(displayableText.getSize() > 0) + displayableText.erase( displayableText.getSize() - 1 ); // Removes the last character as it is going outside of the available space. + + displayableText += "..."; + m_displayed_items_texts.push_back( displayableText ); + } + } + + Invalidate(); +} + +void ListBox::OnScrollbarChanged() { + m_first_displayed_item = static_cast( m_vertical_scrollbar->GetAdjustment()->GetValue() ); + UpdateDisplayedItems(); + + Invalidate(); +} }