Torque3D/Engine/source/gui/buttons/guiButtonBaseCtrl.cpp
marauder2k7 6f0c31468f various fixes
added fix for font cache (stb required size to be read switch back to libpng knocks the stream off)

Added extra control over the rendering of guibtimaps
If guibitmaps are a child of buttons the profile colors now affect how to the button is rendered.
2026-02-17 00:13:10 +00:00

593 lines
18 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "gui/buttons/guiButtonBaseCtrl.h"
#include "console/console.h"
#include "console/engineAPI.h"
#include "gfx/gfxDevice.h"
#include "gui/core/guiCanvas.h"
#include "i18n/lang.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxTrack.h"
IMPLEMENT_CONOBJECT(GuiButtonBaseCtrl);
ConsoleDocClass(GuiButtonBaseCtrl,
"@brief The base class for the various button controls.\n\n"
"This is the base class for the various types of button controls. If no more specific functionality is required than "
"offered by this class, then it can be instantiated and used directly. Otherwise, its subclasses should be used:\n"
"- GuiRadioCtrl (radio buttons)\n"
"- GuiCheckBoxCtrl (checkboxes)\n"
"- GuiButtonCtrl (push buttons with text labels)\n"
"- GuiBitmapButtonCtrl (bitmapped buttons)\n"
"- GuiBitmapButtonTextCtrl (bitmapped buttons with a text label)\n"
"- GuiToggleButtonCtrl (toggle buttons, i.e. push buttons with \"sticky\" behavior)\n"
"- GuiSwatchButtonCtrl (color swatch buttons)\n"
"- GuiBorderButtonCtrl (push buttons for surrounding child controls)\n\n"
"@ingroup GuiButtons"
);
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseDown, void, (), (),
"If #useMouseEvents is true, this is called when the left mouse button is pressed on an (active) "
"button.");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseUp, void, (), (),
"If #useMouseEvents is true, this is called when the left mouse button is release over an (active) "
"button.\n\n"
"@note To trigger actions, better use onClick() since onMouseUp() will also be called when the mouse was "
"not originally pressed on the button.");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onClick, void, (), (),
"Called when the primary action of the button is triggered (e.g. by a left mouse click).");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onDoubleClick, void, (), (),
"Called when the left mouse button is double-clicked on the button.");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onRightClick, void, (), (),
"Called when the right mouse button is clicked on the button.");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseEnter, void, (), (),
"If #useMouseEvents is true, this is called when the mouse cursor moves over the button (only if the button "
"is the front-most visible control, though).");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseLeave, void, (), (),
"If #useMouseEvents is true, this is called when the mouse cursor moves off the button (only if the button "
"had previously received an onMouseEvent() event).");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onMouseDragged, void, (), (),
"If #useMouseEvents is true, this is called when a left mouse button drag is detected, i.e. when the user "
"pressed the left mouse button on the control and then moves the mouse over a certain distance threshold with "
"the mouse button still pressed.");
IMPLEMENT_CALLBACK(GuiButtonBaseCtrl, onHighlighted, void, (bool highlighted), (highlighted),
"This is called when the highlighted state of the button is changed.");
ImplementEnumType(GuiButtonType,
"Type of button control.\n\n"
"@ingroup GuiButtons")
{
GuiButtonBaseCtrl::ButtonTypePush, "PushButton", "A button that triggers an action when clicked."
},
{ GuiButtonBaseCtrl::ButtonTypeCheck, "ToggleButton", "A button that is toggled between on and off state." },
{ GuiButtonBaseCtrl::ButtonTypeRadio, "RadioButton", "A button placed in groups for presenting choices." },
EndImplementEnumType;
//-----------------------------------------------------------------------------
GuiButtonBaseCtrl::GuiButtonBaseCtrl()
{
mDepressed = false;
mHighlighted = false;
mActive = true;
static StringTableEntry sButton = StringTable->insert("Button");
mButtonText = sButton;
mButtonTextID = StringTable->EmptyString();
mStateOn = false;
mRadioGroup = -1;
mButtonType = ButtonTypePush;
mUseMouseEvents = false;
mMouseDragged = false;
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::initPersistFields()
{
docsURL;
addGroup("Button");
addField("text", TypeCaseString, Offset(mButtonText, GuiButtonBaseCtrl),
"Text label to display on button (if button class supports text labels).");
addField("textID", TypeString, Offset(mButtonTextID, GuiButtonBaseCtrl),
"ID of string in string table to use for text label on button.\n\n"
"@see setTextID\n"
"@see GuiControl::langTableMod\n"
"@see LangTable\n\n");
addField("groupNum", TypeS32, Offset(mRadioGroup, GuiButtonBaseCtrl),
"Radio button toggle group number. All radio buttons that are assigned the same #groupNum and that "
"are parented to the same control will synchronize their toggle state, i.e. if one radio button is toggled on "
"all other radio buttons in its group will be toggled off.\n\n"
"The default group is -1.");
addField("buttonType", TYPEID< ButtonType >(), Offset(mButtonType, GuiButtonBaseCtrl),
"Button behavior type.\n");
addField("useMouseEvents", TypeBool, Offset(mUseMouseEvents, GuiButtonBaseCtrl),
"If true, mouse events will be passed on to script. Default is false.\n");
endGroup("Button");
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool GuiButtonBaseCtrl::onWake()
{
if (!Parent::onWake())
return false;
// is we have a script variable, make sure we're in sync
if (mConsoleVariable[0])
mStateOn = Con::getBoolVariable(mConsoleVariable);
if (mButtonTextID && *mButtonTextID != 0)
setTextID(mButtonTextID);
return true;
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::setText(const char* text)
{
mButtonText = StringTable->insert(text, true);
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::setTextID(const char* id)
{
S32 n = Con::getIntVariable(id, -1);
if (n != -1)
{
mButtonTextID = StringTable->insert(id);
setTextID(n);
}
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::setTextID(S32 id)
{
const UTF8* str = getGUIString(id);
if (str)
setText((const char*)str);
//mButtonTextID = id;
}
//-----------------------------------------------------------------------------
const char* GuiButtonBaseCtrl::getText()
{
return mButtonText;
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::setStateOn(bool bStateOn)
{
if (!mActive)
return;
if (mButtonType == ButtonTypeCheck)
{
mStateOn = bStateOn;
}
else if (mButtonType == ButtonTypeRadio)
{
messageSiblings(mRadioGroup);
mStateOn = bStateOn;
}
setUpdate();
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::acceleratorKeyPress(U32)
{
if (!mActive)
return;
//set the bool
mDepressed = true;
if (mProfile->mTabable)
setFirstResponder();
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::acceleratorKeyRelease(U32)
{
if (!mActive)
return;
if (mDepressed)
{
//set the bool
mDepressed = false;
//perform the action
onAction();
}
//update
setUpdate();
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onMouseDown(const GuiEvent& event)
{
if (!mActive)
return;
if (mProfile->mCanKeyFocus)
setFirstResponder();
if (mProfile->isSoundButtonDownValid())
SFX->playOnce(mProfile->getSoundButtonDownProfile());
mMouseDownPoint = event.mousePoint;
mMouseDragged = false;
if (mUseMouseEvents)
onMouseDown_callback();
//lock the mouse
mouseLock();
mDepressed = true;
// If we have a double click then execute the alt command.
if (event.mouseClickCount == 2)
{
onDoubleClick_callback();
execAltConsoleCallback();
}
//update
setUpdate();
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onMouseEnter(const GuiEvent& event)
{
setUpdate();
if (mUseMouseEvents)
onMouseEnter_callback();
if (isMouseLocked())
{
mDepressed = true;
mHighlighted = true;
onHighlighted_callback(mHighlighted);
}
else
{
if (mProfile->isSoundButtonOverValid())
SFX->playOnce(mProfile->getSoundButtonOverProfile());
mHighlighted = true;
if (mButtonType != ButtonTypeRadio)
messageSiblings(mRadioGroup);
onHighlighted_callback(mHighlighted);
}
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onMouseLeave(const GuiEvent&)
{
setUpdate();
if (mUseMouseEvents)
onMouseLeave_callback();
if (isMouseLocked())
mDepressed = false;
mHighlighted = false;
onHighlighted_callback(mHighlighted);
if (mButtonType != ButtonTypeRadio)
messageSiblings(mRadioGroup);
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onMouseUp(const GuiEvent& event)
{
mouseUnlock();
if (!mActive)
return;
setUpdate();
if (mUseMouseEvents)
onMouseUp_callback();
//if we released the mouse within this control, perform the action
if (mDepressed)
onAction();
mDepressed = false;
mMouseDragged = false;
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onRightMouseUp(const GuiEvent& event)
{
onRightClick_callback();
Parent::onRightMouseUp(event);
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onMouseDragged(const GuiEvent& event)
{
if (mUseMouseEvents)
{
// If we haven't started a drag yet, find whether we have moved past
// the tolerance value.
if (!mMouseDragged)
{
Point2I delta = mMouseDownPoint - event.mousePoint;
if (mAbs(delta.x) > 2 || mAbs(delta.y) > 2)
mMouseDragged = true;
}
if (mMouseDragged)
onMouseDragged_callback();
}
Parent::onMouseDragged(event);
}
//-----------------------------------------------------------------------------
bool GuiButtonBaseCtrl::onKeyDown(const GuiEvent& event)
{
//if the control is a dead end, kill the event
if (!mActive)
return true;
//see if the key down is a return or space or not
if ((event.keyCode == KEY_RETURN || event.keyCode == KEY_SPACE)
&& event.modifier == 0)
{
if (mProfile->isSoundButtonDownValid())
SFX->playOnce(mProfile->getSoundButtonDownProfile());
return true;
}
//otherwise, pass the event to it's parent
return Parent::onKeyDown(event);
}
//-----------------------------------------------------------------------------
bool GuiButtonBaseCtrl::onKeyUp(const GuiEvent& event)
{
//if the control is a dead end, kill the event
if (!mActive)
return true;
//see if the key down is a return or space or not
if (mDepressed &&
(event.keyCode == KEY_RETURN || event.keyCode == KEY_SPACE) &&
event.modifier == 0)
{
onAction();
return true;
}
//otherwise, pass the event to it's parent
return Parent::onKeyUp(event);
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::setScriptValue(const char* value)
{
mStateOn = dAtob(value);
// Update the console variable:
if (mConsoleVariable[0])
Con::setBoolVariable(mConsoleVariable, mStateOn);
setUpdate();
}
//-----------------------------------------------------------------------------
const char* GuiButtonBaseCtrl::getScriptValue()
{
return mStateOn ? "1" : "0";
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onAction()
{
if (!mActive)
return;
if (mButtonType == ButtonTypeCheck)
{
mStateOn = mStateOn ? false : true;
}
else if (mButtonType == ButtonTypeRadio)
{
mStateOn = true;
messageSiblings(mRadioGroup);
}
setUpdate();
// Update the console variable:
if (mConsoleVariable[0])
Con::setBoolVariable(mConsoleVariable, mStateOn);
onClick_callback();
Parent::onAction();
}
//-----------------------------------------------------------------------------
void GuiButtonBaseCtrl::onMessage(GuiControl* sender, S32 msg)
{
Parent::onMessage(sender, msg);
if (mRadioGroup == msg)
{
if (mButtonType == ButtonTypeRadio)
{
setUpdate();
mStateOn = (sender == this);
// Update the console variable:
if (mConsoleVariable[0])
Con::setBoolVariable(mConsoleVariable, mStateOn);
}
else if (mButtonType == ButtonTypePush)
{
mHighlighted = (sender == this);
onHighlighted_callback(mHighlighted);
}
}
}
void GuiButtonBaseCtrl::setHighlighted(bool highlighted)
{
mHighlighted = highlighted;
onHighlighted_callback(mHighlighted);
if (mRadioGroup != -1)
{
messageSiblings(mRadioGroup);
}
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod(GuiButtonBaseCtrl, performClick, void, (), ,
"Simulate a click on the button.\n"
"This method will trigger the button's action just as if the button had been pressed by the "
"user.\n\n")
{
object->onAction();
}
//-----------------------------------------------------------------------------
DefineEngineMethod(GuiButtonBaseCtrl, setText, void, (const char* text), ,
"Set the text displayed on the button's label.\n"
"@param text The text to display as the button's text label.\n"
"@note Not all buttons render text labels.\n\n"
"@see getText\n"
"@see setTextID\n")
{
object->setText(text);
}
//-----------------------------------------------------------------------------
DefineEngineMethod(GuiButtonBaseCtrl, setTextID, void, (const char* id), ,
"Set the text displayed on the button's label using a string from the string table "
"assigned to the control.\n\n"
"@param id Name of the variable that contains the integer string ID. Used to look up "
"string in table.\n\n"
"@note Not all buttons render text labels.\n\n"
"@see setText\n"
"@see getText\n"
"@see GuiControl::langTableMod\n"
"@see LangTable\n\n"
"@ref Gui_i18n")
{
object->setTextID(id);
}
//-----------------------------------------------------------------------------
DefineEngineMethod(GuiButtonBaseCtrl, getText, const char*, (), ,
"Get the text display on the button's label (if any).\n\n"
"@return The button's label.")
{
return object->getText();
}
//-----------------------------------------------------------------------------
DefineEngineMethod(GuiButtonBaseCtrl, setStateOn, void, (bool isOn), (true),
"For toggle or radio buttons, set whether the button is currently activated or not. For radio buttons, "
"toggling a button on will toggle all other radio buttons in its group to off.\n\n"
"@param isOn If true, the button will be toggled on (if not already); if false, it will be toggled off.\n\n"
"@note Toggling the state of a button with this method will <em>not</em> not trigger the action associated with the "
"button. To do that, use performClick().")
{
object->setStateOn(isOn);
}
//-----------------------------------------------------------------------------
DefineEngineMethod(GuiButtonBaseCtrl, resetState, void, (), ,
"Reset the mousing state of the button.\n\n"
"This method should not generally be called.")
{
object->resetState();
}
DefineEngineMethod(GuiButtonBaseCtrl, setHighlighted, void, (bool highlighted), (false),
"Reset the mousing state of the button.\n\n"
"This method should not generally be called.")
{
object->setHighlighted(highlighted);
}
DefineEngineMethod(GuiButtonBaseCtrl, isHighlighted, bool, (), ,
"Reset the mousing state of the button.\n\n"
"This method should not generally be called.")
{
return object->isHighlighted();
}