From 372d8cb49d5522a1e35d50c26f49bc12aec83393 Mon Sep 17 00:00:00 2001 From: Zax Zaxu Date: Wed, 25 Nov 2015 22:53:05 +0100 Subject: [PATCH 01/13] 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 02/13] 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(); +} } From 3a83566d6d6d6ed19edbf6449339ede90a60aafc Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Mon, 9 May 2016 01:04:41 +0200 Subject: [PATCH 03/13] Add images to ListBox --- examples/ListBox.cpp | 12 +++- include/SFGUI/ListBox.hpp | 26 ++++++-- src/SFGUI/Engines/BREW/ListBox.cpp | 30 ++++++++-- src/SFGUI/ListBox.cpp | 96 ++++++++++++++++++++++-------- 4 files changed, 128 insertions(+), 36 deletions(-) diff --git a/examples/ListBox.cpp b/examples/ListBox.cpp index 4dd46dda..38574fd9 100644 --- a/examples/ListBox.cpp +++ b/examples/ListBox.cpp @@ -12,6 +12,12 @@ int main() { window.setVerticalSyncEnabled(true); window.setFramerateLimit(30); + sf::Image firstImage; + firstImage.loadFromFile("data/add.png"); + + sf::Image secondImage; + secondImage.loadFromFile("data/delete.png"); + sfg::Desktop desktop; auto window1 = sfg::Window::Create(); @@ -30,6 +36,7 @@ int main() { listbox1->AppendItem( "Sixth item" ); listbox1->AppendItem( "Last one !" ); box1->PackEnd( listbox1 ); + listbox1->SetImagesSize(sf::Vector2f(32.f, 32.f)); window1->Add( box1 ); @@ -43,14 +50,15 @@ int main() { 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( "Second item", firstImage ); + listbox2->AppendItem( "Third item which is very long !", secondImage ); 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 ); + listbox2->SetImagesSize(sf::Vector2f(32.f, 32.f)); window2->Add( box2 ); diff --git a/include/SFGUI/ListBox.hpp b/include/SFGUI/ListBox.hpp index 36b135ac..76be28a3 100644 --- a/include/SFGUI/ListBox.hpp +++ b/include/SFGUI/ListBox.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -14,6 +15,12 @@ namespace sfg { class SFGUI_API ListBox : public Container { public: + + struct Item { + sf::String text; + sf::Image image; + }; + typedef std::shared_ptr Ptr; //!< Shared pointer. typedef std::shared_ptr PtrConst; //!< Shared pointer. typedef int IndexType; @@ -47,16 +54,18 @@ class SFGUI_API ListBox : public Container { 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 AppendItem( const sf::String& str, const sf::Image& image = sf::Image() ); + void InsertItem( IndexType index, const sf::String& str, const sf::Image& image = sf::Image() ); + void PrependItem( const sf::String& str, const sf::Image& image = sf::Image() ); + void ChangeItemText( IndexType index, const sf::String& str ); + void ChangeItemImage( IndexType index, const sf::Image& image ); void RemoveItem( IndexType index ); void Clear(); IndexType GetItemsCount() const; const sf::String& GetItemText( IndexType index ) const; const sf::String& GetDisplayedItemText( IndexType index ) const; + const sf::Image& GetItemImage( IndexType index ) const; IndexType GetHighlightedItem() const; @@ -84,6 +93,11 @@ class SFGUI_API ListBox : public Container { ItemTextPolicy GetItemTextPolicy() const; void SetItemTextPolicy( ItemTextPolicy policy ); + sf::Vector2f GetImagesSize() const; + void SetImagesSize(sf::Vector2f size); + + float GetItemHeight() const; + // Signals. static Signal::SignalID OnSelect; //!< Fired when an entry is selected. @@ -116,7 +130,7 @@ class SFGUI_API ListBox : public Container { void OnScrollbarChanged(); - std::vector m_items; + std::vector m_items; SelectionMode m_selection_mode; std::set m_selected_items; @@ -131,6 +145,8 @@ class SFGUI_API ListBox : public Container { ItemTextPolicy m_item_text_policy; std::vector m_displayed_items_texts; + + sf::Vector2f m_images_size; }; } diff --git a/src/SFGUI/Engines/BREW/ListBox.cpp b/src/SFGUI/Engines/BREW/ListBox.cpp index c6753001..25179485 100644 --- a/src/SFGUI/Engines/BREW/ListBox.cpp +++ b/src/SFGUI/Engines/BREW/ListBox.cpp @@ -43,10 +43,15 @@ std::unique_ptr BREW::CreateListBoxDrawable( std::shared_ptrGetDisplayedItemText( i ); auto metrics = GetTextStringMetrics( itemText, *font, font_size ); - metrics.y = GetFontLineHeight( *font, font_size ); + metrics.y = GetFontLineHeight( *font, font_size ); // Text only size + + auto item_height = listbox->GetItemHeight(); // Height of an item (including the image) sf::Text text( itemText, *font, font_size ); - text.setPosition(itemPosition); + text.setPosition( + itemPosition.x + ( listbox->GetImagesSize() != sf::Vector2f() ? text_padding + listbox->GetImagesSize().x : 0.f ), + itemPosition.y + item_height/2.f - metrics.y / 2.f + ); text.setColor(text_color); if( listbox->IsItemSelected( i ) ) { @@ -56,7 +61,7 @@ std::unique_ptr BREW::CreateListBoxDrawable( std::shared_ptrGetAllocation().width - 2 * border_width, - std::min(metrics.y + text_padding * 2, listbox->GetAllocation().height - (itemPosition.y - text_padding) - border_width) + std::min(item_height + 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 @@ -69,7 +74,7 @@ std::unique_ptr BREW::CreateListBoxDrawable( std::shared_ptrGetAllocation().width - 2 * border_width, - std::min(metrics.y + text_padding * 2, listbox->GetAllocation().height - (itemPosition.y - text_padding) - border_width) + std::min(item_height + 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 @@ -77,9 +82,24 @@ std::unique_ptr BREW::CreateListBoxDrawable( std::shared_ptrGetImagesSize() != sf::Vector2f() && listbox->GetItemImage( i ).getSize() != sf::Vector2u() ) { + auto texture = Renderer::Get().LoadTexture( listbox->GetItemImage( i ) ); + queue->Add( + Renderer::Get().CreateSprite( + sf::FloatRect( + itemPosition.x, + itemPosition.y, + listbox->GetImagesSize().x, + listbox->GetImagesSize().y + ), + texture + ) + ); + } + queue->Add( Renderer::Get().CreateText(text) ); - itemPosition += sf::Vector2f( 0.f, metrics.y + text_padding * 2 ); + itemPosition += sf::Vector2f( 0.f, item_height + text_padding * 2 ); } return queue; diff --git a/src/SFGUI/ListBox.cpp b/src/SFGUI/ListBox.cpp index c4a52ea9..531382b9 100644 --- a/src/SFGUI/ListBox.cpp +++ b/src/SFGUI/ListBox.cpp @@ -12,6 +12,7 @@ namespace sfg { const ListBox::IndexType ListBox::NONE = -1; static const sf::String EMPTY = ""; +static const sf::Image EMPTY_IMAGE = sf::Image(); Signal::SignalID ListBox::OnSelect = 0; @@ -32,7 +33,8 @@ ListBox::ListBox() : m_vertical_scrollbar(nullptr), m_scrollbar_policy(ScrollbarPolicy::DEFAULT), m_item_text_policy(ItemTextPolicy::DEFAULT), - m_displayed_items_texts() + m_displayed_items_texts(), + m_images_size() { m_vertical_scrollbar = Scrollbar::Create(Scrollbar::Orientation::VERTICAL); m_vertical_scrollbar->GetAdjustment()->GetSignal(sfg::Adjustment::OnChange).Connect(std::bind(&ListBox::OnScrollbarChanged, this)); @@ -52,15 +54,16 @@ sf::Vector2f ListBox::CalculateRequisition() { // 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); + for( const Item& item : m_items ) { + items_max_width = std::max(items_max_width, Context::Get().GetEngine().GetTextStringMetrics(item.text, *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) + + ( m_item_text_policy == ItemTextPolicy::RESIZE_LISTBOX ? items_max_width : dots_width ) + + ( IsScrollbarVisible() ? m_vertical_scrollbar->GetRequisition().x : 0.f ) + + ( GetImagesSize() != sf::Vector2f() ? m_images_size.x + text_padding : 0.f ), /* Add a supplementary text padding between the image and the text */ + std::max(std::max(m_vertical_scrollbar->GetRequisition().y, 50.f), GetItemHeight() + 2*text_padding) ); } @@ -69,8 +72,8 @@ const std::string& ListBox::GetName() const { return name; } -void ListBox::AppendItem( const sf::String& str ) { - m_items.push_back( str ); +void ListBox::AppendItem( const sf::String& str, const sf::Image& image ) { + m_items.push_back( Item{ str, image } ); UpdateDisplayedItems(); RequestResize(); @@ -78,8 +81,8 @@ void ListBox::AppendItem( const sf::String& str ) { Invalidate(); } -void ListBox::InsertItem( IndexType index, const sf::String& str ) { - m_items.insert( m_items.begin() + index, str ); +void ListBox::InsertItem( IndexType index, const sf::String& str, const sf::Image& image ) { + m_items.insert( m_items.begin() + index, Item{ str, image } ); // Update next selected indexes (decrement them). std::set new_selected_items; @@ -102,8 +105,8 @@ void ListBox::InsertItem( IndexType index, const sf::String& str ) { Invalidate(); } -void ListBox::PrependItem( const sf::String& str ) { - m_items.insert( m_items.begin(), str ); +void ListBox::PrependItem( const sf::String& str, const sf::Image& image ) { + m_items.insert( m_items.begin(), Item{ str, image } ); // Update selected items indexes. std::set new_selected_items; @@ -123,17 +126,27 @@ void ListBox::PrependItem( const sf::String& str ) { Invalidate(); } -void ListBox::ChangeItem( IndexType index, const sf::String& str ) { +void ListBox::ChangeItemText( IndexType index, const sf::String& str ) { if( index >= static_cast( m_items.size() ) || index < 0 ) { return; } - m_items[ static_cast( index ) ] = str; + m_items[ static_cast( index ) ].text = str; UpdateDisplayedItems(); Invalidate(); } +void ListBox::ChangeItemImage( IndexType index, const sf::Image& image ) { + if( index >= static_cast( m_items.size() ) || index < 0 ) { + return; + } + + m_items[ static_cast( index ) ].image = image; + + Invalidate(); +} + void ListBox::RemoveItem( IndexType index ) { if( index >= static_cast( m_items.size() ) || index < 0 ) { return; @@ -184,7 +197,7 @@ const sf::String& ListBox::GetItemText( IndexType index ) const { return EMPTY; } - return m_items[ static_cast( index )]; + return m_items[ static_cast( index )].text; } const sf::String& ListBox::GetDisplayedItemText( IndexType index ) const { @@ -199,6 +212,14 @@ const sf::String& ListBox::GetDisplayedItemText( IndexType index ) const { } } +const sf::Image& ListBox::GetItemImage( IndexType index ) const { + if( index >= static_cast( m_items.size() ) || index < 0 ) { + return EMPTY_IMAGE; + } + + return m_items[ static_cast( index )].image; +} + ListBox::IndexType ListBox::GetHighlightedItem() const { return m_highlighted_item; } @@ -275,7 +296,7 @@ ListBox::IndexType ListBox::GetSelectedItemIndex( IndexType index ) const { } const sf::String& ListBox::GetSelectedItemText( IndexType index ) const { - return m_items[ static_cast( GetSelectedItemIndex( index ) ) ]; + return m_items[ static_cast( GetSelectedItemIndex( index ) ) ].text; } ListBox::IndexType ListBox::GetFirstDisplayedItemIndex() const { @@ -329,6 +350,32 @@ void ListBox::SetItemTextPolicy( ItemTextPolicy policy ) { Invalidate(); } +sf::Vector2f ListBox::GetImagesSize() const { + return m_images_size; +} + +void ListBox::SetImagesSize(sf::Vector2f size) { + m_images_size = size; + + UpdateDisplayedItems(); + UpdateScrollbarAdjustment(); + UpdateScrollbarAllocation(); + + UpdateDisplayedItemsText(); + + Invalidate(); +} + +float ListBox::GetItemHeight() const { + auto text_padding = Context::Get().GetEngine().GetProperty( "Padding", 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 ); + + return std::max(line_height, m_images_size.y); +} + void ListBox::HandleMouseEnter( int /*x*/, int /*y*/ ) { if( !HasFocus() ) { SetState( State::PRELIGHT ); @@ -416,7 +463,7 @@ ListBox::IndexType ListBox::GetItemAt( float y ) const { 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 ); + auto line_height = GetItemHeight(); IndexType item_index = 0; while(y > border_width + static_cast( item_index ) * ( line_height + text_padding * 2 ) ) { @@ -470,7 +517,7 @@ void ListBox::UpdateDisplayedItems() { 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 ); + auto line_height = GetItemHeight(); // Update the displayed items count. float items_height = 0.f; @@ -531,17 +578,18 @@ void ListBox::UpdateDisplayedItemsText() { 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 ) { + for(auto& item : m_items) { + if( Context::Get().GetEngine().GetTextStringMetrics( item.text, *font, font_size ).x < max_width - ( GetImagesSize() != sf::Vector2f() ? m_images_size.x + text_padding : 0.f ) ) { // The item's text is fully displayable in the listbox's width. - m_displayed_items_texts.push_back( itemText ); + m_displayed_items_texts.push_back( item.text ); } 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() ]; + while( displayableText.getSize() < item.text.getSize() && + Context::Get().GetEngine().GetTextStringMetrics( displayableText, *font, font_size ).x + <= max_width - dots_width - ( GetImagesSize() != sf::Vector2f() ? m_images_size.x + text_padding : 0.f ) ) { + displayableText += item.text[ displayableText.getSize() ]; } if(displayableText.getSize() > 0) From 89e66e3ff333c34e4e883b39298092396819a525 Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Mon, 9 May 2016 10:39:12 +0200 Subject: [PATCH 04/13] Add a configure file --- CMakeLists.txt | 8 ++++++++ include/SFGUI/{Config.hpp => Config.hpp.in} | 0 2 files changed, 8 insertions(+) rename include/SFGUI/{Config.hpp => Config.hpp.in} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6bb8967..ebbcfb4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,9 @@ set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules find_package( OpenGL REQUIRED ) find_package( SFML 2.3 REQUIRED COMPONENTS graphics window system ) +CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/include/SFGUI/Config.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/include/SFGUI/Config.hpp) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + # Find X11 for glX on Linux, checking for UNIX would match other UNIX systems as well if( "${CMAKE_SYSTEM_NAME}" MATCHES "Linux" ) find_package( X11 REQUIRED ) @@ -316,6 +319,11 @@ install( DESTINATION . ) +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include + DESTINATION . +) + install( FILES README AUTHORS LICENSE FONT.LICENSE CHANGELOG DESTINATION "${SHARE_PATH}" diff --git a/include/SFGUI/Config.hpp b/include/SFGUI/Config.hpp.in similarity index 100% rename from include/SFGUI/Config.hpp rename to include/SFGUI/Config.hpp.in From 080bc24815cccae4767f4d68df360e705250dadd Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Tue, 10 May 2016 13:22:20 +0200 Subject: [PATCH 05/13] [WIP] Add a simple FilePickerDialog --- CMakeLists.txt | 13 ++ examples/CMakeLists.txt | 10 +- include/SFGUI/Config.hpp.in | 2 + include/SFGUI/FilePickerDialog.hpp | 61 +++++++++ include/SFGUI/Widgets.hpp | 1 + src/SFGUI/FilePickerDialog.cpp | 190 +++++++++++++++++++++++++++++ 6 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 include/SFGUI/FilePickerDialog.hpp create mode 100644 src/SFGUI/FilePickerDialog.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ebbcfb4c..32487788 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set( SFGUI_BUILD_EXAMPLES true CACHE BOOL "Build examples." ) set( SFGUI_BUILD_DOC false CACHE BOOL "Generate API documentation." ) set( SFGUI_INCLUDE_FONT true CACHE BOOL "Include default font in library (DejaVuSans)." ) set( SFML_STATIC_LIBRARIES false CACHE BOOL "Do you want to link SFML statically?" ) +set( SFGUI_BOOST_FILESYSTEM_SUPPORT false CACHE BOOL "Do you want SFGUI to support Boost.FileSystem? (enable FilePickerDialog widget)" ) # Tell CMake where to find additional find modules set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules/" ) @@ -54,6 +55,7 @@ set( "${INCLUDE_PATH}/SFGUI/Engine.inl" "${INCLUDE_PATH}/SFGUI/Engines/BREW.hpp" "${INCLUDE_PATH}/SFGUI/Entry.hpp" + "${INCLUDE_PATH}/SFGUI/FilePickerDialog.hpp" "${INCLUDE_PATH}/SFGUI/FileResourceLoader.hpp" "${INCLUDE_PATH}/SFGUI/Fixed.hpp" "${INCLUDE_PATH}/SFGUI/Frame.hpp" @@ -132,6 +134,7 @@ set( "${SOURCE_PATH}/SFGUI/Engines/BREW/Window.cpp" "${SOURCE_PATH}/SFGUI/Entry.cpp" "${SOURCE_PATH}/SFGUI/FileResourceLoader.cpp" + "${SOURCE_PATH}/SFGUI/FilePickerDialog.cpp" "${SOURCE_PATH}/SFGUI/Fixed.cpp" "${SOURCE_PATH}/SFGUI/Frame.cpp" "${SOURCE_PATH}/SFGUI/GLCheck.cpp" @@ -194,10 +197,15 @@ if( SFGUI_INCLUDE_FONT ) ) endif() +# Include directories include_directories( "${INCLUDE_PATH}" ) include_directories( "${SOURCE_PATH}" ) include_directories( SYSTEM "${SFML_INCLUDE_DIR}" ) include_directories( SYSTEM "${PROJECT_SOURCE_DIR}/extlibs/libELL/include" ) +if( SFGUI_BOOST_FILESYSTEM_SUPPORT ) + find_package(Boost COMPONENTS system filesystem REQUIRED) + include_directories( SYSTEM ${Boost_INCLUDE_DIR} ) +endif() # Set the library output directory set( LIBRARY_OUTPUT_PATH "${PROJECT_BINARY_DIR}/lib" ) @@ -216,6 +224,11 @@ else() set_target_properties( sfgui PROPERTIES MINSIZEREL_POSTFIX -s ) endif() +# Link to Boost.FileSystem if enabled +if( SFGUI_BOOST_FILESYSTEM_SUPPORT ) + target_link_libraries( sfgui ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ) +endif() + # Tell the compiler to export when necessary. set_target_properties( sfgui PROPERTIES DEFINE_SYMBOL SFGUI_EXPORTS ) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e8f0d547..dacd24c6 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -41,6 +41,12 @@ build_example( "CustomWidget" "CustomWidget.cpp" ) build_example( "ListBox" "ListBox.cpp" ) build_example( "SFGUI-Test" "Test.cpp" ) +if( SFGUI_BOOST_FILESYSTEM_SUPPORT ) + build_example( "FilePickerDialog" "FilePickerDialog.cpp" ) + include_directories( SYSTEM ${Boost_INCLUDE_DIR} ) + target_link_libraries( FilePickerDialog ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ) +endif() + # Copy data directory to build cache directory to be able to run examples from # there. Useful for testing stuff. # Don't try to copy if the directories are the same. @@ -50,13 +56,13 @@ if( NOT ( "${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}" ) ) COMMAND "${CMAKE_COMMAND}" ARGS -E copy_directory "${PROJECT_SOURCE_DIR}/examples/data" "${PROJECT_BINARY_DIR}/examples/data" ) - + add_custom_command( TARGET "Image" COMMAND "${CMAKE_COMMAND}" ARGS -E copy_directory "${PROJECT_SOURCE_DIR}/examples/data" "${PROJECT_BINARY_DIR}/examples/data" ) - + add_custom_command( TARGET "Canvas" COMMAND "${CMAKE_COMMAND}" diff --git a/include/SFGUI/Config.hpp.in b/include/SFGUI/Config.hpp.in index b1107c45..d98e9936 100644 --- a/include/SFGUI/Config.hpp.in +++ b/include/SFGUI/Config.hpp.in @@ -32,3 +32,5 @@ #define SFGUI_DEBUG #include // XXX Only for debugging purposes. #endif + +#cmakedefine SFGUI_BOOST_FILESYSTEM_SUPPORT diff --git a/include/SFGUI/FilePickerDialog.hpp b/include/SFGUI/FilePickerDialog.hpp new file mode 100644 index 00000000..a3bc3981 --- /dev/null +++ b/include/SFGUI/FilePickerDialog.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +#if defined(SFGUI_BOOST_FILESYSTEM_SUPPORT) + +#include + +#include +#include +#include +#include +#include +#include + +namespace sfg { + +class SFGUI_API FilePickerDialog : public Window { + public: + typedef std::shared_ptr Ptr; //!< Shared pointer. + typedef std::shared_ptr PtrConst; //!< Shared pointer. + + static Ptr Create( boost::filesystem::path initial_path ); + + protected: + FilePickerDialog( boost::filesystem::path initial_path ); + + private: + sfg::Box::Ptr m_main_box; + sfg::Box::Ptr m_panel_box; + sfg::ListBox::Ptr m_locations_listbox; + + sfg::Box::Ptr m_directory_box; + sfg::Box::Ptr m_directory_entry_box; + sfg::Button::Ptr m_new_directory_button; + sfg::Entry::Ptr m_current_directory_entry; + sfg::ListBox::Ptr m_directory_paths_listbox; + + sfg::Box::Ptr m_filename_box; + sfg::Entry::Ptr m_filename_entry; + + sfg::Box::Ptr m_buttons_box; + sfg::Button::Ptr m_ok_button; + sfg::Button::Ptr m_cancel_button; + + boost::filesystem::path m_current_path; + + bool m_show_hidden_files; + + void UpdateLocationsPaths(); + void UpdateCurrentDirectoryPath(); + void UpdateDirectoryPaths(); + void UpdateOkButtonState(); + + void OnPathsListBoxSelectionChanged(); + void OnFilenameEntryTextChanged(); +}; + +} + +#endif diff --git a/include/SFGUI/Widgets.hpp b/include/SFGUI/Widgets.hpp index ebc07f25..39eff250 100644 --- a/include/SFGUI/Widgets.hpp +++ b/include/SFGUI/Widgets.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/src/SFGUI/FilePickerDialog.cpp b/src/SFGUI/FilePickerDialog.cpp new file mode 100644 index 00000000..90e14dd4 --- /dev/null +++ b/src/SFGUI/FilePickerDialog.cpp @@ -0,0 +1,190 @@ +#include + +#if defined(SFGUI_BOOST_FILESYSTEM_SUPPORT) + +#include +#if BOOST_OS_LINUX +#include +#include +#include +#endif + +namespace fs = boost::filesystem; + +namespace sfg { + +FilePickerDialog::Ptr FilePickerDialog::Create( boost::filesystem::path initial_path ) { + auto ptr = Ptr( new FilePickerDialog( initial_path ) ); + ptr->Add(ptr->m_main_box); + + ptr->m_main_box->PackEnd(ptr->m_panel_box, true, true); + ptr->m_main_box->PackEnd(ptr->m_buttons_box, false, true); + + ptr->m_panel_box->PackEnd(ptr->m_locations_listbox, false, true); + ptr->m_panel_box->PackEnd(ptr->m_directory_box, true, true); + + ptr->m_directory_box->PackEnd(ptr->m_directory_entry_box, false, true); + ptr->m_directory_box->PackEnd(ptr->m_directory_paths_listbox, true, true); + ptr->m_directory_box->PackEnd(ptr->m_filename_box, false, true); + + ptr->m_directory_entry_box->PackEnd(ptr->m_current_directory_entry, true, true); + ptr->m_directory_entry_box->PackEnd(ptr->m_new_directory_button, false, true); + + ptr->m_filename_box->PackEnd(ptr->m_filename_entry, true, true); + + ptr->m_buttons_box->PackEnd(ptr->m_ok_button, true, true); + ptr->m_buttons_box->PackEnd(ptr->m_cancel_button, false, true); + + ptr->RequestResize(); + ptr->m_locations_listbox->RequestResize(); + ptr->m_new_directory_button->RequestResize(); + ptr->m_current_directory_entry->RequestResize(); + ptr->m_directory_paths_listbox->RequestResize(); + ptr->m_filename_entry->RequestResize(); + ptr->m_ok_button->RequestResize(); + ptr->m_cancel_button->RequestResize(); + + return ptr; +} + +FilePickerDialog::FilePickerDialog( boost::filesystem::path initial_path ) : + Window( Window::Style::TOPLEVEL ), + m_main_box( sfg::Box::Create( sfg::Box::Orientation::VERTICAL, 5.f ) ), + m_panel_box( sfg::Box::Create( sfg::Box::Orientation::HORIZONTAL, 5.f ) ), + m_locations_listbox( sfg::ListBox::Create( ) ), + m_directory_box( sfg::Box::Create( sfg::Box::Orientation::VERTICAL, 5.f ) ), + m_directory_entry_box( sfg::Box::Create( sfg::Box::Orientation::HORIZONTAL, 5.f ) ), + m_new_directory_button( sfg::Button::Create( "+" ) ), + m_current_directory_entry( sfg::Entry::Create( ) ), + m_directory_paths_listbox( sfg::ListBox::Create( ) ), + m_filename_box( sfg::Box::Create( sfg::Box::Orientation::HORIZONTAL, 5.f ) ), + m_filename_entry( sfg::Entry::Create( ) ), + m_buttons_box( sfg::Box::Create( sfg::Box::Orientation::HORIZONTAL, 5.f ) ), + m_ok_button( sfg::Button::Create( "Ok" ) ), + m_cancel_button( sfg::Button::Create( "Cancel" ) ), + m_current_path( initial_path ), + m_show_hidden_files( false ) +{ + UpdateLocationsPaths(); + UpdateCurrentDirectoryPath(); + UpdateDirectoryPaths(); + UpdateOkButtonState(); + + m_directory_paths_listbox->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( &FilePickerDialog::OnPathsListBoxSelectionChanged, this ) ); + m_filename_entry->GetSignal( sfg::Entry::OnTextChanged ).Connect( std::bind( &FilePickerDialog::OnFilenameEntryTextChanged, this ) ); +} + +void FilePickerDialog::UpdateLocationsPaths() { +#if BOOST_OS_WINDOWS + //Test all possible drives and add the existing ones into the location list + for( wchar_t driveLetter = L'A'; driveLetter != L'Z'; driveLetter++ ) { + if( fs::exists( fs::path( driveLetter + L':' + L'\\' ) ) ) { + m_locations_listbox->AppendItem( driveLetter + L':' + L'\\' ); + } + } +#elif BOOST_OS_LINUX + //Add some common locations + m_locations_listbox->AppendItem( "/" ); + + //Add the current user's home + const char *homedir; + if ((homedir = getenv("HOME")) == NULL) { + homedir = getpwuid(getuid())->pw_dir; + } + + m_locations_listbox->AppendItem( std::string(homedir) ); + +#endif +} + +void FilePickerDialog::UpdateCurrentDirectoryPath() { + m_current_directory_entry->SetText( m_current_path.wstring() ); +} + +void FilePickerDialog::UpdateDirectoryPaths() { + m_directory_paths_listbox->Clear(); + m_filename_entry->SetText( "" ); //Clear the selected filename + + try { + if( fs::exists( m_current_path ) && fs::is_directory( m_current_path ) ) { + m_directory_paths_listbox->AppendItem(".."); + + std::vector subdirectories; + std::vector subfiles; + + //Iterate all files and directories + for( fs::directory_entry& e : fs::directory_iterator( m_current_path ) ) { + if( fs::is_directory( e.path() ) ) { + if( e.path().filename().wstring()[0] != L'.' || m_show_hidden_files ) { + subdirectories.push_back( e.path().filename() ); + } + } + else if( fs::is_regular_file( e.path() ) ) { + if( e.path().filename().wstring()[0] != L'.' || m_show_hidden_files ) { + subfiles.push_back( e.path().filename() ); + } + } + } + + std::sort( subdirectories.begin(), subdirectories.end() ); + std::sort( subfiles.begin(), subfiles.end() ); + + for( fs::path& path : subdirectories ) { + m_directory_paths_listbox->AppendItem(path.filename().wstring()); + } + for( fs::path& path : subfiles ) { + m_directory_paths_listbox->AppendItem(path.filename().wstring()); + } + } + } + catch( const fs::filesystem_error& ex ) { + + } +} + +void FilePickerDialog::UpdateOkButtonState() { + if( m_filename_entry->GetText().getSize() > 0 && + fs::exists( fs::absolute( m_filename_entry->GetText().toWideString(), m_current_path ) ) && + fs::is_regular_file( fs::absolute( m_filename_entry->GetText().toWideString(), m_current_path ) ) ) { + m_ok_button->SetState( sfg::Widget::State::NORMAL ); + } + else { + m_ok_button->SetState( sfg::Widget::State::INSENSITIVE ); + } +} + +void FilePickerDialog::OnPathsListBoxSelectionChanged() { + if(m_directory_paths_listbox->GetSelectedItemsCount() > 0) { + fs::path selected_path = m_directory_paths_listbox->GetSelectedItemText(0).toWideString(); + + if( fs::is_directory( fs::absolute(selected_path, m_current_path) ) ) { + fs::path new_path = fs::canonical(selected_path, m_current_path); + m_current_path = new_path; + + UpdateCurrentDirectoryPath(); + UpdateDirectoryPaths(); + UpdateOkButtonState(); + } + else if( fs::is_regular_file( fs::absolute(selected_path, m_current_path) ) ) { + m_filename_entry->SetText( selected_path.wstring() ); + UpdateOkButtonState(); + } + } +} + +void FilePickerDialog::OnFilenameEntryTextChanged() { + m_directory_paths_listbox->ClearSelection(); + + //Try to select the corresponding entry in the folder subpaths list. + for( sfg::ListBox::IndexType i = 0; i < m_directory_paths_listbox->GetItemsCount(); ++i ) { + if( m_filename_entry->GetText() == m_directory_paths_listbox->GetItemText( i ) ) { + m_directory_paths_listbox->SetSelection( i ); + } + } + + UpdateOkButtonState(); +} + +} + +#endif From d9dbb32b5ee62268bac4f76d8a098266b7204b62 Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Tue, 10 May 2016 13:34:54 +0200 Subject: [PATCH 06/13] [WIP] Add the FilePickerDialog example --- examples/FilePickerDialog.cpp | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 examples/FilePickerDialog.cpp diff --git a/examples/FilePickerDialog.cpp b/examples/FilePickerDialog.cpp new file mode 100644 index 00000000..056cf74e --- /dev/null +++ b/examples/FilePickerDialog.cpp @@ -0,0 +1,75 @@ +// Always include the necessary header files. +// Including SFGUI/Widgets.hpp includes everything +// you can possibly need automatically. +#include +#include + +#include + +int main() { + // Create the main SFML window + sf::RenderWindow app_window( sf::VideoMode( 800, 600 ), "SFGUI Window Example", sf::Style::Titlebar | sf::Style::Close ); + + // We have to do this because we don't use SFML to draw. + app_window.resetGLStates(); + + // Create an SFGUI. This is required before doing anything with SFGUI. + sfg::SFGUI sfgui; + + // Create our main SFGUI window + + // Almost everything in SFGUI is handled through smart pointers + // for automatic resource management purposes. You create them + // and they will automatically be destroyed when the time comes. + + // Creation of widgets is always done with it's Create() method + // which will return a smart pointer owning the new widget. + auto window = sfg::FilePickerDialog::Create( "/home/victor" ); + + // Here we can set the window's title bar text. + window->SetTitle( "A really really really really long title" ); + + // For more things to set around the window refer to the + // API documentation. + + // Start the game loop + while ( app_window.isOpen() ) { + // Process events + sf::Event event; + + while ( app_window.pollEvent( event ) ) { + // Every frame we have to send SFML events to the window + // to enable user interactivity. Without doing this your + // GUI is nothing but a big colorful picture ;) + window->HandleEvent( event ); + + // Close window : exit + if ( event.type == sf::Event::Closed ) { + app_window.close(); + } + } + + // Update the GUI, note that you shouldn't normally + // pass 0 seconds to the update method. + window->Update( 0.f ); + + // Clear screen + app_window.clear(); + + // After drawing the rest of your game, you have to let the GUI + // render itself. If you don't do this you will never be able + // to see it ;) + sfgui.Display( app_window ); + + // NOTICE + // Because the window doesn't have any children it will shrink to + // it's minimum size of (0,0) resulting in you not seeing anything + // except the title bar text ;) Don't worry, in the Label example + // you'll get to see more. + + // Update the window + app_window.display(); + } + + return EXIT_SUCCESS; +} From 30e1cd643e58ba77d748a753e8d7abe7d36f1ba2 Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Tue, 10 May 2016 13:54:34 +0200 Subject: [PATCH 07/13] [WIP] Improve the FilePickerDialog --- include/SFGUI/FilePickerDialog.hpp | 1 + src/SFGUI/FilePickerDialog.cpp | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/include/SFGUI/FilePickerDialog.hpp b/include/SFGUI/FilePickerDialog.hpp index a3bc3981..462bba2a 100644 --- a/include/SFGUI/FilePickerDialog.hpp +++ b/include/SFGUI/FilePickerDialog.hpp @@ -52,6 +52,7 @@ class SFGUI_API FilePickerDialog : public Window { void UpdateDirectoryPaths(); void UpdateOkButtonState(); + void OnLocationsListBoxSelectionChanged(); void OnPathsListBoxSelectionChanged(); void OnFilenameEntryTextChanged(); }; diff --git a/src/SFGUI/FilePickerDialog.cpp b/src/SFGUI/FilePickerDialog.cpp index 90e14dd4..e4631778 100644 --- a/src/SFGUI/FilePickerDialog.cpp +++ b/src/SFGUI/FilePickerDialog.cpp @@ -70,6 +70,7 @@ FilePickerDialog::FilePickerDialog( boost::filesystem::path initial_path ) : UpdateDirectoryPaths(); UpdateOkButtonState(); + m_locations_listbox->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( &FilePickerDialog::OnLocationsListBoxSelectionChanged, this ) ); m_directory_paths_listbox->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( &FilePickerDialog::OnPathsListBoxSelectionChanged, this ) ); m_filename_entry->GetSignal( sfg::Entry::OnTextChanged ).Connect( std::bind( &FilePickerDialog::OnFilenameEntryTextChanged, this ) ); } @@ -78,8 +79,11 @@ void FilePickerDialog::UpdateLocationsPaths() { #if BOOST_OS_WINDOWS //Test all possible drives and add the existing ones into the location list for( wchar_t driveLetter = L'A'; driveLetter != L'Z'; driveLetter++ ) { - if( fs::exists( fs::path( driveLetter + L':' + L'\\' ) ) ) { - m_locations_listbox->AppendItem( driveLetter + L':' + L'\\' ); + std::wstring drivePath; + drivePath += driveLetter; + drivePath += L":\\"; + if( fs::exists( fs::path( drivePath ) ) ) { + m_locations_listbox->AppendItem( drivePath ); } } #elif BOOST_OS_LINUX @@ -153,8 +157,21 @@ void FilePickerDialog::UpdateOkButtonState() { } } +void FilePickerDialog::OnLocationsListBoxSelectionChanged() { + if( m_locations_listbox->GetSelectedItemsCount() > 0 ) { + fs::path selected_path = m_locations_listbox->GetSelectedItemText(0).toWideString(); + if( fs::exists( selected_path ) ) { + m_current_path = selected_path; + + UpdateCurrentDirectoryPath(); + UpdateDirectoryPaths(); + UpdateOkButtonState(); + } + } +} + void FilePickerDialog::OnPathsListBoxSelectionChanged() { - if(m_directory_paths_listbox->GetSelectedItemsCount() > 0) { + if( m_directory_paths_listbox->GetSelectedItemsCount() > 0 ) { fs::path selected_path = m_directory_paths_listbox->GetSelectedItemText(0).toWideString(); if( fs::is_directory( fs::absolute(selected_path, m_current_path) ) ) { From 12536949e60834036aa1e09295a4e52718e6b073 Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Thu, 12 May 2016 23:55:25 +0200 Subject: [PATCH 08/13] Add OnOk and OnCancel signals to the FilePickerDialog, add GetSelectedPath() getter --- examples/FilePickerDialog.cpp | 13 +++++++++ include/SFGUI/FilePickerDialog.hpp | 9 +++++++ src/SFGUI/FilePickerDialog.cpp | 42 +++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/examples/FilePickerDialog.cpp b/examples/FilePickerDialog.cpp index 056cf74e..115196dd 100644 --- a/examples/FilePickerDialog.cpp +++ b/examples/FilePickerDialog.cpp @@ -29,6 +29,19 @@ int main() { // Here we can set the window's title bar text. window->SetTitle( "A really really really really long title" ); + window->GetSignal( sfg::FilePickerDialog::OnCancel ).Connect( + []() + { + std::cout << "Path selection cancelled !" << std::endl; + } + ); + window->GetSignal( sfg::FilePickerDialog::OnOk ).Connect( + [window]() + { + std::cout << "Selected path : \"" << window->GetSelectedPath().toAnsiString() << "\"" << std::endl; + } + ); + // For more things to set around the window refer to the // API documentation. diff --git a/include/SFGUI/FilePickerDialog.hpp b/include/SFGUI/FilePickerDialog.hpp index 462bba2a..530b8d91 100644 --- a/include/SFGUI/FilePickerDialog.hpp +++ b/include/SFGUI/FilePickerDialog.hpp @@ -22,6 +22,11 @@ class SFGUI_API FilePickerDialog : public Window { static Ptr Create( boost::filesystem::path initial_path ); + sf::String GetSelectedPath() const; + + static Signal::SignalID OnCancel; + static Signal::SignalID OnOk; + protected: FilePickerDialog( boost::filesystem::path initial_path ); @@ -52,7 +57,11 @@ class SFGUI_API FilePickerDialog : public Window { void UpdateDirectoryPaths(); void UpdateOkButtonState(); + void CancelDialog(); + void OkDialog(); + void OnLocationsListBoxSelectionChanged(); + void OnCurrentDirectoryEntryTextChanged(); void OnPathsListBoxSelectionChanged(); void OnFilenameEntryTextChanged(); }; diff --git a/src/SFGUI/FilePickerDialog.cpp b/src/SFGUI/FilePickerDialog.cpp index e4631778..76103a0c 100644 --- a/src/SFGUI/FilePickerDialog.cpp +++ b/src/SFGUI/FilePickerDialog.cpp @@ -13,6 +13,9 @@ namespace fs = boost::filesystem; namespace sfg { +Signal::SignalID FilePickerDialog::OnCancel = 0; +Signal::SignalID FilePickerDialog::OnOk = 0; + FilePickerDialog::Ptr FilePickerDialog::Create( boost::filesystem::path initial_path ) { auto ptr = Ptr( new FilePickerDialog( initial_path ) ); ptr->Add(ptr->m_main_box); @@ -48,7 +51,7 @@ FilePickerDialog::Ptr FilePickerDialog::Create( boost::filesystem::path initial_ } FilePickerDialog::FilePickerDialog( boost::filesystem::path initial_path ) : - Window( Window::Style::TOPLEVEL ), + Window( Window::Style::TOPLEVEL | Window::Style::CLOSE ), m_main_box( sfg::Box::Create( sfg::Box::Orientation::VERTICAL, 5.f ) ), m_panel_box( sfg::Box::Create( sfg::Box::Orientation::HORIZONTAL, 5.f ) ), m_locations_listbox( sfg::ListBox::Create( ) ), @@ -71,8 +74,26 @@ FilePickerDialog::FilePickerDialog( boost::filesystem::path initial_path ) : UpdateOkButtonState(); m_locations_listbox->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( &FilePickerDialog::OnLocationsListBoxSelectionChanged, this ) ); + m_current_directory_entry->GetSignal( sfg::Entry::OnTextChanged ).Connect( std::bind( &FilePickerDialog::OnCurrentDirectoryEntryTextChanged, this ) ); m_directory_paths_listbox->GetSignal( sfg::ListBox::OnSelect ).Connect( std::bind( &FilePickerDialog::OnPathsListBoxSelectionChanged, this ) ); m_filename_entry->GetSignal( sfg::Entry::OnTextChanged ).Connect( std::bind( &FilePickerDialog::OnFilenameEntryTextChanged, this ) ); + + //Connect buttons and the close button + GetSignal( sfg::Window::OnCloseButton ).Connect( std::bind( &FilePickerDialog::CancelDialog, this ) ); + m_cancel_button->GetSignal( sfg::Widget::OnLeftClick ).Connect( std::bind( &FilePickerDialog::CancelDialog, this ) ); + m_ok_button->GetSignal( sfg::Widget::OnLeftClick ).Connect( std::bind( &FilePickerDialog::OkDialog, this ) ); +} + +sf::String FilePickerDialog::GetSelectedPath() const { + if( m_filename_entry->GetText().getSize() > 0 && + fs::exists( fs::absolute( m_filename_entry->GetText().toWideString(), m_current_path ) ) && + fs::is_regular_file( fs::absolute( m_filename_entry->GetText().toWideString(), m_current_path ) ) ) { + + return sf::String( fs::canonical( m_filename_entry->GetText().toWideString(), m_current_path ).wstring() ); + } + else { + return sf::String(); + } } void FilePickerDialog::UpdateLocationsPaths() { @@ -157,6 +178,18 @@ void FilePickerDialog::UpdateOkButtonState() { } } +void FilePickerDialog::CancelDialog() { + Show(false); + + GetSignals().Emit( OnCancel ); +} + +void FilePickerDialog::OkDialog() { + Show(false); + + GetSignals().Emit( OnOk ); +} + void FilePickerDialog::OnLocationsListBoxSelectionChanged() { if( m_locations_listbox->GetSelectedItemsCount() > 0 ) { fs::path selected_path = m_locations_listbox->GetSelectedItemText(0).toWideString(); @@ -170,6 +203,13 @@ void FilePickerDialog::OnLocationsListBoxSelectionChanged() { } } +void FilePickerDialog::OnCurrentDirectoryEntryTextChanged() { + m_current_path = m_current_directory_entry->GetText().toWideString(); + + UpdateDirectoryPaths(); + UpdateOkButtonState(); +} + void FilePickerDialog::OnPathsListBoxSelectionChanged() { if( m_directory_paths_listbox->GetSelectedItemsCount() > 0 ) { fs::path selected_path = m_directory_paths_listbox->GetSelectedItemText(0).toWideString(); From ca48d16fb3d79eeb45842771e8c81628ef0e24ac Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Fri, 13 May 2016 08:51:03 +0200 Subject: [PATCH 09/13] Fix warnings in ListBox.cpp --- src/SFGUI/ListBox.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/SFGUI/ListBox.cpp b/src/SFGUI/ListBox.cpp index 531382b9..25f832cd 100644 --- a/src/SFGUI/ListBox.cpp +++ b/src/SFGUI/ListBox.cpp @@ -367,7 +367,6 @@ void ListBox::SetImagesSize(sf::Vector2f size) { } float ListBox::GetItemHeight() const { - auto text_padding = Context::Get().GetEngine().GetProperty( "Padding", 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() ); @@ -462,7 +461,6 @@ ListBox::IndexType ListBox::GetItemAt( float y ) const { 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 = GetItemHeight(); IndexType item_index = 0; @@ -516,7 +514,6 @@ void ListBox::UpdateDisplayedItems() { 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 = GetItemHeight(); // Update the displayed items count. From 4775e91b80f0c366e680fc7a7f0573f5ce995630 Mon Sep 17 00:00:00 2001 From: Victor Levasseur Date: Fri, 13 May 2016 10:00:31 +0200 Subject: [PATCH 10/13] Add OnReturnPressed signal to sfg::Entry --- include/SFGUI/Entry.hpp | 1 + src/SFGUI/Entry.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/SFGUI/Entry.hpp b/include/SFGUI/Entry.hpp index 6223607c..8a0156ed 100644 --- a/include/SFGUI/Entry.hpp +++ b/include/SFGUI/Entry.hpp @@ -89,6 +89,7 @@ class SFGUI_API Entry : public Widget { // Signals. static Signal::SignalID OnTextChanged; //!< Fired when the text changes. + static Signal::SignalID OnReturnPressed; //!< Fired when enter is pressed. protected: /** Ctor. diff --git a/src/SFGUI/Entry.cpp b/src/SFGUI/Entry.cpp index a1153843..09bf254d 100644 --- a/src/SFGUI/Entry.cpp +++ b/src/SFGUI/Entry.cpp @@ -10,6 +10,7 @@ namespace sfg { // Signals. Signal::SignalID Entry::OnTextChanged = 0; +Signal::SignalID Entry::OnReturnPressed = 0; Entry::Entry() : m_string(), @@ -242,6 +243,9 @@ void Entry::HandleKeyEvent( sf::Keyboard::Key key, bool press ) { case sf::Keyboard::Right: { MoveCursor( 1 ); } break; + case sf::Keyboard::Return: { + GetSignals().Emit( OnReturnPressed ); + } break; default: break; } } From e1e46c3342f6ef3d394ad2b58878ee9ec994b697 Mon Sep 17 00:00:00 2001 From: Zax37 Date: Sat, 11 Apr 2020 17:08:26 +0200 Subject: [PATCH 11/13] Add button alignment. --- include/SFGUI/Button.hpp | 23 ++++++++++++++++++ src/SFGUI/Button.cpp | 16 +++++++++++++ src/SFGUI/Engines/BREW/Button.cpp | 39 ++++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/include/SFGUI/Button.hpp b/include/SFGUI/Button.hpp index f2ed9c8a..048563d5 100644 --- a/include/SFGUI/Button.hpp +++ b/include/SFGUI/Button.hpp @@ -13,6 +13,16 @@ class Image; */ class SFGUI_API Button : public Bin { public: + enum Alignment { + CENTER, + LEFT = 0x1, + RIGHT = 0x2, + TOP = 0x4, + BOTTOM = 0x8, + HORIZONTAL = LEFT + RIGHT, + VERTICAL = TOP + BOTTOM, + }; + typedef std::shared_ptr