#region CPL License /* Nuclex Framework Copyright (C) 2002-2010 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Xml; using System.Xml.Schema; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; using Nuclex.Support; namespace Nuclex.UserInterface.Visuals.Flat { partial class FlatGuiGraphics { /// Needs to be called before the GUI drawing process begins public void BeginDrawing() { GraphicsDevice graphics = this.spriteBatch.GraphicsDevice; Viewport viewport = graphics.Viewport; graphics.ScissorRectangle = new Rectangle( 0, 0, viewport.Width, viewport.Height ); // On Windows Phone 7, if only the GUI is rendered (no other SpriteBatches) // and the initial spriteBatch.Begin() includes the scissor rectangle, // nothing will be drawn at all, so we don't use beginSpriteBatch() here // and instead call SpriteBatch.Begin() ourselves. Care has to be taken // if something ever gets added to the beginSpriteBatch() method. this.spriteBatch.Begin(); } /// Needs to be called when the GUI drawing process has ended public void EndDrawing() { endSpriteBatch(); } /// Sets the clipping region for any future drawing commands /// Clipping region that will be set /// /// An object that will unset the clipping region upon its destruction. /// /// /// Clipping regions can be stacked, though this is not very typical for /// a game GUI and also not recommended practice due to performance constraints. /// Unless clipping is implemented in software, setting up a clip region /// on current hardware requires the drawing queue to be flushed, negatively /// impacting rendering performance (in technical terms, a clipping region /// change likely causes 2 more DrawPrimitive() calls from the painter). /// public IDisposable SetClipRegion(RectangleF clipRegion) { // Cache the integer values of the clipping region's boundaries int clipX = (int)clipRegion.X; int clipY = (int)clipRegion.Y; int clipRight = clipX + (int)clipRegion.Width; int clipBottom = clipY + (int)clipRegion.Height; // Calculate the viewport's right and bottom coordinates Viewport viewport = this.spriteBatch.GraphicsDevice.Viewport; int viewportRight = viewport.X + viewport.Width; int viewportBottom = viewport.Y + viewport.Height; // Extract the part of the clipping region that lies within the viewport Rectangle scissorRegion = new Rectangle( Math.Max(clipX, viewport.X), Math.Max(clipY, viewport.Y), Math.Min(clipRight, viewportRight) - clipX, Math.Min(clipBottom, viewportBottom) - clipY ); scissorRegion.Width += clipX - scissorRegion.X; scissorRegion.Height += clipY - scissorRegion.Y; // If the clipping region was entirely outside of the viewport (meaning // the calculated width and/or height are negative), use an empty scissor // rectangle instead because XNA doesn't like scissor rectangles with // negative coordinates. if ((scissorRegion.Width <= 0) || (scissorRegion.Height <= 0)) { scissorRegion = Rectangle.Empty; } // All done, take over the new scissor rectangle this.scissorManager.Assign(ref scissorRegion); return this.scissorManager; } /// Draws a GUI element onto the drawing buffer /// Class of the element to draw /// Region that will be covered by the drawn element /// /// /// GUI elements are the basic building blocks of a GUI: /// /// public void DrawElement(string frameName, RectangleF bounds) { Frame frame = lookupFrame(frameName); // Draw all the regions defined for the element. Each region is a small bitmap // that needs to be blit somewhere into the element to form the element's // visual representation step by step. for (int index = 0; index < frame.Regions.Length; ++index) { Rectangle destinationRegion = calculateDestinationRectangle( ref bounds, ref frame.Regions[index].DestinationRegion ); this.spriteBatch.Draw( frame.Regions[index].Texture, destinationRegion, frame.Regions[index].SourceRegion, Color.White ); } } /// Draws text into the drawing buffer for the specified element /// Class of the element for which to draw text /// Region that will be covered by the drawn element /// Text that will be drawn public void DrawString(string frameName, RectangleF bounds, string text) { Frame frame = lookupFrame(frameName); // Draw the text in all anchor locations defined by the skin for (int index = 0; index < frame.Texts.Length; ++index) { this.spriteBatch.DrawString( frame.Texts[index].Font, text, positionText(ref frame.Texts[index], bounds, text), frame.Texts[index].Color ); } } /// Draws a caret for text input at the specified index /// Class of the element for which to draw a caret /// Region that will be covered by the drawn element /// Text for which a caret will be drawn /// Index the caret will be drawn at public void DrawCaret( string frameName, RectangleF bounds, string text, int caretIndex ) { Frame frame = lookupFrame(frameName); this.stringBuilder.Remove(0, this.stringBuilder.Length); this.stringBuilder.Append(text, 0, caretIndex); Vector2 caretPosition, textPosition; for (int index = 0; index < frame.Texts.Length; ++index) { textPosition = positionText(ref frame.Texts[index], bounds, text); caretPosition = frame.Texts[index].Font.MeasureString(this.stringBuilder); caretPosition.X -= CaretWidth; caretPosition.Y = 0.0f; this.spriteBatch.DrawString( frame.Texts[index].Font, "|", textPosition + caretPosition, frame.Texts[index].Color ); } } /// Measures the extents of a string in the frame's area /// Class of the element whose text will be measured /// Region that will be covered by the drawn element /// Text that will be measured /// /// The size and extents of the specified string within the frame /// public RectangleF MeasureString(string frameName, RectangleF bounds, string text) { Frame frame = lookupFrame(frameName); Vector2 size; if (frame.Texts.Length > 0) { size = frame.Texts[0].Font.MeasureString(text); } else { size = Vector2.Zero; } return new RectangleF(0.0f, 0.0f, size.X, size.Y); } /// /// Locates the closest gap between two letters to the provided position /// /// Class of the element in which to find the gap /// Region that will be covered by the drawn element /// Text in which the closest gap will be found /// Position of which to determien the closest gap /// The index of the gap the position is closest to public int GetClosestOpening( string frameName, RectangleF bounds, string text, Vector2 position ) { Frame frame = lookupFrame(frameName); // TODO: Find the closest gap across multiple text anchors // Frames can repeat their text in several places. Though this is probably // not used very often (if at all), it should work here consistently. int closestGap = -1; for (int index = 0; index < frame.Texts.Length; ++index) { Vector2 textPosition = positionText(ref frame.Texts[index], bounds, text); position.X -= textPosition.X; position.Y -= textPosition.Y; float openingX = position.X; int openingIndex = this.openingLocator.FindClosestOpening( frame.Texts[index].Font, text, position.X + CaretWidth ); closestGap = openingIndex; } return closestGap; } /// Starts drawing on the sprite batch private void beginSpriteBatch() { this.spriteBatch.Begin( SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, this.rasterizerState ); } /// Stops drawing on the sprite batch private void endSpriteBatch() { this.spriteBatch.End(); } } } // namespace Nuclex.UserInterface.Visuals.Flat