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
6 #ifdef _IRR_COMPILE_WITH_GUI_
\r
8 #include "CGUIListBox.h"
\r
9 #include "IGUISkin.h"
\r
10 #include "IGUIEnvironment.h"
\r
11 #include "IVideoDriver.h"
\r
12 #include "IGUIFont.h"
\r
13 #include "IGUISpriteBank.h"
\r
14 #include "CGUIScrollBar.h"
\r
23 CGUIListBox::CGUIListBox(IGUIEnvironment* environment, IGUIElement* parent,
\r
24 s32 id, core::rect<s32> rectangle, bool clip,
\r
25 bool drawBack, bool moveOverSelect)
\r
26 : IGUIListBox(environment, parent, id, rectangle), Selected(-1),
\r
27 ItemHeight(0),ItemHeightOverride(0),
\r
28 TotalItemHeight(0), ItemsIconWidth(0), Font(0), IconBank(0),
\r
29 ScrollBar(0), selectTime(0), LastKeyTime(0), Selecting(false), DrawBack(drawBack),
\r
30 MoveOverSelect(moveOverSelect), AutoScroll(true), HighlightWhenNotFocused(true)
\r
33 setDebugName("CGUIListBox");
\r
36 IGUISkin* skin = Environment->getSkin();
\r
38 ScrollBar = new CGUIScrollBar(false, Environment, this, -1,
\r
39 core::recti(0, 0, 1, 1),
\r
41 ScrollBar->setSubElement(true);
\r
42 ScrollBar->setTabStop(false);
\r
43 ScrollBar->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
\r
44 ScrollBar->setVisible(false);
\r
45 ScrollBar->setPos(0);
\r
47 updateScrollBarSize(skin->getSize(EGDS_SCROLLBAR_SIZE));
\r
49 setNotClipped(!clip);
\r
51 // this element can be tabbed to
\r
55 updateAbsolutePosition();
\r
60 CGUIListBox::~CGUIListBox()
\r
73 //! returns amount of list items
\r
74 u32 CGUIListBox::getItemCount() const
\r
76 return Items.size();
\r
80 //! returns string of a list item. the may be a value from 0 to itemCount-1
\r
81 const wchar_t* CGUIListBox::getListItem(u32 id) const
\r
83 if (id>=Items.size())
\r
86 return Items[id].Text.c_str();
\r
90 //! Returns the icon of an item
\r
91 s32 CGUIListBox::getIcon(u32 id) const
\r
93 if (id>=Items.size())
\r
96 return Items[id].Icon;
\r
100 //! adds a list item, returns id of item
\r
101 u32 CGUIListBox::addItem(const wchar_t* text)
\r
103 return addItem(text, -1);
\r
107 //! adds a list item, returns id of item
\r
108 void CGUIListBox::removeItem(u32 id)
\r
110 if (id >= Items.size())
\r
113 if ((u32)Selected==id)
\r
117 else if ((u32)Selected > id)
\r
120 selectTime = os::Timer::getTime();
\r
125 recalculateItemHeight();
\r
129 s32 CGUIListBox::getItemAt(s32 xpos, s32 ypos) const
\r
131 if ( xpos < AbsoluteRect.UpperLeftCorner.X || xpos >= AbsoluteRect.LowerRightCorner.X
\r
132 || ypos < AbsoluteRect.UpperLeftCorner.Y || ypos >= AbsoluteRect.LowerRightCorner.Y
\r
136 if ( ItemHeight == 0 )
\r
139 s32 item = ((ypos - AbsoluteRect.UpperLeftCorner.Y - 1) + ScrollBar->getPos()) / ItemHeight;
\r
140 if ( item < 0 || item >= (s32)Items.size())
\r
146 //! clears the list
\r
147 void CGUIListBox::clear()
\r
150 ItemsIconWidth = 0;
\r
153 ScrollBar->setPos(0);
\r
155 recalculateItemHeight();
\r
159 void CGUIListBox::recalculateItemHeight()
\r
161 IGUISkin* skin = Environment->getSkin();
\r
163 if (Font != skin->getFont())
\r
168 Font = skin->getFont();
\r
169 if ( 0 == ItemHeightOverride )
\r
174 if ( 0 == ItemHeightOverride )
\r
175 ItemHeight = Font->getDimension(L"A").Height + 4;
\r
181 TotalItemHeight = ItemHeight * Items.size();
\r
182 ScrollBar->setMax( core::max_(0, TotalItemHeight - AbsoluteRect.getHeight()) );
\r
183 s32 minItemHeight = ItemHeight > 0 ? ItemHeight : 1;
\r
184 ScrollBar->setSmallStep ( minItemHeight );
\r
185 ScrollBar->setLargeStep ( 2*minItemHeight );
\r
187 if ( TotalItemHeight <= AbsoluteRect.getHeight() )
\r
188 ScrollBar->setVisible(false);
\r
190 ScrollBar->setVisible(true);
\r
193 //! returns id of selected item. returns -1 if no item is selected.
\r
194 s32 CGUIListBox::getSelected() const
\r
200 //! sets the selected item. Set this to -1 if no item should be selected
\r
201 void CGUIListBox::setSelected(s32 id)
\r
203 if ((u32)id>=Items.size())
\r
208 selectTime = os::Timer::getTime();
\r
210 recalculateScrollPos();
\r
213 //! sets the selected item. Set this to -1 if no item should be selected
\r
214 void CGUIListBox::setSelected(const wchar_t *item)
\r
220 for ( index = 0; index < (s32) Items.size(); ++index )
\r
222 if ( Items[index].Text == item )
\r
226 setSelected ( index );
\r
229 //! called if an event happened.
\r
230 bool CGUIListBox::OnEvent(const SEvent& event)
\r
234 switch(event.EventType)
\r
236 case EET_KEY_INPUT_EVENT:
\r
237 if (event.KeyInput.PressedDown &&
\r
238 (event.KeyInput.Key == KEY_DOWN ||
\r
239 event.KeyInput.Key == KEY_UP ||
\r
240 event.KeyInput.Key == KEY_HOME ||
\r
241 event.KeyInput.Key == KEY_END ||
\r
242 event.KeyInput.Key == KEY_NEXT ||
\r
243 event.KeyInput.Key == KEY_PRIOR ) )
\r
245 s32 oldSelected = Selected;
\r
246 switch (event.KeyInput.Key)
\r
258 Selected = (s32)Items.size()-1;
\r
261 Selected += AbsoluteRect.getHeight() / ItemHeight;
\r
264 Selected -= AbsoluteRect.getHeight() / ItemHeight;
\r
271 if (Selected >= (s32)Items.size())
\r
272 Selected = Items.size() - 1; // will set Selected to -1 for empty listboxes which is correct
\r
275 recalculateScrollPos();
\r
279 if (oldSelected != Selected && Parent && !Selecting && !MoveOverSelect)
\r
282 e.EventType = EET_GUI_EVENT;
\r
283 e.GUIEvent.Caller = this;
\r
284 e.GUIEvent.Element = 0;
\r
285 e.GUIEvent.EventType = EGET_LISTBOX_CHANGED;
\r
286 Parent->OnEvent(e);
\r
292 if (!event.KeyInput.PressedDown && ( event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE ) )
\r
297 e.EventType = EET_GUI_EVENT;
\r
298 e.GUIEvent.Caller = this;
\r
299 e.GUIEvent.Element = 0;
\r
300 e.GUIEvent.EventType = EGET_LISTBOX_SELECTED_AGAIN;
\r
301 Parent->OnEvent(e);
\r
305 else if (event.KeyInput.Key == KEY_TAB )
\r
309 else if (event.KeyInput.PressedDown && event.KeyInput.Char)
\r
311 // change selection based on text as it is typed.
\r
312 u32 now = os::Timer::getTime();
\r
314 if (now - LastKeyTime < 500)
\r
316 // add to key buffer if it isn't a key repeat
\r
317 if (!(KeyBuffer.size() == 1 && KeyBuffer[0] == event.KeyInput.Char))
\r
320 KeyBuffer[KeyBuffer.size()-1] = event.KeyInput.Char;
\r
326 KeyBuffer[0] = event.KeyInput.Char;
\r
330 // find the selected item, starting at the current selection
\r
331 s32 start = Selected;
\r
332 // dont change selection if the key buffer matches the current item
\r
333 if (Selected > -1 && KeyBuffer.size() > 1)
\r
335 if (Items[Selected].Text.size() >= KeyBuffer.size() &&
\r
336 KeyBuffer.equals_ignore_case(Items[Selected].Text.subString(0,KeyBuffer.size())))
\r
341 for (current = start+1; current < (s32)Items.size(); ++current)
\r
343 if (Items[current].Text.size() >= KeyBuffer.size())
\r
345 if (KeyBuffer.equals_ignore_case(Items[current].Text.subString(0,KeyBuffer.size())))
\r
347 if (Parent && Selected != current && !Selecting && !MoveOverSelect)
\r
350 e.EventType = EET_GUI_EVENT;
\r
351 e.GUIEvent.Caller = this;
\r
352 e.GUIEvent.Element = 0;
\r
353 e.GUIEvent.EventType = EGET_LISTBOX_CHANGED;
\r
354 Parent->OnEvent(e);
\r
356 setSelected(current);
\r
361 for (current = 0; current <= start; ++current)
\r
363 if (Items[current].Text.size() >= KeyBuffer.size())
\r
365 if (KeyBuffer.equals_ignore_case(Items[current].Text.subString(0,KeyBuffer.size())))
\r
367 if (Parent && Selected != current && !Selecting && !MoveOverSelect)
\r
369 Selected = current;
\r
371 e.EventType = EET_GUI_EVENT;
\r
372 e.GUIEvent.Caller = this;
\r
373 e.GUIEvent.Element = 0;
\r
374 e.GUIEvent.EventType = EGET_LISTBOX_CHANGED;
\r
375 Parent->OnEvent(e);
\r
377 setSelected(current);
\r
387 case EET_GUI_EVENT:
\r
388 switch(event.GUIEvent.EventType)
\r
390 case gui::EGET_SCROLL_BAR_CHANGED:
\r
391 if (event.GUIEvent.Caller == ScrollBar)
\r
394 case gui::EGET_ELEMENT_FOCUS_LOST:
\r
396 if (event.GUIEvent.Caller == this)
\r
404 case EET_MOUSE_INPUT_EVENT:
\r
406 core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
\r
408 switch(event.MouseInput.Event)
\r
410 case EMIE_MOUSE_WHEEL:
\r
411 ScrollBar->setPos(ScrollBar->getPos() + (event.MouseInput.Wheel < 0 ? -1 : 1)*-ItemHeight/2);
\r
414 case EMIE_LMOUSE_PRESSED_DOWN:
\r
420 case EMIE_LMOUSE_LEFT_UP:
\r
424 if (isPointInside(p))
\r
425 selectNew(event.MouseInput.Y);
\r
430 case EMIE_MOUSE_MOVED:
\r
431 if (Selecting || MoveOverSelect)
\r
433 if (isPointInside(p))
\r
435 selectNew(event.MouseInput.Y, true);
\r
449 return IGUIElement::OnEvent(event);
\r
453 void CGUIListBox::selectNew(s32 ypos, bool onlyHover)
\r
455 u32 now = os::Timer::getTime();
\r
456 s32 oldSelected = Selected;
\r
458 Selected = getItemAt(AbsoluteRect.UpperLeftCorner.X, ypos);
\r
459 if (Selected<0 && !Items.empty())
\r
462 recalculateScrollPos();
\r
464 gui::EGUI_EVENT_TYPE eventType = (Selected == oldSelected && now < selectTime + 500) ? EGET_LISTBOX_SELECTED_AGAIN : EGET_LISTBOX_CHANGED;
\r
467 if (Parent && !onlyHover)
\r
470 event.EventType = EET_GUI_EVENT;
\r
471 event.GUIEvent.Caller = this;
\r
472 event.GUIEvent.Element = 0;
\r
473 event.GUIEvent.EventType = eventType;
\r
474 Parent->OnEvent(event);
\r
479 //! Update the position and size of the listbox, and update the scrollbar
\r
480 void CGUIListBox::updateAbsolutePosition()
\r
482 IGUIElement::updateAbsolutePosition();
\r
484 recalculateItemHeight();
\r
488 //! draws the element and its children
\r
489 void CGUIListBox::draw()
\r
494 recalculateItemHeight(); // if the font changed
\r
496 IGUISkin* skin = Environment->getSkin();
\r
497 updateScrollBarSize(skin->getSize(EGDS_SCROLLBAR_SIZE));
\r
499 core::rect<s32>* clipRect = 0;
\r
502 core::rect<s32> frameRect(AbsoluteRect);
\r
506 core::rect<s32> clientClip(AbsoluteRect);
\r
507 clientClip.UpperLeftCorner.Y += 1;
\r
508 clientClip.UpperLeftCorner.X += 1;
\r
509 if (ScrollBar->isVisible())
\r
510 clientClip.LowerRightCorner.X -= ScrollBar->getRelativePosition().getWidth();
\r
511 clientClip.LowerRightCorner.Y -= 1;
\r
512 clientClip.clipAgainst(AbsoluteClippingRect);
\r
514 skin->draw3DSunkenPane(this, skin->getColor(EGDC_3D_HIGH_LIGHT), true,
\r
515 DrawBack, frameRect, &AbsoluteClippingRect);
\r
518 clientClip.clipAgainst(*clipRect);
\r
520 frameRect = AbsoluteRect;
\r
521 frameRect.UpperLeftCorner.X += 1;
\r
522 if (ScrollBar->isVisible())
\r
523 frameRect.LowerRightCorner.X -= ScrollBar->getRelativePosition().getWidth();
\r
525 frameRect.LowerRightCorner.Y = AbsoluteRect.UpperLeftCorner.Y + ItemHeight;
\r
527 frameRect.UpperLeftCorner.Y -= ScrollBar->getPos();
\r
528 frameRect.LowerRightCorner.Y -= ScrollBar->getPos();
\r
530 bool hl = (HighlightWhenNotFocused || Environment->hasFocus(this) || Environment->hasFocus(ScrollBar));
\r
532 for (s32 i=0; i<(s32)Items.size(); ++i)
\r
534 if (frameRect.LowerRightCorner.Y >= AbsoluteRect.UpperLeftCorner.Y &&
\r
535 frameRect.UpperLeftCorner.Y <= AbsoluteRect.LowerRightCorner.Y)
\r
537 if (i == Selected && hl)
\r
538 skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), frameRect, &clientClip);
\r
540 core::rect<s32> textRect = frameRect;
\r
541 textRect.UpperLeftCorner.X += 3;
\r
545 if (IconBank && (Items[i].Icon > -1))
\r
547 core::position2di iconPos = textRect.UpperLeftCorner;
\r
548 iconPos.Y += textRect.getHeight() / 2;
\r
549 iconPos.X += ItemsIconWidth/2;
\r
551 if ( i==Selected && hl )
\r
553 IconBank->draw2DSprite( (u32)Items[i].Icon, iconPos, &clientClip,
\r
554 hasItemOverrideColor(i, EGUI_LBC_ICON_HIGHLIGHT) ?
\r
555 getItemOverrideColor(i, EGUI_LBC_ICON_HIGHLIGHT) : getItemDefaultColor(EGUI_LBC_ICON_HIGHLIGHT),
\r
556 selectTime, os::Timer::getTime(), false, true);
\r
560 IconBank->draw2DSprite( (u32)Items[i].Icon, iconPos, &clientClip,
\r
561 hasItemOverrideColor(i, EGUI_LBC_ICON) ? getItemOverrideColor(i, EGUI_LBC_ICON) : getItemDefaultColor(EGUI_LBC_ICON),
\r
562 0 , (i==Selected) ? os::Timer::getTime() : 0, false, true);
\r
566 textRect.UpperLeftCorner.X += ItemsIconWidth+3;
\r
568 if ( i==Selected && hl )
\r
570 Font->draw(Items[i].Text.c_str(), textRect,
\r
571 hasItemOverrideColor(i, EGUI_LBC_TEXT_HIGHLIGHT) ?
\r
572 getItemOverrideColor(i, EGUI_LBC_TEXT_HIGHLIGHT) : getItemDefaultColor(EGUI_LBC_TEXT_HIGHLIGHT),
\r
573 false, true, &clientClip);
\r
577 Font->draw(Items[i].Text.c_str(), textRect,
\r
578 hasItemOverrideColor(i, EGUI_LBC_TEXT) ? getItemOverrideColor(i, EGUI_LBC_TEXT) : getItemDefaultColor(EGUI_LBC_TEXT),
\r
579 false, true, &clientClip);
\r
582 textRect.UpperLeftCorner.X -= ItemsIconWidth+3;
\r
586 frameRect.UpperLeftCorner.Y += ItemHeight;
\r
587 frameRect.LowerRightCorner.Y += ItemHeight;
\r
590 IGUIElement::draw();
\r
594 //! adds an list item with an icon
\r
595 u32 CGUIListBox::addItem(const wchar_t* text, s32 icon)
\r
601 Items.push_back(i);
\r
602 recalculateItemHeight();
\r
603 recalculateItemWidth(icon);
\r
605 return Items.size() - 1;
\r
609 void CGUIListBox::setSpriteBank(IGUISpriteBank* bank)
\r
611 if ( bank == IconBank )
\r
622 void CGUIListBox::recalculateScrollPos()
\r
627 const s32 selPos = (Selected == -1 ? TotalItemHeight : Selected * ItemHeight) - ScrollBar->getPos();
\r
631 ScrollBar->setPos(ScrollBar->getPos() + selPos);
\r
634 if (selPos > AbsoluteRect.getHeight() - ItemHeight)
\r
636 ScrollBar->setPos(ScrollBar->getPos() + selPos - AbsoluteRect.getHeight() + ItemHeight);
\r
640 void CGUIListBox::updateScrollBarSize(s32 size)
\r
642 if ( size != ScrollBar->getRelativePosition().getWidth() )
\r
644 core::recti r(RelativeRect.getWidth() - size, 0, RelativeRect.getWidth(), RelativeRect.getHeight());
\r
645 ScrollBar->setRelativePosition(r);
\r
649 void CGUIListBox::setAutoScrollEnabled(bool scroll)
\r
651 AutoScroll = scroll;
\r
655 bool CGUIListBox::isAutoScrollEnabled() const
\r
661 bool CGUIListBox::getSerializationLabels(EGUI_LISTBOX_COLOR colorType, core::stringc & useColorLabel, core::stringc & colorLabel) const
\r
663 switch ( colorType )
\r
665 case EGUI_LBC_TEXT:
\r
666 useColorLabel = "UseColText";
\r
667 colorLabel = "ColText";
\r
669 case EGUI_LBC_TEXT_HIGHLIGHT:
\r
670 useColorLabel = "UseColTextHl";
\r
671 colorLabel = "ColTextHl";
\r
673 case EGUI_LBC_ICON:
\r
674 useColorLabel = "UseColIcon";
\r
675 colorLabel = "ColIcon";
\r
677 case EGUI_LBC_ICON_HIGHLIGHT:
\r
678 useColorLabel = "UseColIconHl";
\r
679 colorLabel = "ColIconHl";
\r
688 void CGUIListBox::recalculateItemWidth(s32 icon)
\r
690 if (IconBank && icon > -1 &&
\r
691 IconBank->getSprites().size() > (u32)icon &&
\r
692 IconBank->getSprites()[(u32)icon].Frames.size())
\r
694 u32 rno = IconBank->getSprites()[(u32)icon].Frames[0].rectNumber;
\r
695 if (IconBank->getPositions().size() > rno)
\r
697 const s32 w = IconBank->getPositions()[rno].getWidth();
\r
698 if (w > ItemsIconWidth)
\r
699 ItemsIconWidth = w;
\r
705 void CGUIListBox::setItem(u32 index, const wchar_t* text, s32 icon)
\r
707 if ( index >= Items.size() )
\r
710 Items[index].Text = text;
\r
711 Items[index].Icon = icon;
\r
713 recalculateItemHeight();
\r
714 recalculateItemWidth(icon);
\r
718 //! Insert the item at the given index
\r
719 //! Return the index on success or -1 on failure.
\r
720 s32 CGUIListBox::insertItem(u32 index, const wchar_t* text, s32 icon)
\r
726 Items.insert(i, index);
\r
727 recalculateItemHeight();
\r
728 recalculateItemWidth(icon);
\r
734 void CGUIListBox::swapItems(u32 index1, u32 index2)
\r
736 if ( index1 >= Items.size() || index2 >= Items.size() )
\r
739 ListItem dummmy = Items[index1];
\r
740 Items[index1] = Items[index2];
\r
741 Items[index2] = dummmy;
\r
745 void CGUIListBox::setItemOverrideColor(u32 index, video::SColor color)
\r
747 for ( u32 c=0; c < EGUI_LBC_COUNT; ++c )
\r
749 Items[index].OverrideColors[c].Use = true;
\r
750 Items[index].OverrideColors[c].Color = color;
\r
755 void CGUIListBox::setItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType, video::SColor color)
\r
757 if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
760 Items[index].OverrideColors[colorType].Use = true;
\r
761 Items[index].OverrideColors[colorType].Color = color;
\r
765 void CGUIListBox::clearItemOverrideColor(u32 index)
\r
767 for (u32 c=0; c < (u32)EGUI_LBC_COUNT; ++c )
\r
769 Items[index].OverrideColors[c].Use = false;
\r
774 void CGUIListBox::clearItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType)
\r
776 if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
779 Items[index].OverrideColors[colorType].Use = false;
\r
783 bool CGUIListBox::hasItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const
\r
785 if ( index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
788 return Items[index].OverrideColors[colorType].Use;
\r
792 video::SColor CGUIListBox::getItemOverrideColor(u32 index, EGUI_LISTBOX_COLOR colorType) const
\r
794 if ( (u32)index >= Items.size() || colorType < 0 || colorType >= EGUI_LBC_COUNT )
\r
795 return video::SColor();
\r
797 return Items[index].OverrideColors[colorType].Color;
\r
801 video::SColor CGUIListBox::getItemDefaultColor(EGUI_LISTBOX_COLOR colorType) const
\r
803 IGUISkin* skin = Environment->getSkin();
\r
805 return video::SColor();
\r
807 switch ( colorType )
\r
809 case EGUI_LBC_TEXT:
\r
810 return skin->getColor(EGDC_BUTTON_TEXT);
\r
811 case EGUI_LBC_TEXT_HIGHLIGHT:
\r
812 return skin->getColor(EGDC_HIGH_LIGHT_TEXT);
\r
813 case EGUI_LBC_ICON:
\r
814 return skin->getColor(EGDC_ICON);
\r
815 case EGUI_LBC_ICON_HIGHLIGHT:
\r
816 return skin->getColor(EGDC_ICON_HIGH_LIGHT);
\r
818 return video::SColor();
\r
822 //! set global itemHeight
\r
823 void CGUIListBox::setItemHeight( s32 height )
\r
825 ItemHeight = height;
\r
826 ItemHeightOverride = 1;
\r
830 //! Sets whether to draw the background
\r
831 void CGUIListBox::setDrawBackground(bool draw)
\r
836 //! Access the vertical scrollbar
\r
837 IGUIScrollBar* CGUIListBox::getVerticalScrollBar() const
\r
842 } // end namespace gui
\r
843 } // end namespace irr
\r
845 #endif // _IRR_COMPILE_WITH_GUI_
\r