ebiten
ebiten copied to clipboard
text: a utility function to render texts with a left-upper position
How to render texts with a left-upper position is a very frequently asked question. Having a utility function to do so would be a great feature. Let's think about APIs first.
See also: https://github.com/sedyh/ebiten-cheatsheet#center-text (thank you, @sedyh!)
Sure thing. Do you have any ideas about how you'd like to implement this? I'm not really sure how else to go about it without introducing more to the public interface. Except maybe adding a parameter to to the 'Draw' or 'DrawWithOptions' function but that would introduce incompatibility.
Hi,
I think introducing a new function(s) would be inevitable. Based on your PR #2150:
- For example, how would
x
andj
be rendered? Aren't their baselines different? - Should we consider vertical aligns at the same time?
- Specifying
image.Rectangle
to calculate alignments would be preferable. - Can we have an enum instead of having multiple functions?
For example, how would
x
andj
be rendered? Aren't their baselines different?
I don't fully understand what you mean here. Sorry. Is j
a typo for y
?
Should we consider vertical aligns at the same time?
I don't think that is a bad idea. To clarify you are saying something along the lines of having a TopAlignLeft
, TopCenter
, TopAlignRight
, CenterAlignLeft
, Centered
, CenterAlignRight
, etc... Is that correct?
Specifying image.Rectangle to calculate alignments would be preferable.
I'm still really fresh to ebitengine
, I thought text.BoundString() was taking the rectangle of the given text with the font options is that not the same thing?
Can we have an enum instead of having multiple functions?
I do like this idea better then what was in the pull request. Are you thinking something along the lines of what is below?
type TextAlignment int
const (
ToptAlignLeft TextAlignment = iota
TopAlignCentered
TopAlignRight
CenterAlignLeft
Centered
CenterAlignRight
BottomAlignLeft
BottomAlignCentered
BottomAlignRight
)
func DrawAligned(dst *ebiten.Image, text string, face font.Face, xSkew int, ySkew int, clr color.Color, align TextAlignment) {
// ... Do alignment stuff with text.
}
The xSkew
and ySkew
would be an added offset of where the default destination of the alignment would be allowing for a little more flexibility of the placement of the text. That is something optional and if we want to keep it as simple as possible you may decide against them.
Personally, I like it but anyone newer coming into the API might would be confused. Let me know what you think.
Also if you can elaborate on what you mean with the image.Rectangle
I can see what I can do there. I am very new to ebitengine
, extremely new to game dev and intermediate with Go dev on my best days. So your patience and explanations are appreciated I am here to learn as much as can as well as contribute what I can.
I don't fully understand what you mean here. Sorry. Is j a typo for y?
'y' is also fine. I meant some glyphs that have ascendent and/or descendent.
I'm still really fresh to ebitengine, I thought text.BoundString() was taking the rectangle of the given text with the font options is that not the same thing?
I meant a rectangle as a destination. In your suggestion, for example, the right edge would be the same as the right edge of the destination image. This might be useless when a use don't want to align to the image's edge.
Or, instead of a destination rectangle, a destination point would work and this would be enough. In this case, the text would be rendered based on the specified point. I'll draw a figure later.
IMO, I prefer specifying a point to a rectangle so far. This might be a little tricky but concise.
I do like this idea better then what was in the pull request. Are you thinking something along the lines of what is below?
Yes, the direction seems fine. I think we need more refine. I'll put my suggestion later (maybe tomorrow. Thank you for your patience).
The xSkew and ySkew would be an added offset ...
If we specify a destination rectangle or a destination point, we don't need them, right?
Also if you can elaborate on what you mean with the image.Rectangle I can see what I can do there. I am very new to ebitengine, extremely new to game dev and intermediate with Go dev on my best days. So your patience and explanations are appreciated I am here to learn as much as can as well as contribute what I can.
Thank you for trying to contribute to Ebitengine! I appreciate your PR!
I meant a rectangle as a destination. In your suggestion, for example, the right edge would be the same as the right edge of the destination image. This might be useless when a use don't want to align to the image's edge.
Or, instead of a destination rectangle, a destination point would work and this would be enough. In this case, the text would be rendered based on the specified point. I'll draw a figure later.
I think I understand. I am placing the text based off the dot-position. So if your dst
image is the logical screen and you specified Centered
as your placement, assuming .
is your string, the .
should appear in the center of the screen.
Which seems to coincide with the documentation of the text.Draw()
function.
To further that explanation lets say pass a dst
image other then screen, for example an image created with *ebiten.NewImage(16, 16)
, either way it is an *ebiten.Image
you would specify the location of the the image and then pass that image to text.DrawAligned()
. Assuming you used Centered
in your text.DrawAligned()
function it would place the text passed into the center of the dst image or in this case (8, 8) of the *ebiten.NewImage(16, 16)
.
I hope all that makes sense...
The xSkew
and ySkew
are just added offsets so if you wanted to place text at say a 3rd of the screen height you could pass -screenHeight/3
as the ySkew
and it would move it up the center of the screen. Again this assumes Centered
is the option used. But it would work the same with the other enum options.
It still be better to leave the skews out though since the same thing can be accomplished with little math from text.Draw()
but I think of it as a convenience feature.
I think I understand. I am placing the text based off the dot-position. So if your dst image is the logical screen and you specified Centered as your placement, assuming . is your string, the . should appear in the center of the screen.
I'm not talking about '.' position here. The specified point (or the rectangle's left-upper position) would be used to locate the text that left-upper position is the same as the specified point, for example.
In this case, xSkew
and ySkew
are not needed as the specified position can be changed.
Does this make sense?
With a specified point, the text can be placed like above (left-upper, center-middle, right-lower). There are 9 possibilities.
Should the specified point come from the 'dst' image passed then?
In your left-upper and right-lower examples though, the left-upper example places the text on the right side of the screen and the right-lower example places text to the left side of the screen. That seems a little counter intuitive doesn't it?
I would think aligning text would work like in most text editors like Word.
If you are designing something with multiple lines of centered or left aligned text how do you propose you move it up or down the 'y' axis of the screen? Would that just be broken up through new lines. I can give an example if needed.
Either way I can make it happen in the PR.
Should the specified point come from the 'dst' image passed then?
Sorry but I don't understand this question. My intention is pass a position separately from the destination image.
In your left-upper and right-lower examples though, the left-upper example places the text on the right side of the screen and the right-lower example places text to the left side of the screen. That seems a little counter intuitive doesn't it?
Well, this might be a little counterintuitive. So specifying a rectangle instead of a point might be intuitive, but my concern of passing a rectangle is redundancy.
If you are designing something with multiple lines of centered or left aligned text how do you propose you move it up or down the 'y' axis of the screen? Would that just be broken up through new lines. I can give an example if needed.
Good question. My intention was to center the entire text with multiple lines, if we specify centering.
Sorry but I don't understand this question. My intention is pass a position separately from the destination image.
No worries that answered my question. We should pass the position as a parameter to the function then.
Well, this might be a little counterintuitive. So specifying a rectangle instead of a point might be intuitive, but my concern of passing a rectangle is redundancy.
I agree I feel like creating a separate rectangle object is a little overkill when all we want to do is put some text on the screen or within an empty image or something.
Let me draw up so code and display some examples and we can see which way seems the simplest and most intuitive. I'll post some screen shots here along with the code and see what you like best. I appreciate the patience with this and my probably not so great questions :).
I drew up some code with some examples of the skew. I'll go ahead and post that here. I'll work on something to show based on a specified point as you mentioned. Not entirely sure how to implement that just yet.
Note: The original PR was closed on accident... I'll open another once we decide the design.
Below is an example using the text.DrawAligned()
func (g *Game) Draw(screen *ebiten.Image) {
text.DrawAligned(screen, "Left-Upper", mplusNormalFont, 0, 0, color.White, text.TopAlignLeft)
text.DrawAligned(screen, "Left-Center", mplusNormalFont, 0, 0, color.White, text.CenterAlignLeft)
text.DrawAligned(screen, "Left-Bottom", mplusNormalFont, 0, 0, color.White, text.BottomAlignLeft)
text.DrawAligned(screen, "Left-Upper x, y skew", mplusNormalFont, 20, 30, color.White, text.TopAlignLeft)
text.DrawAligned(screen, "Right-Upper", mplusNormalFont, 0, 0, color.White, text.TopAlignRight)
text.DrawAligned(screen, "Right-Center", mplusNormalFont, 0, 0, color.White, text.CenterAlignRight)
text.DrawAligned(screen, "Right-Bottom", mplusNormalFont, 0, 0, color.White, text.BottomAlignRight)
text.DrawAligned(screen, "Right-Bottom x, y skew", mplusNormalFont, -20, -30, color.White, text.BottomAlignRight)
text.DrawAligned(screen, "Center-Top", mplusNormalFont, 0, 0, color.White, text.TopAlignCentered)
text.DrawAligned(screen, "Centered", mplusNormalFont, 0, 0, color.White, text.Centered)
text.DrawAligned(screen, "Center-Bottom", mplusNormalFont, 0, 0, color.White, text.BottomAlignCentered)
text.DrawAligned(screen, "Centered with ySkew 1", mplusNormalFont, 0, -screenHeight/3, color.White, text.Centered)
text.DrawAligned(screen, "Centered with ySkew 2", mplusNormalFont, 0, screenHeight/3, color.White, text.Centered)
}
This is the resulting output.
I think this method makes it really simple to through together a very basic main menu prototype or even a simple HUD prototype with minimal code and well... math (nobody likes math right? lol). Another example.
func (g *Game) Draw(screen *ebiten.Image) {
text.DrawAligned(screen, "Welcome to Game", mplusNormalFont, 0, -screenHeight/3, color.White, text.Centered)
text.DrawAligned(screen, "Press (P) to Play", mplusNormalFont, 0, -screenHeight/4, color.White, text.Centered)
text.DrawAligned(screen, "Press (Q) to Quit", mplusNormalFont, 0, -screenHeight/5, color.White, text.Centered)
text.DrawAligned(screen, "Health: 100", mplusNormalFont, 20, 10, color.White, text.TopAlignLeft)
text.DrawAligned(screen, "Stamina: 75", mplusNormalFont, 0, 10, color.White, text.TopAlignCentered)
text.DrawAligned(screen, "Gold: 1000", mplusNormalFont, -20, 10, color.White, text.TopAlignRight)
}
Thanks, but the position is still based on the destination image's rectangle, right? What I suggested is to specify a point (or a rectangle) to adjust the text position. I don't want to use the destination image's rectangle as this is a little hard to use.
For example, the left-bottom goes to the bottom of the target image. It is possible to adjust the position by the skews, but I'm not sure this is the correct direction.
Thanks, but the position is still based on the destination image's rectangle, right?
That is correct.
I believe what you are describing can accomplish this already using text.Draw()
correct?.
Are you basing the point off a location of the text's rectangle or a point in the dst
image? From your screenshot above it seems like you are basing off the text's location.
Maybe we can discuss in discord further for quicker clarification on my end? Unless you want to keep design decisions here of course either way is fine with me.
I believe what you are describing can accomplish this already using text.Draw() correct?.
A little different. text.Draw uses the specified point as the dot position. My suggestion is to use this as the left-upper position of the text for example.
Are you basing the point off a location of the text's rectangle or a point in the dst image? From your screenshot above it seems like you are basing off the text's location.
A point in the dst image. The text will be located based on the position. Does this make sense?
Maybe we can discuss in discord further for quicker clarification on my end? Unless you want to keep design decisions here of course either way is fine with me.
Sure but I'm gonna bed soon. Time zone is hard. I'll leave my idea of the APIs later.
Yesterday I was thinking about how to match with the existing APIs. The existing APIs are
-
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color)
-
func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *ebiten.DrawImageOptions)
So, introducing a new function for both a non-option version and an option version would be painful...
Would it make sense to add a new function to adjust the position like this?
-
AdjustPosition(text string, face font.Face, alignment Alignment, basePointX, basePointY int) (int, int)
Then a user can use like this:
x, y := AdjustPosition(str, face, text.UpperLeft, 100, 100)
text.Draw(screen, str, face, x, y, color.White)
or
x, y := AdjustPosition(str, face, text.UpperLeft, 100, 100)
op := ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(x), float64(y))
text.DrawWithOptions(screen, str, face, op)
The returned x
, y
value is some point on the text's rectangle correct? In the case of UpperLeft
the top left corner. I don't understand the need for the base point parameters unless they are just an added offset to the point on the text's rectangle..
I think we may ultimately be trying to accomplish two different things when it comes to the text alignment.
From your previous examples though UpperLeft
would place a point on the top left corner of the text and then you would place the text depending on that point. Is that right?
The returned x, y value is some point on the text's rectangle correct?
No, these are a new 'dot' position in the destination image rectangle that can be passed to Draw
.
I don't understand the need for the base point parameters unless they are just an added offset to the point on the text's rectangle..
The intention is that the user can render a text at (100, 100), that matches the text's upper-left position.
From your previous examples though UpperLeft would place a point on the top left corner of the text and then you would place the text depending on that point. Is that right?
I think it's a little different: AdjustPosition just returns a new dot position rather than a text position. Sorry this might be a little tricky.
That all makes sense. Let me see if I can get something drawn up. I'll post some examples as soon as I can.
So, if the font's ascender [1] is 10 pixel and y offset of 'dot' is 1, AdjustPosition(str, face, text.UpperLeft, 100, 100)
will return 110 and 101.
AdjustDotPosition
might be better than AdjustPosition
by the way.
I'll post some examples as soon as I can.
Thank you!
[1] https://en.wikipedia.org/wiki/Ascender_(typography)
I think I MAY have something worked out... I still don't feel like I did it entirely right because I am not exactly sure how I can obtain the ascender. I did make something that I believe emulates what you describes.
func (g *Game) Draw(screen *ebiten.Image) {
halfHeight := screenHeight / 2
halfWidth := screenWidth / 2
dx, dy := text.AdjustDotPosition("UpperLeft", mplusNormalFont, text.UpperLeft, halfWidth, halfHeight)
text.Draw(screen, "LeftUpper", mplusNormalFont, dx, dy, color.White)
dx, dy = text.AdjustDotPosition("UpperRight", mplusNormalFont, text.UpperRight, halfWidth, halfHeight)
text.Draw(screen, "UpperRight", mplusNormalFont, dx, dy, color.White)
dx, dy = text.AdjustDotPosition("LowerLeft", mplusNormalFont, text.LowerLeft, halfWidth, halfHeight)
text.Draw(screen, "LowerLeft", mplusNormalFont, dx, dy, color.White)
dx, dy = text.AdjustDotPosition("LowerRight", mplusNormalFont, text.LowerRight, halfWidth, halfHeight)
text.Draw(screen, "LowerRight", mplusNormalFont, dx, dy, color.White)
vLine := ebiten.NewImage(1, screenHeight)
vopts := &ebiten.DrawImageOptions{}
vopts.ColorM.Translate(0, 128, 128, 255)
vopts.GeoM.Translate(screenWidth/2, 0)
hLine := ebiten.NewImage(screenWidth, 1)
hopts := &ebiten.DrawImageOptions{}
hopts.ColorM.Translate(0, 128, 128, 255)
hopts.GeoM.Translate(0, screenHeight/2)
screen.DrawImage(hLine, hopts)
screen.DrawImage(vLine, vopts)
}
dx, dy := text.AdjustDotPosition("Centered", mplusNormalFont, text.Center, halfWidth, halfHeight)
text.Draw(screen, "Centered", mplusNormalFont, dx, dy, color.White)
dx, dy := text.AdjustDotPosition("LeftCentered", mplusNormalFont, text.CenterLeft, halfWidth, halfHeight)
text.Draw(screen, "LeftCentered", mplusNormalFont, dx, dy, color.White)
dx, dy := text.AdjustDotPosition("CenterRight", mplusNormalFont, text.CenterRight, halfWidth, halfHeight)
text.Draw(screen, "CenterRight", mplusNormalFont, dx, dy, color.White)
I think the only problem I have with this is that you have to specify the string twice. I guess that can be solved with variables though. Let me know what you think. I may still be doing this completely wrong.
Thank you for working! I'll take a look later.
Just a quick reply:
because I am not exactly sure how I can obtain the ascender
face.Metrics().Ascent
?
https://pkg.go.dev/golang.org/x/image/font#Metrics
I think it would be helpful to look at my etxt package more closely to see how I solved these problems. There's a ton of relevant knowledge there, it's well documented and the critical code is clean. The API is quite different as there are also many other parameters and right to left text and individual glyph handling and all that, but it should be a good design reference. Even if we have the align configuration or others (e.g: text quantization, color, size) in an "options" struct instead of a renderer type with aligns, the general concerns are the same.
You can copy any implementation details that you want from etxt, the real problem is understanding all possible configurable elements when drawing text and determining what's the intended scope for Ebitengine.
Thank for the advice, but rather than implementation, the (my) current focus is how to reconcile with the current APIs.
Yeah, that's exactly what I said in the last line, that implementation is not a problem, API design is. But my point is that even for the API there are parts that are being missed in this discussion. For example, etxt uses two separate types for the vertical and the horizontal aligns. The current discussion is missing baseline aligns. Also, vertical aligning needs to carefully consider the font metrics vs text bounds. Sedyh's version of text centering is not suitable for all use-cases: for example, when aligning text vertically on multiple buttons, you want all button texts to be aligned from the baseline, not perfectly centered from their bounds. And so on.
@KalebHawkins
I did make something that I believe emulates what you describes.
Yeah, the figures are actually what I wanted. Thank you very much!
I think the only problem I have with this is that you have to specify the string twice. I guess that can be solved with variables though. Let me know what you think. I may still be doing this completely wrong.
Right, this redundancy seems inevitable so far... So, in addition to adding AdjustDotPostion, what about adding a utility function only for Draw? I'm sorry we are going back and forth.
// I've not determined the argument order so they might be changed later
func DrawWithAlignment(dst *ebiten.Image, text string, face font.Face, alignment Alignment, x, y int, clr color.Color) {
x, y := AdjustDotPosition(text, face, alignment, x, y)
Draw(dst, text, face, x, y, clr)
}
So if people want to use DrawWithOptions, they can still use AdjustDotPosition. The code would be verbose but it's ok as this is a kind of advanced usages.
@tinne26
But my point is that even for the API there are parts that are being missed in this discussion.
Adding drastic changes to the current package is out of scope in this issue. Let's create another issue and discuss there.
The current discussion is missing baseline aligns. Also, vertical aligning needs to carefully consider the font metrics vs text bounds.
Well, this seems worth discussing in this issue (sorry I missed this first). I'll take a look at other specifications like CSS.
For horizontal aligns, there are 8 types: https://developer.mozilla.org/en-US/docs/Web/CSS/text-align For vertical aligns, there are 8 types: https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align
Random notes:
- Of course we don't need to implement all of them.
- Implementing
justify
might be tricky though it is challenging... In this case, we would need to specify a rectangle instead of a point. - In CSS, there are the (inline-)block type and the block type and these have different notions. For the block type, we might need another spec like flex-box.
- To make things simplify, what about these alignments:
- Horizontal
- Left
- Center
- Right
- Vertical
- Top
- Baseline (of the first line?)
- Middle (of the entire text block?)
- Bottom
- Horizontal
Text renderings are complicated as @tinne26 said.
Yeah, agree, I don't think CSS Sub
and Text-Top
vertical aligns are relevant for Ebitengine, it will only add noise. And yes, baseline has to be based on the first line in case of multiple lines, and middle has to be based on the entire text block.
Horizontal aligns are clear, and for vertical the only thing we may want to consider is separating Middle
into Center
and CenterLine
, as I previously mentioned when discussing Sedyh's approach. To make it clearer: Center
would center text based on string bounds, while CenterLine
would center text based on line height (names could be improved). Notice that these different approaches would produce very different results if text blocks start or end with line breaks, or have all lines starting or ending with spaces or other empty glyphs.
A practical example of why this might be relevant:
- If you have two buttons of the same size, one next to the other, and in one you type "mama" and on the other you type "papa", centering based on bounds will make the button texts look misaligned, as the bounds for "mama" and "papa" have different heights. "baba" and "yaga" would look even worse. Using
CenterLine
, instead, would still keep the text vertically centered in the buttons but both words would share the same y coordinate for their baseline.
This may still be too noisy and tricky, but even if we decide against this separation, I'd argue in favor of using the CenterLine
approach for centering instead of the bounds-based approach, as it's generally more consistent. This is what etxt does, and in general browsers and text editors do not use bounds directly but rather boxes based on font metrics and glyph advances. See etxt/renderer.SelectionRect
.
In general, I'm a proponent of using these boxes instead of strict glyph outline bounds, as bounds are very tricky to work with (e.g. https://github.com/hajimehoshi/ebiten/issues/1992). So, I prefer SelectionRect
metrics to BoundString
for most use-cases. They all have their drawbacks, but it's a point deserving consideration.
Besides that, I think that adding a single AdjustDotPosition
call for the API sounds fine, but maybe we could rename it AdjustDot
or even AlignDot
depending on the names of the align constants.
Earlier today I remembered something important that we have been missing in this dicussion:
- The
AdjustDotPosition
approach has a big limitation: right-aligned text is not truly possible when the text has multiple lines. The right-aligning will align the multiline block, but without more information than a(x, y)
coordinate,text.Draw
can't do proper right-aligning. Technically single-line right align will still work properly, but this behavior on multi-line blocks will be very confusing for regular users.
If this issue is considered significant enough, we may have to rethink the approach, which then would most likely require breaking changes or additional new functions, like DrawWithOptions
or similar (edit: sorry, this collides with the already existing function, I mean a different one with draw options for the align and all that). I actually wrote a design for such an approach for Ebitengine, but it included more concepts that were out the scope of this issue (but I can look for it and share it if we have to go back to the design board due to the right-align issue).
right-aligned text is not truly possible when the text has multiple lines. The right-aligning will align the multiline block, but without more information than a (x, y) coordinate, text.Draw can't do proper right-aligning.
Is that true? The second and the following lines can be aligned to the specified right edge, right?
It can be but not with the AdjustDotPosition
approach alone and without DrawWithAlignment
. With that, you only have (x, y)
information passed to Draw
, when you would require both (x, y)
and the align information (horizontal align info would be enough, vertical is not strictly necessary). Otherwise you can't know the x at which to reset the next line. In fact, this also happens for centered text. You can't do left/center/right multi-line aligns only with (x, y)
information. So you can't combine AdjustDotPosition
with DrawWithOptions
. Nor AdjustDotPosition
with Draw
. It doesn't work for multi-line right and center aligns. DrawWithAlignment
would work by itself, but then you are not covering the DrawWithOptions
functionality. In conclusion, AdjustDotPosition
may be more harmful than helpful and not a good idea.
Edit: basically, that what you said on https://github.com/hajimehoshi/ebiten/issues/2143#issuecomment-1161694721 is not possible, and like Kaleb showed, you still need to pass the align information to draw then, so you can't compose those functions without breaking the API.
Apprently we need more discussion to determine the API...
@KalebHawkins I'm sorry we cannot determine the APIs yet and need more time, and thank you for trying to contribute to Ebitengine.
So, I see four main ways forward:
- Leave the API as it is (if anyone needs the features you can always refer them to etxt).
- Provide the
AdjustDot
function anyway even if it won't work for multi-line centered and right aligned text. This seems hacky as it doesn't really solve the problem, but it's still helpful in many cases and composable. - Add
DrawCenter
andDrawRight
, and leaveDraw
acting as the "DrawLeft" function. As a downside, we can't compose withDrawWithOptions
and others. This seems ugly and limited but at least it's not incorrect. Alternatively, add a singleDrawAligned
function that's the same asDraw
but takes an extra align parameter. - Redesign the text API for v3 in addition to any of the other three approaches. I can make an API proposal for this if there's any interest for it. The alternative minimal changes would involve adding an align parameter to all the drawing functions.
Is it possible to add a new field for DrawTextOptions?
No because ebiten.DrawImageOptions
is used, the same type as for regular draw operations (assuming you were referring to text.DrawWithOptions
). I mean, it's technically possible to add a field and it wouldn't collide with image draw operations, but it would be beyond hacky. But yeah, an API redesign would almost definitely use a new text.Options
type that included all that information (font, color, align, even size to get rid of font faces if going etxt's way, and maybe additional options like horizontal interspacing, line height or interspacing, etc).
But yeah, an API redesign would almost definitely use a new text.Options type that included all that information (font, color, align, even size to get rid of font faces if going etxt's way, and maybe additional options like horizontal interspacing, line height or interspacing, etc).
I agree with it. I think this is the most correct option.
Apprently we need more discussion to determine the API...
@KalebHawkins I'm sorry we cannot determine the APIs yet and need more time, and thank you for trying to contribute to Ebitengine.
No worries at all! I had gotten really busy with work so I wasn't able to keep up with the conversation. Funny how something can sound simple then get complicated really fast lol.