It is currently April 25th, 2024, 8:57 am

[QUESTION] Rainmeter code

Report bugs with the Rainmeter application and suggest features.
User avatar
Yincognito
Rainmeter Sage
Posts: 7157
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

[QUESTION] Rainmeter code

Post by Yincognito »

I located the place in the Rainmeter code where the current fixed width bevel for meters and skin is drawn (those 8 Canvas.Line), and I was wondering if you'd be willing to add a BevelWidth option since I already have the Shape Path technique ready to be converted from plain Ini to C++ for ages now in a potential PR or something. I just want to know if there is willingness to do it and if two shape paths to be filled can replace the lines there, that's all, since there would be no point in knocking on closed doors again. In case it makes things clearer, the INI technique are the two paths in the background meter from the @Resources\Design.inc file in the suite from my forum avatar's links.

Reason: It's a pain to resort to all kinds of hacks to draw the bevel via shapes when using a dynamic window size, considering that Rainmeter presumably already knows those dimensions and could do it without all that meter order / dynamic variables / hiding the already drawn bevel before drawing it again using two redraws to not influence the skin dimensions due to its own previous dimensions.
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: [QUESTION] Rainmeter code

Post by Brian »

I'm open to the idea, but I have little time to look into it at the moment. Submitting a PR would be a huge time saver for me.

Here are some intial thoughts on this project (maybe you have already thought of it, or accounted for it):
  • Shapes draw half of the their stroke outside of the defined bounds, but this can easily be accounted for.
  • There would have to be some limits to the width in case the skin is really small. Maybe the limits of the width can be 1 to the smallest of the skin's width or height. Or maybe a hard limit?
-Brian
User avatar
Yincognito
Rainmeter Sage
Posts: 7157
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: [QUESTION] Rainmeter code

Post by Yincognito »

Brian wrote: June 7th, 2023, 5:20 pm I'm open to the idea, but I have little time to look into it at the moment. Submitting a PR would be a huge time saver for me.

Here are some intial thoughts on this project (maybe you have already thought of it, or accounted for it):
  • Shapes draw half of the their stroke outside of the defined bounds, but this can easily be accounted for.
  • There would have to be some limits to the width in case the skin is really small. Maybe the limits of the width can be 1 to the smallest of the skin's width or height. Or maybe a hard limit?
-Brian
Thanks for responding and being open to it. Don't worry, there is no rush, I'll take my time as well and see if I can do it right, because right now I only have the math approach, nothing else, as the C++ side is still blank for me ATM (which is why I didn't directly PR):

Code: Select all

[Variables]
SkinWidth=300
SkinHeight=50
SkinGradientAngle=270
SkinSolidColor=40,40,40,160
SkinSolidColor2=0,0,0,160
SkinBevelType=1
SkinBevelWidth=0.5

SkinBevelWidthStep=0.1

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

---Meters---

[Background]
Meter=Shape
Shape=Rectangle 0,0,#SkinWidth#,#SkinHeight# | StrokeWidth 0 | Fill LinearGradient SkinGradient
SkinGradient=#SkinGradientAngle# | #SkinSolidColor# ; 0.0 | #SkinSolidColor2# ; 1.0
Shape2=Path TopLeftBevel | StrokeWidth 0 | Stroke Color (255-((#SkinBevelType#-(#SkinBevelType#%2))/2)*255),(255-((#SkinBevelType#-(#SkinBevelType#%2))/2)*255),(255-((#SkinBevelType#-(#SkinBevelType#%2))/2)*255),255 | Fill Color (255-((#SkinBevelType#-(#SkinBevelType#%2))/2)*255),(255-((#SkinBevelType#-(#SkinBevelType#%2))/2)*255),(255-((#SkinBevelType#-(#SkinBevelType#%2))/2)*255),255
TopLeftBevel=0,0 | LineTo #SkinWidth#,0 | LineTo #SkinWidth#,#SkinBevelWidth# | LineTo #SkinBevelWidth#,#SkinBevelWidth# | LineTo #SkinBevelWidth#,#SkinHeight# | LineTo 0,#SkinHeight# | SetNoStroke 1 | ClosePath 1
Shape3=Path BottomRightBevel | StrokeWidth 0 | Stroke Color (255-(#SkinBevelType#%2)*255),(255-(#SkinBevelType#%2)*255),(255-(#SkinBevelType#%2)*255),255 | Fill Color (255-(#SkinBevelType#%2)*255),(255-(#SkinBevelType#%2)*255),(255-(#SkinBevelType#%2)*255),255
BottomRightBevel=0,#SkinHeight# | LineTo #SkinWidth#,#SkinHeight# | LineTo #SkinWidth#,0 | LineTo (#SkinWidth#-#SkinBevelWidth#),#SkinBevelWidth# | LineTo (#SkinWidth#-#SkinBevelWidth#),(#SkinHeight#-#SkinBevelWidth#) | LineTo #SkinBevelWidth#,(#SkinHeight#-#SkinBevelWidth#) | SetNoStroke 1 | ClosePath 1
UpdateDivider=-1
MouseScrollUpAction=[!SetVariable SkinBevelWidth (#SkinBevelWidth#+#SkinBevelWidthStep#)][!UpdateMeter *][!Redraw]
MouseScrollDownAction=[!SetVariable SkinBevelWidth (#SkinBevelWidth#-#SkinBevelWidthStep#)][!UpdateMeter *][!Redraw]
LeftMouseUpAction=[!SetVariable SkinBevelType ((3+#SkinBevelType#+1)%3)][!UpdateMeter *][!Redraw]
DynamicVariables=1
This is from the time the bevel supported only two colors, hence the easy toggling between stroke and fill colors aka bevel types on click, but the two paths are simple enough and work, as it can be seen on scrolling up and down (though I set no limits on the bevel width).

As for the thoughts on it, since I already use a very soft 0.5 as width, I was thinking of a float width with the minimum at 0.0f aka none, and the maximum at half of the minimum between the skin's width and height (half because the bevel is on both sides, vertically and horizontally). I have no problem with the fact that the strokes extend equally on both sides of the coordinate, I've become accustomed to this logical way of drawing, albeit in practice I must always take care to draw lines at the N.5 mark in skins if I want them to be a single pixel. That being said, above I use no stroke at all, just fill.

Bottom line, my only concern here was being open to the idea, not the time when it is pursued. I have to ask though: do you have a path drawing proxy function in the code (Canvas.cpp, Shapes.cpp, Path.cpp, etc.), or it's this the way to approach it?
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: [QUESTION] Rainmeter code

Post by Brian »

That's a pretty good start despite a few pitfalls with overlapping. Also, setting the stroke color doesn't make sense since no stroke is used.

Overlapping issue with different non-solid colors:
Rainmeter_dHjkWXj43y.png
Lining up those corner angles might be tricky.


Internally speaking...while using a shape path geometry makes a lot of sense, it might be a little overkill given all the extra parsing. I would have to check on the performance of it before going that route. There might be better performance with drawing it line by line in a for loop of some kind.

-Brian
You do not have the required permissions to view the files attached to this post.
User avatar
Yincognito
Rainmeter Sage
Posts: 7157
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: [QUESTION] Rainmeter code

Post by Yincognito »

Brian wrote: June 8th, 2023, 5:40 am That's a pretty good start despite a few pitfalls with overlapping. Also, setting the stroke color doesn't make sense since no stroke is used.

Overlapping issue with different non-solid colors:Rainmeter_dHjkWXj43y.png

Lining up those corner angles might be tricky.


Internally speaking...while using a shape path geometry makes a lot of sense, it might be a little overkill given all the extra parsing. I would have to check on the performance of it before going that route. There might be better performance with drawing it line by line in a for loop of some kind.

-Brian
Ouch, good catch, thanks! Didn't try non solid colors, so I missed it. As for using lines in a for, this helps me since the base is already there , and I should be able to manage the math easily. Will come back with an approach for your inspection later on, before translating it into C++. ;-)
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
Yincognito
Rainmeter Sage
Posts: 7157
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: [QUESTION] Rainmeter code

Post by Yincognito »

Brian wrote: June 8th, 2023, 5:40 am That's a pretty good start despite a few pitfalls with overlapping. Also, setting the stroke color doesn't make sense since no stroke is used.

Overlapping issue with different non-solid colors:Rainmeter_dHjkWXj43y.png

Lining up those corner angles might be tricky.


Internally speaking...while using a shape path geometry makes a lot of sense, it might be a little overkill given all the extra parsing. I would have to check on the performance of it before going that route. There might be better performance with drawing it line by line in a for loop of some kind.

-Brian
For the record, the current bevel implementation in Rainmeter has overlapping issues as well, not just my earlier skin code. You can see that if you set similar translucent colors like above as bevel colors, capture a screenshot and zoom in on it in a Photoshop like program. Sure, the overlapping is limited to just 10 pixels (4 in the top left corner and 2 in each of the other corners), but it exists. No need to fix the issue yet though, the variable bevel width implementation might be able to solve it, once it's final.

So far, I've installed Visual Studio and I am progressing with the variable bevel width in C++ using a for loop (everything is fine for integer widths, with or without antialiasing). After lots of trial and error to handle gaps or overlapping at segment joints (even for the triangle start and end cap + hard stop transversal gradient method), I've also found a way to do it via shapes in skin code, like this (I even added the +#AntiAlias#*0.5 parts to the formulas, even though they weren't needed, just to replicate the C++ code better):

Code: Select all

[Variables]
X=150
Y=150
W=300
H=300
AntiAlias=0
GradientAngle=270
SolidColor=0,0,0,0
SolidColor2=0,0,0,0
BevelColor=255,255,255,255
BevelColor2=0,0,0,255
BevelType=1
BevelWidth=40
BevelWidthStep=0.1

[Rainmeter]
Update=1000
AccurateText=1
DynamicWindowSize=1
SkinWidth=600
SkinHeight=600
SolidColor=255,0,0,255
BackgroundMode=2

---Meters---

[Background]
Meter=Shape
Shape=Rectangle (#X#),(#Y#),(#W#),(#H#) | StrokeWidth 0 | Fill LinearGradient SkinGradient
SkinGradient=#GradientAngle# | #SolidColor# ; 0.0 | #SolidColor2# ; 1.0

Shape2=Path TRPath | Stroke LinearGradient TRGrad | StrokeWidth (#BevelWidth#*Sgn(#BevelType#))
TRPath=(#X#+#BevelWidth#/2+#AntiAlias#*0.5),(#Y#+#BevelWidth#/2+#AntiAlias#*0.5) | LineTo (#X#+#W#-#BevelWidth#/2+#AntiAlias#*0.5),(#Y#+#BevelWidth#/2+#AntiAlias#*0.5) | LineTo (#X#+#W#-#BevelWidth#/2+#AntiAlias#*0.5),(#Y#+#H#-#BevelWidth#/2+#AntiAlias#*0.5)
TRGrad=(#BevelType#*180+45) | #BevelColor# ; 0.0 | #BevelColor# ; 0.5 | #BevelColor2# ; 0.5 | #BevelColor2# ; 1.0
Shape3=Path BLPath | Stroke LinearGradient BLGrad | StrokeWidth (#BevelWidth#*Sgn(#BevelType#))
BLPath=(#X#+#W#-#BevelWidth#/2+#AntiAlias#*0.5),(#Y#+#H#-#BevelWidth#/2+#AntiAlias#*0.5) | LineTo (#X#+#BevelWidth#/2+#AntiAlias#*0.5),(#Y#+#H#-#BevelWidth#/2+#AntiAlias#*0.5) | LineTo (#X#+#BevelWidth#/2+#AntiAlias#*0.5),(#Y#+#BevelWidth#/2+#AntiAlias#*0.5)
BLGrad=(#BevelType#*180+45) | #BevelColor# ; 0.0 | #BevelColor# ; 0.5 | #BevelColor2# ; 0.5 | #BevelColor2# ; 1.0

Shape4=Combine Shape2 | Union Shape3

AntiAlias=#AntiAlias#
UpdateDivider=-1
MouseScrollUpAction=[!SetVariable BevelWidth (#BevelWidth#+#BevelWidthStep#)][!UpdateMeter *][!Redraw]
MouseScrollDownAction=[!SetVariable BevelWidth (#BevelWidth#-#BevelWidthStep#)][!UpdateMeter *][!Redraw]
LeftMouseUpAction=[!SetVariable BevelType ((3+#BevelType#+1)%3)][!UpdateMeter *][!Redraw]
MiddleMouseUpAction=[!SetVariable AntiAlias (1-#AntiAlias#)][!UpdateMeter *][!Redraw]
DynamicVariables=1

[Information]
Meter=String
FontColor=255,255,255,255
FontFace=Consolas
FontSize=12
AntiAlias=1
Text=Bevel Width = #BevelWidth##CRLF#Bevel Type  = #BevelType##CRLF#AntiAlias   = #AntiAlias#
DynamicVariables=1
One thing I'm having issues with is drawing subunitar width lines in C++. The Canvas.DrawLine seems to accept float values, so why then in Meter.DrawBevel drawing subunitar width lines via, say, canvas.DrawLine(light, 150.0f, 150.0f, 450.0f, 150.0f, 0.7f);, isn't possible? Is it because of the brush? I mean, I could fake subunitar lines by making unitar width lines more faded using the alpha of the drawing color, but I'd prefer to avoid that route as it requires some alpha blending formulas in order to match the perfect results in the skin above (simple proportionality obviously isn't exactly what's needed)... :???:

Other than this little detail, I think I can handle this eventually, so hopefully no work or time lost should be needed from your side. ;-)
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: [QUESTION] Rainmeter code

Post by Brian »

It's kind of a long story, but the reason is because when Rainmeter was first rendered with GDI+, which uses device-dependent coordinates...aka, pixels. When we switched to D2D, it can render things using device-independent coordinates, meaning it can render at floating point intervals.

So...to make things easier for backward-compatibility, we restrict Rainmeter coordinates (except the shape meter), to integers (or whole pixels).

That doesn't mean we have keep this restriction...it just means we have to take care to make sure older skins work as their were originally designed to preserve backwards compatibility.

-Brian

EDIT: Looks like I missed something in your previous post. Using a floating point stroke width is possible with D2D according to the docs on DrawLine. I would probably have to test things out to see what you are describing.

Also, (you probably already knew this), but the stroke is "centered" on the line...so the width of the drawn line is divided by half on either side of the line...or in your case of a 0.7 width, it would draw 0.35 of a width on both sides of the line....which might effect which "color" is chosen during rendering.
User avatar
Yincognito
Rainmeter Sage
Posts: 7157
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: [QUESTION] Rainmeter code

Post by Yincognito »

Brian wrote: June 12th, 2023, 8:59 pm It's kind of a long story, but the reason is because when Rainmeter was first rendered with GDI+, which uses device-dependent coordinates...aka, pixels. When we switched to D2D, it can render things using device-independent coordinates, meaning it can render at floating point intervals.

So...to make things easier for backward-compatibility, we restrict Rainmeter coordinates (except the shape meter), to integers (or whole pixels).

That doesn't mean we have keep this restriction...it just means we have to take care to make sure older skins work as their were originally designed to preserve backwards compatibility.

-Brian

EDIT: Looks like I missed something in your previous post. Using a floating point stroke width is possible with D2D according to the docs on DrawLine. I would probably have to test things out to see what you are describing.

Also, (you probably already knew this), but the stroke is "centered" on the line...so the width of the drawn line is divided by half on either side of the line...or in your case of a 0.7 width, it would draw 0.35 of a width on both sides of the line....which might effect which "color" is chosen during rendering.
Yep, I only want to draw the fractional leftover of the bevel either inside (for skins) or outside (for meters) of the rectangle that defines the coordinates - I don't intent to alter in any way the said integer restrictions on element coordinates. So, if a 10 x 9 pixels skin has a 2.7 bevel width, it would look like this, with the fractional line segments inside:

Code: Select all

1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 0.7 0.7 0.7 0.7 0.7 0.7 1.0 1.0
1.0 1.0 0.7                 0.7 1.0 1.0
1.0 1.0 0.7                 0.7 1.0 1.0
1.0 1.0 0.7                 0.7 1.0 1.0
1.0 1.0 0.7 0.7 0.7 0.7 0.7 0.7 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
and for a meter of 4 x 3 pixels, the other way around, with the 0.7 line parts on the outside. Obviously, for a 0.7 bevel width, only the fractional part will be drawn, but still based on an integer "anchor" where the for loop "starts", so to speak (bar the antialiasing aka the offsetMode variable, of course).

Yes, I'm perfectly fine with the stroke being centered on the line, it makes no difference in the implementation, and it will be accounted for (if only I could draw those fractional width lines, to begin with, lol). For example, the for loop will have a step of 1 for the integer part of the bevel width, and the last line (which will be outside the loop for simplicity) will be drawn at 0.5 + Frac(BevelWidth) / 2 from the last line in the integer set, so that it's adjacent to it. I simply lack the platform to continue testing and progressing with the whole thing at this stage because of the inability to draw those fractional width lines, hence my question. :confused:

EDIT: By the way, while a canvas.DrawLine(light, 150.0f, 150.0f, 450.0f, 150.0f, 0.7f); will not seem to draw anything, a canvas.DrawLine(light, 150.5f, 150.5f, 450.5f, 150.5f, 0.7f); will, but it will still be the same as a 1.0f width line, which shouldn't happen. Anyway, just this single line is needed to test out what I experienced, temporarily commenting out the content of the function for better visualizing should be enough to see the behavior.
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
Yincognito
Rainmeter Sage
Posts: 7157
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: [QUESTION] Rainmeter code

Post by Yincognito »

Brian wrote: June 7th, 2023, 5:20 pmI'm open to the idea [...]
So, I have been in no rush and occasionally kept playing around with possibilities when it came to using a for loop to draw the lines of the bevel. It's a bit tricky when it comes to antialiasing (only this allows fractional bevel widths to look faded as they should at their edges, albeit it's disabled for skins and requires either different line widths or the 0.5 offset added to their coordinates to make the bevel interior look solid for meters), but that doesn't mean it's not doable (it's unfortunate that one has to write two pages of code in C++ D2D just to draw a shape path, making the line loop preferable).

I was thinking that maybe I looked at this from a narrow perspective when focusing just on the bevel thing. I mean, if one wants to have a differently styled border, the hypothetical bevel width changes won't help much, since it only tackles the straight bevel scenario. So, I was thinking of some alternatives:

1) allowing BackgroundMode=3 / BackgroundMargins to downscale the image too, because currently, only upscaling is possible, not sure if it's an intentional omission or not (even though less flexible than a shape like background, using a scaled image with unscaled margins would be a reasonable alternative, assuming one would be able to preserve some visual details by downscaling a more detailed but larger original image)
2) have an OnBeforeRedraw like option in the [Rainmeter] section, that could allow, for example, updating the size of a background shape meter to the current values of the config width and height before proceeding with the redraw operation (not sure how this will play with not having dynamic variables in the [Rainmeter] section, or getting the size of the skin excluding the old values of the said background meter, but anyway)
3) have some BackgroundMode=5 / BackgroundShape=... options in the [Rainmeter] section, that could allow a shape background based on the internally available m_WindowW / m_WindowH variables that represent the skin size (BackgroundShape could work like BackgroundMargins, by using values relative to the left, top, right and bottom of the skin window)

What do you think about these, any of them feasible or preferable to the bevel width adjustment scenario (no need to work on anything, just your opinion)? I'm guessing 1) or 3) would be relatively doable, but I'm not sure of other implications, if any. Also, as a side note, looking at the Rainmeter code, I see that internally there is a list of measures and meters available after parsing the skin, so I wonder why those things aren't available to Lua as tables - just saying.
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
Brian
Developer
Posts: 2681
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: [QUESTION] Rainmeter code

Post by Brian »

Yincognito wrote: June 29th, 2023, 5:19 pm 1) allowing BackgroundMode=3 / BackgroundMargins to downscale the image too, because currently, only upscaling is possible, not sure if it's an intentional omission or not (even though less flexible than a shape like background, using a scaled image with unscaled margins would be a reasonable alternative, assuming one would be able to preserve some visual details by downscaling a more detailed but larger original image)
I think the original idea of BackgroundMode=3 / BackgroundMargins was to only upscale portions of an image. I am not sure downscaling was thought of or possible when it was written. I think if it can be done without too much difficulty, then go for it.

Yincognito wrote: June 29th, 2023, 5:19 pm 2) have an OnBeforeRedraw like option in the [Rainmeter] section, that could allow, for example, updating the size of a background shape meter to the current values of the config width and height before proceeding with the redraw operation (not sure how this will play with not having dynamic variables in the [Rainmeter] section, or getting the size of the skin excluding the old values of the said background meter, but anyway)
I am not sure what you mean by "OnBeforeRedraw". Like an action option to be used with bangs just before redrawing? DynamicVariables might not be an issue if using nested syntax, but regular #variables# would be. I guess I would have to see a use case for this before going further.

Yincognito wrote: June 29th, 2023, 5:19 pm 3) have some BackgroundMode=5 / BackgroundShape=... options in the [Rainmeter] section, that could allow a shape background based on the internally available m_WindowW / m_WindowH variables that represent the skin size (BackgroundShape could work like BackgroundMargins, by using values relative to the left, top, right and bottom of the skin window)
This idea makes some sense, but I wonder about how to define the Shape(s). Some shapes have defined dimensions (like Rectangle), but some use a center point. Some shapes might not make sense (like Arc, Curve). So, the shape definition might be quite a bit different than a ordinary Shape meter. Internally we would have to account for the stroke width, and adjust it manually to fit inside the skin (so half of it doesn't get drawn outside of the window bounds) - this is not really a problem, just a thought. Probably couldn't have "combined" shapes either. This option might be a lot of extra work and special syntax that could be done more consistently via a meter.

Yincognito wrote: June 29th, 2023, 5:19 pm Also, as a side note, looking at the Rainmeter code, I see that internally there is a list of measures and meters available after parsing the skin, so I wonder why those things aren't available to Lua as tables - just saying.
No idea, that was written before I came along. Not sure there is a huge use case though....at least that I can think of. It's not like you are going to iterate through every measure or meter very often. But then again, I get amazed all the time at what people can think of.

-Brian