Unity3D - Displaying a message at the border of the screen - c#

I want to display a kind of banner everytime the player enters a new location. This should be at the very left border of the screen. My problem is that my current solution behaves inconsistently across resolutions. On higher resolutions the banner is positioned further to the right than needed.
I also use a piece of code to properly position the text every time it is displayed.
public class AreaName : MonoBehaviour
{
private static AreaName instance;
void Start ()
{
if (instance)
Destroy(this);
else
instance = this;
BannerBase = transform.FindChild("BannerBase").GetComponent<Image>();
NameDisplay = BannerBase.transform.FindChild("BannerText").GetComponent<Text>();
}
private Image BannerBase;
private Text NameDisplay;
public static void Display (string areatitle)
{
int width = TextWidth(instance.NameDisplay, areatitle);
instance.BannerBase.rectTransform.position = new Vector3(-305 + width, instance.BannerBase.transform.position.y, 0);
instance.NameDisplay.text = areatitle;
print(width);
}
private static int TextWidth (Text obj, string text)
{
int totalLength = 0;
Font myFont = obj.font; //chatText is my Text component
CharacterInfo characterInfo = new CharacterInfo();
char[] arr = text.ToCharArray();
foreach (char c in arr)
{
myFont.GetCharacterInfo(c, out characterInfo, obj.fontSize);
totalLength += characterInfo.advance;
}
return totalLength;
}
}
Are there any simple errors that I am overseeing? Or am I just understanding the Unity UI system wrong?

You shouldn't need to code nothing to do that.
You have put your banner at the lower left corner of the screen, with a (0,0) pivot, that's all right, but, why is it positioned at x: -320?
If you put your banner at -320 for some reason, at native resolution it probably will work as expected, but if you have activated the Canvas Resizer, at other resolutions it will be moved since is not positioned at the corner at all.

Related

WPF Canvas does not consistently clear drawn geometry

I am working with a WPF control derived from the Canvas that I am drawing a selection of geometry to, through a process that works along these lines, but is far more complex so although this illustrates the process, it is closer to pseudo-code:
public class LineView: Canvas
{
private List<GeometryLine> lines;
public LineView()
{
lines = new List<GeometryLine>();
}
private PathFigure GetPathFigure(List<Point> line, bool close)
{
var size = line.Count;
var points = new PathSegmentCollection(size);
var first = line.First();
for (int i = 1; i < size; i++)
{
points.Add(new LineSegment(line[i], true));
}
var figure = new PathFigure(first, points, close);
return new PathGeometry(new List<PathFigure>(figure));
}
public DrawingGroup DrawLine(GeometryLine line)
{
var geometry = new GeometryGroup();
geometry.Children.Add(GetPathGeometry(line.Points, false));
var d = new GeometryDrawing();
d.Pen = GetPen();
d.Geometry = geometry;
DrawingGroup group = new DrawingGroup();
group.Append();
group.Children.Add(d);
return group;
}
public override void OnRender(DrawingContext drw)
{
if ( 0 < lines.Count )
{
foreach ( var line in lines )
{
drw.DrawDrawing(DrawLine(line));
}
}
}
public void SetLines(List<GeometryLine> newLines)
{
lines = newLines;
this.InvalidateVisual();
}
}
This all works fine to render geometry content on the canvas. However, when the data changes and SetLines is called with a new set of data, it doesn't always clear the canvas - sometimes it will draw the new set of lines over the old set, other times it will clear the canvas. I can't see any pattern as to when it draws over as opposed to when it clears the canvas.
If I call InvalidateVisual from the Render method it will empty the canvas reliably, but it also forces the context to render again. Past questions on this topic indicate that either this.Children.Clear() or InvalidateVisual are the recommended strategies, but neither of them prevents this overdrawing problem. When I look at the Children collection it is always empty.
What do I need to do in order to ensure it will clear the previous geometry and then draw the updated geometry every time it changes?

Draggable Images

I have draggable images on my Windows Form. The PNG's themselves have transparent backgrounds yet it only matches the panel's background color when I load it. There are other labels with different colors I'd like to be able to see as I drag it instead. Anyone know what to use to make this happen?
http://postimg.org/image/d8p4s53pf/
EDIT:
Trying to make the PictureBox truely transparant. The drag stuff is done, but the background is just the background of the panel, and doesn't show controls it passes over.
I used this but it's a bit glitchy.
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle = 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//do nothing
}
protected override void OnMove(EventArgs e)
{
RecreateHandle();
}
Dragable Controls as such are not hard in WinForms. But once they need transparency the options are rather restricted: Each must be completely contained in its Parent; this implies that they all must be nested. No problem to create a set of transparent layers like you have in a graphics program.
But moving even one PictureBox-Pawn across a board full of others simply will not work with WinForms Controls.
But your aim is simple enough to do without any moving controls.
Instead you can simply draw the chess pieces onto the control surface in the Paint event. I have implemented a rudimentary solution and here is the Paint event from it:
List<ChessPiece> pieces = new List<ChessPiece>();
int mpIndex = -1; // index of the moving piece
Rectangle mmr; // rectangle where moving piece is drawn
// board display variables
int pieceSize = 60;
int tileSize = 80;
int borderWidth = 50;
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
foreach(ChessPiece p in pieces)
{
if (!p.onBoard) continue; // skip the piece?
e.Graphics.DrawImage(imageList1.Images[p.pieceIndex],
borderWidth + p.col * tileSize,
borderWidth + p.row * tileSize);
}
// if we have a moving piece..
if (mpIndex >= 0 && !pieces[mpIndex].onBoard)
e.Graphics.DrawImage(imageList1.Images[pieces[mpIndex].pieceIndex],
mmr.Location);
}
As you can see it really isn't very long. It makes use of a few obvious variables and a few less obvious ones. The pieces list and the ImageList must be prepared up front. The rectangle mmr is set in the MouseMove:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
mmr = new Rectangle(e.X - pieceSize / 2,
e.Y - pieceSize / 2, pieceSize, pieceSize);
pictureBox1.Invalidate(); // trigger the painting
}
}
I won't post the MouseUp and -Down events as they contain mostly code that doesn't actually pertain to moving transparent images but to managing the list of pieces..
The main idea is to draw all images from a list of pieces, unless a piece is not on the board. The moving piece is drawn separately, its coordinates are not from the board positions but from the mouse position.
If in the MouseUp event we find that the the move is illegal, we can simply set it back onBoard without changing the position and it will be drawn at its old place.
In the MouseDown we determine the piece clicked on, set its onBoard=false and set the moving index mpIndex.
The class for the pieces is not long or complicated either;
public class ChessPiece
{
public bool onBoard { get; set; } // is the piece standing on the board?
public int row { get; set; } // row
public int col { get; set; } // column
public char piecetype { get; set; } // a little simplistic..not used..
public char color { get; set;} // .. could be an enumeration!
public int pieceIndex { get; set; } // points into imageList with 12 images
public ChessPiece(char color, char type, int r, int c, int ind)
{ onBoard = true; piecetype = type; this.color = color;
row = r; col = c; pieceIndex = ind; }
}
All in all the full code is around 180 lines, uncommented but including 60 lines of setting up the pieces alone and without any chess logic..
Here are the first 3 lines of the code to create the pieces:
void makePieces()
{
ChessPiece
p = new ChessPiece('W', 'R', 7, 0, 8); pieces.Add(p);
p = new ChessPiece('W', 'N', 7, 1, 10); pieces.Add(p);
p = new ChessPiece('W', 'B', 7, 2, 9); pieces.Add(p);
..
So, moving a transparent Png graphic over other Png graphics and a board image is rather simple..
Here is a screenshot that shows a black bishop over a white knight:

adding objects programmatically to powerpoint slides forcing all objects animation

I am adding some objects to a PowerPoint slide on the fly along with some animations. Problem is when I add any object after adding the first object then both the previously added object along with current object comes with animation. How do I make my previously added object stay as it is and only the new one come with animation, here is some code that I am using,
private void AddShapeToSlide(int left, int top, int width, int height)
{
Slide slide = GetCurrentSlide();
Shape shape = slide.Shapes.AddShape(MsoAutoShapeType.msoShapeRectangle, left, top, width, height);
ApplyAnimation(slide, shape);
shape.ZOrder(MsoZOrderCmd.msoBringToFront);
_presentation.SlideShowWindow.Activate();
}
private void ApplyAnimation(Slide slide, Shape shape)
{
shape.AnimationSettings.AdvanceMode = PpAdvanceMode.ppAdvanceOnTime;
shape.AnimationSettings.AdvanceTime = 0;
shape.AnimationSettings.Animate = MsoTriState.msoTrue;
shape.AnimationSettings.EntryEffect = GetPowerPointTransition();
Effect effect = slide.TimeLine.MainSequence.FindFirstAnimationFor(shape);
if (effect != null && shape.AnimationSettings.EntryEffect != PpEntryEffect.ppEffectNone)
{
effect.Timing.Duration = 5;
}
}
Any help or idea is of great value. Thanks In Advance.

Scrolling to a point when WebBrowser is zoomed

I'm trying to zoom into a picture on a webpage loaded in the .Net WebBrowser control.
For example, there's a picture at (100,100) on 100% zoom. Now I want to set the zoom to 200% and have the picture at the top left in the WebBrowser control.
It almost works. If the point is on the left side of the screen it works. But if the point is more to the right of the screen, new calculated points are not correct.
Y is always correct. Only the X is giving me problems.
Anyone know how the ScrollTo is related to the current zoom factor. Or is it also related to other variables? I suspect the problem is also the adaptation of the webpage related to the size of the screen. (on zooming it removes the left and right edge of a webpage that doesn't hold any information. After that it enlarges the content). I need true zoom where the scaling affects only the content, not the layout. There is no reflow caused when scaling.
public class MyBrowser : WebBrowser
private int _zoom = 100;
public int Zoom
{
set
{
_zoom = value;
//perform zoom
object pvaIn = _zoom;
try
{
this._axIWebBrowser2.ExecWB(
OLECMDID.OLECMDID_OPTICAL_ZOOM,
OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
ref pvaIn,
IntPtr.Zero);
}
catch (Exception)
{
throw;
}
}
get
{
return _zoom;
}
}
private Point GetCurrentScroll()
{
int scrollX = this.Document.GetElementsByTagName("HTML")[0].ScrollLeft;
int scrollY = this.Document.GetElementsByTagName("HTML")[0].ScrollTop;
return new Point(scrollX, scrollY);
}
public void ZoomOnPoint(int x, int y, int zoomFactor)
{
int currentZoom = this.Zoom;
Point currentScroll = GetCurrentScroll();
//Set new values
this.Zoom = newZoom;
this.Document.Window.ScrollTo(
currentScroll.X + ((x * currentZoom) / zoomFactor), //X is sometimes correct, mostly on the rightside of the screen
currentScroll.Y + y); //Y is correct
}
}
In WPF, I multiply the X and Y values with the H and V zoom perc, it works :)

Autoscale Font in a TextBox Control so that its as big as possible and still fits in text area bounds

I need a TextBox or some type of Multi-Line Label control which will automatically adjust the font-size to make it as large as possible and yet have the entire message fit inside the bounds of the text area.
I wanted to see if anyone had implemented a user control like this before developing my own.
Example application: have a TextBox which will be half of the area on a windows form. When a message comes in which is will be approximately 100-500 characters it will put all the text in the control and set the font as large as possible. An implementation which uses Mono Supported .NET libraries would be a plus.
If know one has implemented a control already... If someone knows how to test if a given text completely fits inside the text area that would be useful for if I roll my own control.
Edit: I ended up writing an extension to RichTextBox. I will post my code shortly once i've verified that all the kinks are worked out.
I had to solve the same basic problem. The iterative solutions above were very slow. So, I modified it with the following. Same idea. Just uses calculated ratios instead of iterative. Probably, not quite as precise. But, much faster.
For my one-off need, I just threw an event handler on the label holding my text.
private void PromptLabel_TextChanged(object sender, System.EventArgs e)
{
if (PromptLabel.Text.Length == 0)
{
return;
}
float height = PromptLabel.Height * 0.99f;
float width = PromptLabel.Width * 0.99f;
PromptLabel.SuspendLayout();
Font tryFont = PromptLabel.Font;
Size tempSize = TextRenderer.MeasureText(PromptLabel.Text, tryFont);
float heightRatio = height / tempSize.Height;
float widthRatio = width / tempSize.Width;
tryFont = new Font(tryFont.FontFamily, tryFont.Size * Math.Min(widthRatio, heightRatio), tryFont.Style);
PromptLabel.Font = tryFont;
PromptLabel.ResumeLayout();
}
I haven't seen an existing control to do this, but you can do it the hard way by using a RichTextBox and the TextRenderer's MeasureText method and repeatedly resizing the font. It's inefficient, but it works.
This function is an event handler for the 'TextChanged' event on a RichTextBox.
An issue I've noticed:
When typing, the text box will scroll to the current caret even if scrollbars are disabled. This can result in the top line or left side getting chopped off until you move back up or left with the arrow keys. The size calculation is correct assuming you can get the top line to display at the top of the text box. I included some scrolling code that helps sometimes (but not always).
This code assumes word wrap is disabled. It may need modification if word wrap is enabled.
The code:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint wMsg, int wParam, uint lParam);
private static uint EM_LINEINDEX = 0xbb;
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
// If there's no text, return
if (richTextBox1.TextLength == 0) return;
// Get height and width, we'll be using these repeatedly
int height = richTextBox1.Height;
int width = richTextBox1.Width;
// Suspend layout while we mess with stuff
richTextBox1.SuspendLayout();
Font tryFont = richTextBox1.Font;
Size tempSize = TextRenderer.MeasureText( richTextBox1.Text, richTextBox1.Font);
// Make sure it isn't too small first
while (tempSize.Height < height || tempSize.Width < width)
{
tryFont = new Font(tryFont.FontFamily, tryFont.Size + 0.1f, tryFont.Style);
tempSize = TextRenderer.MeasureText(richTextBox1.Text, tryFont);
}
// Now make sure it isn't too big
while (tempSize.Height > height || tempSize.Width > width)
{
tryFont = new Font(tryFont.FontFamily, tryFont.Size - 0.1f, tryFont.Style);
tempSize = TextRenderer.MeasureText(richTextBox1.Text, tryFont);
}
// Swap the font
richTextBox1.Font = tryFont;
// Resume layout
richTextBox1.ResumeLayout();
// Scroll to top (hopefully)
richTextBox1.ScrollToCaret();
SendMessage(richTextBox1.Handle, EM_LINEINDEX, -1, 0);
}
The solution i came up with was to write a control which extends the standard RichTextBox control.
Use the extended control in the same way you would a regular RichTextBox control with the following enhancements:
Call the ScaleFontToFit() method after resizing or text changes.
The Horizontal Alignment field can be used to center align the text.
The Font attributes set in the designer will be used for the entire region. It is not possible to mix fonts as they will changed once the ScaleFontToFit method is called.
This control combines several techniques to determine if the text still fits within it's bounds. If the text area is multiline, it detects if scrollbars are visible. I found a clever way to detect whether or not the scrollbars are visible without requiring any winapi calls using a clever technique I found on one of Patrick Smacchia's posts.. When multiline isn't true, vertical scrollbars never appear so you need to use a different technique which relies on rendering the text using a the Graphics object. The Graphic rendering technique isn't suitable for Multiline boxes because you would have to account for word wrapping.
Here are a few snippets which shows how it works (link to source code is provided below). This code could easily be used to extend other controls.
/// <summary>
/// Sets the font size so the text is as large as possible while still fitting in the text
/// area with out any scrollbars.
/// </summary>
public void ScaleFontToFit()
{
int fontSize = 10;
const int incrementDelta = 5; // amount to increase font by each loop iter.
const int decrementDelta = 1; // amount to decrease to fine tune.
this.SuspendLayout();
// First we set the font size to the minimum. We assume at the minimum size no scrollbars will be visible.
SetFontSize(MinimumFontSize);
// Next, we increment font size until it doesn't fit (or max font size is reached).
for (fontSize = MinFontSize; fontSize < MaxFontSize; fontSize += incrementDelta)
{
SetFontSize(fontSize);
if (!DoesTextFit())
{
//Console.WriteLine("Text Doesn't fit at fontsize = " + fontSize);
break;
}
}
// Finally, we keep decreasing the font size until it fits again.
for (; fontSize > MinFontSize && !DoesTextFit(); fontSize -= decrementDelta)
{
SetFontSize(fontSize);
}
this.ResumeLayout();
}
#region Private Methods
private bool VScrollVisible
{
get
{
Rectangle clientRectangle = this.ClientRectangle;
Size size = this.Size;
return (size.Width - clientRectangle.Width) >= SystemInformation.VerticalScrollBarWidth;
}
}
/**
* returns true when the Text no longer fits in the bounds of this control without scrollbars.
*/
private bool DoesTextFit()
{
if (VScrollVisible)
{
//Console.WriteLine("#1 Vscroll is visible");
return false;
}
// Special logic to handle the single line case... When multiline is false, we cannot rely on scrollbars so alternate methods.
if (this.Multiline == false)
{
Graphics graphics = this.CreateGraphics();
Size stringSize = graphics.MeasureString(this.Text, this.SelectionFont).ToSize();
//Console.WriteLine("String Width/Height: " + stringSize.Width + " " + stringSize.Height + "form... " + this.Width + " " + this.Height);
if (stringSize.Width > this.Width)
{
//Console.WriteLine("#2 Text Width is too big");
return false;
}
if (stringSize.Height > this.Height)
{
//Console.WriteLine("#3 Text Height is too big");
return false;
}
if (this.Lines.Length > 1)
{
//Console.WriteLine("#4 " + this.Lines[0] + " (2): " + this.Lines[1]); // I believe this condition could be removed.
return false;
}
}
return true;
}
private void SetFontSize(int pFontSize)
{
SetFontSize((float)pFontSize);
}
private void SetFontSize(float pFontSize)
{
this.SelectAll();
this.SelectionFont = new Font(this.SelectionFont.FontFamily, pFontSize, this.SelectionFont.Style);
this.SelectionAlignment = HorizontalAlignment;
this.Select(0, 0);
}
#endregion
ScaleFontToFit could be optimized to improve performance but I kept it simple so it'd be easy to understand.
Download the latest source code here. I am still actively working on the project which I developed this control for so it's likely i'll be adding a few other features and enhancements in the near future. So, check the site for the latest code.
My goal is to make this control work on Mac using the Mono framework.
I had a similar requirement for a text box in a panel on a windows form hosted window. (I injected the panel onto the existing form). When the size of the panel changes (in my case) the text would resize to fit the box. Code
parentObject.SizeChanged += (sender, args) =>
{
if (textBox1.Text.Length > 0)
{
int maxSize = 100;
// Make a Graphics object to measure the text.
using (Graphics gr = textBox1.CreateGraphics())
{
for (int i = 1; i <= maxSize; i++)
{
using (var test_font = new Font(textBox1.Font.FontFamily, i))
{
// See how much space the text would
// need, specifying a maximum width.
SizeF text_size =
TextRenderer.MeasureText(
textBox1.Text,
test_font,
new Size(textBox1.Width, int.MaxValue),
TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl);
try
{
if (text_size.Height > textBox1.Height)
{
maxSize = i - 1;
break;
}
}
catch (System.ComponentModel.Win32Exception)
{
// this sometimes throws a "failure to create window handle" error.
// This might happen if the TextBox is invisible and/or
// too small to display a toolbar.
// do whatever here, add/delete, whatever, maybe set to default font size?
maxSize = (int) textBox1.Font.Size;
}
}
}
}
// Use that font size.
textBox1.Font = new Font(textBox1.Font.FontFamily, maxSize);
}
};

Categories