[SNIPPET] Relative positioning & sizing for GUIs

Started by DizzasTeR, Apr 30, 2016, 04:16 AM

Previous topic - Next topic

DizzasTeR

Since GUIs are absolute and we have the ability to get client's resolution, we can manually calculate relative positions so that the GUIs are always correctly positioned and sized in every resolution.

function getRelative( X, Y )
{
    local x = floor( X*sX/1920 );
    local y = floor( Y*sY/1080 );
local cords = { X = x, Y = y }
return cords;
}

- Parameters:
    X: integer for the X screen position or width.
    Y: integer for the Y screen position or height.

- Return:
    Table: Returns a table with relative values X and Y.

- Example:
sX <- GUI.GetScreenSize().X;
sY <- GUI.GetScreenSize().Y;

function getRelative( X, Y )
{
    local x = floor( X*sX/1920 );
    local y = floor( Y*sY/1080 );
local cords = { X = x, Y = y }
return cords;
}

function Script::ScriptLoad()
{
    local posCanvas = getRelative( (sX/2)-50, (sY/2)-50 );
    local sizeCanvas = getRelative( 150, 100 );

    myCanvas <- GUICanvas();
    myCanvas.Position = VectorScreen( posCanvas.X, posCanvas.Y );
    myCanvas.Size = VectorScreen( sizeCanvas.X, sizeCanvas.Y );
}

There may be a better calculation to do this, but this is how I do it.

Regards,
Doom_Kill3R

.

#1
I'm guessing that the whole thing can be translated to:

VectorScreen.rawnewmember("Round", ::floor, null, true);

VectorScreen.rawnewmember("Width", GUI.GetScreenSize().X, null, true);
VectorScreen.rawnewmember("Height", GUI.GetScreenSize().Y, null, true);

VectorScreen.rawnewmember("Relative", function(x, y) {
    return VectorScreen(Round(x * Width), Round(y * Height));
}, null, true);

function onGameResize(width, height)
{
    VectorScreen.rawset("Width", width);
    VectorScreen.rawset("Height", height);
}

// Handle new positions on game window resize
function GUI::GameResize(width, height)
{
    onGameResize.call(::getroottable(), width, height);
}

And used as:
function onScriptLoad()
{
    myCanvas <- GUICanvas();
    myCanvas.Position = VectorScreen.Relative(0.5, 0.5);
    myCanvas.Size = VectorScreen(150, 100);
}

function Script::ScriptLoad()
{
    onScriptLoad.call(::getroottable());
}

NOTE: I haven't tested the code so let me know if you have issues.



EDIT:

I had to update the code with a temporary workaround because it was affected by the "trying to set class" issue on the client. Which is caused by the fact that it calls functions with a class declaration as the `this` environment instead of an instance. Therefore, not knowing about the elements in the root table. Instead you're modifying the class declaration.

For example, the following:
function GUI::GameResize(width, height)
{
    print(typeof(this));
}

Should output 'instance' for functions called on a class instance or 'table' if they're global functions called on the root table. But instead it outputs 'class'. And because the `this` environment in Squirrel, can be any table and both class instances and class declarations are tables. This is valid behavior.



@maxorator When you retrieve a function from a class:
Function fn = Class< GUI >(DefaultVM::GetVM(), _SC("GUI")).GetFunction(_SC("GameResize"));
Be sure to also re-bind the environment to the root table (if used as a static function):
fn = Function(DefaultVM::GetVM(), RootTable(DefaultVM::GetVM()), fn.GetFunc());
Or the object of the instance that you intend on calling it onto. Not sure if it's the same syntax or even implemented on your version of Squirrel/Sqrat.
.

maxorator

Quote from: . on Apr 30, 2016, 04:35 AM@maxorator When you retrieve a function from a class:
Function fn = Class< GUI >(DefaultVM::GetVM(), _SC("GUI")).GetFunction(_SC("GameResize"));
Be sure to also re-bind the environment to the root table (if used as a static function):
fn = Function(DefaultVM::GetVM(), RootTable(DefaultVM::GetVM()), fn.GetFunc());
Or the object of the instance that you intend on calling it onto. Not sure if it's the same syntax or even implemented on your version of Squirrel/Sqrat.
I'm not sure why you consider it to be unexpected behaviour that a static class method has the environment of the class object. This allows it to access other static methods and static fields without prepending the Class. part, which makes sense.

.

#3
Quote from: maxorator on Apr 30, 2016, 12:40 PMI'm not sure why you consider it to be unexpected behaviour that a static class method has the environment of the class object. This allows it to access other static methods and static fields without prepending the Class. part, which makes sense.

Yes but the events will fail since they won't be able to use the root table. Reason for that 'trying to set class' error. Because you won't be able to use the root table implicitly.

When people do:
my_global_var <- GUIElement(...);
The VM will try to create that as a property of the class in which the function was created and called with. And will obviously fail. Same goes with assigning various other things from the root table. Because the VM will think you want them from the class. You literally can't assign stuff outside that class.

Examples:



my_global_var <- 32;

function Script::ScriptLoad()
{
    my_global_var = 16;
}

AN ERROR HAS OCCURED [trying to set 'class']



function Script::ScriptLoad()
{
    my_global_var <- 32;
    // Trying to use this variable outside the Script class
    // will fail because it was created as a member property of the Script class
    print(getroottable().my_global_var);
}

AN ERROR HAS OCCURED [the index 'my_global_var' does not exist]



Anyway, for those that want to get around this issue. You can use the following:

my_global_var <- 32;

function Script::ScriptLoad()
{
    ::my_global_var = 16;
}

function Script::ScriptLoad()
{
    ::my_global_var <- 32;
    print(getroottable().my_global_var);
}

You can probably see that adding :: before everything becomes more annoying than adding Script.XYZ before a couple functions. And will tend to break more often for new scripters and even for advanced ones because it's quite easy to miss a ::
.

maxorator

I'm not about to create some weird hybrid global-static methods that don't act the way Squirrel has defined how static methods work. It's about consistency. Since Script.ScriptLoad() calls it with the class being the environment, so will the client.

.

#5
Although why force people into using static class functions and deal with weird behavior. Why not allow them to simply give you a function to call and set the environment they want.

Function event; // somewhere...

void BindEvent(Object & env, Function & func)
{
    event = Function(env.GetVM(), env, func.GetFunc());
}

RootTable(DefaultVM::Get()).Func(_SC("BindEvent"), &BindEvent);

If you truly want an OOP approach:
BindEvent(this, function() {

});

BindEvent(getroottable(), function() {

});

BindEvent(Script, function() {

});

Makes things more flexible and saves you the trouble of locating the functions you need.
.