8
\$\begingroup\$

I'm writing a textbox class for my XNA game and I'm using SpriteFont to draw the text. It's only a single line textbox so when the text width exceeds the size of the rectangle box, it currently just keeps going.

In Windows textboxes extra text is cut off so that you may only see half a letter. I'd like to emulate this with my custom control. However, I see no overload with SpriteBatch.DrawString that would allow for anything that could cut off text that didn't fit within certain bounds.

Does anyone know of a method that would allow for this? I'm still quite new to the XNA API so I'm not sure what out there exists for this sort of thing.. Thanks

\$\endgroup\$

5 Answers 5

9
\$\begingroup\$

The way I handled this in my simple textbox UI element was to use a scissor rectangle (as Nicol Bolas said while I was writing this :P).

Here's the basic idea:

  1. First off, create a RasterizerState field in whatever class does the drawing of the text like so:

     RasterizerState _rasterizerState = new RasterizerState() { ScissorTestEnable = true };
    

You'll pass this to the spritebatch to tell it to use the GraphicsDevice.ScissorRectangle.

  1. Then, your Draw method would go something like this:

     public void Draw(GameTime gameTime) {
         spriteBatch.Begin();
         //Draw other stuff
         spriteBatch.End();
    
         //Set up the spritebatch to draw using scissoring (for text cropping)
         spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend,
                           null, null, _rasterizerState);
    
         //Optional: Draw the border using a 1x1 white square texture that is 
         //          stretched to fill a slightly larger rectangle behind the
         //          background (below).
         spriteBatch.Draw(_boxTexture, _borderRectangle, _borderColor);
    
         //Optional: Draw the background
         spriteBatch.Draw(_boxTexture, _textboxRectangle, _backColor);
    
         //Copy the current scissor rect so we can restore it after
         Rectangle currentRect = spriteBatch.GraphicsDevice.ScissorRectangle;
    
         //Set the current scissor rectangle
         spriteBatch.GraphicsDevice.ScissorRectangle = _textboxRectangle;
    
         //Draw the text at the top left of the scissor rectangle
         spriteBatch.DrawString(_font, _text, Vector2.Zero, _fontColor);
    
         //Reset scissor rectangle to the saved value
         spriteBatch.GraphicsDevice.ScissorRectangle = currentRect;
    
         //Draw more textboxes or whatever else using the scissor rectangles here
    
         //End the spritebatch
         spriteBatch.End();
     }
    

You can probably get away with not resetting the scissor rectangle for this basic situation, but you need to reset it if you want to draw other things (like those backgrounds). This example also assumed a lot of variables which you can infer (I hope :-) ) from their names.

A few other notes on the variables:

  1. _textboxRectangle is the clipping rectangle to use. I pulled this from my textbox UI class which is where that and basically all other variables comes from.
  2. _borderRectangle is just a rectangle that is a few pixels larger than _textboxRectangle so that when the background is drawn over top of it, it will appear as the border for the box. Pretty simple.

Hope this helps!

\$\endgroup\$
3
\$\begingroup\$

If you want to clip things to a certain region, you use the scissor box. It's a property of the GraphicsDevice: GraphicsDevice.ScissorTestEnable and either RenderState.ScissorRectangle pre-XNA 4.0 or RasterizerState.ScissorTestEnable in XNA 4.0.

You don't need to measure text for it; just set the scissor box to the area you want to clip to. You can draw all the text you want after that. Indeed, scissoring is useful in all kinds of places for GUIs.

Just don't forget to turn off scissoring when you're done.

\$\endgroup\$
5
  • \$\begingroup\$ This actually looks like a great solution, I didn't know about this \$\endgroup\$ Feb 29, 2012 at 4:03
  • \$\begingroup\$ This looks promising! However, I'm not sure exactly where ScissorTestEnable is.. I'm using XNA 4.0. Do I need to create a RenderState? \$\endgroup\$
    – John Price
    Feb 29, 2012 at 4:04
  • \$\begingroup\$ @JohnPrice: Sorry, I had some of the names and links wrong. They're corrected now. \$\endgroup\$ Feb 29, 2012 at 4:17
  • \$\begingroup\$ Thanks, I got it working now. I'm a bit unsure on how to turn off scissoring though. Whenever I call rs.ScissorTestEnable = false; after my drawing I get an exception: Cannot change read-only RasterizerState State objects become read-only the first time they are bound to a GraphicsDevice. \$\endgroup\$
    – John Price
    Feb 29, 2012 at 4:29
  • \$\begingroup\$ Then you'll need to keep two of them around. One with scissor test enable and one with it disabled. Switch the state bindings when you need to change them. \$\endgroup\$ Feb 29, 2012 at 4:30
0
\$\begingroup\$

What you need to do is use SpriteFont.MeasureString()

Loop through your string and check MeasureString(substring) to see when it exceeds your maximum width. Then use that substring to render.

Edit: Sorry didn't realize you wanted that precision.

What you want to use is a ScissorRectangle.

  • Set GraphicsDevice.ScissorRectangle to the rect of your textbox.
  • Set ScissorTestEnable to true in the RasterizerState
  • Pass that RasterizerState to your SpriteBatch.Begin()
\$\endgroup\$
1
  • \$\begingroup\$ Yes that is what I am currently doing but the problem is it doesn't cut off text like the regular Windows textbox does. It gives no option towards rendering half a letter, for example, if the textbox size allows for it. \$\endgroup\$
    – John Price
    Feb 29, 2012 at 3:37
0
\$\begingroup\$

You need to use multiple render targets. You need to create an off-screen texture where you will draw the text-box area, then draw that to the main render target. Here's a post on the MSDN forum about this: http://social.msdn.microsoft.com/forums/en-US/xnaframework/thread/71263016-b26a-44bd-80f3-8cd620b119ff

\$\endgroup\$
0
\$\begingroup\$

You have to create a new RasterizerState to change its state:

RasterizerState rState = new RasterizerState()
{
    ScissorTestEnable = false,
};

GraphicsDevice.RasterizerState = rState;
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .