UltimateZoneBuilder/Source/Core/Rendering/TextLabel.cs
MaxED 3eb05509af Added, Visual mode: "Increase/Decrease Scale", "Reset Texture Offsets" and "Reset Local Texture Offsets" actions now work when used on 3d floor sides.
Fixed: CVARINFO parser was unable to parse negative int/float values.
Fixed: in some cases TextLabel text size was checked before it was calculated. 
Internal, Visual mode: reduced the number of unnecessary geometry updates when changing map geometry.
Updated ZDoom_DECORATE.cfg (atan2 and VectorAngle).
2016-04-19 20:40:42 +00:00

384 lines
13 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program 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
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Drawing;
using SlimDX.Direct3D9;
using SlimDX;
using CodeImp.DoomBuilder.Geometry;
using Font = System.Drawing.Font;
#endregion
namespace CodeImp.DoomBuilder.Rendering
{
public class TextLabel : IDisposable, ID3DResource
{
#region ================== Constants
#endregion
#region ================== Variables
// The text is stored as a polygon in a vertex buffer
private VertexBuffer textbuffer;
private Texture texture;
private Font font; //mxd
// Text settings
private string text;
private Vector2D location; //mxd
private bool transformcoords;
private PixelColor color;
private PixelColor backcolor;
private TextAlignmentX alignx;
private TextAlignmentY aligny;
private bool drawbg; //mxd
//mxd. Label image settings...
private SizeF textsize;
private Size texturesize;
private RectangleF textrect;
private RectangleF bgrect;
private PointF textorigin;
// This keeps track if changes were made
private bool updateneeded;
private bool textureupdateneeded; //mxd
private float lasttranslatex = float.MinValue;
private float lasttranslatey;
private float lastscalex;
private float lastscaley;
//mxd. Rendering
private bool skiprendering;
// Disposing
private bool isdisposed;
#endregion
#region ================== Properties
// Properties
public Vector2D Location { get { return location; } set { location = value; updateneeded = true; } } //mxd
public string Text { get { return text; } set { if(text != value) { text = value; textsize = Size.Empty; textureupdateneeded = true; } } }
public Font Font { get { return font; } set { font = value; textsize = Size.Empty; textureupdateneeded = true; } } //mxd
public bool TransformCoords { get { return transformcoords; } set { transformcoords = value; updateneeded = true; } }
public SizeF TextSize { get { if(textureupdateneeded) Update(General.Map.Renderer2D.TranslateX, General.Map.Renderer2D.TranslateY, General.Map.Renderer2D.Scale, -General.Map.Renderer2D.Scale); return textsize; } }
public TextAlignmentX AlignX { get { return alignx; } set { alignx = value; updateneeded = true; } }
public TextAlignmentY AlignY { get { return aligny; } set { aligny = value; updateneeded = true; } }
public PixelColor Color { get { return color; } set { if(!color.Equals(value)) { color = value; textureupdateneeded = true; } } }
public PixelColor BackColor { get { return backcolor; } set { if(!backcolor.Equals(value)) { backcolor = value; textureupdateneeded = true; } } }
public bool DrawBackground { get { return drawbg; } set { if(drawbg != value) { drawbg = value; textureupdateneeded = true; } } } //mxd
internal Texture Texture { get { return texture; } } //mxd
internal VertexBuffer VertexBuffer { get { return textbuffer; } }
internal bool SkipRendering { get { return skiprendering; } } //mxd
// Disposing
public bool IsDisposed { get { return isdisposed; } }
#endregion
#region ================== Constructor / Disposer
// Constructor
public TextLabel()
{
// Initialize
this.text = "";
this.font = General.Settings.TextLabelFont; //mxd
this.location = new Vector2D(); //mxd
this.color = new PixelColor(255, 255, 255, 255);
this.backcolor = new PixelColor(128, 0, 0, 0);
this.alignx = TextAlignmentX.Center;
this.aligny = TextAlignmentY.Top;
this.textsize = SizeF.Empty; //mxd
this.texturesize = Size.Empty; //mxd
this.updateneeded = true;
this.textureupdateneeded = true; //mxd
// Register as resource
General.Map.Graphics.RegisterResource(this);
// We have no destructor
GC.SuppressFinalize(this);
}
// Diposer
public void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Clean up
UnloadResource();
// Unregister resource
General.Map.Graphics.UnregisterResource(this);
// Done
isdisposed = true;
}
}
#endregion
#region ================== Methods
// This updates the text if needed
internal void Update(float translatex, float translatey, float scalex, float scaley)
{
// Check if transformation changed and needs to be updated
if(transformcoords && (translatex != lasttranslatex || translatey != lasttranslatey ||
scalex != lastscalex || scaley != lastscaley))
{
lasttranslatex = translatex; //mxd
lasttranslatey = translatey; //mxd
lastscalex = scalex; //mxd
lastscaley = scaley; //mxd
updateneeded = true;
}
// Update if needed
if(updateneeded || textureupdateneeded)
{
// Only build when there are any vertices
if(text.Length > 0)
{
// Transform?
Vector2D abspos = (transformcoords ? location.GetTransformed(translatex, translatey, scalex, scaley) : location);
// Update text and texture sizes
if(textsize.IsEmpty || texturesize.IsEmpty)
{
textorigin = new PointF(4, 3);
textrect = new RectangleF(textorigin, General.Interface.MeasureString(text, font));
textrect.Width = (float)Math.Round(textrect.Width);
textrect.Height = (float)Math.Round(textrect.Height);
bgrect = new RectangleF(0, 0, textrect.Width + textorigin.X * 2, textrect.Height + textorigin.Y * 2);
// Store calculated text size...
textsize = new SizeF(textrect.Width + textorigin.X * 2, textrect.Height + textorigin.Y * 2);
// Make PO2 image, for speed and giggles...
texturesize = new Size(General.NextPowerOf2((int)textsize.Width), General.NextPowerOf2((int)textsize.Height));
switch(alignx)
{
case TextAlignmentX.Center: bgrect.X = (texturesize.Width - bgrect.Width) / 2; break;
case TextAlignmentX.Right: bgrect.X = texturesize.Width - bgrect.Width; break;
}
switch(aligny)
{
case TextAlignmentY.Middle: bgrect.Y = (texturesize.Height - bgrect.Height) / 2; break;
case TextAlignmentY.Bottom: bgrect.Y = texturesize.Height - bgrect.Height; break;
}
textrect.X += bgrect.X;
textrect.Y += bgrect.Y;
}
// Align the text horizontally
float beginx = 0;
switch(alignx)
{
case TextAlignmentX.Left: beginx = abspos.x; break;
case TextAlignmentX.Center: beginx = abspos.x - texturesize.Width * 0.5f; break;
case TextAlignmentX.Right: beginx = abspos.x - texturesize.Width; break;
}
// Align the text vertically
float beginy = 0;
switch(aligny)
{
case TextAlignmentY.Top: beginy = abspos.y; break;
case TextAlignmentY.Middle: beginy = abspos.y - texturesize.Height * 0.5f; break;
case TextAlignmentY.Bottom: beginy = abspos.y - texturesize.Height; break;
}
//mxd. Skip when not on screen...
RectangleF abssize = new RectangleF(beginx, beginy, texturesize.Width, texturesize.Height);
Size windowsize = General.Map.Graphics.RenderTarget.ClientSize;
skiprendering = (abssize.Right < 0.1f) || (abssize.Left > windowsize.Width) || (abssize.Bottom < 0.1f) || (abssize.Top > windowsize.Height);
if(skiprendering) return;
//mxd. Update texture if needed
if(textureupdateneeded)
{
// Get rid of old texture
if(texture != null)
{
texture.Dispose();
texture = null;
}
// Create label image
Bitmap img = CreateLabelImage(text, font, color, backcolor, drawbg, textrect, bgrect, texturesize, textorigin);
//texturesize = img.Size;
// Create texture
MemoryStream memstream = new MemoryStream((img.Size.Width * img.Size.Height * 4) + 4096);
img.Save(memstream, ImageFormat.Bmp);
memstream.Seek(0, SeekOrigin.Begin);
texture = Texture.FromStream(General.Map.Graphics.Device, memstream, (int)memstream.Length,
img.Size.Width, img.Size.Height, 1, Usage.None, Format.Unknown,
Pool.Managed, General.Map.Graphics.PostFilter, General.Map.Graphics.MipGenerateFilter, 0);
}
//mxd. Create the buffer
if(textbuffer == null || textbuffer.Disposed)
{
textbuffer = new VertexBuffer(General.Map.Graphics.Device, 4 * FlatVertex.Stride,
Usage.Dynamic | Usage.WriteOnly, VertexFormat.None, Pool.Default);
}
//mxd. Lock the buffer
using(DataStream stream = textbuffer.Lock(0, 4 * FlatVertex.Stride, LockFlags.Discard | LockFlags.NoSystemLock))
{
FlatQuad quad = new FlatQuad(PrimitiveType.TriangleStrip, beginx, beginy, beginx + texturesize.Width, beginy + texturesize.Height);
stream.WriteRange(quad.Vertices);
}
// Done filling the vertex buffer
textbuffer.Unlock();
}
else
{
// No faces in polygon
textsize = SizeF.Empty; //mxd
texturesize = Size.Empty; //mxd
skiprendering = true; //mxd
}
// Text updated
updateneeded = false;
textureupdateneeded = false; //mxd
}
}
//mxd
private static Bitmap CreateLabelImage(string text, Font font, PixelColor color, PixelColor backcolor, bool drawbg, RectangleF textrect, RectangleF bgrect, Size texturesize, PointF textorigin)
{
Bitmap result = new Bitmap(texturesize.Width, texturesize.Height);
using(Graphics g = Graphics.FromImage(result))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
g.CompositingQuality = CompositingQuality.HighQuality;
// Draw text
using(StringFormat sf = new StringFormat())
{
sf.FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.NoWrap;
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
// Draw text with BG
if(drawbg)
{
GraphicsPath p = new GraphicsPath();
float radius = textorigin.X;
const float outlinewidth = 1;
RectangleF pathrect = bgrect;
pathrect.Width -= 1;
pathrect.Height -= 1;
// Left line
p.AddLine(pathrect.Left, pathrect.Bottom - radius + outlinewidth, pathrect.Left, pathrect.Top + radius);
p.AddArc(pathrect.Left, pathrect.Top, radius, radius, 180, 90);
// Top line
p.AddLine(pathrect.Left + radius, pathrect.Top, pathrect.Right - radius, pathrect.Top);
p.AddArc(pathrect.Right - radius, pathrect.Top, radius, radius, 270, 90);
// Right line
p.AddLine(pathrect.Right, pathrect.Top + radius, pathrect.Right, pathrect.Bottom - radius);
p.AddArc(pathrect.Right - radius, pathrect.Bottom - radius, radius, radius, 0, 90);
// Bottom line
p.AddLine(pathrect.Left + radius, pathrect.Bottom, pathrect.Left + radius, pathrect.Bottom);
p.AddArc(pathrect.Left, pathrect.Bottom - radius, radius, radius, 90, 90);
// Fill'n'draw bg
using(SolidBrush brush = new SolidBrush(color.ToColor()))
g.FillPath(brush, p);
using(Pen pen = new Pen(backcolor.ToColor(), outlinewidth))
g.DrawPath(pen, p);
// Draw text
textrect.Inflate(4, 2);
using(SolidBrush brush = new SolidBrush(backcolor.ToColor()))
g.DrawString(text, font, brush, textrect, sf);
}
// Draw plain text
else
{
RectangleF plainbgrect = textrect;
if(text.Length > 1) plainbgrect.Inflate(6, 2);
RectangleF plaintextrect = textrect;
plaintextrect.Inflate(6, 4);
using(SolidBrush brush = new SolidBrush(backcolor.ToColor()))
g.FillRectangle(brush, plainbgrect);
using(SolidBrush brush = new SolidBrush(color.ToColor()))
g.DrawString(text, font, brush, plaintextrect, sf);
}
}
}
return result;
}
// This unloads the resources
public void UnloadResource()
{
// Clean up
if(textbuffer != null)
{
textbuffer.Dispose();
textbuffer = null;
}
if(texture != null) //mxd
{
texture.Dispose();
texture = null;
}
// Need to update before we can render
updateneeded = true;
textureupdateneeded = true; //mxd
}
// This (re)loads the resources
public void ReloadResource() { }
#endregion
}
}