It is currently March 28th, 2024, 10:50 pm

Center & Right Aligned Text in a Variable-Width Skin

Tips and Tricks from the Rainmeter Community
User avatar
Kaelri
Developer
Posts: 1721
Joined: July 25th, 2009, 4:47 am

Center & Right Aligned Text in a Variable-Width Skin

Post by Kaelri »

When you know the width of your skin, it's easy to align a string meter to the left, center, or right edge of the skin. All you have to do is set the meter's X coordinate to wherever the text is anchored. For example, if your skin is 200 pixels wide:

Code: Select all

[LeftText]
Meter=String
StringAlign=Left
X=0

[CenteredText]
Meter=String
StringAlign=Center
X=100

[RightText]
Meter=String
StringAlign=Right
X=200
But what if you don't know the width?

1) One Meter

Let's say you want your skin's width to be adaptive - to get wider when the text is longer, and thinner when the text is shorter. This makes it hard to align the text center or right, because the width is unknown, and therefore so is the X.

The solution? Use Lua to get the width of the text meter, then just move the meter by the same amount:

Code: Select all

function RightStringMeter()

	local StringMeter = SKIN:GetMeter('RightText')
	local W = StringMeter:GetW()
	local Y = StringMeter:GetY()

	local X = W

	SKIN:Bang('!MoveMeter', X, Y, 'CenterText')

end
Centering the meter is almost exactly the same. To get the new X, just divide the width in half:

X = W/2

You may also not want the text area to be pressed right up against the left side of the skin.
If you want to add a little margin to the skin - say, 10 pixels - just add it to the new X.

X = W + 10

Finally, in the interest of performance, you may want to save the meter's width as a variable, and only update the X when the width has changed:

Code: Select all

function Initialize()
	SavedW = nil
end

function RightStringMeter()

	local StringMeter = SKIN:GetMeter('RightText')
	local W = StringMeter:GetW()
	local Y = StringMeter:GetY()

	if W ~= SavedW then
		local X = W
		SKIN:Bang('!MoveMeter', X, Y, 'CenterText')
		SavedW = W
	end

end
Now, that works great if all you have is one meter. But what if you have more than one?

2) Multiple Meters

Let's say you have a whole column of string meters, all supposed to be aligned to the right:

Code: Select all

[RightText1]
Meter=String
StringAlign=Right
Text=This string is pretty long!

[RightText2]
Meter=String
StringAlign=Right
Text=This one is shorter!

[RightText3]
Meter=String
StringAlign=Right
Text=But this one is even longer than the other two!
What you need to do is figure out which one is the longest, and then set all three X values accordingly. So how can you do this? With tables, of course:

Code: Select all

function Initialize()
	SavedMaxW = nil
end

function RightStringMeters()
	StringMeters = {
		SKIN:GetMeter ('RightText1'),
		SKIN:GetMeter ('RightText2'),
		SKIN:GetMeter ('RightText3')
	}
	
	local W = {}
	local Y = {}
	for i,v in ipairs(StringMeters) do
		W[i] = v:GetW()
		Y[i] = v:GetY()
	end

	local MaxW = 0

	for i,v in ipairs(W) do
		if v > MaxWidth then MaxWidth = v end
	end

	if MaxW ~= SavedMaxW then
		SKIN:Bang('!MoveMeter', MaxW, Y[1], 'RightText1')
		SKIN:Bang('!MoveMeter', MaxW, Y[2], 'RightText2')
		SKIN:Bang('!MoveMeter', MaxW, Y[3], 'RightText3')
		SavedMaxW = MaxW
	end
end
The key part is this:

Code: Select all

	for i,v in ipairs(W) do
		if v > MaxWidth then MaxWidth = v end
	end
What happens here is that the script sets a variable for the "maximum width," starting at zero. Then it runs through the whole table of meter widths, and for each meter, it checks whether the width is greater than the max width. If so, it updates the max width to that value; if not, it does nothing. So by the time it gets to the end, the max width will be the same as the widest meter in the table.

3) Dynamic Variable

If you have other meters or measures that depend on knowing the width of the text meters, you may find it easier to just set the number as a variable in the skin. The only downside is that it will require dynamic variables to use, which may slow things down if you have a very large skin.

Code: Select all

function Initialize()
	SavedMaxW = nil
end

function RightStringMeters()
	local StringMeters = {
		SKIN:GetMeter ('RightText1'),
		SKIN:GetMeter ('RightText2'),
		SKIN:GetMeter ('RightText3')
	}
	
	local W = {}
	local Y = {}
	for i,v in ipairs(StringMeters) do
		W[i] = v:GetW()
		Y[i] = v:GetY()
	end

	local MaxW = 0

	for i,v in ipairs(W) do
		if v > MaxWidth then MaxWidth = v end
	end

	if MaxW ~= SavedMaxW then
		SKIN:Bang('!SetVariable', 'MaxW', MaxW)
		SavedMaxW = MaxW
	end
end
One last note: using this method requires your skin to have a dynamic window size.

Code: Select all

[Rainmeter]
DynamicWindowSize=1
Lightz39
Posts: 98
Joined: August 1st, 2012, 12:48 am

Re: Center & Right Aligned Text in a Variable-Width Skin

Post by Lightz39 »

Excellent, was looking for this.
Eastwood
Posts: 35
Joined: November 1st, 2012, 10:50 pm

Re: Center & Right Aligned Text in a Variable-Width Skin

Post by Eastwood »

#EDIT: I realize posting this in the tips and tricks section instead of the help section

Thanks, this was useful information as i am a freshman in Rainmeter!

Although this is close to what i try to achieve, i still can not find a way to implement a solution for my problem.

What i am struggling with:

I would like to get the value of WindowX variable from the Rainmeter.ini file to know the position where my skin is currently dragged at.

In the code of myskin.ini i have placed the following:

Code: Select all

 @includesettings=#SETTINGSPATH#rainmeter.ini
In the code of the rainmeter ini file:

Code: Select all

[Eastwood\myskin]
 Active=1
 anchorx=185
 anchory=0
 WindowX=1223
 WindowY=120
 ...
I have already checked that WindowX and WindowY are automatically updated, but i can not call them in my current skin to use these values.
Perhaps this is due to the "\" in the construction of the called setion variable ?

I tried working out a functional Lua script, but keep failing in reaching the Rainmeter.ini file.
User avatar
jsmorley
Developer
Posts: 22628
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Center & Right Aligned Text in a Variable-Width Skin

Post by jsmorley »

First, you can't "@Include" the Rainmeter.ini file and use the values from it. The include functionality loads a file containing "skin code" into a running skin, so you can include sections like [Variables], or [MeterName] / [MeasureName] and Rainmeter will treat them as if they are just part of the skin. The stuff in Rainmeter.ini is not "skin code" and will have no effect or use in a skin.

Second, you can get the current screen position of a skin by using http://docs.rainmeter.net/manual/variables/built-in-variables#CURRENTCONFIGXYWH. That is fully dynamic and will always represent the current position of the skin.
Eastwood
Posts: 35
Joined: November 1st, 2012, 10:50 pm

Re: Center & Right Aligned Text in a Variable-Width Skin

Post by Eastwood »

Thanks for this answer!
Eliminates allot of frustration over here.

I must have overlooked this variable in all confusion.

Skin is working perfect now!
Harrix
Posts: 1
Joined: January 12th, 2019, 5:54 pm

Re: Center & Right Aligned Text in a Variable-Width Skin

Post by Harrix »

The author has a bug in the code (Max Width and Max W are mixed up). And I did not immediately understand how to connect the script to make it work when changing the content of the skins.

Here is a working example.

TestRightAlign.ini:

Code: Select all

[Rainmeter]
Update = 1000
AccurateText  =  1
DynamicWindowSize  =  1

[LuaScript]
Measure = Script
ScriptFile = #CurrentPath#Script.lua

[RightText1]
Meter = String
StringAlign = Right
Text = This string is pretty long!
Y = R

[RightText2]
Meter = String
StringAlign = Right
Text=This one is shorter!
Y = R

[RightText3]
Meter = String
StringAlign = Right
Text = But this one is even longer than the other two!
Y = R
Script.lua:

Code: Select all

function Initialize()
	SavedMaxW = nil
end

function Update()
    RightStringMeters()
end

function RightStringMeters()
	StringMeters = {
		SKIN:GetMeter ('RightText1'),
		SKIN:GetMeter ('RightText2'),
		SKIN:GetMeter ('RightText3')
	}

	local W = {}
	local Y = {}
	for i,v in ipairs(StringMeters) do
		W[i] = v:GetW()
		Y[i] = v:GetY()
	end

	local MaxW = 0

	for i,v in ipairs(W) do
		if v > MaxW then MaxW = v end
    end

	if MaxW ~= SavedMaxW then
		SKIN:Bang('!MoveMeter', MaxW, Y[1], 'RightText1')
		SKIN:Bang('!MoveMeter', MaxW, Y[2], 'RightText2')
		SKIN:Bang('!MoveMeter', MaxW, Y[3], 'RightText3')
		SavedMaxW = MaxW
	end
end
Both files in this example are in the same skin folder.