mirror of
https://github.com/minetest/minetest.git
synced 2024-12-23 22:52:25 +01:00
940 lines
25 KiB
C
940 lines
25 KiB
C
|
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
||
|
// This file is part of the "Irrlicht Engine".
|
||
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include "IReferenceCounted.h"
|
||
|
#include "rect.h"
|
||
|
#include "irrString.h"
|
||
|
#include "IEventReceiver.h"
|
||
|
#include "EGUIElementTypes.h"
|
||
|
#include "EGUIAlignment.h"
|
||
|
#include "IGUIEnvironment.h"
|
||
|
#include <cassert>
|
||
|
#include <list>
|
||
|
#include <vector>
|
||
|
|
||
|
namespace irr
|
||
|
{
|
||
|
namespace gui
|
||
|
{
|
||
|
//! Base class of all GUI elements.
|
||
|
class IGUIElement : virtual public IReferenceCounted, public IEventReceiver
|
||
|
{
|
||
|
public:
|
||
|
//! Constructor
|
||
|
IGUIElement(EGUI_ELEMENT_TYPE type, IGUIEnvironment *environment, IGUIElement *parent,
|
||
|
s32 id, const core::rect<s32> &rectangle) :
|
||
|
Parent(0),
|
||
|
RelativeRect(rectangle), AbsoluteRect(rectangle),
|
||
|
AbsoluteClippingRect(rectangle), DesiredRect(rectangle),
|
||
|
MaxSize(0, 0), MinSize(1, 1), IsVisible(true), IsEnabled(true),
|
||
|
IsSubElement(false), NoClip(false), ID(id), IsTabStop(false), TabOrder(-1), IsTabGroup(false),
|
||
|
AlignLeft(EGUIA_UPPERLEFT), AlignRight(EGUIA_UPPERLEFT), AlignTop(EGUIA_UPPERLEFT), AlignBottom(EGUIA_UPPERLEFT),
|
||
|
Environment(environment), Type(type)
|
||
|
{
|
||
|
#ifdef _DEBUG
|
||
|
setDebugName("IGUIElement");
|
||
|
#endif
|
||
|
|
||
|
// if we were given a parent to attach to
|
||
|
if (parent) {
|
||
|
parent->addChildToEnd(this);
|
||
|
recalculateAbsolutePosition(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! Destructor
|
||
|
virtual ~IGUIElement()
|
||
|
{
|
||
|
for (auto child : Children) {
|
||
|
child->Parent = nullptr;
|
||
|
child->drop();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! Returns parent of this element.
|
||
|
IGUIElement *getParent() const
|
||
|
{
|
||
|
return Parent;
|
||
|
}
|
||
|
|
||
|
//! Returns the relative rectangle of this element.
|
||
|
core::rect<s32> getRelativePosition() const
|
||
|
{
|
||
|
return RelativeRect;
|
||
|
}
|
||
|
|
||
|
//! Sets the relative rectangle of this element.
|
||
|
/** \param r The absolute position to set */
|
||
|
void setRelativePosition(const core::rect<s32> &r)
|
||
|
{
|
||
|
if (Parent) {
|
||
|
const core::rect<s32> &r2 = Parent->getAbsolutePosition();
|
||
|
|
||
|
core::dimension2df d((f32)(r2.getSize().Width), (f32)(r2.getSize().Height));
|
||
|
|
||
|
if (AlignLeft == EGUIA_SCALE)
|
||
|
ScaleRect.UpperLeftCorner.X = (f32)r.UpperLeftCorner.X / d.Width;
|
||
|
if (AlignRight == EGUIA_SCALE)
|
||
|
ScaleRect.LowerRightCorner.X = (f32)r.LowerRightCorner.X / d.Width;
|
||
|
if (AlignTop == EGUIA_SCALE)
|
||
|
ScaleRect.UpperLeftCorner.Y = (f32)r.UpperLeftCorner.Y / d.Height;
|
||
|
if (AlignBottom == EGUIA_SCALE)
|
||
|
ScaleRect.LowerRightCorner.Y = (f32)r.LowerRightCorner.Y / d.Height;
|
||
|
}
|
||
|
|
||
|
DesiredRect = r;
|
||
|
updateAbsolutePosition();
|
||
|
}
|
||
|
|
||
|
//! Sets the relative rectangle of this element, maintaining its current width and height
|
||
|
/** \param position The new relative position to set. Width and height will not be changed. */
|
||
|
void setRelativePosition(const core::position2di &position)
|
||
|
{
|
||
|
const core::dimension2di mySize = RelativeRect.getSize();
|
||
|
const core::rect<s32> rectangle(position.X, position.Y,
|
||
|
position.X + mySize.Width, position.Y + mySize.Height);
|
||
|
setRelativePosition(rectangle);
|
||
|
}
|
||
|
|
||
|
//! Sets the relative rectangle of this element as a proportion of its parent's area.
|
||
|
/** \note This method used to be 'void setRelativePosition(const core::rect<f32>& r)'
|
||
|
\param r The rectangle to set, interpreted as a proportion of the parent's area.
|
||
|
Meaningful values are in the range [0...1], unless you intend this element to spill
|
||
|
outside its parent. */
|
||
|
void setRelativePositionProportional(const core::rect<f32> &r)
|
||
|
{
|
||
|
if (!Parent)
|
||
|
return;
|
||
|
|
||
|
const core::dimension2di &d = Parent->getAbsolutePosition().getSize();
|
||
|
|
||
|
DesiredRect = core::rect<s32>(
|
||
|
core::floor32((f32)d.Width * r.UpperLeftCorner.X),
|
||
|
core::floor32((f32)d.Height * r.UpperLeftCorner.Y),
|
||
|
core::floor32((f32)d.Width * r.LowerRightCorner.X),
|
||
|
core::floor32((f32)d.Height * r.LowerRightCorner.Y));
|
||
|
|
||
|
ScaleRect = r;
|
||
|
|
||
|
updateAbsolutePosition();
|
||
|
}
|
||
|
|
||
|
//! Gets the absolute rectangle of this element
|
||
|
core::rect<s32> getAbsolutePosition() const
|
||
|
{
|
||
|
return AbsoluteRect;
|
||
|
}
|
||
|
|
||
|
//! Returns the visible area of the element.
|
||
|
core::rect<s32> getAbsoluteClippingRect() const
|
||
|
{
|
||
|
return AbsoluteClippingRect;
|
||
|
}
|
||
|
|
||
|
//! Sets whether the element will ignore its parent's clipping rectangle
|
||
|
/** \param noClip If true, the element will not be clipped by its parent's clipping rectangle. */
|
||
|
void setNotClipped(bool noClip)
|
||
|
{
|
||
|
NoClip = noClip;
|
||
|
updateAbsolutePosition();
|
||
|
}
|
||
|
|
||
|
//! Gets whether the element will ignore its parent's clipping rectangle
|
||
|
/** \return true if the element is not clipped by its parent's clipping rectangle. */
|
||
|
bool isNotClipped() const
|
||
|
{
|
||
|
return NoClip;
|
||
|
}
|
||
|
|
||
|
//! Sets the maximum size allowed for this element
|
||
|
/** If set to 0,0, there is no maximum size */
|
||
|
void setMaxSize(core::dimension2du size)
|
||
|
{
|
||
|
MaxSize = size;
|
||
|
updateAbsolutePosition();
|
||
|
}
|
||
|
|
||
|
//! Sets the minimum size allowed for this element
|
||
|
void setMinSize(core::dimension2du size)
|
||
|
{
|
||
|
MinSize = size;
|
||
|
if (MinSize.Width < 1)
|
||
|
MinSize.Width = 1;
|
||
|
if (MinSize.Height < 1)
|
||
|
MinSize.Height = 1;
|
||
|
updateAbsolutePosition();
|
||
|
}
|
||
|
|
||
|
//! The alignment defines how the borders of this element will be positioned when the parent element is resized.
|
||
|
void setAlignment(EGUI_ALIGNMENT left, EGUI_ALIGNMENT right, EGUI_ALIGNMENT top, EGUI_ALIGNMENT bottom)
|
||
|
{
|
||
|
AlignLeft = left;
|
||
|
AlignRight = right;
|
||
|
AlignTop = top;
|
||
|
AlignBottom = bottom;
|
||
|
|
||
|
if (Parent) {
|
||
|
core::rect<s32> r(Parent->getAbsolutePosition());
|
||
|
|
||
|
core::dimension2df d((f32)r.getSize().Width, (f32)r.getSize().Height);
|
||
|
|
||
|
if (AlignLeft == EGUIA_SCALE)
|
||
|
ScaleRect.UpperLeftCorner.X = (f32)DesiredRect.UpperLeftCorner.X / d.Width;
|
||
|
if (AlignRight == EGUIA_SCALE)
|
||
|
ScaleRect.LowerRightCorner.X = (f32)DesiredRect.LowerRightCorner.X / d.Width;
|
||
|
if (AlignTop == EGUIA_SCALE)
|
||
|
ScaleRect.UpperLeftCorner.Y = (f32)DesiredRect.UpperLeftCorner.Y / d.Height;
|
||
|
if (AlignBottom == EGUIA_SCALE)
|
||
|
ScaleRect.LowerRightCorner.Y = (f32)DesiredRect.LowerRightCorner.Y / d.Height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! How left element border is aligned when parent is resized
|
||
|
EGUI_ALIGNMENT getAlignLeft() const
|
||
|
{
|
||
|
return AlignLeft;
|
||
|
}
|
||
|
|
||
|
//! How right element border is aligned when parent is resized
|
||
|
EGUI_ALIGNMENT getAlignRight() const
|
||
|
{
|
||
|
return AlignRight;
|
||
|
}
|
||
|
|
||
|
//! How top element border is aligned when parent is resized
|
||
|
EGUI_ALIGNMENT getAlignTop() const
|
||
|
{
|
||
|
return AlignTop;
|
||
|
}
|
||
|
|
||
|
//! How bottom element border is aligned when parent is resized
|
||
|
EGUI_ALIGNMENT getAlignBottom() const
|
||
|
{
|
||
|
return AlignBottom;
|
||
|
}
|
||
|
|
||
|
//! Updates the absolute position.
|
||
|
virtual void updateAbsolutePosition()
|
||
|
{
|
||
|
recalculateAbsolutePosition(false);
|
||
|
|
||
|
// update all children
|
||
|
for (auto child : Children) {
|
||
|
child->updateAbsolutePosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! Returns the topmost GUI element at the specific position.
|
||
|
/**
|
||
|
This will check this GUI element and all of its descendants, so it
|
||
|
may return this GUI element. To check all GUI elements, call this
|
||
|
function on device->getGUIEnvironment()->getRootGUIElement(). Note
|
||
|
that the root element is the size of the screen, so doing so (with
|
||
|
an on-screen point) will always return the root element if no other
|
||
|
element is above it at that point.
|
||
|
\param point: The point at which to find a GUI element.
|
||
|
\return The topmost GUI element at that point, or 0 if there are
|
||
|
no candidate elements at this point.
|
||
|
*/
|
||
|
virtual IGUIElement *getElementFromPoint(const core::position2d<s32> &point)
|
||
|
{
|
||
|
IGUIElement *target = 0;
|
||
|
|
||
|
if (isVisible()) {
|
||
|
// we have to search from back to front, because later children
|
||
|
// might be drawn over the top of earlier ones.
|
||
|
auto it = Children.rbegin();
|
||
|
auto ie = Children.rend();
|
||
|
while (it != ie) {
|
||
|
target = (*it)->getElementFromPoint(point);
|
||
|
if (target)
|
||
|
return target;
|
||
|
|
||
|
++it;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (isVisible() && isPointInside(point))
|
||
|
target = this;
|
||
|
|
||
|
return target;
|
||
|
}
|
||
|
|
||
|
//! Returns true if a point is within this element.
|
||
|
/** Elements with a shape other than a rectangle should override this method */
|
||
|
virtual bool isPointInside(const core::position2d<s32> &point) const
|
||
|
{
|
||
|
return AbsoluteClippingRect.isPointInside(point);
|
||
|
}
|
||
|
|
||
|
//! Adds a GUI element as new child of this element.
|
||
|
virtual void addChild(IGUIElement *child)
|
||
|
{
|
||
|
if (child && child != this) {
|
||
|
addChildToEnd(child);
|
||
|
child->updateAbsolutePosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! Removes a child.
|
||
|
virtual void removeChild(IGUIElement *child)
|
||
|
{
|
||
|
assert(child->Parent == this);
|
||
|
Children.erase(child->ParentPos);
|
||
|
child->Parent = nullptr;
|
||
|
child->drop();
|
||
|
}
|
||
|
|
||
|
//! Removes all children.
|
||
|
virtual void removeAllChildren()
|
||
|
{
|
||
|
while (!Children.empty()) {
|
||
|
auto child = Children.back();
|
||
|
child->remove();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! Removes this element from its parent.
|
||
|
virtual void remove()
|
||
|
{
|
||
|
if (Parent)
|
||
|
Parent->removeChild(this);
|
||
|
}
|
||
|
|
||
|
//! Draws the element and its children.
|
||
|
virtual void draw()
|
||
|
{
|
||
|
if (isVisible()) {
|
||
|
for (auto child : Children)
|
||
|
child->draw();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! animate the element and its children.
|
||
|
virtual void OnPostRender(u32 timeMs)
|
||
|
{
|
||
|
if (isVisible()) {
|
||
|
for (auto child : Children)
|
||
|
child->OnPostRender(timeMs);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//! Moves this element.
|
||
|
virtual void move(core::position2d<s32> absoluteMovement)
|
||
|
{
|
||
|
setRelativePosition(DesiredRect + absoluteMovement);
|
||
|
}
|
||
|
|
||
|
//! Returns true if element is visible.
|
||
|
virtual bool isVisible() const
|
||
|
{
|
||
|
return IsVisible;
|
||
|
}
|
||
|
|
||
|
//! Check whether the element is truly visible, taking into accounts its parents' visibility
|
||
|
/** \return true if the element and all its parents are visible,
|
||
|
false if this or any parent element is invisible. */
|
||
|
virtual bool isTrulyVisible() const
|
||
|
{
|
||
|
if (!IsVisible)
|
||
|
return false;
|
||
|
|
||
|
if (!Parent)
|
||
|
return true;
|
||
|
|
||
|
return Parent->isTrulyVisible();
|
||
|
}
|
||
|
|
||
|
//! Sets the visible state of this element.
|
||
|
virtual void setVisible(bool visible)
|
||
|
{
|
||
|
IsVisible = visible;
|
||
|
}
|
||
|
|
||
|
//! Returns true if this element was created as part of its parent control
|
||
|
virtual bool isSubElement() const
|
||
|
{
|
||
|
return IsSubElement;
|
||
|
}
|
||
|
|
||
|
//! Sets whether this control was created as part of its parent.
|
||
|
/** For example, it is true when a scrollbar is part of a listbox.
|
||
|
SubElements are not saved to disk when calling guiEnvironment->saveGUI() */
|
||
|
virtual void setSubElement(bool subElement)
|
||
|
{
|
||
|
IsSubElement = subElement;
|
||
|
}
|
||
|
|
||
|
//! If set to true, the focus will visit this element when using the tab key to cycle through elements.
|
||
|
/** If this element is a tab group (see isTabGroup/setTabGroup) then
|
||
|
ctrl+tab will be used instead. */
|
||
|
void setTabStop(bool enable)
|
||
|
{
|
||
|
IsTabStop = enable;
|
||
|
}
|
||
|
|
||
|
//! Returns true if this element can be focused by navigating with the tab key
|
||
|
bool isTabStop() const
|
||
|
{
|
||
|
return IsTabStop;
|
||
|
}
|
||
|
|
||
|
//! Sets the priority of focus when using the tab key to navigate between a group of elements.
|
||
|
/** See setTabGroup, isTabGroup and getTabGroup for information on tab groups.
|
||
|
Elements with a lower number are focused first */
|
||
|
void setTabOrder(s32 index)
|
||
|
{
|
||
|
// negative = autonumber
|
||
|
if (index < 0) {
|
||
|
TabOrder = 0;
|
||
|
IGUIElement *el = getTabGroup();
|
||
|
while (IsTabGroup && el && el->Parent)
|
||
|
el = el->Parent;
|
||
|
|
||
|
IGUIElement *first = 0, *closest = 0;
|
||
|
if (el) {
|
||
|
// find the highest element number
|
||
|
el->getNextElement(-1, true, IsTabGroup, first, closest, true, true);
|
||
|
if (first) {
|
||
|
TabOrder = first->getTabOrder() + 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else
|
||
|
TabOrder = index;
|
||
|
}
|
||
|
|
||
|
//! Returns the number in the tab order sequence
|
||
|
s32 getTabOrder() const
|
||
|
{
|
||
|
return TabOrder;
|
||
|
}
|
||
|
|
||
|
//! Sets whether this element is a container for a group of elements which can be navigated using the tab key.
|
||
|
/** For example, windows are tab groups.
|
||
|
Groups can be navigated using ctrl+tab, providing isTabStop is true. */
|
||
|
void setTabGroup(bool isGroup)
|
||
|
{
|
||
|
IsTabGroup = isGroup;
|
||
|
}
|
||
|
|
||
|
//! Returns true if this element is a tab group.
|
||
|
bool isTabGroup() const
|
||
|
{
|
||
|
return IsTabGroup;
|
||
|
}
|
||
|
|
||
|
//! Returns the container element which holds all elements in this element's tab group.
|
||
|
IGUIElement *getTabGroup()
|
||
|
{
|
||
|
IGUIElement *ret = this;
|
||
|
|
||
|
while (ret && !ret->isTabGroup())
|
||
|
ret = ret->getParent();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
//! Returns true if element is enabled
|
||
|
/** Currently elements do _not_ care about parent-states.
|
||
|
So if you want to affect children you have to enable/disable them all.
|
||
|
The only exception to this are sub-elements which also check their parent.
|
||
|
*/
|
||
|
virtual bool isEnabled() const
|
||
|
{
|
||
|
if (isSubElement() && IsEnabled && getParent())
|
||
|
return getParent()->isEnabled();
|
||
|
|
||
|
return IsEnabled;
|
||
|
}
|
||
|
|
||
|
//! Sets the enabled state of this element.
|
||
|
virtual void setEnabled(bool enabled)
|
||
|
{
|
||
|
IsEnabled = enabled;
|
||
|
}
|
||
|
|
||
|
//! Sets the new caption of this element.
|
||
|
virtual void setText(const wchar_t *text)
|
||
|
{
|
||
|
Text = text;
|
||
|
}
|
||
|
|
||
|
//! Returns caption of this element.
|
||
|
virtual const wchar_t *getText() const
|
||
|
{
|
||
|
return Text.c_str();
|
||
|
}
|
||
|
|
||
|
//! Sets the new caption of this element.
|
||
|
virtual void setToolTipText(const wchar_t *text)
|
||
|
{
|
||
|
ToolTipText = text;
|
||
|
}
|
||
|
|
||
|
//! Returns caption of this element.
|
||
|
virtual const core::stringw &getToolTipText() const
|
||
|
{
|
||
|
return ToolTipText;
|
||
|
}
|
||
|
|
||
|
//! Returns id. Can be used to identify the element.
|
||
|
virtual s32 getID() const
|
||
|
{
|
||
|
return ID;
|
||
|
}
|
||
|
|
||
|
//! Sets the id of this element
|
||
|
virtual void setID(s32 id)
|
||
|
{
|
||
|
ID = id;
|
||
|
}
|
||
|
|
||
|
//! Called if an event happened.
|
||
|
bool OnEvent(const SEvent &event) override
|
||
|
{
|
||
|
return Parent ? Parent->OnEvent(event) : false;
|
||
|
}
|
||
|
|
||
|
//! Brings a child to front
|
||
|
/** \return True if successful, false if not. */
|
||
|
virtual bool bringToFront(IGUIElement *child)
|
||
|
{
|
||
|
if (child->Parent != this)
|
||
|
return false;
|
||
|
if (std::next(child->ParentPos) == Children.end()) // already there
|
||
|
return true;
|
||
|
Children.erase(child->ParentPos);
|
||
|
child->ParentPos = Children.insert(Children.end(), child);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//! Moves a child to the back, so it's siblings are drawn on top of it
|
||
|
/** \return True if successful, false if not. */
|
||
|
virtual bool sendToBack(IGUIElement *child)
|
||
|
{
|
||
|
if (child->Parent != this)
|
||
|
return false;
|
||
|
if (child->ParentPos == Children.begin()) // already there
|
||
|
return true;
|
||
|
Children.erase(child->ParentPos);
|
||
|
child->ParentPos = Children.insert(Children.begin(), child);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//! Returns list with children of this element
|
||
|
virtual const std::list<IGUIElement *> &getChildren() const
|
||
|
{
|
||
|
return Children;
|
||
|
}
|
||
|
|
||
|
//! Finds the first element with the given id.
|
||
|
/** \param id: Id to search for.
|
||
|
\param searchchildren: Set this to true, if also children of this
|
||
|
element may contain the element with the searched id and they
|
||
|
should be searched too.
|
||
|
\return Returns the first element with the given id. If no element
|
||
|
with this id was found, 0 is returned. */
|
||
|
virtual IGUIElement *getElementFromId(s32 id, bool searchchildren = false) const
|
||
|
{
|
||
|
IGUIElement *e = 0;
|
||
|
|
||
|
for (auto child : Children) {
|
||
|
if (child->getID() == id)
|
||
|
return child;
|
||
|
|
||
|
if (searchchildren)
|
||
|
e = child->getElementFromId(id, true);
|
||
|
|
||
|
if (e)
|
||
|
return e;
|
||
|
}
|
||
|
|
||
|
return e;
|
||
|
}
|
||
|
|
||
|
//! returns true if the given element is a child of this one.
|
||
|
//! \param child: The child element to check
|
||
|
bool isMyChild(IGUIElement *child) const
|
||
|
{
|
||
|
if (!child)
|
||
|
return false;
|
||
|
do {
|
||
|
if (child->Parent)
|
||
|
child = child->Parent;
|
||
|
|
||
|
} while (child->Parent && child != this);
|
||
|
|
||
|
return child == this;
|
||
|
}
|
||
|
|
||
|
//! searches elements to find the closest next element to tab to
|
||
|
/** \param startOrder: The TabOrder of the current element, -1 if none
|
||
|
\param reverse: true if searching for a lower number
|
||
|
\param group: true if searching for a higher one
|
||
|
\param first: element with the highest/lowest known tab order depending on search direction
|
||
|
\param closest: the closest match, depending on tab order and direction
|
||
|
\param includeInvisible: includes invisible elements in the search (default=false)
|
||
|
\param includeDisabled: includes disabled elements in the search (default=false)
|
||
|
\return true if successfully found an element, false to continue searching/fail */
|
||
|
bool getNextElement(s32 startOrder, bool reverse, bool group,
|
||
|
IGUIElement *&first, IGUIElement *&closest, bool includeInvisible = false,
|
||
|
bool includeDisabled = false) const
|
||
|
{
|
||
|
// we'll stop searching if we find this number
|
||
|
s32 wanted = startOrder + (reverse ? -1 : 1);
|
||
|
if (wanted == -2)
|
||
|
wanted = 1073741824; // maximum s32
|
||
|
|
||
|
auto it = Children.begin();
|
||
|
|
||
|
s32 closestOrder, currentOrder;
|
||
|
|
||
|
while (it != Children.end()) {
|
||
|
// ignore invisible elements and their children
|
||
|
if (((*it)->isVisible() || includeInvisible) &&
|
||
|
(group == true || (*it)->isTabGroup() == false)) {
|
||
|
// ignore disabled, but children are checked (disabled is currently per element ignoring parent states)
|
||
|
if ((*it)->isEnabled() || includeDisabled) {
|
||
|
// only check tab stops and those with the same group status
|
||
|
if ((*it)->isTabStop() && ((*it)->isTabGroup() == group)) {
|
||
|
currentOrder = (*it)->getTabOrder();
|
||
|
|
||
|
// is this what we're looking for?
|
||
|
if (currentOrder == wanted) {
|
||
|
closest = *it;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// is it closer than the current closest?
|
||
|
if (closest) {
|
||
|
closestOrder = closest->getTabOrder();
|
||
|
if ((reverse && currentOrder > closestOrder && currentOrder < startOrder) || (!reverse && currentOrder < closestOrder && currentOrder > startOrder)) {
|
||
|
closest = *it;
|
||
|
}
|
||
|
} else if ((reverse && currentOrder < startOrder) || (!reverse && currentOrder > startOrder)) {
|
||
|
closest = *it;
|
||
|
}
|
||
|
|
||
|
// is it before the current first?
|
||
|
if (first) {
|
||
|
closestOrder = first->getTabOrder();
|
||
|
|
||
|
if ((reverse && closestOrder < currentOrder) || (!reverse && closestOrder > currentOrder)) {
|
||
|
first = *it;
|
||
|
}
|
||
|
} else {
|
||
|
first = *it;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// search within children
|
||
|
if ((*it)->getNextElement(startOrder, reverse, group, first, closest, includeInvisible, includeDisabled)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
++it;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//! Returns the type of the gui element.
|
||
|
/** This is needed for the .NET wrapper but will be used
|
||
|
later for serializing and deserializing.
|
||
|
If you wrote your own GUIElements, you need to set the type for your element as first parameter
|
||
|
in the constructor of IGUIElement. For own (=unknown) elements, simply use EGUIET_ELEMENT as type */
|
||
|
EGUI_ELEMENT_TYPE getType() const
|
||
|
{
|
||
|
return Type;
|
||
|
}
|
||
|
|
||
|
//! Returns true if the gui element supports the given type.
|
||
|
/** This is mostly used to check if you can cast a gui element to the class that goes with the type.
|
||
|
Most gui elements will only support their own type, but if you derive your own classes from interfaces
|
||
|
you can overload this function and add a check for the type of the base-class additionally.
|
||
|
This allows for checks comparable to the dynamic_cast of c++ with enabled rtti.
|
||
|
Note that you can't do that by calling BaseClass::hasType(type), but you have to do an explicit
|
||
|
comparison check, because otherwise the base class usually just checks for the member variable
|
||
|
Type which contains the type of your derived class.
|
||
|
*/
|
||
|
virtual bool hasType(EGUI_ELEMENT_TYPE type) const
|
||
|
{
|
||
|
return type == Type;
|
||
|
}
|
||
|
|
||
|
//! Returns the type name of the gui element.
|
||
|
/** This is needed serializing elements. */
|
||
|
virtual const c8 *getTypeName() const
|
||
|
{
|
||
|
return GUIElementTypeNames[Type];
|
||
|
}
|
||
|
|
||
|
//! Returns the name of the element.
|
||
|
/** \return Name as character string. */
|
||
|
virtual const c8 *getName() const
|
||
|
{
|
||
|
return Name.c_str();
|
||
|
}
|
||
|
|
||
|
//! Sets the name of the element.
|
||
|
/** \param name New name of the gui element. */
|
||
|
virtual void setName(const c8 *name)
|
||
|
{
|
||
|
Name = name;
|
||
|
}
|
||
|
|
||
|
//! Sets the name of the element.
|
||
|
/** \param name New name of the gui element. */
|
||
|
virtual void setName(const core::stringc &name)
|
||
|
{
|
||
|
Name = name;
|
||
|
}
|
||
|
|
||
|
//! Returns whether the element takes input from the IME
|
||
|
virtual bool acceptsIME()
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
// not virtual because needed in constructor
|
||
|
void addChildToEnd(IGUIElement *child)
|
||
|
{
|
||
|
if (child) {
|
||
|
child->grab(); // prevent destruction when removed
|
||
|
child->remove(); // remove from old parent
|
||
|
child->LastParentRect = getAbsolutePosition();
|
||
|
child->Parent = this;
|
||
|
child->ParentPos = Children.insert(Children.end(), child);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifndef NDEBUG
|
||
|
template <typename Iterator>
|
||
|
static size_t _fastSetChecksum(Iterator begin, Iterator end)
|
||
|
{
|
||
|
std::hash<typename Iterator::value_type> hasher;
|
||
|
size_t checksum = 0;
|
||
|
for (Iterator it = begin; it != end; ++it) {
|
||
|
size_t h = hasher(*it);
|
||
|
checksum ^= 966073049 + (h * 3432918353) + ((h >> 16) * 461845907);
|
||
|
}
|
||
|
return checksum;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Reorder children [from, to) to the order given by `neworder`
|
||
|
void reorderChildren(
|
||
|
std::list<IGUIElement *>::iterator from,
|
||
|
std::list<IGUIElement *>::iterator to,
|
||
|
const std::vector<IGUIElement *> &neworder)
|
||
|
{
|
||
|
assert(_fastSetChecksum(from, to) == _fastSetChecksum(neworder.begin(), neworder.end()));
|
||
|
for (auto e : neworder) {
|
||
|
*from = e;
|
||
|
e->ParentPos = from;
|
||
|
++from;
|
||
|
}
|
||
|
assert(from == to);
|
||
|
}
|
||
|
|
||
|
// not virtual because needed in constructor
|
||
|
void recalculateAbsolutePosition(bool recursive)
|
||
|
{
|
||
|
core::rect<s32> parentAbsolute(0, 0, 0, 0);
|
||
|
core::rect<s32> parentAbsoluteClip;
|
||
|
f32 fw = 0.f, fh = 0.f;
|
||
|
|
||
|
if (Parent) {
|
||
|
parentAbsolute = Parent->AbsoluteRect;
|
||
|
|
||
|
if (NoClip) {
|
||
|
IGUIElement *p = this;
|
||
|
while (p->Parent)
|
||
|
p = p->Parent;
|
||
|
parentAbsoluteClip = p->AbsoluteClippingRect;
|
||
|
} else
|
||
|
parentAbsoluteClip = Parent->AbsoluteClippingRect;
|
||
|
}
|
||
|
|
||
|
const s32 diffx = parentAbsolute.getWidth() - LastParentRect.getWidth();
|
||
|
const s32 diffy = parentAbsolute.getHeight() - LastParentRect.getHeight();
|
||
|
|
||
|
if (AlignLeft == EGUIA_SCALE || AlignRight == EGUIA_SCALE)
|
||
|
fw = (f32)parentAbsolute.getWidth();
|
||
|
|
||
|
if (AlignTop == EGUIA_SCALE || AlignBottom == EGUIA_SCALE)
|
||
|
fh = (f32)parentAbsolute.getHeight();
|
||
|
|
||
|
switch (AlignLeft) {
|
||
|
case EGUIA_UPPERLEFT:
|
||
|
break;
|
||
|
case EGUIA_LOWERRIGHT:
|
||
|
DesiredRect.UpperLeftCorner.X += diffx;
|
||
|
break;
|
||
|
case EGUIA_CENTER:
|
||
|
DesiredRect.UpperLeftCorner.X += diffx / 2;
|
||
|
break;
|
||
|
case EGUIA_SCALE:
|
||
|
DesiredRect.UpperLeftCorner.X = core::round32(ScaleRect.UpperLeftCorner.X * fw);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (AlignRight) {
|
||
|
case EGUIA_UPPERLEFT:
|
||
|
break;
|
||
|
case EGUIA_LOWERRIGHT:
|
||
|
DesiredRect.LowerRightCorner.X += diffx;
|
||
|
break;
|
||
|
case EGUIA_CENTER:
|
||
|
DesiredRect.LowerRightCorner.X += diffx / 2;
|
||
|
break;
|
||
|
case EGUIA_SCALE:
|
||
|
DesiredRect.LowerRightCorner.X = core::round32(ScaleRect.LowerRightCorner.X * fw);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (AlignTop) {
|
||
|
case EGUIA_UPPERLEFT:
|
||
|
break;
|
||
|
case EGUIA_LOWERRIGHT:
|
||
|
DesiredRect.UpperLeftCorner.Y += diffy;
|
||
|
break;
|
||
|
case EGUIA_CENTER:
|
||
|
DesiredRect.UpperLeftCorner.Y += diffy / 2;
|
||
|
break;
|
||
|
case EGUIA_SCALE:
|
||
|
DesiredRect.UpperLeftCorner.Y = core::round32(ScaleRect.UpperLeftCorner.Y * fh);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (AlignBottom) {
|
||
|
case EGUIA_UPPERLEFT:
|
||
|
break;
|
||
|
case EGUIA_LOWERRIGHT:
|
||
|
DesiredRect.LowerRightCorner.Y += diffy;
|
||
|
break;
|
||
|
case EGUIA_CENTER:
|
||
|
DesiredRect.LowerRightCorner.Y += diffy / 2;
|
||
|
break;
|
||
|
case EGUIA_SCALE:
|
||
|
DesiredRect.LowerRightCorner.Y = core::round32(ScaleRect.LowerRightCorner.Y * fh);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
RelativeRect = DesiredRect;
|
||
|
|
||
|
const s32 w = RelativeRect.getWidth();
|
||
|
const s32 h = RelativeRect.getHeight();
|
||
|
|
||
|
// make sure the desired rectangle is allowed
|
||
|
if (w < (s32)MinSize.Width)
|
||
|
RelativeRect.LowerRightCorner.X = RelativeRect.UpperLeftCorner.X + MinSize.Width;
|
||
|
if (h < (s32)MinSize.Height)
|
||
|
RelativeRect.LowerRightCorner.Y = RelativeRect.UpperLeftCorner.Y + MinSize.Height;
|
||
|
if (MaxSize.Width && w > (s32)MaxSize.Width)
|
||
|
RelativeRect.LowerRightCorner.X = RelativeRect.UpperLeftCorner.X + MaxSize.Width;
|
||
|
if (MaxSize.Height && h > (s32)MaxSize.Height)
|
||
|
RelativeRect.LowerRightCorner.Y = RelativeRect.UpperLeftCorner.Y + MaxSize.Height;
|
||
|
|
||
|
RelativeRect.repair();
|
||
|
|
||
|
AbsoluteRect = RelativeRect + parentAbsolute.UpperLeftCorner;
|
||
|
|
||
|
if (!Parent)
|
||
|
parentAbsoluteClip = AbsoluteRect;
|
||
|
|
||
|
AbsoluteClippingRect = AbsoluteRect;
|
||
|
AbsoluteClippingRect.clipAgainst(parentAbsoluteClip);
|
||
|
|
||
|
LastParentRect = parentAbsolute;
|
||
|
|
||
|
if (recursive) {
|
||
|
// update all children
|
||
|
for (auto child : Children) {
|
||
|
child->recalculateAbsolutePosition(recursive);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
//! List of all children of this element
|
||
|
std::list<IGUIElement *> Children;
|
||
|
|
||
|
//! Pointer to the parent
|
||
|
IGUIElement *Parent;
|
||
|
|
||
|
//! Our position in the parent list. Only valid when Parent != nullptr
|
||
|
std::list<IGUIElement *>::iterator ParentPos;
|
||
|
|
||
|
//! relative rect of element
|
||
|
core::rect<s32> RelativeRect;
|
||
|
|
||
|
//! absolute rect of element
|
||
|
core::rect<s32> AbsoluteRect;
|
||
|
|
||
|
//! absolute clipping rect of element
|
||
|
core::rect<s32> AbsoluteClippingRect;
|
||
|
|
||
|
//! the rectangle the element would prefer to be,
|
||
|
//! if it was not constrained by parent or max/min size
|
||
|
core::rect<s32> DesiredRect;
|
||
|
|
||
|
//! for calculating the difference when resizing parent
|
||
|
core::rect<s32> LastParentRect;
|
||
|
|
||
|
//! relative scale of the element inside its parent
|
||
|
core::rect<f32> ScaleRect;
|
||
|
|
||
|
//! maximum and minimum size of the element
|
||
|
core::dimension2du MaxSize, MinSize;
|
||
|
|
||
|
//! is visible?
|
||
|
bool IsVisible;
|
||
|
|
||
|
//! is enabled?
|
||
|
bool IsEnabled;
|
||
|
|
||
|
//! is a part of a larger whole and should not be serialized?
|
||
|
bool IsSubElement;
|
||
|
|
||
|
//! does this element ignore its parent's clipping rectangle?
|
||
|
bool NoClip;
|
||
|
|
||
|
//! caption
|
||
|
core::stringw Text;
|
||
|
|
||
|
//! tooltip
|
||
|
core::stringw ToolTipText;
|
||
|
|
||
|
//! users can set this for identifying the element by string
|
||
|
core::stringc Name;
|
||
|
|
||
|
//! users can set this for identifying the element by integer
|
||
|
s32 ID;
|
||
|
|
||
|
//! tab stop like in windows
|
||
|
bool IsTabStop;
|
||
|
|
||
|
//! tab order
|
||
|
s32 TabOrder;
|
||
|
|
||
|
//! tab groups are containers like windows, use ctrl+tab to navigate
|
||
|
bool IsTabGroup;
|
||
|
|
||
|
//! tells the element how to act when its parent is resized
|
||
|
EGUI_ALIGNMENT AlignLeft, AlignRight, AlignTop, AlignBottom;
|
||
|
|
||
|
//! GUI Environment
|
||
|
IGUIEnvironment *Environment;
|
||
|
|
||
|
//! type of element
|
||
|
EGUI_ELEMENT_TYPE Type;
|
||
|
};
|
||
|
|
||
|
} // end namespace gui
|
||
|
} // end namespace irr
|