mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
2497 lines
86 KiB
C++
2497 lines
86 KiB
C++
#include "stdafx.h"
|
|
|
|
#include "V8DataModel/GuiObject.h"
|
|
#include "Network/Players.h"
|
|
#include "GfxBase/Adorn.h"
|
|
#include "Gui/Guidraw.h"
|
|
#include "V8DataModel/TweenService.h"
|
|
#include "V8DataModel/PlayerGui.h"
|
|
#include "V8DataModel/ScreenGui.h"
|
|
#include "V8DataModel/BillboardGui.h"
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "V8DataModel/UserInputService.h"
|
|
#include "v8datamodel/ScrollingFrame.h"
|
|
#include "v8datamodel/GuiService.h"
|
|
#include "v8datamodel/Folder.h"
|
|
#include "v8datamodel/ImageLabel.h"
|
|
#include "v8datamodel/ImageButton.h"
|
|
#include "Script/ScriptContext.h"
|
|
#include "FastLog.h"
|
|
|
|
#include "VMProtectSDK.h"
|
|
|
|
#define MAX_BORDER_SIZE_PIXEL 100
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(ElasticEasingUseTwoPi, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(TurnOffFakeEventsForInputEvents, false)
|
|
FASTFLAGVARIABLE(TweenCallbacksDuringRenderStep, false)
|
|
|
|
FASTFLAGVARIABLE(FixSlice9Scale, true)
|
|
|
|
namespace RBX {
|
|
|
|
const char* const sGuiObject = "GuiObject";
|
|
|
|
|
|
|
|
REFLECTION_BEGIN();
|
|
//////////////////////////////////////////////////////////
|
|
static const Reflection::BoundFuncDesc<GuiObject, bool(UDim2, UDim2, GuiObject::TweenEasingDirection, GuiObject::TweenEasingStyle, float, bool, Lua::WeakFunctionRef)> func_tweenSizeAndPosition(&GuiObject::tweenSizeAndPosition, "TweenSizeAndPosition", "endSize", "endPosition", "easingDirection", GuiObject::EASING_DIRECTION_OUT, "easingStyle", GuiObject::EASING_STYLE_QUAD, "time", 1, "override", false, "callback", Lua::WeakFunctionRef(), Security::None);
|
|
static const Reflection::BoundFuncDesc<GuiObject, bool(UDim2, GuiObject::TweenEasingDirection, GuiObject::TweenEasingStyle, float, bool, Lua::WeakFunctionRef)> func_tweenPosition(&GuiObject::tweenPosition, "TweenPosition", "endPosition", "easingDirection", GuiObject::EASING_DIRECTION_OUT, "easingStyle", GuiObject::EASING_STYLE_QUAD, "time", 1, "override", false, "callback", Lua::WeakFunctionRef(), Security::None);
|
|
static const Reflection::BoundFuncDesc<GuiObject, bool(UDim2, GuiObject::TweenEasingDirection, GuiObject::TweenEasingStyle, float, bool, Lua::WeakFunctionRef)> func_tweenSize(&GuiObject::tweenSize, "TweenSize", "endSize", "easingDirection", GuiObject::EASING_DIRECTION_OUT, "easingStyle", GuiObject::EASING_STYLE_QUAD, "time", 1, "override", false, "callback", Lua::WeakFunctionRef(), Security::None);
|
|
|
|
static const Reflection::PropDescriptor<GuiObject, UDim2> prop_Size("Size", category_Data, &GuiObject::getSize, &GuiObject::setSize);
|
|
static const Reflection::PropDescriptor<GuiObject, UDim2> prop_Position("Position", category_Data, &GuiObject::getPosition, &GuiObject::setPosition);
|
|
static const Reflection::PropDescriptor<GuiObject, int> prop_BorderSizePixel("BorderSizePixel", category_Data, &GuiObject::getBorderSizePixel, &GuiObject::setBorderSizePixel);
|
|
const Reflection::PropDescriptor<GuiObject, int> GuiObject::prop_ZIndex("ZIndex", category_Data, &GuiObject::getZIndex, &GuiObject::setZIndex);
|
|
|
|
static const Reflection::PropDescriptor<GuiObject, float> prop_Rotation("Rotation", category_Data, &GuiObject::getRotation, &GuiObject::setRotation);
|
|
|
|
static const Reflection::EnumPropDescriptor<GuiObject, GuiObject::SizeConstraint> prop_SizeConstraint("SizeConstraint", category_Data, &GuiObject::getSizeConstraint, &GuiObject::setSizeConstraint);
|
|
|
|
static const Reflection::PropDescriptor<GuiObject, BrickColor> prop_BorderColor("BorderColor", category_Data, &GuiObject::getBorderColor, &GuiObject::setBorderColor, Reflection::PropertyDescriptor::LEGACY_SCRIPTING);
|
|
static const Reflection::PropDescriptor<GuiObject, Color3> prop_BorderColor3("BorderColor3", category_Data, &GuiObject::getBorderColor3, &GuiObject::setBorderColor3);
|
|
|
|
static const Reflection::PropDescriptor<GuiObject, BrickColor> prop_BackgroundColor("BackgroundColor", category_Data, &GuiObject::getBackgroundColor, &GuiObject::setBackgroundColor, Reflection::PropertyDescriptor::LEGACY_SCRIPTING);
|
|
static const Reflection::PropDescriptor<GuiObject, Color3> prop_BackgroundColor3("BackgroundColor3", category_Data, &GuiObject::getBackgroundColor3, &GuiObject::setBackgroundColor3);
|
|
|
|
static const Reflection::PropDescriptor<GuiObject, float> prop_BackgroundTransparency("BackgroundTransparency", category_Data, &GuiObject::getBackgroundTransparency, &GuiObject::setBackgroundTransparency);
|
|
|
|
static const Reflection::PropDescriptor<GuiObject, bool> prop_Draggable("Draggable", category_Behavior, &GuiObject::getDraggable, &GuiObject::setDraggable);
|
|
static const Reflection::PropDescriptor<GuiObject, bool> prop_Clipping("ClipsDescendants", category_Behavior, &GuiObject::getClipping, &GuiObject::setClipping);
|
|
|
|
const Reflection::PropDescriptor<GuiObject, bool> GuiObject::prop_Visible("Visible", category_Data, &GuiObject::getVisible, &GuiObject::setVisible);
|
|
static const Reflection::PropDescriptor<GuiObject, bool> prop_Active("Active", category_Data, &GuiObject::getActive, &GuiObject::setActive);
|
|
static const Reflection::PropDescriptor<GuiObject, bool> prop_Selectable("Selectable", category_Data, &GuiObject::getSelectable, &GuiObject::setSelectable, Reflection::PropertyDescriptor::STANDARD, Security::None);
|
|
|
|
static const Reflection::PropDescriptor<GuiObject, float> prop_Transparency("Transparency", category_Data, &GuiObject::getTransparencyLegacy, &GuiObject::setTransparencyLegacy, Reflection::PropertyDescriptor::LEGACY_SCRIPTING);
|
|
|
|
static Reflection::RemoteEventDesc<GuiObject, void(int, int)> event_MouseEnter(&GuiObject::mouseEnterSignal, "MouseEnter", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiObject, void(int, int)> event_MouseLeave(&GuiObject::mouseLeaveSignal, "MouseLeave", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiObject, void(int, int)> event_MouseMoved(&GuiObject::mouseMovedSignal, "MouseMoved", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiObject, void(int, int)> event_MouseWheelForward (&GuiObject::mouseWheelForwardSignal, "MouseWheelForward", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiObject, void(int, int)> event_MouseWheelBackward (&GuiObject::mouseWheelBackwardSignal, "MouseWheelBackward", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
|
|
static Reflection::RemoteEventDesc<GuiObject, void(int, int)> event_DragStopped(&GuiObject::dragStoppedSignal, "DragStopped" , "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiObject, void(UDim2)> event_DragBegin(&GuiObject::dragBeginSignal, "DragBegin", "initialPosition", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
|
|
// High Level touch events
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<const RBX::Reflection::ValueArray>)> event_TapGesture(&GuiObject::tapGestureEvent, "TouchTap", "touchPositions", Security::None);
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<const RBX::Reflection::ValueArray>,float,float,InputObject::UserInputState)> event_PinchGesture(&GuiObject::pinchGestureEvent, "TouchPinch", "touchPositions", "scale", "velocity", "state", Security::None);
|
|
static Reflection::EventDesc<GuiObject, void(UserInputService::SwipeDirection, int)> event_SwipeGesture(&GuiObject::swipeGestureEvent, "TouchSwipe", "swipeDirection", "numberOfTouches",Security::None);
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<const RBX::Reflection::ValueArray>,InputObject::UserInputState)> event_LongPressGesture(&GuiObject::longPressGestureEvent, "TouchLongPress", "touchPositions", "state", Security::None);
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<const RBX::Reflection::ValueArray>,float,float,InputObject::UserInputState)> event_RotateGesture(&GuiObject::rotateGestureEvent, "TouchRotate", "touchPositions","rotation", "velocity", "state", Security::None);
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<const RBX::Reflection::ValueArray>,Vector2,Vector2,InputObject::UserInputState)> event_PanGesture(&GuiObject::panGestureEvent, "TouchPan", "touchPositions", "totalTranslation", "velocity", "state", Security::None);
|
|
|
|
// Low Level Generic input events
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<Instance>)> event_InputBegin(&GuiObject::inputBeganEvent, "InputBegan", "input", Security::None);
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<Instance>)> event_InputUpdate(&GuiObject::inputChangedEvent, "InputChanged", "input", Security::None);
|
|
static Reflection::EventDesc<GuiObject, void(shared_ptr<Instance>)> event_InputEnd(&GuiObject::inputEndedEvent, "InputEnded", "input", Security::None);
|
|
|
|
// Gamepad selection overrides
|
|
static Reflection::RefPropDescriptor<GuiObject, GuiObject> prop_nextSelectionUp("NextSelectionUp", category_Behavior, &GuiObject::getNextSelectionUp, &GuiObject::setNextSelectionUp, Reflection::PropertyDescriptor::STANDARD);
|
|
static Reflection::RefPropDescriptor<GuiObject, GuiObject> prop_nextSelectionDown("NextSelectionDown", category_Behavior, &GuiObject::getNextSelectionDown, &GuiObject::setNextSelectionDown, Reflection::PropertyDescriptor::STANDARD);
|
|
static Reflection::RefPropDescriptor<GuiObject, GuiObject> prop_nextSelectionLeft("NextSelectionLeft", category_Behavior, &GuiObject::getNextSelectionLeft, &GuiObject::setNextSelectionLeft, Reflection::PropertyDescriptor::STANDARD);
|
|
static Reflection::RefPropDescriptor<GuiObject, GuiObject> prop_nextSelectionRight("NextSelectionRight", category_Behavior, &GuiObject::getNextSelectionRight, &GuiObject::setNextSelectionRight, Reflection::PropertyDescriptor::STANDARD);
|
|
|
|
static Reflection::RefPropDescriptor<GuiObject, GuiObject> prop_SelectionImageObject("SelectionImageObject", category_Appearance, &GuiObject::getSelectionImageObject, &GuiObject::setSelectionImageObject, Reflection::PropertyDescriptor::STANDARD);
|
|
|
|
static Reflection::EventDesc<GuiObject, void()> event_SelectionGained(&GuiObject::selectionGainedEvent, "SelectionGained", Security::None);
|
|
static Reflection::EventDesc<GuiObject, void()> event_SelectionLost(&GuiObject::selectionLostEvent, "SelectionLost", Security::None);
|
|
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiObject,event_MouseEnter, "MouseEnter", MouseEnter);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiObject,event_MouseLeave, "MouseLeave", MouseLeave);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiObject,event_MouseMoved, "MouseMoved", MouseMoved);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiObject,event_MouseWheelForward, "MouseWheelForward", MouseWheelForward);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiObject,event_MouseWheelBackward, "MouseWheelBackward", MouseWheelBackward);
|
|
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiObject,event_DragStopped, "DragStopped", DragStopped);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiObject,event_DragBegin, "DragBegin", DragBegin);
|
|
REFLECTION_END();
|
|
|
|
namespace Reflection
|
|
{
|
|
template<>
|
|
EnumDesc<RBX::GuiObject::TweenEasingDirection>::EnumDesc()
|
|
:EnumDescriptor("EasingDirection")
|
|
{
|
|
addPair(RBX::GuiObject::EASING_DIRECTION_IN, "In");
|
|
addPair(RBX::GuiObject::EASING_DIRECTION_OUT, "Out");
|
|
addPair(RBX::GuiObject::EASING_DIRECTION_IN_OUT, "InOut");
|
|
}
|
|
template<>
|
|
RBX::GuiObject::TweenEasingDirection& Variant::convert<RBX::GuiObject::TweenEasingDirection>(void)
|
|
{
|
|
return genericConvert<RBX::GuiObject::TweenEasingDirection>();
|
|
}
|
|
|
|
template<>
|
|
EnumDesc<RBX::GuiObject::TweenEasingStyle>::EnumDesc()
|
|
:EnumDescriptor("EasingStyle")
|
|
{
|
|
addPair(RBX::GuiObject::EASING_STYLE_LINEAR, "Linear");
|
|
addPair(RBX::GuiObject::EASING_STYLE_SINE, "Sine");
|
|
addPair(RBX::GuiObject::EASING_STYLE_BACK, "Back");
|
|
addPair(RBX::GuiObject::EASING_STYLE_QUAD, "Quad");
|
|
addPair(RBX::GuiObject::EASING_STYLE_QUART, "Quart");
|
|
addPair(RBX::GuiObject::EASING_STYLE_QUINT, "Quint");
|
|
addPair(RBX::GuiObject::EASING_STYLE_BOUNCE, "Bounce");
|
|
addPair(RBX::GuiObject::EASING_STYLE_ELASTIC, "Elastic");
|
|
}
|
|
template<>
|
|
RBX::GuiObject::TweenEasingStyle& Variant::convert<RBX::GuiObject::TweenEasingStyle>(void)
|
|
{
|
|
return genericConvert<RBX::GuiObject::TweenEasingStyle>();
|
|
}
|
|
|
|
template<>
|
|
EnumDesc<RBX::GuiObject::TweenStatus>::EnumDesc()
|
|
:EnumDescriptor("TweenStatus")
|
|
{
|
|
addPair(RBX::GuiObject::TWEEN_CANCELED, "Canceled");
|
|
addPair(RBX::GuiObject::TWEEN_COMPLETED, "Completed");
|
|
}
|
|
template<>
|
|
RBX::GuiObject::TweenStatus& Variant::convert<RBX::GuiObject::TweenStatus>(void)
|
|
{
|
|
return genericConvert<RBX::GuiObject::TweenStatus>();
|
|
}
|
|
}
|
|
template<>
|
|
bool StringConverter<GuiObject::TweenEasingStyle>::convertToValue(const std::string& text, GuiObject::TweenEasingStyle& value)
|
|
{
|
|
return Reflection::EnumDesc<GuiObject::TweenEasingStyle>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<GuiObject::TweenEasingDirection>::convertToValue(const std::string& text, GuiObject::TweenEasingDirection& value)
|
|
{
|
|
return Reflection::EnumDesc<GuiObject::TweenEasingDirection>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
|
|
template<>
|
|
bool RBX::StringConverter<GuiObject::TweenStatus>::convertToValue(const std::string& text, GuiObject::TweenStatus& value)
|
|
{
|
|
return Reflection::EnumDesc<GuiObject::TweenStatus>::singleton().convertToValue(text.c_str(),value);
|
|
}
|
|
|
|
GuiObject::GuiObject(const char* name, bool active)
|
|
: DescribedNonCreatable<GuiObject, GuiBase2d, sGuiObject>(name)
|
|
, size()
|
|
, sizeConstraint(RELATIVE_XY)
|
|
, position()
|
|
, borderSizePixel(1)
|
|
, borderColor(BrickColor::brickBlack().color3())
|
|
, backgroundColor(BrickColor::brickGray().color3())
|
|
, visible(true)
|
|
, active(active)
|
|
, backgroundTransparency(0.0f)
|
|
, guiState(Gui::NOTHING)
|
|
, lastMouseDownType(InputObject::TYPE_NONE)
|
|
, serverGuiObject(false)
|
|
, draggable(false)
|
|
, dragging(false)
|
|
, clipping(false)
|
|
, selectionBox(false)
|
|
, selectable(false)
|
|
, draggingEndedConnection(NULL)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiObject,GuiObject::mouseEnterSignal,event_MouseEnter,MouseEnter)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiObject,GuiObject::mouseLeaveSignal,event_MouseLeave,MouseLeave)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiObject,GuiObject::mouseMovedSignal,event_MouseMoved,MouseMoved)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiObject,GuiObject::dragStoppedSignal,event_DragStopped,DragStopped)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiObject,GuiObject::dragBeginSignal,event_DragBegin,DragBegin)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiObject,GuiObject::mouseWheelForwardSignal, event_MouseWheelForward, MouseWheelForward)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiObject,GuiObject::mouseWheelBackwardSignal, event_MouseWheelBackward, MouseWheelBackward)
|
|
{
|
|
CONNECT_EVENT_REPLICATOR(MouseEnter);
|
|
CONNECT_EVENT_REPLICATOR(MouseLeave);
|
|
CONNECT_EVENT_REPLICATOR(MouseMoved);
|
|
CONNECT_EVENT_REPLICATOR(DragStopped);
|
|
CONNECT_EVENT_REPLICATOR(DragBegin);
|
|
CONNECT_EVENT_REPLICATOR(MouseWheelForward);
|
|
CONNECT_EVENT_REPLICATOR(MouseWheelBackward);
|
|
}
|
|
|
|
static void InvokeCallback(boost::function<void(GuiObject::TweenStatus)> func, GuiObject::TweenStatus status)
|
|
{
|
|
func(status);
|
|
}
|
|
|
|
void GuiObject::UpdateTween(Tween& tween, GuiObject* obj, boost::function<void(GuiObject*, UDim2)> updateFunc, float timeStep)
|
|
{
|
|
if(tween.delayTime > 0)
|
|
{
|
|
tween.delayTime -= (float) timeStep;
|
|
if(tween.delayTime <= 0)
|
|
{
|
|
updateFunc(obj, tween.start);
|
|
tween.elapsedTime += std::abs<float>(tween.delayTime);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Normal time update
|
|
tween.elapsedTime += (float) timeStep;
|
|
}
|
|
if(tween.elapsedTime >= tween.totalTime)
|
|
{
|
|
updateFunc(obj, tween.end);
|
|
|
|
if(tween.callback)
|
|
{
|
|
if (FFlag::TweenCallbacksDuringRenderStep)
|
|
{
|
|
if (TweenService* tweenService = RBX::ServiceProvider::create<TweenService>(obj))
|
|
{
|
|
tweenService->addTweenCallback(tween.callback, TWEEN_COMPLETED);
|
|
}
|
|
}
|
|
else if(DataModel* dm = DataModel::get(obj))
|
|
{
|
|
dm->submitTask(boost::bind(&InvokeCallback, tween.callback, TWEEN_COMPLETED), DataModelJob::Write);
|
|
}
|
|
tween.callback.clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
updateFunc(obj, GuiObject::TweenInterpolate(tween.style, tween.variance,
|
|
tween.elapsedTime, tween.totalTime, tween.start, tween.end));
|
|
}
|
|
}
|
|
|
|
static void InvokeTweenStatusCallback(boost::weak_ptr<GuiObject> weakThis, Lua::WeakFunctionRef callback, GuiObject::TweenStatus tweenStatus)
|
|
{
|
|
if (Lua::ThreadRef threadRef = callback.lock())
|
|
{
|
|
if (shared_ptr<GuiObject> strongThis = weakThis.lock())
|
|
{
|
|
if (ScriptContext* sc = ServiceProvider::create<ScriptContext>(strongThis.get()))
|
|
{
|
|
Reflection::Tuple args;
|
|
args.values.push_back(tweenStatus);
|
|
|
|
try
|
|
{
|
|
sc->callInNewThread(callback, args);
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "Unexpected error while invoking callback: %s", e.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void InvokeRemoveOnTweenEnd(boost::weak_ptr<GuiObject> weakThis, Lua::WeakFunctionRef callback)
|
|
{
|
|
if(shared_ptr<GuiObject> strongThis = weakThis.lock())
|
|
strongThis->setParent(NULL);
|
|
}
|
|
|
|
bool GuiObject::tweenPosition(UDim2 endPosition, TweenEasingDirection style, TweenEasingStyle variance,float time, bool overwrite, Lua::WeakFunctionRef callback)
|
|
{
|
|
return tweenPositionDelay(getPosition(), endPosition, time, style, variance, 0.0f, overwrite, boost::bind(&InvokeTweenStatusCallback, weak_from(this), callback, _1));
|
|
}
|
|
|
|
bool GuiObject::tweenPosition(UDim2 endPosition, TweenEasingDirection style, TweenEasingStyle variance,float time, bool overwrite, bool removeOnCallback)
|
|
{
|
|
TweenService* tweenService = ServiceProvider::create<TweenService>(this);
|
|
if(removeOnCallback)
|
|
return tweenPositionDelay(getPosition(), endPosition, time, style, variance, 0.0f, overwrite, boost::bind(&InvokeRemoveOnTweenEnd, weak_from(this), Lua::WeakFunctionRef()), tweenService);
|
|
else
|
|
return tweenPositionDelay(getPosition(), endPosition, time, style, variance, 0.0f, overwrite, boost::bind(&InvokeTweenStatusCallback, weak_from(this), Lua::WeakFunctionRef(), _1), tweenService);
|
|
}
|
|
|
|
bool GuiObject::tweenPosition(UDim2 endPosition, TweenEasingDirection style, TweenEasingStyle variance,float time, bool overwrite, bool removeOnCallback, TweenService* tweenService)
|
|
{
|
|
if(removeOnCallback)
|
|
return tweenPositionDelay(getPosition(), endPosition, time, style, variance, 0.0f, overwrite, boost::bind(&InvokeRemoveOnTweenEnd, weak_from(this), Lua::WeakFunctionRef()), tweenService);
|
|
else
|
|
return tweenPositionDelay(getPosition(), endPosition, time, style, variance, 0.0f, overwrite, boost::bind(&InvokeTweenStatusCallback, weak_from(this), Lua::WeakFunctionRef(), _1), tweenService);
|
|
}
|
|
|
|
bool GuiObject::tweenSize(UDim2 endSize, TweenEasingDirection style, TweenEasingStyle variance, float time, bool overwrite, Lua::WeakFunctionRef callback)
|
|
{
|
|
return tweenSizeDelay(getSize(), endSize, time, style, variance, 0.0f, overwrite, boost::bind(&InvokeTweenStatusCallback, weak_from(this), callback, _1));
|
|
}
|
|
|
|
bool GuiObject::tweenSizeAndPosition(UDim2 endSize, UDim2 endPosition, TweenEasingDirection style, TweenEasingStyle variance, float time, bool overwrite, Lua::WeakFunctionRef callback)
|
|
{
|
|
if(!overwrite){
|
|
if(tweens && (tweens->positionTween || tweens->sizeTween)){
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool result = tweenSize(endSize, style, variance, time, overwrite, callback);
|
|
result = tweenPosition(endPosition, style, variance, time, overwrite, Lua::WeakFunctionRef()) && result;
|
|
|
|
return result;
|
|
}
|
|
bool GuiObject::tweenPositionDelay(UDim2 startPosition, UDim2 endPosition, float time, TweenEasingDirection style, TweenEasingStyle variance, float delayTime, bool overwrite, boost::function<void(TweenStatus)> callback)
|
|
{
|
|
TweenService* tweenService = ServiceProvider::create<TweenService>(this);
|
|
return tweenPositionDelay(startPosition, endPosition, time, style, variance, delayTime, overwrite, callback, tweenService);
|
|
}
|
|
|
|
bool GuiObject::tweenPositionDelay(UDim2 startPosition, UDim2 endPosition, float time, TweenEasingDirection style, TweenEasingStyle variance, float delayTime, bool overwrite, boost::function<void(TweenStatus)> callback, TweenService* tweenService)
|
|
{
|
|
if(!tweenService)
|
|
throw std::runtime_error("Can only tween objects in the workspace");
|
|
|
|
if(!tweens)
|
|
{
|
|
tweens.reset(new Tweens());
|
|
tweenService->addTweeningObject(weak_from(this));
|
|
}
|
|
|
|
if(tweens->positionTween)
|
|
{
|
|
if(!overwrite)
|
|
return false;
|
|
|
|
if(tweens->positionTween->callback)
|
|
{
|
|
if (FFlag::TweenCallbacksDuringRenderStep)
|
|
{
|
|
tweenService->addTweenCallback(tweens->positionTween->callback, TWEEN_CANCELED);
|
|
}
|
|
else if(DataModel* dm = DataModel::get(this))
|
|
{
|
|
dm->submitTask(boost::bind(&InvokeCallback, tweens->positionTween->callback, TWEEN_CANCELED), DataModelJob::Write);
|
|
}
|
|
}
|
|
tweens->positionTween.reset();
|
|
}
|
|
RBXASSERT(!tweens->positionTween);
|
|
|
|
tweens->positionTween.reset(new Tween(startPosition, endPosition, time, style, variance, delayTime));
|
|
tweens->positionTween->callback = callback;
|
|
return true;
|
|
}
|
|
bool GuiObject::tweenSizeDelay(UDim2 startSize, UDim2 endSize, float time, TweenEasingDirection style, TweenEasingStyle variance, float delayTime, bool overwrite, boost::function<void(TweenStatus)> callback)
|
|
{
|
|
TweenService* tweenService = ServiceProvider::create<TweenService>(this);
|
|
if(!tweenService)
|
|
throw std::runtime_error("Can only tween objects in the workspace");
|
|
|
|
if(!tweens)
|
|
{
|
|
tweens.reset(new Tweens());
|
|
tweenService->addTweeningObject(weak_from(this));
|
|
}
|
|
|
|
if(tweens->sizeTween)
|
|
{
|
|
if(!overwrite)
|
|
return false;
|
|
|
|
if(tweens->sizeTween->callback)
|
|
{
|
|
if (FFlag::TweenCallbacksDuringRenderStep)
|
|
{
|
|
tweenService->addTweenCallback(tweens->sizeTween->callback, TWEEN_CANCELED);
|
|
}
|
|
else if(DataModel* dm = DataModel::get(this))
|
|
{
|
|
dm->submitTask(boost::bind(&InvokeCallback, tweens->sizeTween->callback, TWEEN_CANCELED), DataModelJob::Write);
|
|
}
|
|
}
|
|
tweens->sizeTween.reset();
|
|
}
|
|
RBXASSERT(!tweens->sizeTween);
|
|
|
|
tweens->sizeTween.reset(new Tween(startSize, endSize, time, style, variance, delayTime));
|
|
tweens->sizeTween->callback = callback;
|
|
return true;
|
|
|
|
}
|
|
|
|
bool GuiObject::tweenStep(const double& timeStep)
|
|
{
|
|
if(!tweens)
|
|
return true;
|
|
|
|
if(tweens->positionTween){
|
|
UpdateTween(*(tweens->positionTween), this, &GuiObject::setPosition, (float)timeStep);
|
|
if(tweens->positionTween->isDone())
|
|
tweens->positionTween.reset();
|
|
}
|
|
if(tweens->sizeTween){
|
|
UpdateTween(*(tweens->sizeTween), this, &GuiObject::setSize, (float)timeStep);
|
|
if(tweens->sizeTween->isDone())
|
|
tweens->sizeTween.reset();
|
|
|
|
}
|
|
|
|
if(tweens->empty())
|
|
tweens.reset();
|
|
|
|
return !tweens;
|
|
}
|
|
|
|
UDim2 GuiObject::TweenInterpolate(TweenEasingDirection style, TweenEasingStyle variance,
|
|
float elapsedTime, float totalTime, const UDim2& startValue, const UDim2& endValue)
|
|
{
|
|
switch(variance)
|
|
{
|
|
case EASING_STYLE_LINEAR:
|
|
return (endValue-startValue) * (elapsedTime/totalTime) + startValue;
|
|
case EASING_STYLE_SINE:
|
|
{
|
|
float factor = 1;
|
|
switch(style)
|
|
{
|
|
case EASING_DIRECTION_IN:
|
|
factor = 1 - cos(elapsedTime/totalTime * Math::piHalf());
|
|
break;
|
|
case EASING_DIRECTION_OUT:
|
|
factor = sin(elapsedTime/totalTime * Math::piHalf());
|
|
break;
|
|
case EASING_DIRECTION_IN_OUT:
|
|
factor = -0.5 * (cos(Math::pi()*elapsedTime/totalTime) - 1);
|
|
break;
|
|
}
|
|
return (endValue-startValue) * factor + startValue;
|
|
}
|
|
case EASING_STYLE_BACK:
|
|
{
|
|
float factor = 1;
|
|
const float s = 1.70158;
|
|
switch(style)
|
|
{
|
|
case EASING_DIRECTION_IN:
|
|
{
|
|
const float t = elapsedTime/totalTime;
|
|
factor = t*t*((s + 1)*t - s);
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_OUT:
|
|
{
|
|
const float t = elapsedTime/totalTime-1;
|
|
factor = t*t*((s+1)*t+s) + 1;
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_IN_OUT:
|
|
{
|
|
float t = elapsedTime / (totalTime*0.5);
|
|
if(t < 1)
|
|
factor = 0.5*t*t*(((s*1.525) + 1)*t - (s*1.525));
|
|
else{
|
|
t -= 2;
|
|
factor = 0.5*(t*t*(((s*1.525)+1)*t+(s*.1525)) + 2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return ( endValue-startValue) * factor + startValue;
|
|
}
|
|
case EASING_STYLE_QUAD:
|
|
{
|
|
float factor = 1;
|
|
switch(style)
|
|
{
|
|
case EASING_DIRECTION_IN:
|
|
{
|
|
float t = elapsedTime/totalTime;
|
|
factor = t*t;
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_OUT:
|
|
{
|
|
float t = elapsedTime/totalTime;
|
|
factor = -t*(t-2);
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_IN_OUT:
|
|
{
|
|
float t = elapsedTime/(totalTime * 0.5);
|
|
|
|
if (t < 1)
|
|
factor = 0.5*t*t;
|
|
else
|
|
factor = -0.5 * ((t-1)*(t-3) - 1);
|
|
break;
|
|
}
|
|
}
|
|
return (endValue-startValue)*factor + startValue;
|
|
}
|
|
case EASING_STYLE_QUART:
|
|
{
|
|
float factor = 1;
|
|
switch(style){
|
|
case EASING_DIRECTION_IN:
|
|
{
|
|
const float t = elapsedTime/totalTime;
|
|
factor = pow(t, 4);
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_OUT:
|
|
{
|
|
const float t = elapsedTime/totalTime - 1;
|
|
factor = -(pow(t, 4)-1);
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_IN_OUT:
|
|
{
|
|
const float t = elapsedTime/(totalTime * 0.5);
|
|
if (t < 1)
|
|
factor = 0.5 * pow(t,4);
|
|
else
|
|
factor = -0.5 * (pow(t-2, 4) -2);
|
|
break;
|
|
}
|
|
}
|
|
return (endValue-startValue)*factor + startValue;
|
|
}
|
|
case EASING_STYLE_QUINT:
|
|
{
|
|
float factor = 1;
|
|
switch(style){
|
|
case EASING_DIRECTION_IN:
|
|
{
|
|
const float t = elapsedTime/totalTime;
|
|
factor = pow(t,5);
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_OUT:
|
|
{
|
|
const float t = elapsedTime/totalTime - 1;
|
|
factor = pow(t,5) + 1;
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_IN_OUT:
|
|
{
|
|
const float t = elapsedTime/(totalTime * 0.5);
|
|
|
|
if(t < 1)
|
|
factor = 0.5*pow(t,5);
|
|
else
|
|
factor = 0.5*(pow(t-2, 5) + 2);
|
|
break;
|
|
}
|
|
}
|
|
return (endValue-startValue)*factor + startValue;
|
|
}
|
|
case EASING_STYLE_BOUNCE:
|
|
{
|
|
switch(style){
|
|
case EASING_DIRECTION_IN:
|
|
return (endValue-startValue) - TweenInterpolate(EASING_DIRECTION_OUT, EASING_STYLE_BOUNCE, totalTime-elapsedTime, totalTime, UDim2(), endValue-startValue) + startValue;
|
|
case EASING_DIRECTION_OUT:
|
|
{
|
|
const float timePercent = elapsedTime/totalTime;
|
|
float factor = 1;
|
|
if(timePercent < 1/2.75)
|
|
factor = 7.5625*pow(timePercent,2);
|
|
else if(timePercent < 2/2.75)
|
|
factor = 7.5625*pow(timePercent-1.5/2.75, 2) + .75;
|
|
else if(timePercent < 2.5/2.75)
|
|
factor = 7.5625*pow(timePercent-2.25/2.75, 2) + .9375;
|
|
else
|
|
factor = 7.5625*pow(timePercent-2.625/2.75, 2) + .984375;
|
|
return (endValue-startValue)*factor + startValue;
|
|
}
|
|
case EASING_DIRECTION_IN_OUT:
|
|
if(elapsedTime < totalTime*0.5)
|
|
return TweenInterpolate(EASING_DIRECTION_IN, EASING_STYLE_BOUNCE, elapsedTime*2, totalTime, UDim2(), endValue-startValue)*0.5 + startValue;
|
|
else
|
|
return TweenInterpolate(EASING_DIRECTION_OUT, EASING_STYLE_BOUNCE, elapsedTime*2-totalTime, totalTime, UDim2(), endValue-startValue)*0.5 + (endValue-startValue)*.5 + startValue;
|
|
default:
|
|
RBXASSERT(0);
|
|
return UDim2();
|
|
}
|
|
}
|
|
case EASING_STYLE_ELASTIC:
|
|
{
|
|
if(elapsedTime == 0)
|
|
return startValue;
|
|
float t = elapsedTime/totalTime;
|
|
if(t >= 1)
|
|
return endValue;
|
|
|
|
float factor = 1;
|
|
switch(style) {
|
|
case EASING_DIRECTION_IN:
|
|
{
|
|
float p = totalTime * .3;
|
|
float s = p / 4;
|
|
t -= 1;
|
|
factor = -(pow(2,10*t) * sin((t*totalTime-s)*(DFFlag::ElasticEasingUseTwoPi ? Math::twoPi() : Math::piHalf())/p));
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_OUT:
|
|
{
|
|
float p=totalTime*.3;
|
|
float s = p/4;
|
|
factor = 1 + pow(2,-10*t) * sin( (t*totalTime-s)*(DFFlag::ElasticEasingUseTwoPi ? Math::twoPi() : Math::piHalf())/p );
|
|
break;
|
|
}
|
|
case EASING_DIRECTION_IN_OUT:
|
|
{
|
|
t = elapsedTime / (totalTime * 0.5);
|
|
float p=totalTime*(.3*1.5);
|
|
float s = p/4;
|
|
|
|
if (t < 1) {
|
|
t -= 1;
|
|
factor = -.5 * pow(2,10*t) * sin( (t*totalTime-s)*(DFFlag::ElasticEasingUseTwoPi ? Math::twoPi() : Math::piHalf())/p );
|
|
}
|
|
else {
|
|
t -= 1;
|
|
factor = 1 + 0.5 * pow(2,-10*t) * sin( (t*totalTime-s)*(DFFlag::ElasticEasingUseTwoPi ? Math::twoPi() : Math::piHalf())/p );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return (endValue-startValue)*factor + startValue;
|
|
}
|
|
default:
|
|
RBXASSERT(0);
|
|
return UDim2();
|
|
}
|
|
}
|
|
|
|
void GuiObject::setServerGuiObject()
|
|
{
|
|
serverGuiObject = true;
|
|
//Since we are running the server, we need to be watching for local listeners
|
|
eventReplicatorMouseEnter.setListenerMode(!mouseEnterSignal.empty());
|
|
eventReplicatorMouseLeave.setListenerMode(!mouseLeaveSignal.empty());
|
|
eventReplicatorMouseMoved.setListenerMode(!mouseMovedSignal.empty());
|
|
eventReplicatorMouseWheelForward .setListenerMode(!mouseWheelForwardSignal.empty());
|
|
eventReplicatorMouseWheelBackward .setListenerMode(!mouseWheelBackwardSignal.empty());
|
|
|
|
eventReplicatorDragBegin.setListenerMode(!dragBeginSignal.empty());
|
|
eventReplicatorDragStopped.setListenerMode(!dragStoppedSignal.empty());
|
|
}
|
|
|
|
static Vector2 getConstrainedSize(const Vector2& parentSize, GuiObject::SizeConstraint sizeConstraint)
|
|
{
|
|
switch(sizeConstraint){
|
|
case GuiObject::RELATIVE_XX: return Vector2(parentSize.x, parentSize.x);
|
|
case GuiObject::RELATIVE_YY: return Vector2(parentSize.y, parentSize.y);
|
|
case GuiObject::RELATIVE_XY:
|
|
default: return parentSize;
|
|
}
|
|
}
|
|
|
|
Vector2 GuiObject::getAbsolutePosition() const
|
|
{
|
|
if (GuiService* guiService = RBX::ServiceProvider::find<GuiService>(this))
|
|
{
|
|
Vector4 guiInset = guiService->getGlobalGuiInset();
|
|
return Vector2(absolutePosition.x - guiInset.x, absolutePosition.y - guiInset.y);
|
|
}
|
|
return absolutePosition;
|
|
}
|
|
|
|
void GuiObject::handleDrag(RBX::Vector2 mousePosition)
|
|
{
|
|
if(mousePosition != lastMousePosition)
|
|
{
|
|
Vector2 mouseDelta = mousePosition - lastMousePosition;
|
|
setPosition(RBX::UDim2(position.x.scale,position.x.offset + mouseDelta.x, position.y.scale,position.y.offset + mouseDelta.y));
|
|
lastMousePosition = mousePosition;
|
|
}
|
|
}
|
|
|
|
void GuiObject::handleDragging(const shared_ptr<InputObject>& event)
|
|
{
|
|
if (mouseIsOver(event->get2DPosition()) && event->getUserInputState() == InputObject::INPUT_STATE_BEGIN && !dragging)
|
|
{
|
|
if (event->isLeftMouseDownEvent() || event->isTouchEvent())
|
|
{
|
|
dragging = true;
|
|
handleDragBegin(event->get2DPosition());
|
|
dragBeginSignal(getPosition());
|
|
if (draggingBeganInputObject == shared_ptr<InputObject>())
|
|
{
|
|
if(UserInputService* inputService = RBX::ServiceProvider::find<UserInputService>(this))
|
|
{
|
|
draggingBeganInputObject = shared_from(event.get());
|
|
draggingEndedConnection = inputService->coreInputEndedEvent.connect(boost::bind(&GuiObject::draggingEnded,this,_1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (dragging)
|
|
{
|
|
handleDrag(event->get2DPosition());
|
|
}
|
|
}
|
|
|
|
void GuiObject::handleDragBegin(Vector2 mousePosition)
|
|
{
|
|
lastMousePosition = mousePosition;
|
|
}
|
|
|
|
bool GuiObject::descendantOfBillboardGui()
|
|
{
|
|
Instance* parent = getParent();
|
|
if(!parent)
|
|
return false;
|
|
else if( parent->fastDynamicCast<BillboardGui>() )
|
|
return true;
|
|
|
|
if(GuiObject* guiObject = parent->fastDynamicCast<GuiObject>())
|
|
return guiObject->descendantOfBillboardGui();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GuiObject::recalculateAbsolutePlacement(const Rect2D& parentViewport)
|
|
{
|
|
GuiObject* guiParent = fastDynamicCast<GuiObject>(getParent());
|
|
Rotation2D parentRotation = guiParent ? guiParent->getAbsoluteRotation() : Rotation2D();
|
|
|
|
bool result = false;
|
|
|
|
// Update size
|
|
Vector2 constrainedParentSize = getConstrainedSize(parentViewport.wh(), sizeConstraint);
|
|
|
|
result = setAbsoluteSize(size * constrainedParentSize) || result;
|
|
|
|
// Update position; note that we rotate the center of the element
|
|
Vector2 transformedPosition = parentRotation.rotate(parentViewport.x0y0() + (position*parentViewport.wh()) + absoluteSizeFloat * 0.5f) - absoluteSizeFloat * 0.5f;
|
|
|
|
result = setAbsolutePosition(transformedPosition);
|
|
|
|
// Update rotation
|
|
Rotation2D transformedRotation = Rotation2D(parentRotation.getAngle().combine(rotation), absolutePosition + absoluteSizeFloat * 0.5f);
|
|
|
|
result = setAbsoluteRotation(transformedRotation) || result;
|
|
|
|
return result;
|
|
}
|
|
|
|
void GuiObject::forceResize()
|
|
{
|
|
if(GuiBase2d* parent = Instance::fastDynamicCast<GuiBase2d>(getParent()))
|
|
{
|
|
handleResize(parent->getCanvasRect(), true);
|
|
}
|
|
}
|
|
void GuiObject::checkForResize()
|
|
{
|
|
Folder* folderParent = NULL;
|
|
|
|
if(GuiBase2d* parent = Instance::fastDynamicCast<GuiBase2d>(getParent()))
|
|
{
|
|
handleResize(parent->getCanvasRect(), false);
|
|
}
|
|
else if ((folderParent = Instance::fastDynamicCast<Folder>(getParent())))
|
|
{
|
|
GuiBase2d* guiParent = NULL;
|
|
Instance *instParent = folderParent->getParent();
|
|
while (instParent != NULL && guiParent == NULL && folderParent != NULL)
|
|
{
|
|
folderParent = Instance::fastDynamicCast<Folder>(instParent);
|
|
guiParent = Instance::fastDynamicCast<GuiBase2d>(instParent);
|
|
instParent = instParent->getParent();
|
|
}
|
|
if (guiParent)
|
|
{
|
|
handleResize(guiParent->getCanvasRect(), false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GuiObject::setSize(UDim2 value)
|
|
{
|
|
if(size != value){
|
|
size = value;
|
|
raisePropertyChanged(prop_Size);
|
|
|
|
checkForResize();
|
|
}
|
|
}
|
|
|
|
void GuiObject::setSizeConstraint(SizeConstraint value)
|
|
{
|
|
if(sizeConstraint != value){
|
|
sizeConstraint = value;
|
|
raisePropertyChanged(prop_SizeConstraint);
|
|
|
|
checkForResize();
|
|
}
|
|
}
|
|
|
|
void GuiObject::setPosition(UDim2 value)
|
|
{
|
|
RBXASSERT(isFinite(value.x.scale) || isFinite(value.y.scale));
|
|
|
|
if(position != value){
|
|
position = value;
|
|
raisePropertyChanged(prop_Position);
|
|
|
|
checkForResize();
|
|
}
|
|
}
|
|
|
|
void GuiObject::setRotation(float value)
|
|
{
|
|
if(rotation.getValue() != value){
|
|
rotation = RotationAngle(value);
|
|
raisePropertyChanged(prop_Rotation);
|
|
|
|
checkForResize();
|
|
}
|
|
}
|
|
|
|
bool GuiObject::setAbsoluteRotation(const Rotation2D& value)
|
|
{
|
|
if (absoluteRotation != value)
|
|
{
|
|
absoluteRotation = value;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GuiObject::setSelectionImageObject(GuiObject* value)
|
|
{
|
|
if (value != selectionImageObject.get())
|
|
{
|
|
selectionImageObject = shared_from(value);
|
|
raisePropertyChanged(prop_SelectionImageObject);
|
|
}
|
|
}
|
|
|
|
void GuiObject::setClipping(bool value)
|
|
{
|
|
if(clipping != value)
|
|
{
|
|
clipping = value;
|
|
raisePropertyChanged(prop_Clipping);
|
|
}
|
|
}
|
|
|
|
GuiObject* GuiObject::firstAncestorClipping()
|
|
{
|
|
Instance* parent = getParent();
|
|
while(parent != NULL)
|
|
{
|
|
if(GuiObject* guiParent = fastDynamicCast<GuiObject>(parent))
|
|
if(guiParent->getClipping())
|
|
return guiParent;
|
|
parent = parent->getParent();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void GuiObject::setDraggable(bool value)
|
|
{
|
|
if(draggable != value)
|
|
{
|
|
draggable = value;
|
|
raisePropertyChanged(prop_Draggable);
|
|
}
|
|
}
|
|
|
|
void GuiObject::setBorderSizePixel(int value)
|
|
{
|
|
if(value < 0) value = 0;
|
|
|
|
if (value > MAX_BORDER_SIZE_PIXEL ) value = MAX_BORDER_SIZE_PIXEL;
|
|
|
|
if(borderSizePixel != value){
|
|
borderSizePixel = value;
|
|
raisePropertyChanged(prop_BorderSizePixel);
|
|
}
|
|
}
|
|
|
|
void GuiObject::setZIndex(int value)
|
|
{
|
|
if(RBX::Security::Context::current().hasPermission(RBX::Security::RobloxScript)){
|
|
//We can access level 0
|
|
if(value < GuiBase::minZIndex())
|
|
value = GuiBase::minZIndex();
|
|
}
|
|
else{
|
|
if(value < GuiBase::minZIndex2d())
|
|
value = GuiBase::minZIndex2d();
|
|
}
|
|
if(value > GuiBase::maxZIndex2d()) value = GuiBase::maxZIndex2d();
|
|
|
|
if(zIndex != value){
|
|
zIndex = value;
|
|
raisePropertyChanged(prop_ZIndex);
|
|
}
|
|
}
|
|
|
|
Rect2D GuiObject::getClippedRect()
|
|
{
|
|
GuiObject* ancestorClipper = firstAncestorClipping();
|
|
Rect2D clippedRect = getRect2D();
|
|
while(ancestorClipper != NULL)
|
|
{
|
|
clippedRect = ancestorClipper->getRect2D().intersect(clippedRect);
|
|
ancestorClipper = ancestorClipper->firstAncestorClipping();
|
|
}
|
|
|
|
return clippedRect;
|
|
}
|
|
|
|
void GuiObject::setBackgroundColor(BrickColor value)
|
|
{
|
|
setBackgroundColor3(value.color3());
|
|
}
|
|
|
|
void GuiObject::setBackgroundColor3(Color3 value)
|
|
{
|
|
if(backgroundColor != value){
|
|
backgroundColor = value;
|
|
raisePropertyChanged(prop_BackgroundColor);
|
|
raisePropertyChanged(prop_BackgroundColor3);
|
|
}
|
|
}
|
|
|
|
void GuiObject::setBorderColor(BrickColor value)
|
|
{
|
|
setBorderColor3(value.color3());
|
|
}
|
|
|
|
void GuiObject::setBorderColor3(Color3 value)
|
|
{
|
|
if(borderColor != value){
|
|
borderColor = value;
|
|
raisePropertyChanged(prop_BorderColor);
|
|
raisePropertyChanged(prop_BorderColor3);
|
|
}
|
|
}
|
|
|
|
void GuiObject::setVisible(bool value)
|
|
{
|
|
if(visible != value){
|
|
visible = value;
|
|
raisePropertyChanged(prop_Visible);
|
|
if(!visible){
|
|
guiState = Gui::NOTHING;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GuiObject::setActive(bool value)
|
|
{
|
|
if(active != value)
|
|
{
|
|
active = value;
|
|
raisePropertyChanged(prop_Active);
|
|
if(!active){
|
|
guiState = Gui::NOTHING;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GuiObject::setSelectable(bool value)
|
|
{
|
|
if(selectable != value)
|
|
{
|
|
selectable = value;
|
|
raisePropertyChanged(prop_Selectable);
|
|
}
|
|
}
|
|
|
|
|
|
GuiObject* getRawPointer(weak_ptr<GuiObject> weakObject)
|
|
{
|
|
GuiObject* rawPointer = NULL;
|
|
if (shared_ptr<GuiObject> sharedPointer = weakObject.lock())
|
|
{
|
|
rawPointer = sharedPointer.get();
|
|
}
|
|
return rawPointer;
|
|
}
|
|
|
|
GuiObject* GuiObject::getNextSelectionUp() const
|
|
{
|
|
return getRawPointer(nextSelectionUp);
|
|
}
|
|
void GuiObject::setNextSelectionUp(GuiObject* value)
|
|
{
|
|
if (value != getRawPointer(nextSelectionUp))
|
|
{
|
|
nextSelectionUp = weak_from(value);
|
|
raisePropertyChanged(prop_nextSelectionUp);
|
|
}
|
|
}
|
|
|
|
GuiObject* GuiObject::getNextSelectionDown() const
|
|
{
|
|
return getRawPointer(nextSelectionDown);
|
|
}
|
|
void GuiObject::setNextSelectionDown(GuiObject* value)
|
|
{
|
|
if (value != getRawPointer(nextSelectionDown))
|
|
{
|
|
nextSelectionDown = weak_from(value);
|
|
raisePropertyChanged(prop_nextSelectionDown);
|
|
}
|
|
}
|
|
|
|
GuiObject* GuiObject::getNextSelectionLeft() const
|
|
{
|
|
return getRawPointer(nextSelectionLeft);
|
|
}
|
|
void GuiObject::setNextSelectionLeft(GuiObject* value)
|
|
{
|
|
if (value != getRawPointer(nextSelectionLeft))
|
|
{
|
|
nextSelectionLeft = weak_from(value);
|
|
raisePropertyChanged(prop_nextSelectionLeft);
|
|
}
|
|
}
|
|
|
|
GuiObject* GuiObject::getNextSelectionRight() const
|
|
{
|
|
return getRawPointer(nextSelectionRight);
|
|
}
|
|
void GuiObject::setNextSelectionRight(GuiObject* value)
|
|
{
|
|
if (value != getRawPointer(nextSelectionRight))
|
|
{
|
|
nextSelectionRight = weak_from(value);
|
|
raisePropertyChanged(prop_nextSelectionRight);
|
|
}
|
|
}
|
|
|
|
void GuiObject::onPropertyChanged(const Reflection::PropertyDescriptor& descriptor)
|
|
{
|
|
Super::onPropertyChanged(descriptor);
|
|
|
|
eventReplicatorMouseEnter.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseLeave.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseMoved.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseWheelForward.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseWheelBackward.onPropertyChanged(descriptor);
|
|
|
|
eventReplicatorDragStopped.onPropertyChanged(descriptor);
|
|
eventReplicatorDragBegin.onPropertyChanged(descriptor);
|
|
}
|
|
|
|
|
|
void GuiObject::onAncestorChanged(const AncestorChanged& event)
|
|
{
|
|
Super::onAncestorChanged(event);
|
|
|
|
if(!serverGuiObject && Network::Players::serverIsPresent(this,false)){
|
|
setServerGuiObject();
|
|
}
|
|
|
|
checkForResize();
|
|
}
|
|
|
|
bool GuiObject::askSetParent(const Instance* instance) const
|
|
{
|
|
if(const GuiBase2d* guiBase = Instance::fastDynamicCast<GuiBase2d>(instance)){
|
|
return !guiBase->isGuiLeaf();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void GuiObject::setBackgroundTransparency(float value)
|
|
{
|
|
if (value != backgroundTransparency) {
|
|
backgroundTransparency = value;
|
|
raisePropertyChanged(prop_BackgroundTransparency);
|
|
}
|
|
}
|
|
|
|
float GuiObject::getRenderBackgroundAlpha() const
|
|
{
|
|
return G3D::clamp(1.0f - getBackgroundTransparency(), 0.0f, 1.0f);
|
|
|
|
}
|
|
Color4 GuiObject::getRenderBackgroundColor4() const
|
|
{
|
|
return Color4(getBackgroundColor3(), getRenderBackgroundAlpha());
|
|
}
|
|
|
|
|
|
//Used to render these object in old pure-adorn system
|
|
void GuiObject::legacyRender2d(Adorn* adorn, const Rect2D& parentRectangle)
|
|
{
|
|
//Only allow this when the object is not in the workspace
|
|
if(getParent() == NULL){
|
|
handleResize(parentRectangle, true);
|
|
|
|
recursiveRender2d(adorn);
|
|
}
|
|
}
|
|
|
|
void GuiObject::render2d(Adorn* adorn)
|
|
{
|
|
render2dImpl(adorn, getRenderBackgroundColor4());
|
|
|
|
renderStudioSelectionBox(adorn);
|
|
}
|
|
|
|
//Common base that saves the rect for later use
|
|
void GuiObject::render2dImpl( Adorn* adorn,
|
|
const Color4& _backgroundColor,
|
|
Rect2D& rect)
|
|
{
|
|
rect = getRect2D(); // virtual call to GuiBase
|
|
|
|
float alpha = _backgroundColor.a;
|
|
|
|
if(alpha > 0)
|
|
{
|
|
Color4 _borderColor = Color4(getBorderColor3(), alpha);
|
|
int _borderSize = getBorderSizePixel();
|
|
|
|
GuiObject* clippingObject = firstAncestorClipping();
|
|
|
|
if (clippingObject == NULL || !absoluteRotation.empty())
|
|
{
|
|
adorn->rect2d(rect, _backgroundColor, absoluteRotation);
|
|
if(_borderSize > 0) adorn->outlineRect2d(rect, (float)_borderSize, _borderColor, absoluteRotation);
|
|
}
|
|
else
|
|
{
|
|
adorn->rect2d(rect, _backgroundColor, clippingObject->getClippedRect());
|
|
if(_borderSize > 0) adorn->outlineRect2d(rect, (float)_borderSize, _borderColor, clippingObject->getClippedRect());
|
|
}
|
|
}
|
|
}
|
|
|
|
void GuiObject::renderSelectionFrame(Adorn* adorn, GuiObject* baseSelectionObject)
|
|
{
|
|
if (selectionImageObject && selectionImageObject.get())
|
|
{
|
|
baseSelectionObject = selectionImageObject.get();
|
|
}
|
|
|
|
if (baseSelectionObject && baseSelectionObject->getVisible())
|
|
{
|
|
// need to set selectedGuiObject renderer to match this object's size, pos and rotation temporarily
|
|
const UDim2 selectedGuiObjPos = baseSelectionObject->getPosition();
|
|
Vector2 absolutePosition = getAbsolutePosition() + (getAbsoluteSize() * Vector2(selectedGuiObjPos.x.scale, selectedGuiObjPos.y.scale)) + Vector2(selectedGuiObjPos.x.offset, selectedGuiObjPos.y.offset);
|
|
if (GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(this))
|
|
{
|
|
absolutePosition += guiService->getGlobalGuiInset().xy();
|
|
}
|
|
|
|
const UDim2 selectedGuiObjSize = baseSelectionObject->getSize();
|
|
Vector2 absoluteSize = (getAbsoluteSize() * Vector2(selectedGuiObjSize.x.scale, selectedGuiObjSize.y.scale)) + Vector2(selectedGuiObjSize.x.offset, selectedGuiObjSize.y.offset);
|
|
|
|
Vector2 originalAbsPosition = baseSelectionObject->getAbsolutePosition();
|
|
const Vector2 originalAbsSize = baseSelectionObject->getAbsoluteSize();
|
|
const Rotation2D originalRotation = baseSelectionObject->getAbsoluteRotation();
|
|
const RotationAngle selectionObjectRotation = RotationAngle(baseSelectionObject->getRotation());
|
|
|
|
baseSelectionObject->setAbsolutePosition(absolutePosition, false);
|
|
baseSelectionObject->setAbsoluteSize(absoluteSize, false);
|
|
baseSelectionObject->setAbsoluteRotation( Rotation2D(getAbsoluteRotation().getAngle().combine(selectionObjectRotation), baseSelectionObject->getRect2D().center()) );
|
|
|
|
|
|
// we are setup, now render the selection
|
|
baseSelectionObject->renderBackground2d(adorn);
|
|
baseSelectionObject->render2d(adorn);
|
|
|
|
|
|
// we rendered, now set the properties back to normal
|
|
if (GuiService* guiService = RBX::ServiceProvider::find<GuiService>(this))
|
|
{
|
|
Vector4 guiInset = guiService->getGlobalGuiInset();
|
|
originalAbsPosition += guiInset.xy();
|
|
}
|
|
|
|
baseSelectionObject->setAbsolutePosition(originalAbsPosition, false);
|
|
baseSelectionObject->setAbsoluteSize(originalAbsSize, false);
|
|
baseSelectionObject->setAbsoluteRotation(originalRotation);
|
|
}
|
|
}
|
|
|
|
void GuiObject::renderStudioSelectionBox(Adorn* adorn)
|
|
{
|
|
if(selectionBox)
|
|
{
|
|
Rect2D outlineRect = getRect2D();
|
|
outlineRect = Rect2D::xyxy(outlineRect.x0() - 1.0f, outlineRect.y0() - 1.0f,outlineRect.x1() + 1.0f,outlineRect.y1() + 1.0f);
|
|
adorn->outlineRect2d(outlineRect,3.0f,RBX::Color4(RBX::Color3(0.1f, 0.6f, 1.0f)), absoluteRotation);
|
|
}
|
|
}
|
|
//Used by children without text
|
|
void GuiObject::render2dImpl( Adorn* adorn,
|
|
const Color4& _backgroundColor)
|
|
{
|
|
Rect2D rect;
|
|
render2dImpl(adorn, _backgroundColor, rect);
|
|
}
|
|
|
|
Rect2D GuiObject::Scale9Rect2D(const Rect2D& rect, float border, float minSize)
|
|
{
|
|
float scale = 1.0f;
|
|
scale = std::min(scale, rect.width() / minSize);
|
|
scale = std::min(scale, rect.height() / minSize);
|
|
|
|
float xInset = border*scale;
|
|
float yInset = border*scale;
|
|
|
|
return Rect2D::xywh(rect.x0() + xInset, rect.y0() + yInset, rect.width() - xInset*2, rect.height() - yInset*2);
|
|
}
|
|
|
|
static void computeScale9Sizes(Rect output[9], const Rect2D& rect, const Vector2int16& scaleEdgeSize, const Vector2& minSize)
|
|
{
|
|
float scale = 1.0f;
|
|
scale = std::min(scale, (rect.width() / minSize.x));
|
|
scale = std::min(scale, (rect.height() / minSize.y));
|
|
|
|
float xSize = scaleEdgeSize.x*scale;
|
|
float ySize = scaleEdgeSize.y*scale;
|
|
|
|
//(x0, y0)-------(xLeft, y0)-------(xRight, y0)-------(x1,y0)
|
|
// | | | |
|
|
// | 1 | 2 | 3 |
|
|
// | | | |
|
|
//(x0, yTop)-----(xLeft, yTop)-----(xRight, yTop)-----(x1,yTop)
|
|
// | | | |
|
|
// | 4 | 5 | 6 |
|
|
// | | | |
|
|
//(x0, yBottom)--(xLeft, yBottom)--(xRight, yBottom)--(x1,yBottom)
|
|
// | | | |
|
|
// | 7 | 8 | 9 |
|
|
// | | | |
|
|
//(x0, y1)-------(xLeft, y1)-------(xRight, y1)-------(x1,y1)
|
|
|
|
float xLeft = rect.x0() + floor(xSize);
|
|
float xRight = rect.x1() - floor(xSize);
|
|
|
|
float yTop = rect.y0() + floor(ySize);
|
|
float yBottom = rect.y1() - floor(ySize);
|
|
|
|
output[0] = Rect(rect.x0(), rect.y0(), xLeft, yTop);
|
|
output[1] = Rect(xLeft, rect.y0(), xRight, yTop);
|
|
output[2] = Rect(xRight, rect.y0(), rect.x1(), yTop);
|
|
output[3] = Rect(rect.x0(), yTop, xLeft, yBottom);
|
|
output[4] = Rect(xLeft, yTop, xRight, yBottom);
|
|
output[5] = Rect(xRight, yTop, rect.x1(), yBottom);
|
|
output[6] = Rect(rect.x0(), yBottom, xLeft, rect.y1());
|
|
output[7] = Rect(xLeft, yBottom, xRight, rect.y1());
|
|
output[8] = Rect(xRight, yBottom, rect.x1(), rect.y1());
|
|
}
|
|
|
|
void GuiObject::render2dScale9Impl( Adorn* adorn,
|
|
const TextureId& texId,
|
|
const Vector2int16& scaleEdgeSize,
|
|
const Vector2& minSize,
|
|
GuiDrawImage& guiImageDraw,
|
|
Rect2D& rect,
|
|
GuiObject* clippingObject)
|
|
{
|
|
rect = getRect2D(); // virtual call to GuiBase2d
|
|
|
|
Rect sizes[9];
|
|
computeScale9Sizes(sizes, rect, scaleEdgeSize, minSize);
|
|
|
|
if(guiImageDraw.setImage(adorn, texId, GuiDrawImage::NORMAL, NULL, this, ".Texture"))
|
|
{
|
|
Color4 color = Color4(Color3::white(), 1.0f - getBackgroundTransparency());
|
|
|
|
for(int i = 0; i<9; ++i)
|
|
{
|
|
int rowNum = i / 3;
|
|
int colNum = i % 3;
|
|
|
|
if(sizes[i].size().x > 0 && sizes[i].size().y > 0)
|
|
{
|
|
Vector2 texul(colNum * 0.33333f, rowNum * 0.33333f);
|
|
Vector2 texbr((colNum + 1) * 0.33333f, (rowNum + 1) * 0.33333f);
|
|
if(clippingObject == NULL || !absoluteRotation.empty())
|
|
guiImageDraw.render2d(adorn, true, sizes[i], texul, texbr, color, absoluteRotation, Gui::NOTHING, false);
|
|
else
|
|
guiImageDraw.render2d(adorn, true, sizes[i], texul, texbr, color, clippingObject->getClippedRect(), Gui::NOTHING, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void computeScale9Sizes2(Rect output[9], Rect uvs[9], const Rect2D& renderRect, Rect2D& sliceRect, const Rect2D& textureBounds, const Vector2& textureSize)
|
|
{
|
|
// Scale the corners down, only if there is not enough room for all the corners.
|
|
float nonScalingX = sliceRect.x0() + (textureBounds.width() - sliceRect.x1());
|
|
float nonScalingY = sliceRect.y0() + (textureBounds.height() - sliceRect.y1());
|
|
|
|
float scale = 1.0f;
|
|
scale = std::min(scale, (renderRect.width() / nonScalingX));
|
|
scale = std::min(scale, (renderRect.height() / nonScalingY));
|
|
|
|
//(x0, y0)-------(xLeft, y0)-------(xRight, y0)-------(x1,y0)
|
|
// | | | |
|
|
// | 0 | 1 | 2 |
|
|
// | | | |
|
|
//(x0, yTop)-----(xLeft, yTop)-----(xRight, yTop)-----(x1,yTop)
|
|
// | | | |
|
|
// | 3 | 4 | 5 |
|
|
// | | | |
|
|
//(x0, yBottom)--(xLeft, yBottom)--(xRight, yBottom)--(x1,yBottom)
|
|
// | | | |
|
|
// | 6 | 7 | 8 |
|
|
// | | | |
|
|
//(x0, y1)-------(xLeft, y1)-------(xRight, y1)-------(x1,y1)
|
|
|
|
float xLeft;
|
|
float yTop;
|
|
|
|
if (FFlag::FixSlice9Scale)
|
|
{
|
|
xLeft = std::max(renderRect.x0(), renderRect.x0() + (sliceRect.x0() * scale));
|
|
yTop = std::max(renderRect.y0(), renderRect.y0() + (sliceRect.y0() * scale));
|
|
}
|
|
else
|
|
{
|
|
xLeft = std::max(renderRect.x0(), (renderRect.x0() + sliceRect.x0()) * scale);
|
|
yTop = std::max(renderRect.y0(), (renderRect.y0() + sliceRect.y0()) * scale);
|
|
}
|
|
|
|
float xRight = std::min(renderRect.x1(), renderRect.x1() - (textureBounds.width() - sliceRect.x1()) * scale);
|
|
float yBottom = std::min(renderRect.y1(), renderRect.y1() - (textureBounds.height() - sliceRect.y1()) * scale);
|
|
|
|
output[0] = Rect(renderRect.x0(),renderRect.y0(), xLeft, yTop);
|
|
output[1] = Rect(xLeft, renderRect.y0(), xRight, yTop);
|
|
output[2] = Rect(xRight, renderRect.y0(), renderRect.x1(), yTop);
|
|
output[3] = Rect(renderRect.x0(),yTop, xLeft, yBottom);
|
|
output[4] = Rect(xLeft, yTop, xRight, yBottom);
|
|
output[5] = Rect(xRight, yTop, renderRect.x1(), yBottom);
|
|
output[6] = Rect(renderRect.x0(),yBottom, xLeft, renderRect.y1());
|
|
output[7] = Rect(xLeft, yBottom, xRight, renderRect.y1());
|
|
output[8] = Rect(xRight, yBottom, renderRect.x1(), renderRect.y1());
|
|
|
|
// Set-up for for UV coords
|
|
sliceRect = Rect2D::xyxy(sliceRect.x0y0() + textureBounds.x0y0(), sliceRect.x1y1() + textureBounds.x0y0());
|
|
|
|
xLeft = sliceRect.x0();
|
|
xRight = sliceRect.x1();
|
|
|
|
yTop = sliceRect.y0();
|
|
yBottom = sliceRect.y1();
|
|
|
|
float innerXRight = xRight;
|
|
float innerYBottom = yBottom;
|
|
if (output[4].size().x < xRight - xLeft)
|
|
innerXRight = xLeft + output[4].size().x;
|
|
if (output[4].size().y < yBottom - yTop)
|
|
innerYBottom = yTop + output[4].size().y;
|
|
|
|
uvs[0] = Rect(Rect2D::xyxy(textureBounds.x0(), textureBounds.y0(), xLeft, yTop) / textureSize);
|
|
uvs[1] = Rect(Rect2D::xyxy(xLeft, textureBounds.y0(), innerXRight, yTop) / textureSize);
|
|
uvs[2] = Rect(Rect2D::xyxy(xRight, textureBounds.y0(), textureBounds.x1(), yTop) / textureSize);
|
|
uvs[3] = Rect(Rect2D::xyxy(textureBounds.x0(), yTop, xLeft, innerYBottom) / textureSize);
|
|
uvs[4] = Rect(Rect2D::xyxy(xLeft, yTop, innerXRight, innerYBottom) / textureSize);
|
|
uvs[5] = Rect(Rect2D::xyxy(xRight, yTop, textureBounds.x1(), innerYBottom) / textureSize);
|
|
uvs[6] = Rect(Rect2D::xyxy(textureBounds.x0(), yBottom, xLeft, textureBounds.y1()) / textureSize);
|
|
uvs[7] = Rect(Rect2D::xyxy(xLeft, yBottom, innerXRight, textureBounds.y1()) / textureSize);
|
|
uvs[8] = Rect(Rect2D::xyxy(xRight, yBottom, textureBounds.x1(), textureBounds.y1()) / textureSize);
|
|
}
|
|
|
|
void GuiObject::render2dScale9Impl2(Adorn* adorn,
|
|
const TextureId& texId,
|
|
GuiDrawImage& guiImageDraw,
|
|
Rect2D& scaleRect,
|
|
GuiObject* clippingObject,
|
|
Color4& color,
|
|
Rect2D* overrideRect,
|
|
Rect2D* imageOffsetRect)
|
|
{
|
|
Rect2D rect = getRect2D(); // virtual call to GuiBase2d
|
|
if (overrideRect)
|
|
{
|
|
rect = *overrideRect;
|
|
}
|
|
|
|
Rect sizes[9];
|
|
Rect uvs[9];
|
|
|
|
Vector2 imageSize;
|
|
if(guiImageDraw.setImage(adorn, texId, GuiDrawImage::NORMAL, &imageSize, this, ".Texture"))
|
|
{
|
|
Rect2D centerRect = scaleRect;
|
|
Rect2D imageSizeRect = Rect2D::xywh(Vector2::zero(), imageSize);
|
|
if (imageOffsetRect)
|
|
{
|
|
imageSizeRect = *imageOffsetRect;
|
|
}
|
|
|
|
computeScale9Sizes2(sizes, uvs, rect, centerRect, imageSizeRect, imageSize);
|
|
float imageWidth = rect.width();
|
|
float imageHeight = rect.height();
|
|
for(int i = 0; i<9; ++i)
|
|
{
|
|
if(sizes[i].size().x > 0 && sizes[i].size().y > 0 && imageWidth > 0 && imageHeight > 0)
|
|
{
|
|
if(clippingObject == NULL || !absoluteRotation.empty())
|
|
guiImageDraw.render2d(adorn, true, sizes[i], uvs[i].low, uvs[i].high, color, absoluteRotation, Gui::NOTHING, false);
|
|
else
|
|
guiImageDraw.render2d(adorn, true, sizes[i], uvs[i].low, uvs[i].high, color, clippingObject->getClippedRect(), Gui::NOTHING, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector2 GuiObject::render2dTextImpl( Adorn* adorn,
|
|
const Color4& _backgroundColor,
|
|
const std::string& _textName,
|
|
TextService::Font _font,
|
|
TextService::FontSize _fontSizeEnum,
|
|
const Color4& _textColor,
|
|
const Color4& _textStrokeColor,
|
|
bool _textWrap,
|
|
bool _textScale,
|
|
TextService::XAlignment _xalignment,
|
|
TextService::YAlignment _yalignment)
|
|
{
|
|
//Background color
|
|
Rect2D rect;
|
|
render2dImpl(adorn, _backgroundColor, rect);
|
|
|
|
return render2dTextImpl(adorn, rect, _textName, _font, _fontSizeEnum, _textColor, _textStrokeColor, _textWrap, _textScale, _xalignment, _yalignment);
|
|
}
|
|
float GuiObject::getFontSizeScale(bool _textScale, bool _textWrap, TextService::FontSize _fontSizeEnum, const Rect2D& rect)
|
|
{
|
|
float normalFontSize = convertFontSize(_fontSizeEnum);
|
|
return normalFontSize;
|
|
}
|
|
|
|
float GuiObject::getScaledFontSize( const Rect2D& rect,
|
|
const std::string& _textName,
|
|
TextService::Font _font,
|
|
bool _textWrap,
|
|
float _fontSize)
|
|
{
|
|
if(TextService* textService = ServiceProvider::create<TextService>(this))
|
|
{
|
|
if(Typesetter* typesetter = textService->getTypesetter(_font))
|
|
{
|
|
float biggestLegal = convertFontSize(TextService::SIZE_SMALLEST);
|
|
float min = biggestLegal;
|
|
float max = convertFontSize(TextService::SIZE_LARGEST);
|
|
|
|
bool textFits = false;
|
|
|
|
while(biggestLegal <= max)
|
|
{
|
|
float mid = min + floor((max - min) / 2.0f);
|
|
typesetter->measure(_textName,
|
|
mid,
|
|
_textWrap ? rect.wh() : Vector2::zero(),
|
|
&textFits);
|
|
|
|
if(textFits && biggestLegal < mid)
|
|
{
|
|
biggestLegal = mid;
|
|
min = mid + 1.0f;
|
|
}
|
|
else
|
|
max = mid - 1.0f;
|
|
}
|
|
return biggestLegal;
|
|
}
|
|
}
|
|
|
|
return _fontSize;
|
|
}
|
|
|
|
Vector2 GuiObject::render2dTextImpl( Adorn* adorn,
|
|
const Rect2D& rectIn,
|
|
const std::string& _textName,
|
|
TextService::Font _font,
|
|
TextService::FontSize _fontSizeEnum,
|
|
const Color4& _textColor,
|
|
const Color4& _textStrokeColor,
|
|
bool _textWrap,
|
|
bool _textScale,
|
|
TextService::XAlignment _xalignment,
|
|
TextService::YAlignment _yalignment)
|
|
{
|
|
Rect2D rect;
|
|
float _fontSize = 0;
|
|
bool autoScale = _textScale && adorn->useFontSmoothScalling();
|
|
|
|
rect = autoScale ? getRect2DFloat() : rectIn;
|
|
if (adorn->useFontSmoothScalling())
|
|
_fontSize = getFontSizeScale(_textScale, _textWrap, _fontSizeEnum, rect);
|
|
else
|
|
_fontSize = _textScale ? getScaledFontSize(rect,_textName,_font,_textWrap,_fontSize) : getFontSizeScale(_textScale, _textWrap, _fontSizeEnum, rect);
|
|
|
|
Vector2 pos;
|
|
switch(_xalignment){
|
|
case TextService::XALIGNMENT_LEFT:
|
|
pos.x = rect.x0();
|
|
break;
|
|
case TextService::XALIGNMENT_RIGHT:
|
|
pos.x = rect.x1();
|
|
break;
|
|
case TextService::XALIGNMENT_CENTER:
|
|
pos.x = rect.center().x;
|
|
break;
|
|
}
|
|
switch(_yalignment){
|
|
case TextService::YALIGNMENT_TOP:
|
|
pos.y = rect.y0();
|
|
break;
|
|
case TextService::YALIGNMENT_CENTER:
|
|
pos.y = rect.center().y;
|
|
break;
|
|
case TextService::YALIGNMENT_BOTTOM:
|
|
pos.y = rect.y1();
|
|
break;
|
|
}
|
|
|
|
GuiObject* clippingObject = firstAncestorClipping();
|
|
Vector2 wh = _textWrap ? rect.wh() : Vector2::zero();
|
|
|
|
if (clippingObject == NULL || !absoluteRotation.empty())
|
|
{
|
|
return adorn->drawFont2D( _textName,
|
|
pos,
|
|
_fontSize,
|
|
autoScale,
|
|
_textColor,
|
|
_textStrokeColor,
|
|
TextService::ToTextFont(_font),
|
|
TextService::ToTextXAlign(_xalignment),
|
|
TextService::ToTextYAlign(_yalignment),
|
|
wh,
|
|
getClipping() ? rect : RBX::Rect2D::xyxy(-1,-1,-1,-1),
|
|
absoluteRotation);
|
|
}
|
|
else
|
|
{
|
|
RBX::Rect2D clippedRect = clippingObject->getClippedRect();
|
|
if(getClipping())
|
|
clippedRect = clippedRect.intersect(rect);
|
|
|
|
return adorn->drawFont2D( _textName,
|
|
pos,
|
|
_fontSize,
|
|
autoScale,
|
|
_textColor,
|
|
_textStrokeColor,
|
|
TextService::ToTextFont(_font),
|
|
TextService::ToTextXAlign(_xalignment),
|
|
TextService::ToTextYAlign(_yalignment),
|
|
wh,
|
|
clippedRect);
|
|
}
|
|
}
|
|
|
|
void GuiObject::fireGenericInputEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
// state and type should always be set appropriately
|
|
RBXASSERT(event->getUserInputState() != InputObject::INPUT_STATE_NONE && event->getUserInputType() != InputObject::TYPE_NONE);
|
|
|
|
if( event->isKeyEvent() ) //todo: make these work? not sure if key events should register or not
|
|
return;
|
|
|
|
if (!event->isPublicEvent())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if we send a "fake" mouse event on a touch device, don't register as generic event
|
|
// fake mouse events are needed from touch for backwards compatibility
|
|
if(RBX::UserInputService* inputService = RBX::ServiceProvider::find<RBX::UserInputService>(this))
|
|
if(event->isMouseEvent() && !inputService->getMouseEnabled() && inputService->getTouchEnabled())
|
|
return;
|
|
|
|
bool isOver = mouseIsOver(event->get2DPosition());
|
|
|
|
// we have seen this object before, but it is no longer over, call end
|
|
if (interactedInputObjects.find(event) != interactedInputObjects.end() && !isOver)
|
|
{
|
|
inputEndedEvent(event);
|
|
interactedInputObjects.erase(event);
|
|
return;
|
|
}
|
|
|
|
if (!isOver)
|
|
return;
|
|
|
|
if (!DFFlag::TurnOffFakeEventsForInputEvents || event->isPublicEvent())
|
|
{
|
|
switch (event->getUserInputState())
|
|
{
|
|
case InputObject::INPUT_STATE_BEGIN:
|
|
inputBeganEvent(event);
|
|
interactedInputObjects[event] = true;
|
|
break;
|
|
case InputObject::INPUT_STATE_CHANGE:
|
|
// haven't seen this object before, store then call began
|
|
if (interactedInputObjects.find(event) == interactedInputObjects.end())
|
|
{
|
|
interactedInputObjects[event] = true;
|
|
inputBeganEvent(event);
|
|
}
|
|
else
|
|
inputChangedEvent(event);
|
|
break;
|
|
case InputObject::INPUT_STATE_END:
|
|
inputEndedEvent(event);
|
|
interactedInputObjects.erase(event);
|
|
break;
|
|
case InputObject::INPUT_STATE_NONE:
|
|
default: // intentional fall thru (none and default do not fire anything)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
GuiResponse GuiObject::process(const shared_ptr<InputObject>& event)
|
|
{
|
|
fireGenericInputEvent(event);
|
|
|
|
//Only process mouse events for "Active" objects
|
|
if (event->isMouseEvent())
|
|
{
|
|
return processMouseEvent(event);
|
|
}
|
|
else if (event->isKeyEvent())
|
|
{
|
|
return processKeyEvent(event);
|
|
}
|
|
else if (event->isTouchEvent())
|
|
{
|
|
return processTouchEvent(event);
|
|
}
|
|
else if (event->isGamepadEvent())
|
|
{
|
|
return processGamepadEvent(event);
|
|
}
|
|
else
|
|
{
|
|
return GuiResponse::notSunk();
|
|
}
|
|
}
|
|
|
|
void GuiObject::fireGestureEvent(const UserInputService::Gesture& gesture, const shared_ptr<const RBX::Reflection::ValueArray>& touchPositions, const shared_ptr<const Reflection::Tuple>& args)
|
|
{
|
|
switch (gesture)
|
|
{
|
|
case UserInputService::GESTURE_TAP:
|
|
tapGestureEvent(touchPositions);
|
|
break;
|
|
case UserInputService::GESTURE_SWIPE:
|
|
RBXASSERT(args->values.size() == 2);
|
|
swipeGestureEvent(args->values[0].cast<UserInputService::SwipeDirection>(),args->values[1].cast<int>());
|
|
break;
|
|
case UserInputService::GESTURE_ROTATE:
|
|
RBXASSERT(args->values.size() == 3);
|
|
rotateGestureEvent(touchPositions,args->values[0].cast<float>(),args->values[1].cast<float>(), args->values[2].cast<InputObject::UserInputState>());
|
|
break;
|
|
case UserInputService::GESTURE_PINCH:
|
|
RBXASSERT(args->values.size() == 3);
|
|
pinchGestureEvent(touchPositions,args->values[0].cast<float>(),args->values[1].cast<float>(), args->values[2].cast<InputObject::UserInputState>());
|
|
break;
|
|
case UserInputService::GESTURE_LONGPRESS:
|
|
RBXASSERT(args->values.size() == 1);
|
|
longPressGestureEvent(touchPositions,args->values[0].cast<InputObject::UserInputState>());
|
|
break;
|
|
case UserInputService::GESTURE_PAN:
|
|
RBXASSERT(args->values.size() == 3);
|
|
panGestureEvent(touchPositions,args->values[0].cast<RBX::Vector2>(),args->values[1].cast<RBX::Vector2>(),args->values[2].cast<InputObject::UserInputState>());
|
|
break;
|
|
case UserInputService::GESTURE_NONE:
|
|
default: // intentional fall thru
|
|
break;
|
|
}
|
|
}
|
|
|
|
GuiResponse GuiObject::processGesture(const UserInputService::Gesture& gesture, const shared_ptr<const RBX::Reflection::ValueArray>& touchPositions, const shared_ptr<const Reflection::Tuple>& args)
|
|
{
|
|
if (!visible)
|
|
return GuiResponse::notSunk();
|
|
|
|
bool gestureIsOver = true;
|
|
// make sure all touches were in view
|
|
for(Reflection::ValueArray::const_iterator iter = touchPositions->begin(); iter != touchPositions->end(); ++iter)
|
|
{
|
|
if( !mouseIsOver(iter->get<RBX::Vector2>()) )
|
|
{
|
|
gestureIsOver = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( gestureIsOver )
|
|
{
|
|
if (!active)
|
|
{
|
|
GuiResponse notSunkResponseOver = GuiResponse::notSunkMouseWasOver();
|
|
notSunkResponseOver.setTarget(this);
|
|
|
|
return notSunkResponseOver;
|
|
}
|
|
|
|
fireGestureEvent(gesture,touchPositions,args);
|
|
|
|
GuiResponse sunkResponse = GuiResponse::sunk();
|
|
sunkResponse.setTarget(this);
|
|
|
|
return sunkResponse;
|
|
}
|
|
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
bool GuiObject::mouseIsOver(const Vector2& mousePosition)
|
|
{
|
|
Rect rect(getRect2D());
|
|
|
|
if (!absoluteRotation.empty())
|
|
{
|
|
Vector2 mousePositionLocal = absoluteRotation.inverse().rotate(mousePosition);
|
|
|
|
return rect.pointInRect(mousePositionLocal);
|
|
}
|
|
|
|
if (GuiObject* clippingObject = firstAncestorClipping())
|
|
{
|
|
RBX::Rect2D clippedRect = getRect2D().intersect(clippingObject->getClippedRect());
|
|
rect = Rect(clippedRect);
|
|
}
|
|
|
|
return rect.pointInRect(mousePosition);
|
|
}
|
|
|
|
GuiResponse GuiObject::processKeyEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse GuiObject::processTouchEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
if (getActive() && getDraggable())
|
|
{
|
|
handleDragging(event);
|
|
}
|
|
return (mouseIsOver(event->get2DPosition()) && getActive()) ? GuiResponse::sunk() : GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse GuiObject::processGamepadEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse GuiObject::processMouseEventInternal(const shared_ptr<InputObject>& event, bool fireEvents)
|
|
{
|
|
bool mouseOver = mouseIsOver(event->get2DPosition());
|
|
bool eventMoved = (event->getUserInputType() == InputObject::TYPE_MOUSEMOVEMENT);
|
|
bool eventLeftButtonDown = event->isLeftMouseDownEvent();
|
|
bool eventRightButtonDown = event->isRightMouseDownEvent();
|
|
bool eventButtonDown = (eventLeftButtonDown || eventRightButtonDown);
|
|
bool eventMouseWheelForward = event->isMouseWheelForward();
|
|
bool eventMouseWheelBackward = event->isMouseWheelBackward();
|
|
bool wasNothing = (guiState == Gui::NOTHING);
|
|
bool wasHover = (guiState == Gui::HOVER);
|
|
bool wasDownOver = (guiState == Gui::DOWN_OVER);
|
|
bool wasDownAway = (guiState == Gui::DOWN_AWAY);
|
|
|
|
Vector2 mousePosition = event->get2DPosition();
|
|
|
|
// 1. Figure out events
|
|
if (fireEvents)
|
|
{
|
|
if (mouseOver && (wasNothing || wasDownAway)) {mouseEnterSignal(mousePosition.x, mousePosition.y);}
|
|
if (mouseOver && eventMoved) {mouseMovedSignal(mousePosition.x, mousePosition.y);}
|
|
if (mouseOver && eventMouseWheelForward) {mouseWheelForwardSignal(mousePosition.x, mousePosition.y);}
|
|
if (mouseOver && eventMouseWheelBackward) {mouseWheelBackwardSignal(mousePosition.x, mousePosition.y);}
|
|
if (!mouseOver && (wasHover || wasDownOver)) {mouseLeaveSignal(mousePosition.x, mousePosition.y);}
|
|
}
|
|
|
|
if(UserInputService* inputService = RBX::ServiceProvider::find<UserInputService>(this))
|
|
{
|
|
if (eventLeftButtonDown && mouseOver)
|
|
{
|
|
inputService->setLastDownGuiObject(weak_from(this), InputObject::TYPE_MOUSEBUTTON1);
|
|
}
|
|
else if (eventRightButtonDown && mouseOver)
|
|
{
|
|
inputService->setLastDownGuiObject(weak_from(this), InputObject::TYPE_MOUSEBUTTON2);
|
|
}
|
|
}
|
|
|
|
if(UserInputService* inputService = RBX::ServiceProvider::find<UserInputService>(this))
|
|
{
|
|
if (getActive() && getDraggable() && inputService->getMouseEnabled() && !inputService->getTouchEnabled())
|
|
{
|
|
handleDragging(event);
|
|
}
|
|
}
|
|
|
|
// 2. Do State Table
|
|
Gui::WidgetState oldState = guiState;
|
|
guiState = Gui::NOTHING;
|
|
switch (oldState)
|
|
{
|
|
case Gui::NOTHING:
|
|
case Gui::HOVER:
|
|
{
|
|
if (mouseOver && eventButtonDown) {
|
|
guiState = Gui::DOWN_OVER;
|
|
}
|
|
else if (mouseOver) {
|
|
guiState = Gui::HOVER;
|
|
}
|
|
break;
|
|
}
|
|
case Gui::DOWN_OVER:
|
|
case Gui::DOWN_AWAY:
|
|
{
|
|
if (mouseOver && (eventButtonDown || eventMoved)) {
|
|
guiState = Gui::DOWN_OVER;
|
|
}
|
|
else if (!mouseOver && (eventButtonDown || eventMoved)) {
|
|
guiState = Gui::DOWN_AWAY;
|
|
}
|
|
else if (event->getUserInputType() != lastMouseDownType)
|
|
{
|
|
guiState = Gui::DOWN_AWAY;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (eventButtonDown && mouseOver)
|
|
{
|
|
lastMouseDownType = event->getUserInputType();
|
|
}
|
|
|
|
return (mouseOver && getActive()) ? GuiResponse::notSunkMouseWasOver() : GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse GuiObject::processMouseEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
return processMouseEventInternal(event, true);
|
|
}
|
|
|
|
void GuiObject::draggingEnded(const shared_ptr<Instance>& event)
|
|
{
|
|
if (InputObject* inputObject = Instance::fastDynamicCast<InputObject>(event.get()))
|
|
{
|
|
if (draggingBeganInputObject.get() == inputObject)
|
|
{
|
|
draggingEndedConnection.disconnect();
|
|
Vector2 mousePosition = inputObject->get2DPosition();
|
|
Vector2 dragPosition = Vector2(mousePosition.x, mousePosition.y);
|
|
if (GuiService* guiService = RBX::ServiceProvider::find<RBX::GuiService>(this))
|
|
{
|
|
dragPosition -= guiService->getGlobalGuiInset().xy();
|
|
}
|
|
dragStoppedSignal(dragPosition.x, dragPosition.y);
|
|
draggingBeganInputObject.reset();
|
|
dragging = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool GuiObject::isCurrentlyVisible()
|
|
{
|
|
// Huh? This line of code doesn't make sense to me: -Erik
|
|
// this code is used to see if this object is being rendered, so
|
|
// if the object itself, or any of its parents are not not set to render
|
|
// we should return false
|
|
if(getVisible() == false)
|
|
return false;
|
|
|
|
Rect2D clippedRect = getRect2D();
|
|
|
|
if (clippedRect.wh().length() > 0.0f)
|
|
{
|
|
if (GuiObject* clippingObject = firstAncestorClipping())
|
|
{
|
|
clippedRect = clippedRect.intersect(clippingObject->getClippedRect());
|
|
if (clippedRect.wh() == Vector2::zero())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (GuiService* guiService = RBX::ServiceProvider::find<GuiService>(this))
|
|
{
|
|
Vector2 screenRes = guiService->getScreenResolution();
|
|
if (!clippedRect.intersects(Rect2D::xywh(Vector2::zero() + guiService->getGlobalGuiInset().xy(),screenRes)))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (GuiObject* parentGuiObject = Instance::fastDynamicCast<GuiObject>(getParent()))
|
|
return parentGuiObject->isCurrentlyVisible();
|
|
|
|
if (Instance::fastDynamicCast<RBX::ScreenGui>(getParent()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool GuiObject::isSelectedObject()
|
|
{
|
|
if (GuiService* guiService = RBX::ServiceProvider::find<GuiService>(this))
|
|
{
|
|
if (GuiObject* selectedGuiObject = guiService->getSelectedGuiObject())
|
|
{
|
|
return selectedGuiObject == this;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
const char* const sGuiButton = "GuiButton";
|
|
|
|
REFLECTION_BEGIN();
|
|
static const Reflection::BoundFuncDesc<GuiButton, void(std::string)> func_setVerb(&GuiButton::setVerb,"SetVerb", "verb", Security::RobloxScript);
|
|
|
|
static Reflection::RemoteEventDesc<GuiButton, void()> event_MouseButton1Click(&GuiButton::mouseButton1ClickSignal, "MouseButton1Click", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiButton, void()> event_MouseButton2Click(&GuiButton::mouseButton2ClickSignal, "MouseButton2Click", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiButton, void(int, int)> event_MouseButton1Down (&GuiButton::mouseButton1DownSignal, "MouseButton1Down", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiButton, void(int, int)> event_MouseButton1Up (&GuiButton::mouseButton1UpSignal, "MouseButton1Up", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiButton, void(int, int)> event_MouseButton2Down (&GuiButton::mouseButton2DownSignal, "MouseButton2Down", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
static Reflection::RemoteEventDesc<GuiButton, void(int, int)> event_MouseButton2Up (&GuiButton::mouseButton2UpSignal, "MouseButton2Up", "x", "y", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiButton,event_MouseButton1Click,"MouseButton1Click",MouseButton1Click);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiButton,event_MouseButton2Click,"MouseButton2Click",MouseButton2Click);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiButton,event_MouseButton1Down, "MouseButton1Down", MouseButton1Down);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiButton,event_MouseButton1Up, "MouseButton1Up", MouseButton1Up);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiButton,event_MouseButton2Down, "MouseButton2Down", MouseButton2Down);
|
|
IMPLEMENT_EVENT_REPLICATOR(GuiButton,event_MouseButton2Up, "MouseButton2Up", MouseButton2Up);
|
|
|
|
static const Reflection::PropDescriptor<GuiButton, bool> prop_AutoButtonColor("AutoButtonColor", category_Data, &GuiButton::getAutoButtonColor, &GuiButton::setAutoButtonColor);
|
|
static const Reflection::PropDescriptor<GuiButton, bool> prop_Selected("Selected", category_Data, &GuiButton::getSelected, &GuiButton::setSelected);
|
|
const Reflection::PropDescriptor<GuiButton, bool> GuiButton::prop_Modal("Modal", category_Data, &GuiButton::getModal, &GuiButton::setModal);
|
|
static const Reflection::EnumPropDescriptor<GuiButton, GuiButton::Style> prop_Style("Style", category_Data, &GuiButton::getStyle, &GuiButton::setStyle);
|
|
REFLECTION_END();
|
|
|
|
|
|
namespace Reflection {
|
|
template<>
|
|
EnumDesc<GuiButton::Style>::EnumDesc()
|
|
:EnumDescriptor("ButtonStyle")
|
|
{
|
|
addPair(GuiButton::CUSTOM_STYLE, "Custom");
|
|
addPair(GuiButton::ROBLOX_RED_STYLE, "RobloxButtonDefault");
|
|
addPair(GuiButton::ROBLOX_GREY_STYLE, "RobloxButton");
|
|
addPair(GuiButton::ROBLOX_BUTTON_ROUND_STYLE, "RobloxRoundButton");
|
|
addPair(GuiButton::ROBLOX_BUTTON_ROUND_DEFAULT_STYLE, "RobloxRoundDefaultButton");
|
|
addPair(GuiButton::ROBLOX_BUTTON_ROUND_DROPDOWN_STYLE, "RobloxRoundDropdownButton");
|
|
}
|
|
}//namespace Reflection
|
|
|
|
GuiButton::GuiButton(const char* name)
|
|
: DescribedNonCreatable<GuiButton,GuiObject,sGuiButton>(name, true)
|
|
, autoButtonColor (true)
|
|
, selected(false)
|
|
, clicked(false)
|
|
, modal(false)
|
|
, style(CUSTOM_STYLE)
|
|
, verb(NULL)
|
|
, verbToSet("")
|
|
, shouldFireClickedEvent(true)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiButton,GuiButton::mouseButton1ClickSignal, event_MouseButton1Click,MouseButton1Click)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiButton,GuiButton::mouseButton2ClickSignal, event_MouseButton2Click,MouseButton2Click)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiButton,GuiButton::mouseButton1DownSignal, event_MouseButton1Down, MouseButton1Down)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiButton,GuiButton::mouseButton1UpSignal, event_MouseButton1Up, MouseButton1Up)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiButton,GuiButton::mouseButton2DownSignal, event_MouseButton2Down, MouseButton2Down)
|
|
, CONSTRUCT_EVENT_REPLICATOR(GuiButton,GuiButton::mouseButton2UpSignal, event_MouseButton2Up, MouseButton2Up)
|
|
{
|
|
selectable = true;
|
|
CONNECT_EVENT_REPLICATOR(MouseButton1Click);
|
|
CONNECT_EVENT_REPLICATOR(MouseButton2Click);
|
|
CONNECT_EVENT_REPLICATOR(MouseButton1Down);
|
|
CONNECT_EVENT_REPLICATOR(MouseButton1Up);
|
|
CONNECT_EVENT_REPLICATOR(MouseButton2Down);
|
|
CONNECT_EVENT_REPLICATOR(MouseButton2Up);
|
|
}
|
|
|
|
void GuiButton::setServerGuiObject()
|
|
{
|
|
Super::setServerGuiObject();
|
|
|
|
eventReplicatorMouseButton1Click .setListenerMode(!mouseButton1ClickSignal.empty());
|
|
eventReplicatorMouseButton2Click .setListenerMode(!mouseButton2ClickSignal.empty());
|
|
eventReplicatorMouseButton1Down .setListenerMode(!mouseButton1DownSignal.empty());
|
|
eventReplicatorMouseButton1Up .setListenerMode(!mouseButton1UpSignal.empty());
|
|
eventReplicatorMouseButton2Down .setListenerMode(!mouseButton2DownSignal.empty());
|
|
eventReplicatorMouseButton2Up .setListenerMode(!mouseButton2UpSignal.empty());
|
|
}
|
|
void GuiButton::onPropertyChanged(const Reflection::PropertyDescriptor& descriptor)
|
|
{
|
|
Super::onPropertyChanged(descriptor);
|
|
|
|
eventReplicatorMouseButton1Click.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseButton2Click.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseButton1Down.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseButton1Up.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseButton2Down.onPropertyChanged(descriptor);
|
|
eventReplicatorMouseButton2Up.onPropertyChanged(descriptor);
|
|
}
|
|
|
|
Rect2D GuiButton::getChildRect2D() const
|
|
{
|
|
switch(style)
|
|
{
|
|
case GuiButton::CUSTOM_STYLE:
|
|
return getRect2D();
|
|
case GuiButton::ROBLOX_RED_STYLE:
|
|
case GuiButton::ROBLOX_GREY_STYLE:
|
|
return Scale9Rect2D(getRect2D(), 12, 36);
|
|
case GuiButton::ROBLOX_BUTTON_ROUND_STYLE:
|
|
case GuiButton::ROBLOX_BUTTON_ROUND_DEFAULT_STYLE:
|
|
case GuiButton::ROBLOX_BUTTON_ROUND_DROPDOWN_STYLE:
|
|
return Scale9Rect2D(getRect2D(), 12, 26);
|
|
default:
|
|
RBXASSERT(0);
|
|
return getRect2D();
|
|
}
|
|
}
|
|
|
|
void GuiButton::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
|
{
|
|
Super::onServiceProvider(oldProvider, newProvider);
|
|
|
|
if(newProvider && verbToSet != "")
|
|
setVerb(verbToSet);
|
|
}
|
|
|
|
void GuiButton::setVerb(std::string verbString)
|
|
{
|
|
// People were modifying verbString when it was loaded into a register by using CE.
|
|
VMProtectBeginMutation(NULL);
|
|
if(Workspace* workspace = ServiceProvider::find<Workspace>(this))
|
|
{
|
|
#ifdef RBX_STUDIO_BUILD
|
|
verb = workspace->getVerb(verbString);
|
|
#else
|
|
verb = workspace->getWhitelistVerb(verbString);
|
|
if (verb && verb->getVerbSecurity())
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND4>(RBX::Security::hackFlag10, HATE_VERB_SNATCH);
|
|
verb = NULL;
|
|
}
|
|
#endif
|
|
verbToSet = "";
|
|
}
|
|
else
|
|
verbToSet = verbString; // our object isn't in the workspace yet, save string for later use (when we reset our parent)
|
|
VMProtectEnd();
|
|
}
|
|
|
|
void GuiButton::setModal(bool value)
|
|
{
|
|
if(modal != value)
|
|
{
|
|
modal = value;
|
|
raisePropertyChanged(prop_Modal);
|
|
}
|
|
}
|
|
|
|
void GuiButton::setStyle(Style value)
|
|
{
|
|
if(style != value){
|
|
style = value;
|
|
raisePropertyChanged(prop_Style);
|
|
|
|
switch(style)
|
|
{
|
|
case GuiButton::CUSTOM_STYLE:
|
|
images.reset();
|
|
forceResize();
|
|
break;
|
|
case GuiButton::ROBLOX_RED_STYLE:
|
|
case GuiButton::ROBLOX_GREY_STYLE:
|
|
case GuiButton::ROBLOX_BUTTON_ROUND_STYLE:
|
|
case GuiButton::ROBLOX_BUTTON_ROUND_DEFAULT_STYLE:
|
|
case GuiButton::ROBLOX_BUTTON_ROUND_DROPDOWN_STYLE:
|
|
if(!images.get())
|
|
{
|
|
images.reset(new GuiDrawImage[4]);
|
|
}
|
|
forceResize();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GuiButton::setAutoButtonColor(bool value)
|
|
{
|
|
if(autoButtonColor != value){
|
|
autoButtonColor = value;
|
|
raisePropertyChanged(prop_AutoButtonColor);
|
|
}
|
|
}
|
|
|
|
void GuiButton::setSelected(bool value)
|
|
{
|
|
if(selected != value)
|
|
{
|
|
selected = value;
|
|
raisePropertyChanged(prop_Selected);
|
|
}
|
|
}
|
|
|
|
void GuiButton::render2dButtonImpl(Adorn* adorn, Rect2D& rect)
|
|
{
|
|
static const TextureId sGreyTransp = TextureId("rbxasset://textures/ui/btn_greyTransp.png");
|
|
static const TextureId sGrey = TextureId("rbxasset://textures/ui/btn_grey.png");
|
|
static const TextureId sWhite = TextureId("rbxasset://textures/ui/btn_white.png");
|
|
static const TextureId sRed = TextureId("rbxasset://textures/ui/btn_red.png");
|
|
static const TextureId sRedGlow = TextureId("rbxasset://textures/ui/btn_redGlow.png");
|
|
static const TextureId sNewGrey = TextureId("rbxasset://textures/ui/btn_newGrey.png");
|
|
static const TextureId sNewGreyGlow = TextureId("rbxasset://textures/ui/btn_newGreyGlow.png");
|
|
static const TextureId sNewBlue = TextureId("rbxasset://textures/ui/btn_newBlue.png");
|
|
static const TextureId sNewBlueGlow = TextureId("rbxasset://textures/ui/btn_newBlueGlow.png");
|
|
static const TextureId sNewWhite = TextureId("rbxasset://textures/ui/btn_newWhite.png");
|
|
static const TextureId sNewWhiteGlow = TextureId("rbxasset://textures/ui/btn_newWhiteGlow.png");
|
|
|
|
GuiObject* clippingObject = firstAncestorClipping();
|
|
|
|
switch(style)
|
|
{
|
|
case CUSTOM_STYLE:
|
|
{
|
|
Color4 renderColor = getRenderBackgroundColor4();
|
|
|
|
if (getAutoButtonColor()) {
|
|
Color3 myBackColor = getBackgroundColor3();
|
|
|
|
switch (guiState)
|
|
{
|
|
case Gui::HOVER: renderColor = Color4(myBackColor.lerp(Color3::black(), 0.3f), renderColor.a); break;
|
|
case Gui::DOWN_OVER: renderColor = Color4(myBackColor.lerp(Color3::white(), 0.3f), renderColor.a); break;
|
|
case Gui::DOWN_AWAY: renderColor = Color4(myBackColor.lerp(Color3::black(), 0.3f), renderColor.a); break;
|
|
case Gui::NOTHING: break;
|
|
}
|
|
}
|
|
|
|
Super::render2dImpl(adorn, renderColor, rect);
|
|
return;
|
|
}
|
|
case ROBLOX_GREY_STYLE:
|
|
{
|
|
if(getAutoButtonColor())
|
|
{
|
|
if (!getActive()) {
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sGreyTransp, Vector2int16(8,8), Vector2int16(36,36), images[0], rect, clippingObject);
|
|
}
|
|
else if (!getSelected()) {
|
|
switch(guiState)
|
|
{
|
|
case Gui::NOTHING:
|
|
case Gui::DOWN_AWAY:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sGrey, Vector2int16(8,8), Vector2int16(36,36), images[1], rect, clippingObject);
|
|
break;
|
|
case Gui::HOVER:
|
|
case Gui::DOWN_OVER:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sWhite, Vector2int16(8,8), Vector2int16(36,36), images[2], rect, clippingObject);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch(guiState)
|
|
{
|
|
case Gui::NOTHING:
|
|
case Gui::HOVER:
|
|
case Gui::DOWN_AWAY:
|
|
case Gui::DOWN_OVER:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sRed, Vector2int16(8,8), Vector2int16(36,36), images[3], rect, clippingObject);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(images.get());
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sGrey, Vector2int16(8,8), Vector2int16(36,36), images[1], rect, clippingObject);
|
|
}
|
|
rect = getChildRect2D();
|
|
break;
|
|
}
|
|
case ROBLOX_RED_STYLE:
|
|
{
|
|
if(getAutoButtonColor())
|
|
{
|
|
if (!getActive()) {
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sGreyTransp, Vector2int16(8,8), Vector2int16(36,36), images[0], rect, clippingObject);
|
|
}
|
|
else if (!getSelected()) {
|
|
switch(guiState)
|
|
{
|
|
case Gui::NOTHING:
|
|
case Gui::DOWN_AWAY:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sRed, Vector2int16(8,8), Vector2int16(36,36), images[1], rect, clippingObject);
|
|
break;
|
|
case Gui::HOVER:
|
|
case Gui::DOWN_OVER:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sRedGlow, Vector2int16(8,8), Vector2int16(36,36), images[2], rect, clippingObject);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch(guiState)
|
|
{
|
|
case Gui::NOTHING:
|
|
case Gui::HOVER:
|
|
case Gui::DOWN_AWAY:
|
|
case Gui::DOWN_OVER:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sRedGlow, Vector2int16(8,8), Vector2int16(36,36), images[3], rect, clippingObject);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(images.get());
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sRed, Vector2int16(8,8), Vector2int16(36,36), images[1], rect, clippingObject);
|
|
}
|
|
rect = getChildRect2D();
|
|
break;
|
|
}
|
|
case ROBLOX_BUTTON_ROUND_STYLE:
|
|
case ROBLOX_BUTTON_ROUND_DEFAULT_STYLE:
|
|
case ROBLOX_BUTTON_ROUND_DROPDOWN_STYLE:
|
|
{
|
|
const TextureId *pNormal = NULL;
|
|
const TextureId *pButtonPressed = NULL;
|
|
switch (style) {
|
|
case ROBLOX_BUTTON_ROUND_DEFAULT_STYLE:
|
|
pNormal = &sNewBlue;
|
|
pButtonPressed = &sNewBlueGlow;
|
|
break;
|
|
default:
|
|
case ROBLOX_BUTTON_ROUND_STYLE:
|
|
pNormal = &sNewGrey;
|
|
pButtonPressed = &sNewGreyGlow;
|
|
break;
|
|
case ROBLOX_BUTTON_ROUND_DROPDOWN_STYLE:
|
|
pNormal = &sNewWhite;
|
|
pButtonPressed = &sNewWhiteGlow;
|
|
break;
|
|
}
|
|
Vector2int16 cornerSize = Vector2int16(6,6);
|
|
Vector2int16 minSize = Vector2int16(20,20);
|
|
|
|
if(getAutoButtonColor())
|
|
{
|
|
if (!getActive()) {
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, sNewGrey, cornerSize, minSize, images[0], rect, clippingObject);
|
|
}
|
|
else if (!getSelected()) {
|
|
switch(guiState)
|
|
{
|
|
case Gui::NOTHING:
|
|
case Gui::DOWN_AWAY:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, *pNormal, cornerSize, minSize, images[1], rect, clippingObject);
|
|
break;
|
|
case Gui::HOVER:
|
|
case Gui::DOWN_OVER:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, *pButtonPressed, cornerSize, minSize, images[2], rect, clippingObject);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch(guiState)
|
|
{
|
|
case Gui::NOTHING:
|
|
case Gui::HOVER:
|
|
case Gui::DOWN_AWAY:
|
|
case Gui::DOWN_OVER:
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, *pButtonPressed, cornerSize, minSize, images[3], rect, clippingObject);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(images.get());
|
|
setBackgroundTransparency(0.0f);
|
|
render2dScale9Impl(adorn, *pNormal, cornerSize, minSize, images[1], rect, clippingObject);
|
|
}
|
|
rect = getChildRect2D();
|
|
break;
|
|
}
|
|
}
|
|
|
|
renderStudioSelectionBox(adorn);
|
|
}
|
|
|
|
GuiResponse GuiButton::processTouchEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
shouldFireClickedEvent = true;
|
|
|
|
if ( event->isTouchEvent() && mouseIsOver(event->get2DPosition()) && getActive() && !getDraggable())
|
|
{
|
|
if( RBX::ScrollingFrame* scrollFrame = findFirstAncestorOfType<RBX::ScrollingFrame>())
|
|
{
|
|
bool processedInput = scrollFrame->processInputFromDescendant(event);
|
|
shouldFireClickedEvent = !scrollFrame->isTouchScrolling();
|
|
|
|
if (processedInput)
|
|
{
|
|
return GuiResponse::sunk();
|
|
}
|
|
}
|
|
}
|
|
|
|
return Super::processTouchEvent(event);
|
|
}
|
|
|
|
GuiResponse GuiButton::checkForSelectedObjectClick(const shared_ptr<InputObject>& event)
|
|
{
|
|
if (GuiService* guiService = RBX::ServiceProvider::find<GuiService>(this))
|
|
{
|
|
if (GuiObject* selectedGuiObject = guiService->getSelectedGuiObject())
|
|
{
|
|
if (selectedGuiObject == this)
|
|
{
|
|
Rect2D rect = getRect2D();
|
|
Vector2 pos = Vector2(rect.x0() + rect.width()/2.0f, rect.y0() + rect.height()/2.0f);
|
|
|
|
if (event->getUserInputState() == InputObject::INPUT_STATE_BEGIN)
|
|
{
|
|
lastSelectedObjectEvent = event;
|
|
mouseButton1DownSignal(pos.x, pos.y);
|
|
}
|
|
if (event->getUserInputState() == InputObject::INPUT_STATE_END)
|
|
{
|
|
mouseButton1UpSignal(pos.x, pos.y);
|
|
shared_ptr<InputObject> lastSharedObj = lastSelectedObjectEvent.lock();
|
|
if (event == lastSharedObj)
|
|
{
|
|
mouseButton1ClickSignal();
|
|
|
|
VMProtectBeginMutation(NULL);
|
|
if (verb != NULL)
|
|
{
|
|
clicked = false;
|
|
if (verb->isEnabled())
|
|
{
|
|
Verb::doItWithChecks(verb, RBX::DataModel::get(this));
|
|
}
|
|
}
|
|
VMProtectEnd();
|
|
lastSelectedObjectEvent = weak_ptr<InputObject>();
|
|
}
|
|
}
|
|
return GuiResponse::sunk();
|
|
}
|
|
}
|
|
}
|
|
|
|
return GuiResponse::notSunk();
|
|
}
|
|
|
|
GuiResponse GuiButton::processKeyEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
// todo: don't hard code selection key? maybe this is ok
|
|
if (event->getKeyCode() == SDLK_RETURN)
|
|
{
|
|
GuiResponse response = checkForSelectedObjectClick(event);
|
|
if (response.wasSunk())
|
|
{
|
|
return response;
|
|
}
|
|
}
|
|
|
|
return Super::processKeyEvent(event);
|
|
}
|
|
|
|
GuiResponse GuiButton::processGamepadEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
// todo: don't hard code selection button? maybe this is ok
|
|
if (event->getKeyCode() == SDLK_GAMEPAD_BUTTONA)
|
|
{
|
|
GuiResponse response = checkForSelectedObjectClick(event);
|
|
if (response.wasSunk())
|
|
{
|
|
return response;
|
|
}
|
|
}
|
|
|
|
return Super::processGamepadEvent(event);
|
|
}
|
|
|
|
GuiResponse GuiButton::processMouseEvent(const shared_ptr<InputObject>& event)
|
|
{
|
|
Vector2 mousePosition = event->get2DPosition();
|
|
|
|
bool mouseOver = mouseIsOver(mousePosition);
|
|
bool eventLeftButtonDown = event->isLeftMouseDownEvent();
|
|
bool eventLeftButtonUp = event->isLeftMouseUpEvent();
|
|
bool eventRightButtonDown = event->isRightMouseDownEvent();
|
|
bool eventRightButtonUp = event->isRightMouseUpEvent();
|
|
bool wasDownOver = (guiState == Gui::DOWN_OVER);
|
|
bool sinkEvent = false;
|
|
|
|
if (eventLeftButtonUp && wasDownOver && shouldFireClickedEvent) {sinkEvent = true; clicked = true; mouseButton1ClickSignal();}
|
|
if (eventRightButtonUp && wasDownOver && shouldFireClickedEvent) {sinkEvent = true; mouseButton2ClickSignal();}
|
|
if (mouseOver && eventLeftButtonDown) {sinkEvent = true; mouseButton1DownSignal(mousePosition.x, mousePosition.y);}
|
|
if (mouseOver && eventRightButtonDown) {sinkEvent = true; mouseButton2DownSignal(mousePosition.x, mousePosition.y);}
|
|
if (mouseOver && eventLeftButtonUp) {sinkEvent = true; mouseButton1UpSignal(mousePosition.x, mousePosition.y);}
|
|
if (mouseOver && eventRightButtonUp) {sinkEvent = true; mouseButton2UpSignal(mousePosition.x, mousePosition.y);}
|
|
|
|
GuiResponse answer = Super::processMouseEvent(event); //We need to call our classParent to deal with the state table
|
|
|
|
VMProtectBeginMutation(NULL);
|
|
if (verb != NULL && clicked)
|
|
{
|
|
clicked = false;
|
|
if (verb->isEnabled())
|
|
{
|
|
Verb::doItWithChecks(verb, RBX::DataModel::get(this));
|
|
}
|
|
}
|
|
VMProtectEnd();
|
|
|
|
if (sinkEvent)
|
|
{
|
|
return GuiResponse::sunk();
|
|
}
|
|
|
|
return answer; // used(this) will set a target and sink this event at the PlayerGui processing level - no other GuiObjects will see it.
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
const char* const sGuiLabel = "GuiLabel";
|
|
|
|
GuiLabel::GuiLabel(const char* name)
|
|
: DescribedNonCreatable<GuiLabel,GuiObject,sGuiLabel>(name, false)
|
|
{}
|
|
|
|
|
|
|
|
|
|
}
|