1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
\r
2 // This file is part of the "Irrlicht Engine".
\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
5 #include "CGUIListBox.h"
\r
7 #include "CGUIListBox.h"
\r
8 #include "IGUISkin.h"
\r
9 #include "IGUIEnvironment.h"
\r
10 #include "IVideoDriver.h"
\r
11 #include "IGUIFont.h"
\r
12 #include "IGUISpriteBank.h"
\r
13 #include "CGUIScrollBar.h"
\r
22 CGUIListBox::CGUIListBox(IGUIEnvironment* environment, IGUIElement* parent,
\r
23 s32 id, core::rect<s32> rectangle, bool clip,
\r
24 bool drawBack, bool moveOverSelect)
\r
25 : IGUIListBox(environment, parent, id, rectangle), Selected(-1),
\r
26 ItemHeight(0),ItemHeightOverride(0),
\r
27 TotalItemHeight(0), ItemsIconWidth(0), Font(0), IconBank(0),
\r
28 ScrollBar(0), selectTime(0), LastKeyTime(0), Selecting(false), DrawBack(drawBack),
\r
29 MoveOverSelect(moveOverSelect), AutoScroll(true), HighlightWhenNotFocused(true)
\r
32 setDebugName("CGUIListBox");
\r
35 IGUISkin* skin = Environment->getSkin();
\r
37 ScrollBar = new CGUIScrollBar(false, Environment, this, -1,
\r
38 core::recti(0, 0, 1, 1),
\r
40 ScrollBar->setSubElement(true);
\r
41 ScrollBar->setTabStop(false);
\r
42 ScrollBar->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
\r
43 ScrollBar->setVisible(false);
\r
44 ScrollBar->setPos(0);
\r
46 updateScrollBarSize(skin->getSize(EGDS_SCROLLBAR_SIZE));
\r
48 setNotClipped(!clip);
\r
50 // this element can be tabbed to
\r
54 updateAbsolutePosition();
\r
59 CGUIListBox::~CGUIListBox()
\r
72 //! returns amount of list items
\r
73 u32 CGUIListBox::getItemCount() const
\r
75 return Items.size();
\r
79 //! returns string of a list item. the may be a value from 0 to itemCount-1
\r
80 const wchar_t* CGUIListBox::getListItem(u32 id) const
\r
82 if (id>=Items.size())
\r
85 return Items[id].Text.c_str();
\r
89 //! Returns the icon of an item
\r
90 s32 CGUIListBox::getIcon(u32 id) const
\r
92 if (id>=Items.size())
\r
95 return Items[id].Icon;
\r
99 //! adds a list item, returns id of item
\r
100 u32 CGUIListBox::addItem(const wchar_t* text)
\r
102 return addItem(text, -1);
\r
106 //! adds a list item, returns id of item
\r
107 void CGUIListBox::removeItem(u32 id)
\r
109 if (id >= Items.size())
\r
112 if ((u32)Selected==id)
\r
116 else if ((u32)Selected > id)
\r
119 selectTime = os::Timer::getTime();
\r
124 recalculateItemHeight();
\r
128 s32 CGUIListBox::getItemAt(s32 xpos, s32 ypos) const
\r
130 if ( xpos < AbsoluteRect.UpperLeftCorner.X || xpos >= AbsoluteRect.LowerRightCorner.X
\r
131 || ypos < AbsoluteRect.UpperLeftCorner.Y || ypos >= AbsoluteRect.LowerRightCorner.Y
\r
135 if ( ItemHeight == 0 )
\r
138 s32 item = ((ypos - AbsoluteRect.UpperLeftCorner.Y - 1) + ScrollBar->getPos()) / ItemHeight;
\r
139 if ( item < 0 || item >= (s32)Items.size())
\r
145 //! clears the list
\r
146 void CGUIListBox::clear()
\r
149 ItemsIconWidth = 0;
\r
152 ScrollBar->setPos(0);
\r
154 recalculateItemHeight();
\r
158 void CGUIListBox::recalculateItemHeight()
\r
160 IGUISkin* skin = Environment->getSkin();
\r
162 if (Font != skin->getFont())
\r
167 Font = skin->getFont();
\r
168 if ( 0 == ItemHeightOverride )
\r
173 if ( 0 == ItemHeightOverride )
\r
174 ItemHeight = Font->getDimension(L"A").Height + 4;
\r
180 TotalItemHeight = ItemHeight * Items.size();
\r
181 ScrollBar->setMax( core::max_(0, TotalItemHeight - AbsoluteRect.getHeight()) );
\r
182 s32 minItemHeight = ItemHeight > 0 ? ItemHeight : 1;
\r
183 ScrollBar->setSmallStep ( minItemHeight );
\r
184 ScrollBar->setLargeStep ( 2*minItemHeight );
\r
186 if ( TotalItemHeight <= AbsoluteRect.getHeight() )
\r
187 ScrollBar->setVisible(false);
\r
189 ScrollBar->setVisible(true);
\r
192 //! returns id of selected item. returns -1 if no item is selected.
\r
193 s32 CGUIListBox::getSelected() const
\r
199 //! sets the selected item. Set this to -1 if no item should be selected
\r
200 void CGUIListBox::setSelected(s32 id)
\r
202 if ((u32)id>=Items.size())
\r
207 selectTime = os::Timer::getTime();
\r
209 recalculateScrollPos();
\r
212 //! sets the selected item. Set this to -1 if no item should be selected
\r
213 void CGUIListBox::setSelected(const wchar_t *item)
\r
219 for ( index = 0; index < (s32) Items.size(); ++index )
\r
221 if ( Items[index].Text == item )
\r
225 setSelected ( index );
\r
228 //! called if an event happened.
\r
229 bool CGUIListBox::OnEvent(const SEvent& event)
\r
233 switch(event.EventType)
\r
235 case EET_KEY_INPUT_EVENT:
\r
236 if (event.KeyInput.PressedDown &&
\r
237 (event.KeyInput.Key == KEY_DOWN ||
\r
238 event.KeyInput.Key == KEY_UP ||
\r
239 event.KeyInput.Key == KEY_HOME ||
\r
240 event.KeyInput.Key == KEY_END ||
\r
241 event.KeyInput.Key == KEY_NEXT ||
\r
242 event.KeyInput.Key == KEY_PRIOR ) )
\r
244 s32 oldSelected = Selected;
\r
245 switch (event.KeyInput.Key)
\r
257 Selected = (s32)Items.size()-1;
\r
260 Selected += AbsoluteRect.getHeight() / ItemHeight;
\r
263 Selected -= AbsoluteRect.getHeight() / ItemHeight;
\r
270 if (Selected >= (s32)Items.size())
\r
271 Selected = Items.size() - 1; // will set Selected to -1 for empty listboxes which is correct
\r
274 recalculateScrollPos();
\r
278 if (oldSelected != Selected && Parent && !Selecting && !MoveOverSelect)
\r
281 e.EventType = EET_GUI_EVENT;
\r
282 e.GUIEvent.Caller = this;
\r
283 e.GUIEvent.Element = 0;
\r
284 e.GUIEvent.EventType = EGET_LISTBOX_CHANGED;
\r
285 Parent->OnEvent(e);
\r
291 if (!event.KeyInput.PressedDown && ( event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE ) )
\r
296 e.EventType = EET_GUI_EVENT;
\r
297 e.GUIEvent.Caller = this;
\r
298 e.GUIEvent.Element = 0;
\r
299 e.GUIEvent.EventType = EGET_LISTBOX_SELECTED_AGAIN;
\r
300 Parent->OnEvent(e);
\r
304 else if (event.KeyInput.Key == KEY_TAB )
\r
308 else if (event.KeyInput.PressedDown && event.KeyInput.Char)
\r
310 // change selection based on text as it is typed.
\r
311 u32 now = os::Timer::getTime();
\r
313 if (now - LastKeyTime < 500)
\r
315 // add to key buffer if it isn't a key repeat
\r
316 if (!(KeyBuffer.size() == 1 && KeyBuffer[0] == event.KeyInput.Char))
\r
319 KeyBuffer[KeyBuffer.size()-1] = event.KeyInput.Char;
\r
325 KeyBuffer[0] = event.KeyInput.Char;
\r
329 // find the selected item, starting at the current selection
\r
330 s32 start = Selected;
\r
331 // dont change selection if the key buffer matches the current item
\r
332 if (Selected > -1 && KeyBuffer.size() > 1)
\r
334 if (Items[Selected].Text.size() >= KeyBuffer.size() &&
\r
335 KeyBuffer.equals_ignore_case(Items[Selected].Text.subString(0,KeyBuffer.size())))
\r
340 for (current = start+1; current < (s32)Items.size(); ++current)
\r
342 if (Items[current].Text.size() >= KeyBuffer.size())
\r
344 if (KeyBuffer.equals_ignore_case(Items[current].Text.subString(0,KeyBuffer.size())))
\r
346 if (Parent && Selected != current && !Selecting && !MoveOverSelect)
\r
349 e.EventType = EET_GUI_EVENT;
\r
350 e.GUIEvent.Caller = this;
\r
351 e.GUIEvent.Element = 0;
\r
352 e.GUIEvent.EventType = EGET_LISTBOX_CHANGED;
\r
353 Parent->OnEvent(e);
\r
355 setSelected(current);
\r
360 for (current = 0; current <= start; ++current)
\r
362 if (Items[current].Text.size() >= KeyBuffer.size())
\r
364 if (KeyBuffer.equals_ignore_case(Items[current].Text.subString(0,KeyBuffer.size())))
\r
366 if (Parent && Selected != current && !Selecting && !MoveOverSelect)
\r
368 Selected = current;
\r
370 e.EventType = EET_GUI_EVENT;
\r
371 e.GUIEvent.Caller = this;
\r
372 e.GUIEvent.Element = 0;
\r
373 e.GUIEvent.EventType = EGET_LISTBOX_CHANGED;
\r
374 Parent->OnEvent(e);
\r
376 setSelected(current);
\r
386 case EET_GUI_EVENT:
\r
387 switch(event.GUIEvent.EventType)
\r
389 case gui::EGET_SCROLL_BAR_CHANGED:
\r
390 if (event.GUIEvent.Caller == ScrollBar)
\r
393 case gui::EGET_ELEMENT_FOCUS_LOST:
\r
395 if (event.GUIEvent.Caller == this)
\r
403 case EET_MOUSE_INPUT_EVENT:
\r
405 core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
\r
407 switch(event.MouseInput.Event)
\r
409 case EMIE_MOUSE_WHEEL:
\r
410 ScrollBar->setPos(ScrollBar->getPos() + (event.MouseInput.Wheel < 0 ? -1 : 1)*-ItemHeight/2);
\r
413 case EMIE_LMOUSE_PRESSED_DOWN:
\r
419 case EMIE_LMOUSE_LEFT_UP:
\r
423 if (isPointInside(p))
\r
424 selectNew(event.MouseInput.Y);
\r
429 case EMIE_MOUSE_MOVED:
\r
430 if (Selecting || MoveOverSelect)
\r
432 if (isPointInside(p))
\r
434 selectNew(event.MouseInput.Y, true);
\r
448 return IGUIElement::OnEvent(event);
\r
452 void CGUIListBox::selectNew(s32 ypos, bool onlyHover)
\r
454 u32 now = os::Timer::getTime();
\r
455 s32 oldSelected = Selected;
\r
457 Selected = getItemAt(AbsoluteRect.UpperLeftCorner.X, ypos);
\r
458 if (Selected<0 && !Items.empty())
\r
461 recalculateScrollPos();
\r
463 gui::EGUI_EVENT_TYPE eventType = (Selected == oldSelected && now < selectTime + 500) ? EGET_LISTBOX_SELECTED_AGAIN : EGET_LISTBOX_CHANGED;
\r
466 if (Parent && !onlyHover)
\r
469 event.EventType = EET_GUI_EVENT;
\r
470 event.GUIEvent.Caller = this;
\r
471 event.GUIEvent.Element = 0;
\r
472 event.GUIEvent.EventType = eventType;
\r
473 Parent->OnEvent(event);
\r
478 //! Update the position and size of the listbox, and update the scrollbar
\r
479 void CGUIListBox::updateAbsolutePosition()
\r
481 IGUIElement::updateAbsolutePosition();
\r
483 recalculateItemHeight();
\r
487 //! draws the element and its children
\r
488 void CGUIListBox::draw()
\r
493 recalculateItemHeight(); // if the font changed
\r
495 IGUISkin* skin = Environment->getSkin();
\r
496 updateScrollBarSize(skin->getSize(EGDS_SCROLLBAR_SIZE));
\r
498 core::rect<s32>* clipRect = 0;
\r
501 core::rect<s32> frameRect(AbsoluteRect);
\r
505 core::rect<s32> clientClip(AbsoluteRect);
\r
506 clientClip.UpperLeftCorner.Y += 1;
\r
507 clientClip.UpperLeftCorner.X += 1;
\r
508 if (ScrollBar->isVisible())
\r
509 clientClip.LowerRightCorner.X -= ScrollBar->getRelativePosition().getWidth();
\r
510 clientClip.LowerRightCorner.Y -= 1;
\r
511 clientClip.clipAgainst(AbsoluteClippingRect);
\r
513 skin->draw3DSunkenPane(this, skin->getColor(EGDC_3D_HIGH_LIGHT), true,
\r
514 DrawBack, frameRect, &AbsoluteClippingRect);
\r
517 clientClip.clipAgainst(*clipRect);
\r
519 frameRect = AbsoluteRect;
\r
520 frameRect.UpperLeftCorner.X += 1;
\r
521 if (ScrollBar->isVisible())
\r
522 frameRect.LowerRightCorner.X -= ScrollBar->getRelativePosition().getWidth();
\r
524 frameRect.LowerRightCorner.Y = AbsoluteRect.UpperLeftCorner.Y + ItemHeight;
\r
526 frameRect.UpperLeftCorner.Y -= ScrollBar->getPos();
\r
527 frameRect.LowerRightCorner.Y -= ScrollBar->getPos();
\r
529 bool hl = (HighlightWhenNotFocused || Environment->hasFocus(this) || Environment->hasFocus(ScrollBar));
\r
531 for (s32 i=0; i<(s32)Items.size(); ++i)
\r
533 if (frameRect.LowerRightCorner.Y >= AbsoluteRect.UpperLeftCorner.Y &&
\r
534 frameRect.UpperLeftCorner.Y <= AbsoluteRect.LowerRightCorner.Y)
\r
536 if (i == Selected && hl)
\r
537 skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), frameRect, &clientClip);
\r
539 core::rect<s32> textRect = frameRect;
\r
540 textRect.UpperLeftCorner.X += 3;
\r
544 if (IconBank && (Items[i].Icon > -1))
\r
546 core::position2di iconPos = textRect.UpperLeftCorner;
\r
547 iconPos.Y += textRect.getHeight() / 2;
\r
548 iconPos.X += ItemsIconWidth/2;
\r
550 if ( i==Selected && hl )
\r
552 IconBank->draw2DSprite( (u32)Items[i].Icon, iconPos, &clientClip,
\r
553 hasItemOverrideColor(i, EGUI_LBC_ICON_HIGHLIGHT) ?
\r
554 getItemOverrideColor(i, EGUI_LBC_ICON_HIGHLIGHT) : getItemDefaultColor(EGUI_LBC_ICON_HIGHLIGHT),
\r
555 selectTime, os::Timer::getTime(), false, true);
\r
559 IconBank->draw2DSprite( (u32)Items[i].Icon, iconPos, &clientClip,
\r
560 hasItemOverrideColor(i, EGUI_LBC_ICON) ? getItemOverrideColor(i, EGUI_LBC_ICON) : getItemDefaultColor(EGUI_LBC_ICON),
\r
561 0 , (i==Selected) ? os::Timer::getTime() : 0, false, true);
\r
565 textRect.UpperLeftCorner.X += ItemsIconWidth+3;
\r
567 if ( i==Selected && hl )
\r
569 Font->draw(Items[i].Text.c_str(), textRect,
\r
570 hasItemOverrideColor(i, EGUI_LBC_TEXT_HIGHLIGHT) ?
\r
571 getItemOverrideColor(i, EGUI_LBC_TEXT_HIGHLIGHT) : getItemDefaultColor(EGUI_LBC_TEXT_HIGHLIGHT),
\r
572 false, true, &clientClip);
\r
576 Font->draw(Items[i].Text.c_str(), textRect,
\r
577 hasItemOverrideColor(i, EGUI_LBC_TEXT) ? getItemOverrideColor(i, EGUI_LBC_TEXT) : getItemDefaultColor(EGUI_LBC_TEXT),
\r
578 false, true, &clientClip);
\r
581 textRect.UpperLeftCorner.X -= ItemsIconWidth+3;
\r
585 frameRect.UpperLeftCorner.Y += ItemHeight;
\r
586 frameRect.LowerRightCorner.Y += ItemHeight;
\r
589 IGUIElement::draw();
\r
593 //! adds an list item with an icon
\r
594 u32 CGUIListBox::addItem(const wchar_t* text, s32 icon)
\r
600 Items.push_back(i);
\r
601 recalculateItemHeight();
\r
602 recalculateItemWidth(icon);
\r
604 return Items.size() - 1;
\r
608 void CGUIListBox::setSpriteBank(IGUISpriteBank* bank)
\r
610 if ( bank == IconBank )
\r
621 void CGUIListBox::recalculateScrollPos()
\r
626 const s32 selPos = (Selected == -1 ? TotalItemHeight : Selected * ItemHeight) - ScrollBar->getPos();
\r
630 ScrollBar->setPos(ScrollBar->getPos() + selPos);
\r
633 if (selPos > AbsoluteRect.getHeight() - ItemHeight)
\r
635 ScrollBar->setPos(ScrollBar->getPos() + selPos - AbsoluteRect.getHeight() + ItemHeight);
\r
639 void CGUIListBox::updateScrollBarSize(s32 size)
\r
641 if ( size != ScrollBar->getRelativePosition().getWidth() )
\r
643 core::recti r(RelativeRect.getWidth() - size, 0, RelativeRect.getWidth(), RelativeRect.getHeight());
\r
644 ScrollBar->setRelativePosition(r);
\r
648 void CGUIListBox::setAutoScrollEnabled(bool scroll)
\r
650 AutoScroll = scroll;
\r
654 bool CGUIListBox::isAutoScrollEnabled() const
\r
660 bool CGUIListBox::getSerializationLabels(EGUI_LISTBOX_COLOR colorType, core::stringc & useColorLabel, core::stringc & colorLabel) const
\r
662 switch ( colorType )
\r
664 case EGUI_LBC_TEXT:
\r
665 useColorLabel = "UseColText";
\r
666 colorLabel = "ColText";
\r
668 case EGUI_LBC_TEXT_HIGHLIGHT:
\r
669 useColorLabel = "UseColTextHl";
\r
670 colorLabel = "ColTextHl";
\r
672 case EGUI_LBC_ICON:
\r
673 useColorLabel = "UseColIcon";
\r
674 colorLabel = "ColIcon";
\r
676 case EGUI_LBC_ICON_HIGHLIGHT:
\r
677 useColorLabel = "UseColIconHl";
\r
678 colorLabel = "ColIconHl";
\r
687 void CGUIListBox::recalculateItemWidth(s32 icon)
\r
689 if (IconBank && icon > -1 &&
\r
690 IconBank->getSprites().size() > (u32)icon &&
\r
691 IconBank->getSprites()[(u32)icon].Frames.size())
\r
693 u32 rno = IconBank->getSprites()[(u32)icon].Frames[0].rectNumber;
\r
694 if (IconBank->getPositions().size() > rno)
\r
696 const s32 w = IconBank->getPositions()[rno].getWidth();
\r
697 if (w > ItemsIconWidth)
\r
698 ItemsIconWidth = w;
\r
704 void CGUIListBox::setItem(u32 index, const wchar_t* text, s32 icon)
\r
706 if ( index >= Items.size() )
\r
709 Items[index].Text = text;
\r
710 Items[index].Icon = icon;
\r
712 recalculateItemHeight();
\r
713 recalculateItemWidth(icon);
\r
717 //! Insert the item at the given index
\r
718 //! Return the index on success or -1 on failure.
\r
719 s32 CGUIListBox::insertItem(u32 index, const wchar_t* text, s32 icon)
\r
725 Items.insert(i, index);
\r
726 recalculateItemHeight();
\r
727 recalculateItemWidth(icon);
\r
733 void CGUIListBox::swapItems(u32 index1, u32 index2)
\r
735 if ( index1 >= Items.size() || index2 >= Items.size() )
\r
738 ListItem dummmy = Items[index1];
\r
739 Items[index1] = Items[index2];
\r
740 Items[index2] = dummmy;
\r
744 void CGUIListBox::setItemOverrideColor(u32 index, video::SColor color)
\r
746 for ( u32 c=0; c < EGUI_LBC_COUNT; ++c )
\r
748 Items[index].OverrideColors[c].Use = true;
\r
749 Items[index].OverrideColors[c].Color = color;
\r
754 void CGUIListBox::setItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType, video::SColor color)
\r
756 if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
759 Items[index].OverrideColors[colorType].Use = true;
\r
760 Items[index].OverrideColors[colorType].Color = color;
\r
764 void CGUIListBox::clearItemOverrideColor(u32 index)
\r
766 for (u32 c=0; c < (u32)EGUI_LBC_COUNT; ++c )
\r
768 Items[index].OverrideColors[c].Use = false;
\r
773 void CGUIListBox::clearItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType)
\r
775 if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
778 Items[index].OverrideColors[colorType].Use = false;
\r
782 bool CGUIListBox::hasItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const
\r
784 if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
787 return Items[index].OverrideColors[colorType].Use;
\r
791 video::SColor CGUIListBox::getItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const
\r
793 if ( (u32)index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
794 return video::SColor();
\r
796 return Items[index].OverrideColors[colorType].Color;
\r
800 video::SColor CGUIListBox::getItemDefaultColor(EGUI_LISTBOX_COLOR colorType) const
\r
802 IGUISkin* skin = Environment->getSkin();
\r
804 return video::SColor();
\r
806 switch ( colorType )
\r
808 case EGUI_LBC_TEXT:
\r
809 return skin->getColor(EGDC_BUTTON_TEXT);
\r
810 case EGUI_LBC_TEXT_HIGHLIGHT:
\r
811 return skin->getColor(EGDC_HIGH_LIGHT_TEXT);
\r
812 case EGUI_LBC_ICON:
\r
813 return skin->getColor(EGDC_ICON);
\r
814 case EGUI_LBC_ICON_HIGHLIGHT:
\r
815 return skin->getColor(EGDC_ICON_HIGH_LIGHT);
\r
817 return video::SColor();
\r
821 //! set global itemHeight
\r
822 void CGUIListBox::setItemHeight( s32 height )
\r
824 ItemHeight = height;
\r
825 ItemHeightOverride = 1;
\r
829 //! Sets whether to draw the background
\r
830 void CGUIListBox::setDrawBackground(bool draw)
\r
835 //! Access the vertical scrollbar
\r
836 IGUIScrollBar* CGUIListBox::getVerticalScrollBar() const
\r
841 } // end namespace gui
\r
842 } // end namespace irr
\r