mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-24 04:41:10 +00:00
2458ea3d32
Changed, internal: made TextLabel and IRenderer2D public properties compatible with DB2 implementation. Fixed some more cases when sidedefs belonging to linedefs, which were moved on top of existing linedefs, were incorrectly reassigned when applying Edit Selection and Drag Geometry modes. Fixed, Map Analysis mode: error checks must be updated after switching map format.
447 lines
15 KiB
C#
447 lines
15 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 interface ITextLabel //mxd. Methods and properties required to render a textlabel
|
|
{
|
|
// Required to render text label
|
|
bool SkipRendering { get; }
|
|
Texture Texture { get; }
|
|
VertexBuffer VertexBuffer { get; }
|
|
|
|
// Access/setup
|
|
Font Font { get; }
|
|
string Text { get; set; }
|
|
PixelColor Color { get; set; }
|
|
PixelColor BackColor { get; set; }
|
|
|
|
void Update(float translatex, float translatey, float scalex, float scaley);
|
|
}
|
|
|
|
public class TextLabel : IDisposable, ID3DResource, ITextLabel
|
|
{
|
|
#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;
|
|
|
|
//mxd. Compatibility
|
|
private float scale;
|
|
|
|
// 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
|
|
public Texture Texture { get { return texture; } } //mxd
|
|
public VertexBuffer VertexBuffer { get { return textbuffer; } }
|
|
public bool SkipRendering { get { return skiprendering; } } //mxd
|
|
|
|
//mxd. Compatibility settings
|
|
[Obsolete("Backcolor property is deprecated, please use BackColor property instead.")]
|
|
public PixelColor Backcolor { get { return BackColor; } set { BackColor = value.WithAlpha(128); } }
|
|
|
|
[Obsolete("Scale property is deprecated, please assign the font directly using Font property instead.")]
|
|
public float Scale
|
|
{
|
|
get { return scale; }
|
|
set
|
|
{
|
|
scale = value;
|
|
font = new Font(new FontFamily(General.Settings.TextLabelFontName), (float)Math.Round(scale * 0.75f), (General.Settings.TextLabelFontBold ? FontStyle.Bold : FontStyle.Regular));
|
|
textsize = Size.Empty;
|
|
textureupdateneeded = true;
|
|
}
|
|
}
|
|
|
|
[Obsolete("Rectangle property is deprecated, please use Location property instead.")]
|
|
public RectangleF Rectangle { get { return new RectangleF(location.x, location.y, 0f, 0f); } set { location = new Vector2D(value.X, value.Y); updateneeded = true; } }
|
|
|
|
// 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);
|
|
}
|
|
|
|
//mxd. Compatibility constructor...
|
|
[Obsolete("TextLabel(int capacity) is deprecated, please use TextLabel() instead.")]
|
|
public TextLabel(int unused)
|
|
{
|
|
// Initialize
|
|
this.text = "";
|
|
this.font = General.Settings.TextLabelFont;
|
|
this.location = new Vector2D();
|
|
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;
|
|
this.texturesize = Size.Empty;
|
|
this.updateneeded = true;
|
|
this.textureupdateneeded = true;
|
|
|
|
// 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
|
|
public 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
|
|
}
|
|
}
|