using System; using Orvid.Graphics; using OForms.Controls; using System.Collections.Generic; namespace OForms.Windows { /// /// The class that represents a single window. /// public class Window : IDisposable { /// /// The name of the window. /// public string Name; /// /// The parent WindowManager of this window. /// public WindowManager Parent; /// /// The location of the window. Use this internally only /// if you are reading the location. If you are changing /// the location, use the public Location property instead, /// so that the bounds, get properly reset. /// private Vec2 iLocation; /// /// The location of the window. /// public Vec2 Location { get { return iLocation; } set { Parent.NeedToRedrawAll = true; iLocation = value; ComputeBounds(); // Doesn't change the physical size, // so buffers don't get reset. } } /// /// The minimum allowable window size in the X direction. /// private const int MinXWindowSize = 50; /// /// The minimum allowable window size in the Y direction. /// private const int MinYWindowSize = 40; /// /// The size of the window. Use this internally only /// if you are reading the size. If you are changing /// the size, use the public Size property instead, /// so that the bounds, and buffers get properly reset. /// private Vec2 iSize; /// /// The size of the window. /// public Vec2 Size { get { return iSize; } set { Parent.NeedToRedrawAll = true; if (value.X < MinXWindowSize) { value.X = MinXWindowSize; } if (value.Y < MinYWindowSize) { value.Y = MinYWindowSize; } iSize = value; ComputeBounds(); ResetBuffers(); } } /// /// The Bounds of this window. /// public BoundingBox Bounds; /// /// The BoundingBox for the window's contents. /// private BoundingBox ContentBounds; /// /// The BoundingBox for the header of the window. /// private BoundingBox HeaderBounds; /// /// The BoundingBox for the Close button. /// private BoundingBox CloseButtonBounds; /// /// The BoundingBox for the Maximize/Restore button. /// private BoundingBox MaxButtonBounds; /// /// The BoundingBox for the Minimize button. /// private BoundingBox MinButtonBounds; /// /// The height of the header. (in pixels) /// private const int HeaderHeight = 15; /// /// The thickness of the window border. (in pixels) /// private const int WindowBorderSize = 1; /// /// The overall buffer for the window. /// private Image WindowBuffer; /// /// The buffer for the content of the window. /// private Image ContentBuffer; /// /// The buffer for the header of the window. /// private Image HeaderBuffer; /// /// True if this is the currently active window. /// private bool isActiveWindow = false; /// /// True if this is the currently active window. /// public bool IsActiveWindow { get { return isActiveWindow; } set { if (value != isActiveWindow) { if (value) { FadingIn = true; } isActiveWindow = value; } } } /// /// True if the window is in the process of being dragged. /// private bool IsDragging = false; /// /// True if the window should fade in when selected. /// public bool ShouldFadeIn = true; /// /// True if we are fading in. /// private bool FadingIn = false; /// /// The current state of the window. /// Don't use this field, use CurrentState /// instead. /// private WindowState iCurrentState = WindowState.Normal; /// /// The current WindowState of the window. /// public WindowState CurrentState { get { return iCurrentState; } set { if (iCurrentState != value) { if (iCurrentState == WindowState.Maximized) { if (value == WindowState.Minimized) { WasMaximized = true; iCurrentState = value; } else // It means window state is normal. { WasMaximized = false; this.Location = PrevLoc; this.Size = PrevSize; iCurrentState = value; } } // Should only be set to normal from the minimized state. else if (iCurrentState == WindowState.Minimized) { if (WasMaximized) { iCurrentState = WindowState.Maximized; } else { iCurrentState = value; } } else if (iCurrentState == WindowState.Normal) { if (value == WindowState.Maximized) { PrevLoc = iLocation; this.Location = Vec2.Zero; PrevSize = iSize; this.Size = new Vec2(Parent.Size.X, Parent.Size.Y - WindowManager.TaskBarHeight); iCurrentState = WindowState.Maximized; } else { iCurrentState = value; } } else { throw new Exception("Unknown WindowState!"); } ResetBuffers(); ComputeBounds(); } } } /// /// Location of the window before it was maximized. /// private Vec2 PrevLoc; /// /// Size of the window before it was maximized. /// private Vec2 PrevSize; /// /// True if the window was Maximized when the window got Minimized. /// private bool WasMaximized = false; /// /// True if we're currently resizing. /// private bool IsResizing = false; /// /// The size of the window when resizing started. /// private Vec2 InitSizeOnResizeStart; /// /// The location of the mouse when resizing started. /// private Vec2 InitResizeLocation; /// /// The location of the window when window dragging started. /// private Vec2 InitWindowLocOnDragStart; /// /// The location of the mouse when window dragging started. /// private Vec2 InitDraggingLocation; /// /// True if the last mouse move event had the mouse in /// the window header. /// private bool WasInHeader = false; /// /// True if the last mouse move event had the mouse /// over the Close button. /// private bool WasOverClose = false; /// /// True if the last mouse move event had the mouse /// over the Maximize/Restore button. /// private bool WasOverMax = false; /// /// True if the last mouse move event had the mouse /// over the Minimize button. /// private bool WasOverMin = false; /// /// The color to clear the ContentBuffer with when the window is inactive. /// public Pixel ClearInactiveColor = Colors.Brown; /// /// The color to clear the ContentBuffer with when the window is active. /// public Pixel ClearColor = Colors.Brown; /// /// True if the header has been drawn. /// private bool DrawnHeader = false; /// /// The controls in the window. /// public List Controls = new List(); #region Colors /// /// The current background color of the Close button. /// private Pixel CurCloseButtonColor = Colors.Red; /// /// The current background color of the Maximize/Restore button. /// private Pixel CurMaxButtonColor = Colors.Red; /// /// The current background color of the Minimize button. /// private Pixel CurMinButtonColor = Colors.Red; #region Default Colors /// /// The default background color of the Close button. /// private static Pixel DefaultCloseButtonColor = Colors.Red; /// /// The default background color of the Maximize/Restore button. /// private static Pixel DefaultMaxButtonColor = Colors.Red; /// /// The default background color of the Minimize button. /// private static Pixel DefaultMinButtonColor = Colors.Red; #endregion #endregion /// /// The default constructor for a window. /// /// The initial location of the window. /// The size of the window. /// The name of the window. public Window(Vec2 loc, Vec2 size, string name) { iLocation = loc; iSize = size; Name = name; ResetBuffers(); ComputeBounds(); } /// /// Gets a string representing this window. /// /// The window as a string. public override string ToString() { return this.Name; } /// /// Closes this window. /// public void Close() { Parent.CloseWindow(this); } /// /// Does the actual closing of this window. /// internal void DoClose() { foreach (Control c in Controls) { c.DoClosing(); } this.Dispose(); } /// /// Disposes of all of the resources used by this window. /// public void Dispose() { this.Bounds = null; this.CloseButtonBounds = null; this.ContentBounds = null; this.ContentBuffer = null; foreach (Control c in Controls) { c.DoBeforeDispose(); c.Dispose(); c.DoAfterDispose(); } this.Controls = null; this.HeaderBounds = null; this.HeaderBuffer = null; this.MaxButtonBounds = null; this.MinButtonBounds = null; this.Parent = null; this.WindowBuffer = null; this.Name = null; } /// /// Resets all of the buffers, and redraws the HeaderBuffer. /// private void ResetBuffers() { WindowBuffer = new Image(iSize); WindowBuffer.Clear(Colors.BurlyWood); DrawnHeader = false; RedrawHeader(); // Draw the header. ContentBuffer = new Image(new Vec2(iSize.X - WindowBorderSize - WindowBorderSize, iSize.Y - HeaderHeight)); } /// /// Redraws the header. /// private void RedrawHeader() { HeaderBuffer = new Image(new Vec2(iSize.X, HeaderHeight)); HeaderBuffer.Clear(Colors.BlueViolet); DrawnHeader = false; //HeaderBuffer.DrawString(new Vec2(3, 3), Name, WindowManager.WindowFont, 10, Orvid.Graphics.FontSupport.FontStyle.Normal, Colors.Black); RedrawCloseButton(); RedrawMaxRestButton(); RedrawMinButton(); } #region Draw Close Button /// /// Re-Draws the Close button on the HeaderBuffer. /// private void RedrawCloseButton() { Vec2 tl = new Vec2(iSize.X - HeaderHeight + 2, WindowBorderSize + 1); Vec2 tr = new Vec2(iSize.X - WindowBorderSize - 2, WindowBorderSize + 1); Vec2 br = new Vec2(iSize.X - WindowBorderSize - 2, HeaderHeight - 2); Vec2 bl = new Vec2(iSize.X - HeaderHeight + 2, HeaderHeight - 2); HeaderBuffer.DrawRectangle(tl, br, CurCloseButtonColor); HeaderBuffer.DrawLines(new Vec2[] { tl, tr, br, bl, tl }, Colors.Green); HeaderBuffer.DrawLine(new Vec2(iSize.X - WindowBorderSize - 4, WindowBorderSize + 3), new Vec2(iSize.X - HeaderHeight + 4, HeaderHeight - 4), Colors.Green); HeaderBuffer.DrawLine(new Vec2(iSize.X - HeaderHeight + 4, WindowBorderSize + 3), new Vec2(iSize.X - WindowBorderSize - 4, HeaderHeight - 4), Colors.Green); DrawnHeader = false; } #endregion #region Draw Maximize/Restore Button /// /// Re-Draws the Maximize/Restore button on the HeaderBuffer. /// private void RedrawMaxRestButton() { Vec2 tl = new Vec2(iSize.X - (HeaderHeight + HeaderHeight - 2), WindowBorderSize + 1); Vec2 tr = new Vec2(iSize.X - (HeaderHeight + WindowBorderSize + 2), WindowBorderSize + 1); Vec2 br = new Vec2(iSize.X - (HeaderHeight + WindowBorderSize + 2), HeaderHeight - 2); Vec2 bl = new Vec2(iSize.X - (HeaderHeight + HeaderHeight - 2), HeaderHeight - 2); if (CurrentState == WindowState.Maximized) // Draw Restore Button. { #region Draw Restore Button HeaderBuffer.DrawRectangle(tl, br, CurMaxButtonColor); HeaderBuffer.DrawLines(new Vec2[] { tl, tr, br, bl, tl }, Colors.Green); HeaderBuffer.DrawLines(new Vec2[] { new Vec2(tl.X + 4, tl.Y + 2), new Vec2(tr.X - 2, tr.Y + 2), new Vec2(tr.X - 2, tr.Y + 3), new Vec2(tl.X + 4, tl.Y + 3), new Vec2(tl.X + 4, tl.Y + 5), new Vec2(tl.X + 2, tl.Y + 5), new Vec2(tl.X + 2, bl.Y - 2), new Vec2(tr.X - 4, bl.Y - 2), new Vec2(tr.X - 4, tl.Y + 5), new Vec2(tl.X + 4, tl.Y + 5), new Vec2(tl.X + 4, tl.Y + 6), new Vec2(tl.X + 2, tl.Y + 6), new Vec2(tr.X - 4, tl.Y + 6), new Vec2(tr.X - 4, tl.Y + 7), new Vec2(tr.X - 2, tl.Y + 7), new Vec2(tr.X - 2, tl.Y + 2), }, Colors.Green); #endregion } else // Draw Maximize Button. { #region Draw Maximize Button HeaderBuffer.DrawRectangle(tl, br, CurMaxButtonColor); HeaderBuffer.DrawLines(new Vec2[] { tl, tr, br, bl, tl }, Colors.Green); HeaderBuffer.DrawLines(new Vec2[] { new Vec2(tl.X + 2, tl.Y + 2), new Vec2(tr.X - 2, tr.Y + 2), new Vec2(br.X - 2, br.Y - 2), new Vec2(bl.X + 2, bl.Y - 2), new Vec2(tl.X + 2, tl.Y + 2), }, Colors.Green); HeaderBuffer.DrawLine(new Vec2(tl.X + 2, tl.Y + 3), new Vec2(tr.X - 2, tr.Y + 3), Colors.Green); #endregion } DrawnHeader = false; } #endregion #region Redraw Minimize Button /// /// Re-Draws the Minimize button on the HeaderBuffer. /// private void RedrawMinButton() { Vec2 tl = new Vec2(iSize.X - (HeaderHeight + HeaderHeight + HeaderHeight - 2), WindowBorderSize + 1); Vec2 tr = new Vec2(iSize.X - (HeaderHeight + HeaderHeight + WindowBorderSize + 2), WindowBorderSize + 1); Vec2 br = new Vec2(iSize.X - (HeaderHeight + HeaderHeight + WindowBorderSize + 2), HeaderHeight - 2); Vec2 bl = new Vec2(iSize.X - (HeaderHeight + HeaderHeight + HeaderHeight - 2), HeaderHeight - 2); HeaderBuffer.DrawRectangle(tl, br, CurMinButtonColor); HeaderBuffer.DrawLines(new Vec2[] { tl, tr, br, bl, tl }, Colors.Green); HeaderBuffer.DrawLine(new Vec2(br.X - 3, br.Y - 2), new Vec2(bl.X + 3, bl.Y - 2), Colors.Green); HeaderBuffer.DrawLine(new Vec2(br.X - 3, br.Y - 3), new Vec2(bl.X + 3, bl.Y - 3), Colors.Green); DrawnHeader = false; } #endregion /// /// Re-Computes all of the bounding boxes. /// private void ComputeBounds() { this.Bounds = new BoundingBox( iLocation.X, iLocation.X + iSize.X, iLocation.Y + iSize.Y, iLocation.Y ); this.HeaderBounds = new BoundingBox( iLocation.X, iLocation.X + iSize.X, iLocation.Y + HeaderHeight, iLocation.Y ); this.ContentBounds = new BoundingBox( iLocation.X + WindowBorderSize, iLocation.X + (iSize.X - WindowBorderSize - WindowBorderSize), iLocation.Y + (iSize.Y - HeaderHeight), iLocation.Y + HeaderHeight ); this.CloseButtonBounds = new BoundingBox( iLocation.X + (iSize.X - HeaderHeight + 2), iLocation.X + (iSize.X - WindowBorderSize - 2), iLocation.Y + (HeaderHeight - 2), iLocation.Y + (WindowBorderSize + 1) ); this.MaxButtonBounds = new BoundingBox( iLocation.X + (iSize.X - (HeaderHeight + HeaderHeight - 2)), iLocation.X + (iSize.X - (HeaderHeight + WindowBorderSize + 2)), iLocation.Y + (HeaderHeight - 2), iLocation.Y + (WindowBorderSize + 1) ); this.MinButtonBounds = new BoundingBox( iLocation.X + (iSize.X - (HeaderHeight + HeaderHeight + HeaderHeight - 2)), iLocation.X + (iSize.X - (HeaderHeight + HeaderHeight + WindowBorderSize + 2)), iLocation.Y + (HeaderHeight - 2), iLocation.Y + (WindowBorderSize + 1) ); } /// /// Draws this window on the specified image. /// /// The image to draw the window to. internal void Draw(Image i) { if (!DrawnHeader) { WindowBuffer.DrawImage(Vec2.Zero, HeaderBuffer); WindowBuffer.DrawLine(Vec2.Zero, new Vec2(iSize.X - WindowBorderSize, 0), Colors.Black); WindowBuffer.DrawLine(new Vec2(iSize.X - WindowBorderSize, 0), iSize - WindowBorderSize, Colors.Black); WindowBuffer.DrawLine(new Vec2(0, iSize.Y - WindowBorderSize), Vec2.Zero, Colors.Black); DrawnHeader = true; } if (IsActiveWindow) { if (FadingIn) { ContentBuffer.Clear(new Pixel(ClearColor.R, ClearColor.G, ClearColor.B, 128)); FadingIn = false; } else { ContentBuffer.Clear(ClearColor); } } else { ContentBuffer.Clear(ClearInactiveColor); } foreach (Control c in Controls) { c.Draw(ContentBuffer); } WindowBuffer.DrawImage(new Vec2(WindowBorderSize, HeaderHeight), ContentBuffer); WindowBuffer.DrawLine(iSize - WindowBorderSize, new Vec2(0, iSize.Y - WindowBorderSize), Colors.Black); WindowBuffer.DrawLine(new Vec2(WindowBorderSize, HeaderHeight), new Vec2(iSize.X - WindowBorderSize - 1, HeaderHeight), new Pixel(214, 211, 206, 255)); i.DrawImage(iLocation, WindowBuffer); } #region Do Events /// /// Processes a MouseClick event. /// /// The location of the mouse. /// The buttons that are pressed. internal void DoClick(Vec2 loc, MouseButtons button) { if (IsActiveWindow) { if (ContentBounds.IsInBounds(loc)) { Vec2 RelativeLoc; foreach (Control c in Controls) { RelativeLoc = new Vec2(loc.X - Location.X - WindowBorderSize, loc.Y - Location.Y - HeaderHeight); if (c.Bounds.IsInBounds(RelativeLoc)) { c.DoClick(RelativeLoc, button); } } } else { if (HeaderBounds.IsInBounds(loc)) { if (CloseButtonBounds.IsInBounds(loc)) { this.Close(); } else if (MaxButtonBounds.IsInBounds(loc)) { if (CurrentState == WindowState.Maximized) { Parent.RestoreWindow(this); } else { Parent.MaximizeWindow(this); } } else if (MinButtonBounds.IsInBounds(loc)) { Parent.MinimizeWindow(this); } } else // Window border was clicked, and we don't care about it. { //throw new Exception("Unknown part of the window clicked!"); } } } else { Parent.BringWindowToFront(this); } } /// /// Processes a MouseUp event. /// /// The location of the mouse. /// The buttons that are still pressed down. internal void DoMouseUp(Vec2 loc, MouseButtons button) { if (IsDragging) { IsDragging = false; InitDraggingLocation = null; InitWindowLocOnDragStart = null; } else if (IsResizing) { IsResizing = false; InitResizeLocation = null; InitSizeOnResizeStart = null; } else { Vec2 RelativeLoc; foreach (Control c in Controls) { if (c.IsMouseDown) { RelativeLoc = new Vec2(loc.X - Location.X - WindowBorderSize, loc.Y - Location.Y - HeaderHeight); c.IsMouseDown = false; c.DoMouseUp(RelativeLoc, button); } } } } /// /// Processes a MouseDown event. /// /// The location of the mouse. /// The buttons that are down. internal void DoMouseDown(Vec2 loc, MouseButtons button) { if (ContentBounds.IsInBounds(loc)) { Vec2 RelativeLoc; foreach (Control c in Controls) { RelativeLoc = new Vec2(loc.X - Location.X - WindowBorderSize, loc.Y - Location.Y - HeaderHeight); if (c.Bounds.IsInBounds(RelativeLoc)) { c.IsMouseDown = true; c.DoMouseDown(RelativeLoc, button); } } } else { if (HeaderBounds.IsInBounds(loc)) { IsDragging = true; InitDraggingLocation = loc; InitWindowLocOnDragStart = this.iLocation; } else // The border of the window was pressed, begin resize. { IsResizing = true; InitSizeOnResizeStart = this.iSize; InitResizeLocation = loc; } } } /// /// Checks if we were over buttons, /// and reset their colors if needed. /// private void CheckOldButtons() { if (WasOverClose) { CurCloseButtonColor = DefaultCloseButtonColor; WasOverClose = false; RedrawCloseButton(); } else if (WasOverMax) { CurMaxButtonColor = DefaultMaxButtonColor; WasOverMax = false; RedrawMaxRestButton(); } else if (WasOverMin) { CurMinButtonColor = DefaultMinButtonColor; WasOverMin = false; RedrawMinButton(); } } /// /// Processes a MouseMove event. /// /// The new location of the mouse. /// The buttons on the mouse that are pressed. internal void DoMouseMove(Vec2 newLoc, MouseButtons button) { if (IsDragging) { Vec2 Transform = newLoc - InitDraggingLocation; this.Location = InitWindowLocOnDragStart + Transform; } else if (IsResizing) { Vec2 Transform = newLoc - InitResizeLocation; this.Size = InitSizeOnResizeStart + Transform; } else { if (WasInHeader) { if (HeaderBounds.IsInBounds(newLoc)) { if (CloseButtonBounds.IsInBounds(newLoc)) { if (!WasOverClose) { CheckOldButtons(); CurCloseButtonColor = Colors.Brown; WasOverClose = true; RedrawCloseButton(); return; } // Otherwise we've already done whats // needed for being over the close button. return; } else if (MaxButtonBounds.IsInBounds(newLoc)) { if (!WasOverMax) { CheckOldButtons(); CurMaxButtonColor = Colors.Brown; WasOverMax = true; RedrawMaxRestButton(); return; } // Otherwise we've already done whats // needed for being over the maximize/restore button. return; } else if (MinButtonBounds.IsInBounds(newLoc)) { if (!WasOverMin) { CheckOldButtons(); CurMinButtonColor = Colors.Brown; WasOverMin = true; RedrawMinButton(); return; } // Otherwise we've already done whats // needed for being over the minimize button. return; } else { CheckOldButtons(); } } else // It's not in the header anymore. { CheckOldButtons(); WasInHeader = false; } } if (ContentBounds.IsInBounds(newLoc)) { Vec2 RelativeLoc; foreach (Control c in Controls) { RelativeLoc = new Vec2(newLoc.X - Location.X - WindowBorderSize, newLoc.Y - Location.Y - HeaderHeight); if (!c.IsIn) { if (c.Bounds.IsInBounds(RelativeLoc)) { c.IsIn = true; c.DoMouseEnter(RelativeLoc, button); } } else { if (!c.Bounds.IsInBounds(RelativeLoc)) { c.IsIn = false; c.DoMouseLeave(RelativeLoc, button); } } } } else if (HeaderBounds.IsInBounds(newLoc)) { WasInHeader = true; if (CloseButtonBounds.IsInBounds(newLoc)) { CurCloseButtonColor = Colors.Brown; WasOverClose = true; RedrawCloseButton(); } else if (MaxButtonBounds.IsInBounds(newLoc)) { CurMaxButtonColor = Colors.Brown; WasOverMax = true; RedrawMaxRestButton(); } else if (MinButtonBounds.IsInBounds(newLoc)) { CurMinButtonColor = Colors.Brown; WasOverMin = true; RedrawMinButton(); } } else // the mouse was in the window border. { } } } #endregion public static bool operator ==(Window w, Window w2) { if (w.Name == w2.Name && w.Size == w2.Size && w.Location == w2.Location) { return true; } return false; } public static bool operator !=(Window w, Window w2) { return ((w == w2) == false); } public override bool Equals(object obj) { return (this == (Window)obj); } public override int GetHashCode() { return base.GetHashCode(); } } }