Index: ps/trunk/source/gui/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp (revision 17148) +++ ps/trunk/source/gui/CDropDown.cpp (revision 17149) @@ -1,522 +1,524 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CDropDown.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "soundmanager/ISoundManager.h" CDropDown::CDropDown() : m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1) { AddSetting(GUIST_float, "button_width"); AddSetting(GUIST_float, "dropdown_size"); AddSetting(GUIST_float, "dropdown_buffer"); // AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_CStrW, "sound_closed"); AddSetting(GUIST_CStrW, "sound_disabled"); AddSetting(GUIST_CStrW, "sound_enter"); AddSetting(GUIST_CStrW, "sound_leave"); AddSetting(GUIST_CStrW, "sound_opened"); // AddSetting(GUIST_CGUISpriteInstance, "sprite"); // Background that sits around the size AddSetting(GUIST_CGUISpriteInstance, "sprite_list"); // Background of the drop down list AddSetting(GUIST_CGUISpriteInstance, "sprite2"); // Button that sits to the right AddSetting(GUIST_CGUISpriteInstance, "sprite2_over"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled"); AddSetting(GUIST_EVAlign, "text_valign"); // Add these in CList! And implement TODO //AddSetting(GUIST_CColor, "textcolor_over"); //AddSetting(GUIST_CColor, "textcolor_pressed"); //AddSetting(GUIST_CColor, "textcolor_disabled"); // Scrollbar is forced to be true. GUI::SetSetting(this, "scrollbar", true); } CDropDown::~CDropDown() { } void CDropDown::SetupText() { SetupListRect(); CList::SetupText(); } void CDropDown::HandleMessage(SGUIMessage& Message) { // Important switch (Message.type) { case GUIM_SETTINGS_UPDATED: { // Update cached list rect if (Message.value == "size" || Message.value == "absolute" || Message.value == "dropdown_size" || Message.value == "dropdown_buffer" || Message.value == "scrollbar_style" || Message.value == "button_width") { SetupListRect(); } break; } case GUIM_MOUSE_MOTION: { if (!m_Open) break; CPos mouse = GetMousePos(); if (!GetListRect().PointInside(mouse)) break; bool scrollbar; CGUIList* pList; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSettingPointer(this, "list", pList); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); CRect rect = GetListRect(); mouse.y += scroll; int set = -1; for (int i = 0; i < (int)pList->m_Items.size(); ++i) { if (mouse.y >= rect.top + m_ItemsYPositions[i] && mouse.y < rect.top + m_ItemsYPositions[i+1] && // mouse is not over scroll-bar - !(mouse.x >= GetScrollBar(0).GetOuterRect().left && - mouse.x <= GetScrollBar(0).GetOuterRect().right)) + (m_HideScrollBar || + mouse.x < GetScrollBar(0).GetOuterRect().left || + mouse.x > GetScrollBar(0).GetOuterRect().right)) { set = i; } } if (set != -1) { m_ElementHighlight = set; //UpdateAutoScroll(); } break; } case GUIM_MOUSE_ENTER: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_enter", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } case GUIM_MOUSE_LEAVE: { GUI::GetSetting(this, "selected", m_ElementHighlight); bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_leave", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } // We can't inherent this routine from CList, because we need to include // a mouse click to open the dropdown, also the coordinates are changed. case GUIM_MOUSE_PRESS_LEFT: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_disabled", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } if (!m_Open) { CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); if (pList->m_Items.empty()) return; m_Open = true; GetScrollBar(0).SetZ(GetBufferedZ()); GUI::GetSetting(this, "selected", m_ElementHighlight); // Start at the position of the selected item, if possible. GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_opened", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); return; // overshadow } else { CPos mouse = GetMousePos(); // If the regular area is pressed, then abort, and close. if (m_CachedActualSize.PointInside(mouse)) { m_Open = false; GetScrollBar(0).SetZ(GetBufferedZ()); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); return; // overshadow } - if (!(mouse.x >= GetScrollBar(0).GetOuterRect().left && - mouse.x <= GetScrollBar(0).GetOuterRect().right) && - mouse.y >= GetListRect().top) + if (m_HideScrollBar || + mouse.x < GetScrollBar(0).GetOuterRect().left || + mouse.x > GetScrollBar(0).GetOuterRect().right || + mouse.y < GetListRect().top) { m_Open = false; GetScrollBar(0).SetZ(GetBufferedZ()); } } break; } case GUIM_MOUSE_WHEEL_DOWN: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) break; ++m_ElementHighlight; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_MOUSE_WHEEL_UP: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight - 1 < 0) break; m_ElementHighlight--; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_LOST_FOCUS: { if (m_Open) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } m_Open = false; break; } case GUIM_LOAD: SetupListRect(); break; default: break; } // Important that this is after, so that overshadowed implementations aren't processed CList::HandleMessage(Message); // As HandleMessage functions return void, we need to manually verify // whether the child list's items were modified. if (CList::GetModified()) SetupText(); } InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev) { InReaction result = IN_PASS; bool update_highlight = false; if (ev->ev.type == SDL_KEYDOWN) { int szChar = ev->ev.key.keysym.sym; switch (szChar) { case '\r': m_Open = false; result = IN_HANDLED; break; case SDLK_HOME: case SDLK_END: case SDLK_UP: case SDLK_DOWN: case SDLK_PAGEUP: case SDLK_PAGEDOWN: if (!m_Open) return IN_PASS; // Set current selected item to highlighted, before // then really processing these in CList::ManuallyHandleEvent() GUI::SetSetting(this, "selected", m_ElementHighlight); update_highlight = true; break; default: // If we have inputed a character try to get the closest element to it. // TODO: not too nice and doesn't deal with dashes. if (m_Open && ((szChar >= SDLK_a && szChar <= SDLK_z) || szChar == SDLK_SPACE || (szChar >= SDLK_0 && szChar <= SDLK_9) #if !SDL_VERSION_ATLEAST(2,0,0) || (szChar >= SDLK_KP0 && szChar <= SDLK_KP9))) #else // SDL2 || (szChar >= SDLK_KP_0 && szChar <= SDLK_KP_9))) #endif { // arbitrary 1 second limit to add to string or start fresh. // maximal amount of characters is 100, which imo is far more than enough. if (timer_Time() - m_TimeOfLastInput > 1.0 || m_InputBuffer.length() >= 100) m_InputBuffer = szChar; else m_InputBuffer += szChar; m_TimeOfLastInput = timer_Time(); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); // let's look for the closest element // basically it's alphabetic order and "as many letters as we can get". int closest = -1; int bestIndex = -1; int difference = 1250; for (int i = 0; i < (int)pList->m_Items.size(); ++i) { int indexOfDifference = 0; int diff = 0; for (size_t j = 0; j < m_InputBuffer.length(); ++j) { diff = abs(pList->m_Items[i].GetOriginalString().LowerCase()[j] - (int)m_InputBuffer[j]); if (diff == 0) indexOfDifference = j+1; else break; } if (indexOfDifference > bestIndex || (indexOfDifference >= bestIndex && diff < difference)) { bestIndex = indexOfDifference; closest = i; difference = diff; } } // let's select the closest element. There should basically always be one. if (closest != -1) { GUI::SetSetting(this, "selected", closest); update_highlight = true; GetScrollBar(0).SetPos(m_ItemsYPositions[closest] - 60); } result = IN_HANDLED; } break; } } if (CList::ManuallyHandleEvent(ev) == IN_HANDLED) result = IN_HANDLED; if (update_highlight) GUI::GetSetting(this, "selected", m_ElementHighlight); return result; } void CDropDown::SetupListRect() { float size, buffer, button_width; GUI::GetSetting(this, "dropdown_size", size); GUI::GetSetting(this, "dropdown_buffer", buffer); GUI::GetSetting(this, "button_width", button_width); if (m_ItemsYPositions.empty() || m_ItemsYPositions.back() >= size) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom+buffer, m_CachedActualSize.right, m_CachedActualSize.bottom+buffer + size); m_HideScrollBar = false; } else { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom+buffer, m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width, m_CachedActualSize.bottom+buffer + m_ItemsYPositions.back()); // We also need to hide the scrollbar m_HideScrollBar = true; } } CRect CDropDown::GetListRect() const { return m_CachedListRect; } bool CDropDown::MouseOver() { if(!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); if (m_Open) { CRect rect(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, GetListRect().bottom); return rect.PointInside(GetMousePos()); } else return m_CachedActualSize.PointInside(GetMousePos()); } void CDropDown::Draw() { if (!GetGUI()) return; float bz = GetBufferedZ(); float dropdown_size, button_width; GUI::GetSetting(this, "dropdown_size", dropdown_size); GUI::GetSetting(this, "button_width", button_width); CGUISpriteInstance* sprite; CGUISpriteInstance* sprite2; CGUISpriteInstance* sprite2_second; int cell_id, selected = 0; CColor color; GUI::GetSettingPointer(this, "sprite", sprite); GUI::GetSettingPointer(this, "sprite2", sprite2); GUI::GetSetting(this, "cell_id", cell_id); GUI::GetSetting(this, "selected", selected); GUI::GetSetting(this, "textcolor", color); bool enabled; GUI::GetSetting(this, "enabled", enabled); GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); if (button_width > 0.f) { CRect rect(m_CachedActualSize.right-button_width, m_CachedActualSize.top, m_CachedActualSize.right, m_CachedActualSize.bottom); if (!enabled) { GUI::GetSettingPointer(this, "sprite2_disabled", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_Open) { GUI::GetSettingPointer(this, "sprite2_pressed", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_MouseHovering) { GUI::GetSettingPointer(this, "sprite2_over", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else GetGUI()->DrawSprite(*sprite2, cell_id, bz+0.05f, rect); } if (selected != -1) // TODO: Maybe check validity completely? { // figure out clipping rectangle CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right-button_width, m_CachedActualSize.bottom); CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); DrawText(selected, color, pos, bz+0.1f, cliparea); } bool* scrollbar = NULL; bool old; GUI::GetSettingPointer(this, "scrollbar", scrollbar); old = *scrollbar; if (m_Open) { if (m_HideScrollBar) *scrollbar = false; DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); if (m_HideScrollBar) *scrollbar = old; } } // When a dropdown list is opened, it needs to be visible above all the other // controls on the page. The only way I can think of to do this is to increase // its z value when opened, so that it's probably on top. float CDropDown::GetBufferedZ() const { float bz = CList::GetBufferedZ(); if (m_Open) return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value else return bz; } Index: ps/trunk/source/gui/CGUIScrollBarVertical.cpp =================================================================== --- ps/trunk/source/gui/CGUIScrollBarVertical.cpp (revision 17148) +++ ps/trunk/source/gui/CGUIScrollBarVertical.cpp (revision 17149) @@ -1,196 +1,196 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CGUIScrollBarVertical.h" #include "GUI.h" #include "ps/CLogger.h" CGUIScrollBarVertical::CGUIScrollBarVertical() { } CGUIScrollBarVertical::~CGUIScrollBarVertical() { } void CGUIScrollBarVertical::SetPosFromMousePos(const CPos& mouse) { if (!GetStyle()) return; /** * Calculate the position for the top of the item being scrolled */ float emptyBackground = m_Length - m_BarSize; if (GetStyle()->m_UseEdgeButtons) emptyBackground -= GetStyle()->m_Width * 2; m_Pos = m_PosWhenPressed + GetMaxPos() * (mouse.y - m_BarPressedAtPos.y) / emptyBackground; } void CGUIScrollBarVertical::Draw() { if (!GetStyle()) { LOGWARNING("Attempt to draw scrollbar without a style."); return; } - if (GetGUI() && GetMaxPos() != 1) + if (GetGUI() && IsVisible()) { CRect outline = GetOuterRect(); // Draw background GetGUI()->DrawSprite(GetStyle()->m_SpriteBackVertical, 0, m_Z+0.1f, CRect(outline.left, outline.top+(GetStyle()->m_UseEdgeButtons?GetStyle()->m_Width:0), outline.right, outline.bottom-(GetStyle()->m_UseEdgeButtons?GetStyle()->m_Width:0)) ); if (GetStyle()->m_UseEdgeButtons) { // Get Appropriate sprites const CGUISpriteInstance* button_top; const CGUISpriteInstance* button_bottom; // figure out what sprite to use for top button if (m_ButtonMinusHovered) { if (m_ButtonMinusPressed) button_top = &GUI<>::FallBackSprite(GetStyle()->m_SpriteButtonTopPressed, GetStyle()->m_SpriteButtonTop); else button_top = &GUI<>::FallBackSprite(GetStyle()->m_SpriteButtonTopOver, GetStyle()->m_SpriteButtonTop); } else button_top = &GetStyle()->m_SpriteButtonTop; // figure out what sprite to use for bottom button if (m_ButtonPlusHovered) { if (m_ButtonPlusPressed) button_bottom = &GUI<>::FallBackSprite(GetStyle()->m_SpriteButtonBottomPressed, GetStyle()->m_SpriteButtonBottom); else button_bottom = &GUI<>::FallBackSprite(GetStyle()->m_SpriteButtonBottomOver, GetStyle()->m_SpriteButtonBottom); } else button_bottom = &GetStyle()->m_SpriteButtonBottom; // Draw top button GetGUI()->DrawSprite(*button_top, 0, m_Z+0.2f, CRect(outline.left, outline.top, outline.right, outline.top+GetStyle()->m_Width) ); // Draw bottom button GetGUI()->DrawSprite(*button_bottom, 0, m_Z+0.2f, CRect(outline.left, outline.bottom-GetStyle()->m_Width, outline.right, outline.bottom) ); } // Draw bar GetGUI()->DrawSprite(GetStyle()->m_SpriteBarVertical, 0, m_Z + 0.2f, GetBarRect()); } } void CGUIScrollBarVertical::HandleMessage(SGUIMessage& Message) { IGUIScrollBar::HandleMessage(Message); } CRect CGUIScrollBarVertical::GetBarRect() const { CRect ret; if (!GetStyle()) return ret; // Get from where the scroll area begins to where it ends float from = m_Y; float to = m_Y + m_Length - m_BarSize; if (GetStyle()->m_UseEdgeButtons) { from += GetStyle()->m_Width; to -= GetStyle()->m_Width; } // Setup rectangle ret.top = from + (to - from) * (m_Pos / GetMaxPos()); ret.bottom = ret.top + m_BarSize; ret.right = m_X + ((m_RightAligned)?(0.f):(GetStyle()->m_Width)); ret.left = ret.right - GetStyle()->m_Width; return ret; } CRect CGUIScrollBarVertical::GetOuterRect() const { CRect ret; if (!GetStyle()) return ret; ret.top = m_Y; ret.bottom = m_Y+m_Length; ret.right = m_X + ((m_RightAligned)?(0):(GetStyle()->m_Width)); ret.left = ret.right - GetStyle()->m_Width; return ret; } bool CGUIScrollBarVertical::HoveringButtonMinus(const CPos& mouse) { if (!GetStyle()) return false; float StartX = (m_RightAligned)?(m_X-GetStyle()->m_Width):(m_X); return (mouse.x >= StartX && mouse.x <= StartX + GetStyle()->m_Width && mouse.y >= m_Y && mouse.y <= m_Y + GetStyle()->m_Width); } bool CGUIScrollBarVertical::HoveringButtonPlus(const CPos& mouse) { if (!GetStyle()) return false; float StartX = (m_RightAligned)?(m_X-GetStyle()->m_Width):(m_X); return (mouse.x > StartX && mouse.x < StartX + GetStyle()->m_Width && mouse.y > m_Y + m_Length - GetStyle()->m_Width && mouse.y < m_Y + m_Length); } Index: ps/trunk/source/gui/CList.cpp =================================================================== --- ps/trunk/source/gui/CList.cpp (revision 17148) +++ ps/trunk/source/gui/CList.cpp (revision 17149) @@ -1,503 +1,504 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CList.h" #include "CGUIScrollBarVertical.h" #include "lib/external_libraries/libsdl.h" #include "ps/CLogger.h" #include "soundmanager/ISoundManager.h" CList::CList() : m_Modified(false) { // Add sprite_disabled! TODO AddSetting(GUIST_float, "buffer_zone"); AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_bool, "scrollbar"); AddSetting(GUIST_CStr, "scrollbar_style"); AddSetting(GUIST_CStrW, "sound_disabled"); AddSetting(GUIST_CStrW, "sound_selected"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); AddSetting(GUIST_CGUISpriteInstance, "sprite_selectarea"); AddSetting(GUIST_int, "cell_id"); AddSetting(GUIST_EAlign, "text_align"); AddSetting(GUIST_CColor, "textcolor"); AddSetting(GUIST_CColor, "textcolor_selected"); AddSetting(GUIST_int, "selected"); // Index selected. -1 is none. AddSetting(GUIST_CStrW, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); // Each list item has both a name (in 'list') and an associated data string (in 'list_data') AddSetting(GUIST_CGUIList, "list"); AddSetting(GUIST_CGUIList, "list_data"); // TODO: this should be a list of raw strings, not of CGUIStrings GUI::SetSetting(this, "scrollbar", false); // Nothing is selected as default. GUI::SetSetting(this, "selected", -1); // Add scroll-bar CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(); bar->SetRightAligned(true); AddScrollBar(bar); } CList::~CList() { } void CList::SetupText() { if (!GetGUI()) return; m_Modified = true; CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); //ENSURE(m_GeneratedTexts.size()>=1); m_ItemsYPositions.resize(pList->m_Items.size()+1); // Delete all generated texts. Some could probably be saved, // but this is easier, and this function will never be called // continuously, or even often, so it'll probably be okay. for (SGUIText* const& t : m_GeneratedTexts) delete t; m_GeneratedTexts.clear(); CStrW font; if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) // Use the default if none is specified // TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style. font = L"default"; bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); float width = GetListRect().GetWidth(); // remove scrollbar if applicable if (scrollbar && GetScrollBar(0).GetStyle()) width -= GetScrollBar(0).GetStyle()->m_Width; float buffer_zone = 0.f; GUI::GetSetting(this, "buffer_zone", buffer_zone); // Generate texts float buffered_y = 0.f; for (size_t i = 0; i < pList->m_Items.size(); ++i) { // Create a new SGUIText. Later on, input it using AddText() SGUIText* text = new SGUIText(); *text = GetGUI()->GenerateText(pList->m_Items[i], font, width, buffer_zone, this); m_ItemsYPositions[i] = buffered_y; buffered_y += text->m_Size.cy; AddText(text); } m_ItemsYPositions[pList->m_Items.size()] = buffered_y; // Setup scrollbar if (scrollbar) { GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight()); CRect rect = GetListRect(); GetScrollBar(0).SetX(rect.right); GetScrollBar(0).SetY(rect.top); GetScrollBar(0).SetZ(GetBufferedZ()); GetScrollBar(0).SetLength(rect.bottom - rect.top); } } void CList::HandleMessage(SGUIMessage& Message) { IGUIScrollBarOwner::HandleMessage(Message); //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! m_Modified = false; switch (Message.type) { case GUIM_SETTINGS_UPDATED: if (Message.value == "list") SetupText(); // If selected is changed, call "SelectionChange" if (Message.value == "selected") { // TODO: Check range // TODO only works if lower-case, shouldn't it be made case sensitive instead? ScriptEvent("selectionchange"); } if (Message.value == "scrollbar") SetupText(); // Update scrollbar if (Message.value == "scrollbar_style") { CStr scrollbar_style; GUI::GetSetting(this, Message.value, scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); SetupText(); } break; case GUIM_MOUSE_PRESS_LEFT: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_disabled", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } bool scrollbar; CGUIList* pList; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSettingPointer(this, "list", pList); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); CRect rect = GetListRect(); CPos mouse = GetMousePos(); mouse.y += scroll; int set = -1; for (int i = 0; i < (int)pList->m_Items.size(); ++i) { if (mouse.y >= rect.top + m_ItemsYPositions[i] && mouse.y < rect.top + m_ItemsYPositions[i+1] && // mouse is not over scroll-bar - !(mouse.x >= GetScrollBar(0).GetOuterRect().left && - mouse.x <= GetScrollBar(0).GetOuterRect().right)) + (!scrollbar || !GetScrollBar(0).IsVisible() || + mouse.x < GetScrollBar(0).GetOuterRect().left || + mouse.x > GetScrollBar(0).GetOuterRect().right)) { set = i; } } if (set != -1) { GUI::SetSetting(this, "selected", set); UpdateAutoScroll(); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } break; } case GUIM_LOAD: { CStr scrollbar_style; GUI::GetSetting(this, "scrollbar_style", scrollbar_style); GetScrollBar(0).SetScrollBarStyle(scrollbar_style); break; } default: break; } IGUITextOwner::HandleMessage(Message); } InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev) { InReaction result = IN_PASS; if (ev->ev.type == SDL_KEYDOWN) { int szChar = ev->ev.key.keysym.sym; switch (szChar) { case SDLK_HOME: SelectFirstElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_END: SelectLastElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_UP: SelectPrevElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_DOWN: SelectNextElement(); UpdateAutoScroll(); result = IN_HANDLED; break; case SDLK_PAGEUP: GetScrollBar(0).ScrollMinusPlenty(); result = IN_HANDLED; break; case SDLK_PAGEDOWN: GetScrollBar(0).ScrollPlusPlenty(); result = IN_HANDLED; break; default: // Do nothing result = IN_PASS; } } return result; } void CList::Draw() { int selected; GUI::GetSetting(this, "selected", selected); DrawList(selected, "sprite", "sprite_selectarea", "textcolor"); } void CList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) IGUIScrollBarOwner::Draw(); if (GetGUI()) { CRect rect = GetListRect(); CGUISpriteInstance* sprite = NULL; CGUISpriteInstance* sprite_selectarea = NULL; int cell_id; GUI::GetSettingPointer(this, _sprite, sprite); GUI::GetSettingPointer(this, _sprite_selected, sprite_selectarea); GUI::GetSetting(this, "cell_id", cell_id); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); GetGUI()->DrawSprite(*sprite, cell_id, bz, rect); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); if (selected != -1) { ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()); // Get rectangle of selection: CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); if (rect_sel.top <= rect.bottom && rect_sel.bottom >= rect.top) { if (rect_sel.bottom > rect.bottom) rect_sel.bottom = rect.bottom; if (rect_sel.top < rect.top) rect_sel.top = rect.top; if (scrollbar) { // Remove any overlapping area of the scrollbar. if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && rect_sel.right <= GetScrollBar(0).GetOuterRect().right) rect_sel.right = GetScrollBar(0).GetOuterRect().left; if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && rect_sel.left < GetScrollBar(0).GetOuterRect().right) rect_sel.left = GetScrollBar(0).GetOuterRect().right; } GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel); } } CColor color; GUI::GetSetting(this, _textcolor, color); for (size_t i = 0; i < pList->m_Items.size(); ++i) { if (m_ItemsYPositions[i+1] - scroll < 0 || m_ItemsYPositions[i] - scroll > rect.GetHeight()) continue; // Clipping area (we'll have to substract the scrollbar) CRect cliparea = GetListRect(); if (scrollbar) { if (cliparea.right > GetScrollBar(0).GetOuterRect().left && cliparea.right <= GetScrollBar(0).GetOuterRect().right) cliparea.right = GetScrollBar(0).GetOuterRect().left; if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && cliparea.left < GetScrollBar(0).GetOuterRect().right) cliparea.left = GetScrollBar(0).GetOuterRect().right; } DrawText(i, color, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz+0.1f, cliparea); } } } void CList::AddItem(const CStrW& str, const CStrW& data) { CGUIList* pList; CGUIList* pListData; GUI::GetSettingPointer(this, "list", pList); GUI::GetSettingPointer(this, "list_data", pListData); CGUIString gui_string; gui_string.SetValue(str); pList->m_Items.push_back(gui_string); CGUIString data_string; data_string.SetValue(data); pListData->m_Items.push_back(data_string); // TODO Temp SetupText(); } bool CList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) { int elmt_item = pFile->GetElementID("item"); if (child.GetNodeName() == elmt_item) { AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); return true; } return false; } void CList::SelectNextElement() { int selected; GUI::GetSetting(this, "selected", selected); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); if (selected != (int)pList->m_Items.size()-1) { ++selected; GUI::SetSetting(this, "selected", selected); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } } void CList::SelectPrevElement() { int selected; GUI::GetSetting(this, "selected", selected); if (selected > 0) { --selected; GUI::SetSetting(this, "selected", selected); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } } void CList::SelectFirstElement() { int selected; GUI::GetSetting(this, "selected", selected); if (selected >= 0) GUI::SetSetting(this, "selected", 0); } void CList::SelectLastElement() { int selected; GUI::GetSetting(this, "selected", selected); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); if (selected != (int)pList->m_Items.size()-1) GUI::SetSetting(this, "selected", (int)pList->m_Items.size()-1); } void CList::UpdateAutoScroll() { int selected; bool scrollbar; float scroll; GUI::GetSetting(this, "selected", selected); GUI::GetSetting(this, "scrollbar", scrollbar); CRect rect = GetListRect(); // No scrollbar, no scrolling (at least it's not made to work properly). if (!scrollbar) return; scroll = GetScrollBar(0).GetPos(); // Check upper boundary if (m_ItemsYPositions[selected] < scroll) { GetScrollBar(0).SetPos(m_ItemsYPositions[selected]); return; // this means, if it wants to align both up and down at the same time // this will have precedence. } // Check lower boundary if (m_ItemsYPositions[selected+1]-rect.GetHeight() > scroll) GetScrollBar(0).SetPos(m_ItemsYPositions[selected+1]-rect.GetHeight()); } Index: ps/trunk/source/gui/IGUIScrollBar.h =================================================================== --- ps/trunk/source/gui/IGUIScrollBar.h (revision 17148) +++ ps/trunk/source/gui/IGUIScrollBar.h (revision 17149) @@ -1,447 +1,452 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* A GUI ScrollBar --Overview-- A GUI Scrollbar, this class doesn't present all functionality to the scrollbar, it just controls the drawing and a wrapper for interaction with it. --Usage-- Used in everywhere scrollbars are needed, like in a combobox for instance. --More info-- Check GUI.h */ #ifndef INCLUDED_IGUISCROLLBAR #define INCLUDED_IGUISCROLLBAR #include "GUI.h" /** * The GUI Scroll-bar style. Tells us how scroll-bars look and feel. * * A scroll-bar style can choose whether to support horizontal, vertical * or both. * * @see IGUIScrollBar */ struct SGUIScrollBarStyle { //-------------------------------------------------------- /** @name General Settings */ //-------------------------------------------------------- //@{ /** * Width of bar, also both sides of the edge buttons. */ float m_Width; /** * Scrollable with the wheel. */ bool m_ScrollWheel; /** * How much (in percent, 0.1f = 10%) to scroll each time * the wheel is admitted, or the buttons are pressed. */ float m_ScrollSpeed; /** * Whether or not the edge buttons should appear or not. Sometimes * you actually don't want them, like perhaps in a combo box. */ bool m_ScrollButtons; /** * Sometimes there is *a lot* to scroll, but to prevent the scroll "bar" * from being almost invisible (or ugly), you can set a minimum in pixel * size. */ float m_MinimumBarSize; /** * Sometimes you would like your scroll bar to have a fixed maximum size * so that the texture does not get too stretched, you can set a maximum * in pixels. */ float m_MaximumBarSize; /** * True if you want edge buttons, i.e. buttons that can be pressed in order * to scroll. */ bool m_UseEdgeButtons; //@} //-------------------------------------------------------- /** @name Vertical Sprites */ //-------------------------------------------------------- //@{ CGUISpriteInstance m_SpriteButtonTop; CGUISpriteInstance m_SpriteButtonTopPressed; CGUISpriteInstance m_SpriteButtonTopDisabled; CGUISpriteInstance m_SpriteButtonTopOver; CGUISpriteInstance m_SpriteButtonBottom; CGUISpriteInstance m_SpriteButtonBottomPressed; CGUISpriteInstance m_SpriteButtonBottomDisabled; CGUISpriteInstance m_SpriteButtonBottomOver; CGUISpriteInstance m_SpriteBarVertical; CGUISpriteInstance m_SpriteBarVerticalOver; CGUISpriteInstance m_SpriteBarVerticalPressed; CGUISpriteInstance m_SpriteBackVertical; //@} //-------------------------------------------------------- /** @name Horizontal Sprites */ //-------------------------------------------------------- //@{ CGUISpriteInstance m_SpriteButtonLeft; CGUISpriteInstance m_SpriteButtonLeftPressed; CGUISpriteInstance m_SpriteButtonLeftDisabled; CGUISpriteInstance m_SpriteButtonRight; CGUISpriteInstance m_SpriteButtonRightPressed; CGUISpriteInstance m_SpriteButtonRightDisabled; CGUISpriteInstance m_SpriteBackHorizontal; CGUISpriteInstance m_SpriteBarHorizontal; //@} }; /** * The GUI Scroll-bar, used everywhere there is a scroll-bar in the game. * * To include a scroll-bar to an object, inherent the object from * IGUIScrollBarOwner and call AddScrollBar() to add the scroll-bars. * * It's also important that the scrollbar is located within the parent * object's mouse over area. Otherwise the input won't be sent to the * scroll-bar. * * The class does not provide all functionality to the scroll-bar, many * things the parent of the scroll-bar, must provide. Like a combo-box. */ class IGUIScrollBar { public: IGUIScrollBar(); virtual ~IGUIScrollBar(); public: /** * Draw the scroll-bar */ virtual void Draw() = 0; /** * If an object that contains a scrollbar has got messages, send * them to the scroll-bar and it will see if the message regarded * itself. * * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message) = 0; /** * Set m_Pos with g_mouse_x/y input, i.e. when draggin. */ virtual void SetPosFromMousePos(const CPos& mouse) = 0; /** * Hovering the scroll minus button * * @param mouse current mouse position * @return True if mouse positions are hovering the button */ virtual bool HoveringButtonMinus(const CPos& UNUSED(mouse)) { return false; } /** * Hovering the scroll plus button * * @param mouse current mouse position * @return True if mouse positions are hovering the button */ virtual bool HoveringButtonPlus(const CPos& UNUSED(mouse)) { return false; } /** * Get scroll-position */ float GetPos() const { return m_Pos; } /** * Set scroll-position by hand */ virtual void SetPos(float f) { m_Pos = f; UpdatePosBoundaries(); } /** * Get the value of m_Pos that corresponds to the bottom of the scrollable region */ float GetMaxPos() const { return std::max(1.f, m_ScrollRange - m_ScrollSpace); } /** + * Get the value of m_Pos that corresponds to the bottom of the scrollable region + */ + float IsVisible() const { return GetMaxPos() != 1.f; } + + /** * Increase scroll one step */ virtual void ScrollPlus() { m_Pos += 30.f; UpdatePosBoundaries(); } /** * Decrease scroll one step */ virtual void ScrollMinus() { m_Pos -= 30.f; UpdatePosBoundaries(); } /** * Increase scroll three steps */ virtual void ScrollPlusPlenty() { m_Pos += 90.f; UpdatePosBoundaries(); } /** * Decrease scroll three steps */ virtual void ScrollMinusPlenty() { m_Pos -= 90.f; UpdatePosBoundaries(); } /** * Set host object, must be done almost at creation of scroll bar. * @param pOwner Pointer to host object. */ void SetHostObject(IGUIScrollBarOwner* pOwner) { m_pHostObject = pOwner; } /** * Get GUI pointer * @return CGUI pointer */ CGUI* GetGUI() const; /** * Set GUI pointer * @param pGUI pointer to CGUI object. */ void SetGUI(CGUI* pGUI) { m_pGUI = pGUI; } /** * Set Width * @param width Width */ void SetWidth(float width) { m_Width = width; } /** * Set X Position * @param x Position in this axis */ void SetX(float x) { m_X = x; } /** * Set Y Position * @param y Position in this axis */ void SetY(float y) { m_Y = y; } /** * Set Z Position * @param z Position in this axis */ void SetZ(float z) { m_Z = z; } /** * Set Length of scroll bar * @param length Length */ void SetLength(float length) { m_Length = length; } /** * Set content length * @param range Maximum scrollable range */ void SetScrollRange(float range) { m_ScrollRange = std::max(range, 1.f); SetupBarSize(); UpdatePosBoundaries(); } /** * Set space that is visible in the scrollable control. * @param space Visible area in the scrollable control. */ void SetScrollSpace(float space) { m_ScrollSpace = space; SetupBarSize(); UpdatePosBoundaries(); } /** * Set bar pressed * @param b True if bar is pressed */ void SetBarPressed(bool b) { m_BarPressed = b; } /** * Set Scroll bar style string * @param style String with scroll bar style reference name */ void SetScrollBarStyle(const CStr& style) { m_ScrollBarStyle = style; } /** * Get style used by the scrollbar * @return Scroll bar style struct. */ const SGUIScrollBarStyle* GetStyle() const; /** * Get the rectangle of the actual BAR. not the whole scroll-bar. * @return Rectangle, CRect */ virtual CRect GetBarRect() const = 0; /** * Get the rectangle of the outline of the scrollbar, every component of the * scroll-bar should be inside this area. * @return Rectangle, CRect */ virtual CRect GetOuterRect() const = 0; protected: /** * Sets up bar size */ void SetupBarSize(); /** * Call every time m_Pos has been updated. */ void UpdatePosBoundaries(); protected: //@} //-------------------------------------------------------- /** @name Settings */ //-------------------------------------------------------- //@{ /** * Width of the scroll bar */ float m_Width; /** * Absolute X Position */ float m_X; /** * Absolute Y Position */ float m_Y; /** * Absolute Z Position */ float m_Z; /** * Total length of scrollbar, including edge buttons. */ float m_Length; /** * Content that can be scrolled, in pixels */ float m_ScrollRange; /** * Content that can be viewed at a time, in pixels */ float m_ScrollSpace; /** * Use input from the scroll-wheel? True or false. */ float m_BarSize; /** * Scroll bar style reference name */ CStr m_ScrollBarStyle; /** * Pointer to scroll bar style used. */ SGUIScrollBarStyle *m_pStyle; /** * Host object, prerequisite! */ IGUIScrollBarOwner *m_pHostObject; /** * Reference to CGUI object, these cannot work stand-alone */ CGUI *m_pGUI; /** * Mouse position when bar was pressed */ CPos m_BarPressedAtPos; //@} //-------------------------------------------------------- /** @name States */ //-------------------------------------------------------- //@{ /** * If the bar is currently being pressed and dragged. */ bool m_BarPressed; /** * Bar being hovered or not */ bool m_BarHovered; /** * Scroll buttons hovered */ bool m_ButtonMinusHovered, m_ButtonPlusHovered; /** * Scroll buttons pressed */ bool m_ButtonMinusPressed, m_ButtonPlusPressed; /** * Position of scroll bar, 0 means scrolled all the way to one side. * It is measured in pixels, it is up to the host to make it actually * apply in pixels. */ float m_Pos; /** * Position from 0.f to 1.f it had when the bar was pressed. */ float m_PosWhenPressed; //@} }; #endif // INCLUDED_IGUISCROLLBAR