Wraplist update to prevent multiple copies of single items
#1
See original thread here.

Could one of the developers try out this code for guilib/GUIWrappingListContainer.cpp? I'm working on getting my hands on one of my employer's copies of VS 2003, but probably won't be able to for a week or two. If someone could test this and submit it as a patch if it works, I would be very grateful.
Code:
#include "include.h"
#include "GUIWrappingListContainer.h"
#include "GUIListItem.h"

CGUIWrappingListContainer::CGUIWrappingListContainer(DWORD dwParentID, DWORD dwControlId, float posX, float posY, float width, float height, ORIENTATION orientation, int scrollTime, int fixedPosition)
    : CGUIBaseContainer(dwParentID, dwControlId, posX, posY, width, height, orientation, scrollTime)
{
  m_cursor = fixedPosition;
  ControlType = GUICONTAINER_FIXEDLIST;
  m_type = VIEW_TYPE_LIST;
}

CGUIWrappingListContainer::~CGUIWrappingListContainer(void)
{
}

void CGUIWrappingListContainer::Render()
{
  if (!IsVisible()) return CGUIBaseContainer::Render();

  ValidateOffset();

  if (m_bInvalidated)
    UpdateLayout();

  m_scrollOffset += m_scrollSpeed * (m_renderTime - m_scrollLastTime);
  if ((m_scrollSpeed < 0 && m_scrollOffset < m_offset * m_layout.Size(m_orientation)) ||
      (m_scrollSpeed > 0 && m_scrollOffset > m_offset * m_layout.Size(m_orientation)))
  {
    m_scrollOffset = m_offset * m_layout.Size(m_orientation);
    m_scrollSpeed = 0;
  }
  m_scrollLastTime = m_renderTime;

  int offset = (int)floorf(m_scrollOffset / m_layout.Size(m_orientation));
  // Free memory not used on scre  if (m_scrollSpeed)
  if ((int)m_items.size() > m_itemsPerPage)
    FreeMemory(CorrectOffset(offset, 0), CorrectOffset(offset, m_itemsPerPage + 1));

  g_graphicsContext.SetViewPort(m_posX, m_posY, m_width, m_height);
  float posX = m_posX;
  float posY = m_posY;
  if (m_orientation == VERTICAL)
    posY += (offset * m_layout.Size(m_orientation) - m_scrollOffset);
  else
    posX += (offset * m_layout.Size(m_orientation) - m_scrollOffset);;

  float focusedPosX = 0;
  float focusedPosY = 0;
  CGUIListItem *focusedItem = NULL;
  int current = offset;
  int currentitem = 0;  // position of the current item in the display list (base 0)
  int displayeditems = 1;  // number of items displayed so far (leave 1 for the focused item)
  int displaystartitem = (int)(m_itemsPerPage / 2) - (int)(m_items.size()); // middle of the displaylist - 1/2 the number of total items
  while (posX < m_posX + m_width && posY < m_posY + m_height && m_items.size())
  {
    CGUIListItem *item = m_items[CorrectOffset(current, 0)];
    bool focused = (current == m_offset + m_cursor) && m_bHasFocus;

    // render our item
    if (focused)
    {
      focusedPosX = posX;
      focusedPosY = posY;
      focusedItem = item;
    }
    else
    {
      if (currentitem >= displaystartitem  && displayeditems < m_items.size())
      {
        RenderItem(posX, posY, item, focused);
        displayeditems++;
      }
    }

    // increment our position
    if (m_orientation == VERTICAL)
      posY += focused ? m_focusedLayout.Size(m_orientation) : m_layout.Size(m_orientation);
    else
      posX += focused ? m_focusedLayout.Size(m_orientation) : m_layout.Size(m_orientation);

    current++;
    currentitem++;
  }

  // render focused item last so it can overlap other items
  if (focusedItem)
    RenderItem(focusedPosX, focusedPosY, focusedItem, true);

  g_graphicsContext.RestoreViewPort();

  if (m_pageControl)
  { // tell our pagecontrol (scrollbar or whatever) to update
    CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, CorrectOffset(offset, 0));
    SendWindowMessage(msg);
  }
  CGUIBaseContainer::Render();
}

bool CGUIWrappingListContainer::OnAction(const CAction &action)
{
  switch (action.wID)
  {
  case ACTION_PAGE_UP:
    Scroll(-m_itemsPerPage);
    return true;
  case ACTION_PAGE_DOWN:
    Scroll(m_itemsPerPage);
    return true;
    // smooth scrolling (for analog controls)
  case ACTION_SCROLL_UP:
    {
      m_analogScrollCount += action.fAmount1 * action.fAmount1;
      bool handled = false;
      while (m_analogScrollCount > 0.4)
      {
        handled = true;
        m_analogScrollCount -= 0.4f;
        Scroll(-1);
      }
      return handled;
    }
    break;
  case ACTION_SCROLL_DOWN:
    {
      m_analogScrollCount += action.fAmount1 * action.fAmount1;
      bool handled = false;
      while (m_analogScrollCount > 0.4)
      {
        handled = true;
        m_analogScrollCount -= 0.4f;
        Scroll(1);
      }
      return handled;
    }
    break;
  }
  return CGUIBaseContainer::OnAction(action);
}

bool CGUIWrappingListContainer::OnMessage(CGUIMessage& message)
{
  if (message.GetControlId() == GetID() )
  {
    if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
    {
      int item = message.GetParam1();
      if (item >= 0 && item < (int)m_items.size())
        ScrollToOffset(item - m_cursor);
      return true;
    }
  }
  return CGUIBaseContainer::OnMessage(message);
}

bool CGUIWrappingListContainer::MoveUp(DWORD control)
{
  Scroll(-1);
  return true;
}

bool CGUIWrappingListContainer::MoveDown(DWORD control)
{
  Scroll(+1);
  return true;
}

// scrolls the said amount
void CGUIWrappingListContainer::Scroll(int amount)
{
  ScrollToOffset(m_offset + amount);
}

void CGUIWrappingListContainer::ValidateOffset()
{
  // no need to check the range here
}

int CGUIWrappingListContainer::CorrectOffset(int offset, int cursor) const
{
  if (m_items.size())
  {
    int correctOffset = (offset + cursor) % (int)m_items.size();
    if (correctOffset < 0) correctOffset += m_items.size();
    return correctOffset;
  }
  return 0;
}

void CGUIWrappingListContainer::MoveToItem(int item)
{
  // TODO: Implement this...
  ScrollToOffset(item - m_cursor);
}
Reply
#2
A couple of points:

1. This won't work if the focused item isn't the middle one.

2. This is not likely to work nicely with scrolling (where the items onscreen are not nicely centered, and the currently focused item may infact be offscreen), but I'm not really sure at this point if this is an issue or not.

As I said, it's non-trivial Wink

Cheers,
Jonathan
Always read the XBMC online-manual, FAQ and search the forum before posting.
Do not e-mail XBMC-Team members directly asking for support. Read/follow the forum rules.
For troubleshooting and bug reporting please make sure you read this first.


Image
Reply
#3
I just played around with my videos and music and wasn't able to find an instance where a wraplist didn't have the middle item focused. When I move to the side, it shifts everything over by 1 and brings a new item into view from the side. The middle item remains in focus. Is that behavior that can be changed by the skin developer?
Reply
#4
Yes it is up to the skinner just look at this it focuses on the 4th item of 5
Image
and this is the code that you might want to take into account note focused position 0 is the first item
Quote:<control type="wraplist" id="56">
<posx>190</posx>
<posy>100</posy>
<width>520</width>
<height>425</height>
<onleft>56</onleft>
<onright>56</onright>
<onup>9000</onup>
<ondown>9000</ondown>
<scrolltime>200</scrolltime>
<viewtype label="DVD">wraplist</viewtype>
<pagecontrol>-</pagecontrol>
<orientation>horizontal</orientation>
<focusposition>3</focusposition>

<include>contentpanelslide</include>
<itemlayout height="300" width="104">
Reply
#5
Ok thanks. I'll play around with the code a bit more and see what I can do.
Reply
#6
Just the bit that grabs the focused item in the render loop should tell you which is which.

Cheers,
Jonathan
Always read the XBMC online-manual, FAQ and search the forum before posting.
Do not e-mail XBMC-Team members directly asking for support. Read/follow the forum rules.
For troubleshooting and bug reporting please make sure you read this first.


Image
Reply
#7
Hmm... this change seemed too easy. What am I missing? Instead of basing displaystartitem on 1/2 the number of displayed items (the middle), I'm using m_offset + m_cursor. That should be the location of the focused item (I ignore m_bHasFocus, cause that doesn't affect what I'm doing). I then take 1/2 the number of total items and move that many places to the left of the focused item. Since this can result in displaystartitem being outside the displayed area, I change it to 0 or m_itemsPerPage - 1 (depending on if it's to the left/right or top/bottom) if it's out of range. I'm still not grasping how it works entirely, so it might be that these bound checks are unnecessary.

It seems like a trivial change and I can't figure out why it wouldn't work, unless there's something in the scroller code that would prevent it. I can't figure out what's going on in that part of the code. Smile

Here's what I came up with:
Code:
#include "include.h"
#include "GUIWrappingListContainer.h"
#include "GUIListItem.h"

CGUIWrappingListContainer::CGUIWrappingListContainer(DWORD dwParentID, DWORD dwControlId, float posX, float posY, float width, float height, ORIENTATION orientation, int scrollTime, int fixedPosition)
    : CGUIBaseContainer(dwParentID, dwControlId, posX, posY, width, height, orientation, scrollTime)
{
  m_cursor = fixedPosition;
  ControlType = GUICONTAINER_FIXEDLIST;
  m_type = VIEW_TYPE_LIST;
}

CGUIWrappingListContainer::~CGUIWrappingListContainer(void)
{
}

void CGUIWrappingListContainer::Render()
{
  if (!IsVisible()) return CGUIBaseContainer::Render();

  ValidateOffset();

  if (m_bInvalidated)
    UpdateLayout();

  m_scrollOffset += m_scrollSpeed * (m_renderTime - m_scrollLastTime);
  if ((m_scrollSpeed < 0 && m_scrollOffset < m_offset * m_layout.Size(m_orientation)) ||
      (m_scrollSpeed > 0 && m_scrollOffset > m_offset * m_layout.Size(m_orientation)))
  {
    m_scrollOffset = m_offset * m_layout.Size(m_orientation);
    m_scrollSpeed = 0;
  }
  m_scrollLastTime = m_renderTime;

  int offset = (int)floorf(m_scrollOffset / m_layout.Size(m_orientation));
  // Free memory not used on scre  if (m_scrollSpeed)
  if ((int)m_items.size() > m_itemsPerPage)
    FreeMemory(CorrectOffset(offset, 0), CorrectOffset(offset, m_itemsPerPage + 1));

  g_graphicsContext.SetViewPort(m_posX, m_posY, m_width, m_height);
  float posX = m_posX;
  float posY = m_posY;
  if (m_orientation == VERTICAL)
    posY += (offset * m_layout.Size(m_orientation) - m_scrollOffset);
  else
    posX += (offset * m_layout.Size(m_orientation) - m_scrollOffset);;

  float focusedPosX = 0;
  float focusedPosY = 0;
  CGUIListItem *focusedItem = NULL;
  int current = offset;
  int currentitem = 0;  // position of the current item in the display list (base 0)
  int displayeditems = 1;  // number of items displayed so far (leave 1 for the focused item)
  int displaystartitem = (m_offset + m_cursor) - (int)(m_items.size() / 2); // location of focused item - 1/2 the number of total items

  // If the start location is out of the view, bring it just inside the view.
  if (displaystartitem < 0)
    displaystartitem = 0;
  if (displaystartitem >= m_itemsPerPage)
    displaystartitem = m_itemsPerPage - 1;

  while (posX < m_posX + m_width && posY < m_posY + m_height && m_items.size())
  {
    CGUIListItem *item = m_items[CorrectOffset(current, 0)];
    bool focused = (current == m_offset + m_cursor) && m_bHasFocus;

    // render our item
    if (focused)
    {
      focusedPosX = posX;
      focusedPosY = posY;
      focusedItem = item;
    }
    else
    {
      if (currentitem >= displaystartitem  && displayeditems < m_items.size())
      {
        RenderItem(posX, posY, item, focused);
        displayeditems++;
      }
    }

    // increment our position
    if (m_orientation == VERTICAL)
      posY += focused ? m_focusedLayout.Size(m_orientation) : m_layout.Size(m_orientation);
    else
      posX += focused ? m_focusedLayout.Size(m_orientation) : m_layout.Size(m_orientation);

    current++;
    currentitem++;
  }

  // render focused item last so it can overlap other items
  if (focusedItem)
    RenderItem(focusedPosX, focusedPosY, focusedItem, true);

  g_graphicsContext.RestoreViewPort();

  if (m_pageControl)
  { // tell our pagecontrol (scrollbar or whatever) to update
    CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, CorrectOffset(offset, 0));
    SendWindowMessage(msg);
  }
  CGUIBaseContainer::Render();
}

bool CGUIWrappingListContainer::OnAction(const CAction &action)
{
  switch (action.wID)
  {
  case ACTION_PAGE_UP:
    Scroll(-m_itemsPerPage);
    return true;
  case ACTION_PAGE_DOWN:
    Scroll(m_itemsPerPage);
    return true;
    // smooth scrolling (for analog controls)
  case ACTION_SCROLL_UP:
    {
      m_analogScrollCount += action.fAmount1 * action.fAmount1;
      bool handled = false;
      while (m_analogScrollCount > 0.4)
      {
        handled = true;
        m_analogScrollCount -= 0.4f;
        Scroll(-1);
      }
      return handled;
    }
    break;
  case ACTION_SCROLL_DOWN:
    {
      m_analogScrollCount += action.fAmount1 * action.fAmount1;
      bool handled = false;
      while (m_analogScrollCount > 0.4)
      {
        handled = true;
        m_analogScrollCount -= 0.4f;
        Scroll(1);
      }
      return handled;
    }
    break;
  }
  return CGUIBaseContainer::OnAction(action);
}

bool CGUIWrappingListContainer::OnMessage(CGUIMessage& message)
{
  if (message.GetControlId() == GetID() )
  {
    if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
    {
      int item = message.GetParam1();
      if (item >= 0 && item < (int)m_items.size())
        ScrollToOffset(item - m_cursor);
      return true;
    }
  }
  return CGUIBaseContainer::OnMessage(message);
}

bool CGUIWrappingListContainer::MoveUp(DWORD control)
{
  Scroll(-1);
  return true;
}

bool CGUIWrappingListContainer::MoveDown(DWORD control)
{
  Scroll(+1);
  return true;
}

// scrolls the said amount
void CGUIWrappingListContainer::Scroll(int amount)
{
  ScrollToOffset(m_offset + amount);
}

void CGUIWrappingListContainer::ValidateOffset()
{
  // no need to check the range here
}

int CGUIWrappingListContainer::CorrectOffset(int offset, int cursor) const
{
  if (m_items.size())
  {
    int correctOffset = (offset + cursor) % (int)m_items.size();
    if (correctOffset < 0) correctOffset += m_items.size();
    return correctOffset;
  }
  return 0;
}

void CGUIWrappingListContainer::MoveToItem(int item)
{
  // TODO: Implement this...
  ScrollToOffset(item - m_cursor);
}
Reply
#8
PS: I promise when I get VS 2003, I'll test this stuff on my own without bothering you guys so much. Smile
Reply
#9
I fail to see how that code can possibly work. Some hints:

m_offset is the first item in the list (it may be a partial item if we're scrolling). m_cursor is the offset into the list of the focused item. m_offset in particular can be outside the range of the number of items. This makes scrolling nice and easy - we just increment m_offset by the amount we're scrolling.

The wraparound is handled by CorrectOffset() which takes the (possibly out of range) offset and corrects it so that it's modulo the number of items.

It's not as simple making sure you don't render an item more than once - think of 4 items in a list that holds 4. When scrolling, you want the 4th item to be half on each side. The key is that you should only change things when there is strictly less than the number of items per page in the list. In this case, you effectively want to reduce the size in pixels of the list. You can probably do this by altering posX, posY, and the width and height at rendertime, though whether or not it'll look any good in practice is questionable.

Note that the size in pixels is specified by the skinner, and can include partial items in a normal view.

Good luck getting VS2003 - note that the free version of 2005 may in fact be fine for building XBMC_PC (though it doesn't build XBMC ofcourse).

Cheers,
Jonathan
Always read the XBMC online-manual, FAQ and search the forum before posting.
Do not e-mail XBMC-Team members directly asking for support. Read/follow the forum rules.
For troubleshooting and bug reporting please make sure you read this first.


Image
Reply

Logout Mark Read Team Forum Stats Members Help
Wraplist update to prevent multiple copies of single items0