aboutsummaryrefslogtreecommitdiff
path: root/ShiftOS.Frontend/GUI
diff options
context:
space:
mode:
Diffstat (limited to 'ShiftOS.Frontend/GUI')
-rw-r--r--ShiftOS.Frontend/GUI/Button.cs56
-rw-r--r--ShiftOS.Frontend/GUI/Control.cs707
-rw-r--r--ShiftOS.Frontend/GUI/ItemGroup.cs64
-rw-r--r--ShiftOS.Frontend/GUI/ListBox.cs162
-rw-r--r--ShiftOS.Frontend/GUI/PictureBox.cs128
-rw-r--r--ShiftOS.Frontend/GUI/ProgressBar.cs58
-rw-r--r--ShiftOS.Frontend/GUI/TextControl.cs112
-rw-r--r--ShiftOS.Frontend/GUI/TextInput.cs138
8 files changed, 1425 insertions, 0 deletions
diff --git a/ShiftOS.Frontend/GUI/Button.cs b/ShiftOS.Frontend/GUI/Button.cs
new file mode 100644
index 0000000..c2e55b9
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/Button.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Xna.Framework;
+using ShiftOS.Engine;
+using ShiftOS.Frontend.GraphicsSubsystem;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public class Button : TextControl
+ {
+ public Button()
+ {
+ TextAlign = TextAlign.MiddleCenter;
+ Text = "Click me!";
+ }
+
+ protected override void OnLayout()
+ {
+ if(AutoSize == true)
+ {
+ int borderwidth = SkinEngine.LoadedSkin.ButtonBorderWidth * 2;
+
+ using (var gfx = Graphics.FromImage(new Bitmap(1, 1)))
+ {
+ var measure = gfx.MeasureString(this.Text, this.Font);
+ Width = borderwidth + (int)measure.Width + 16;
+ Height = borderwidth + (int)measure.Height + 12;
+ }
+ }
+ }
+
+ protected override void OnPaint(GraphicsContext gfx)
+ {
+ var bgCol = UIManager.SkinTextures["ButtonBackgroundColor"];
+ var fgCol = SkinEngine.LoadedSkin.ControlTextColor.ToMonoColor();
+ if (ContainsMouse)
+ bgCol = UIManager.SkinTextures["ButtonHoverColor"];
+ if (MouseLeftDown)
+ bgCol = UIManager.SkinTextures["ButtonPressedColor"];
+
+ gfx.DrawRectangle(0, 0, Width, Height, UIManager.SkinTextures["ControlTextColor"]);
+ gfx.DrawRectangle(1, 1, Width - 2, Height - 2, bgCol);
+
+ var measure = gfx.MeasureString(Text, Font);
+
+ var loc = new Vector2((Width - measure.X) / 2, (Height - measure.Y) / 2);
+
+ gfx.DrawString(Text, (int)loc.X, (int)loc.Y, fgCol, Font);
+
+ }
+ }
+}
diff --git a/ShiftOS.Frontend/GUI/Control.cs b/ShiftOS.Frontend/GUI/Control.cs
new file mode 100644
index 0000000..c16792b
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/Control.cs
@@ -0,0 +1,707 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using System.Drawing;
+using ShiftOS.Frontend.GraphicsSubsystem;
+using System.Drawing.Imaging;
+using System.Drawing.Drawing2D;
+using Microsoft.Xna.Framework;
+using System.Runtime.InteropServices;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public abstract class Control
+ {
+ private int _x = 0;
+ private int _y = 0;
+ private int _w = 0;
+ private int _h = 0;
+ private Control _parent = null;
+ private List<Control> _children = new List<Control>();
+ private bool _wasMouseInControl = false;
+ private bool _leftState = false;
+ private bool _rightState = false;
+ private bool _middleState = false;
+ private bool _visible = true;
+ private DockStyle _dock = DockStyle.None;
+ private bool _focused = false;
+ private bool _autoSize = false;
+ private double _opacity = 1.0;
+ private bool _invalidated = true;
+ private Bitmap _texCache = null;
+ private Anchor _anchor = null;
+ private int _mouseX = 0;
+ private int _mouseY = 0;
+ private bool _captureMouse = false;
+
+ public bool RequiresPaint
+ {
+ get
+ {
+ bool requires_child_repaint = false;
+ foreach (var child in _children)
+ {
+ requires_child_repaint = child.RequiresPaint;
+ if (requires_child_repaint)
+ break;
+ }
+ return _invalidated || requires_child_repaint;
+ }
+ }
+
+ public Image TextureCache
+ {
+ get
+ {
+ return _texCache;
+ }
+ }
+
+ public byte[] PaintCache
+ {
+ get
+ {
+ var data = _texCache.LockBits(new System.Drawing.Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+ var rgb = new byte[Math.Abs(data.Stride) * data.Height];
+ Marshal.Copy(data.Scan0, rgb, 0, rgb.Length);
+ for(int i = 0; i < rgb.Length; i += 4)
+ {
+ byte r = rgb[i];
+ byte b = rgb[i + 2];
+ rgb[i] = b;
+ rgb[i + 2] = r;
+ }
+ _texCache.UnlockBits(data);
+ return rgb;
+ }
+ }
+
+ public bool CaptureMouse
+ {
+ get
+ {
+ return _captureMouse;
+ }
+ set
+ {
+ _captureMouse = value;
+ }
+ }
+
+ public int MouseX
+ {
+ get
+ {
+ return _mouseX;
+ }
+ }
+
+ public int MouseY
+ {
+ get
+ {
+ return _mouseY;
+ }
+ }
+
+
+ public Anchor Anchor
+ {
+ get
+ {
+ return _anchor;
+ }
+ set
+ {
+ if (_anchor == value)
+ return;
+
+ _anchor = value;
+ Invalidate();
+ }
+ }
+
+ public void Invalidate()
+ {
+ _invalidated = true;
+ foreach(var child in _children)
+ {
+ child.Invalidate();
+ }
+ }
+
+ public double Opacity
+ {
+ get
+ {
+ return _opacity;
+ }
+ set
+ {
+ if (_opacity == value)
+ return;
+ _opacity = value;
+ Invalidate();
+ }
+ }
+
+ public bool AutoSize
+ {
+ get
+ {
+ return _autoSize;
+ }
+ set
+ {
+ _autoSize = value;
+ }
+ }
+
+ //Thank you, StackOverflow.
+ public static Bitmap ResizeImage(Image image, int width, int height)
+ {
+ var destRect = new System.Drawing.Rectangle(0, 0, width, height);
+ var destImage = new Bitmap(width, height);
+
+ destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
+
+ using (var graphics = Graphics.FromImage(destImage))
+ {
+ graphics.CompositingMode = CompositingMode.SourceCopy;
+ graphics.CompositingQuality = CompositingQuality.HighQuality;
+ graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
+ graphics.SmoothingMode = SmoothingMode.HighQuality;
+ graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
+
+ using (var wrapMode = new ImageAttributes())
+ {
+ wrapMode.SetWrapMode(WrapMode.TileFlipXY);
+ graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
+ }
+ }
+
+ return destImage;
+ }
+
+ public DockStyle Dock
+ {
+ get
+ {
+ return _dock;
+ }
+ set
+ {
+ _dock = value;
+ }
+ }
+
+ public bool ContainsMouse
+ {
+ get { return _wasMouseInControl; }
+ }
+
+ public bool Visible
+ {
+ get
+ {
+ return _visible;
+ }
+ set
+ {
+ if (_visible == value)
+ return;
+
+ _visible = value;
+ Invalidate();
+ }
+ }
+
+ public void AddControl(Control ctrl)
+ {
+ if (!_children.Contains(ctrl))
+ {
+ ctrl._parent = this;
+ _children.Add(ctrl);
+ Invalidate();
+ }
+ }
+
+ public bool MouseLeftDown
+ {
+ get
+ {
+ return _leftState;
+ }
+ }
+
+ public bool MouseMiddleDown
+ {
+ get
+ {
+ return _middleState;
+ }
+ }
+
+ public bool MouseRightDown
+ {
+ get
+ {
+ return _rightState;
+ }
+ }
+
+
+
+ public int X
+ {
+ get
+ {
+ return _x;
+ }
+ set
+ {
+ if (_x == value)
+ return;
+ _x = value;
+ Invalidate();
+ }
+ }
+
+ public int Y
+ {
+ get
+ {
+ return _y;
+ }
+ set
+ {
+ if (_y == value)
+ return;
+ _y = value;
+ Invalidate();
+ }
+ }
+
+ public int Width
+ {
+ get
+ {
+ return _w;
+ }
+ set
+ {
+ if (_w == value)
+ return;
+ _w = value;
+ Invalidate();
+ }
+ }
+
+ public int Height
+ {
+ get
+ {
+ return _h;
+ }
+ set
+ {
+ if (_h == value)
+ return;
+ _h = value;
+ Invalidate();
+ }
+ }
+
+ public Control Parent
+ {
+ get
+ {
+ return _parent;
+ }
+ }
+
+ public Control[] Children
+ {
+ get
+ {
+ return _children.ToArray();
+ }
+ }
+
+ public Point PointToParent(int x, int y)
+ {
+ return new Point(x + _x, y + _y);
+ }
+
+ public Point PointToScreen(int x, int y)
+ {
+ var parentCoords = PointToParent(x, y);
+ Control parent = this._parent;
+ while(parent != null)
+ {
+ parentCoords = parent.PointToParent(parentCoords.X, parentCoords.Y);
+ parent = parent.Parent;
+ }
+ return parentCoords;
+ }
+
+ public void ClearControls()
+ {
+ _children.Clear();
+ Invalidate();
+ }
+
+ public Point PointToLocal(int x, int y)
+ {
+ return new GUI.Point(x - _x, y - _y);
+ }
+
+ public virtual void MouseStateChanged() { }
+
+ protected virtual void OnPaint(GraphicsContext gfx)
+ {
+ gfx.DrawRectangle(0, 0, Width, Height, UIManager.SkinTextures["ControlColor"]);
+ }
+
+ public void SendToBack()
+ {
+ if(_parent != null)
+ {
+ _parent._children.Remove(this);
+ _parent._children.Insert(0, this);
+ }
+ else
+ {
+ UIManager.SendToBack(this);
+ }
+ }
+
+ public void InvalidateTopLevel()
+ {
+ var parent = this;
+ while (parent.Parent != null)
+ parent = parent.Parent;
+ parent.Invalidate();
+ }
+
+ public void Paint(GraphicsContext gfx)
+ {
+ if (_visible == true)
+ {
+ OnPaint(gfx);
+ int draw_x = gfx.X;
+ int draw_y = gfx.Y;
+ int draw_width = gfx.Width;
+ int draw_height = gfx.Height;
+ foreach (var ctrl in _children)
+ {
+ if (ctrl.Visible == true)
+ {
+ gfx.X = draw_x + ctrl.X;
+ gfx.Y = draw_y + ctrl.Y;
+ gfx.Width = ctrl.Width;
+ gfx.Height = ctrl.Height;
+ ctrl.Paint(gfx);
+ gfx.X = draw_x;
+ gfx.Y = draw_y;
+ }
+ gfx.Width = draw_width;
+ gfx.Height = draw_height;
+ }
+ _invalidated = false;
+ }
+ }
+
+ public void Layout()
+ {
+ //Dock style
+ if(_parent != null)
+ {
+ if(_anchor != null)
+ {
+
+ }
+
+ switch (_dock)
+ {
+ case DockStyle.Top:
+ X = 0;
+ Y = 0;
+ Width = _parent.Width;
+ break;
+ case DockStyle.Left:
+ X = 0;
+ Y = 0;
+ Height = _parent.Height;
+ break;
+ case DockStyle.Right:
+ Y = 0;
+ X = _parent.Width - Width;
+ Height = _parent.Height;
+ break;
+ case DockStyle.Bottom:
+ X = 0;
+ Y = _parent.Height - Height;
+ Width = _parent.Width;
+ break;
+ case DockStyle.Fill:
+ X = 0;
+ Y = 0;
+ Width = _parent.Width;
+ Height = _parent.Height;
+ break;
+ }
+ }
+ OnLayout();
+ foreach (var child in _children)
+ child.Layout();
+ }
+
+ protected virtual void OnLayout()
+ {
+ //do nothing
+ }
+
+ public bool IsFocusedControl
+ {
+ get
+ {
+ return UIManager.FocusedControl == this;
+ }
+ }
+
+ public bool ContainsFocusedControl
+ {
+ get
+ {
+ if (UIManager.FocusedControl == null)
+ return false;
+ else
+ {
+ bool contains = false;
+
+ var ctrl = UIManager.FocusedControl;
+ while(ctrl.Parent != null)
+ {
+ ctrl = ctrl.Parent;
+ if (ctrl == this)
+ contains = true;
+ }
+ return contains;
+ }
+ }
+ }
+
+ public virtual bool ProcessMouseState(MouseState state)
+ {
+ //If we aren't rendering the control, we aren't accepting input.
+ if (_visible == false)
+ return false;
+
+
+ //Firstly, we get the mouse coordinates in the local space
+ var coords = PointToLocal(state.Position.X, state.Position.Y);
+ _mouseX = coords.X;
+ _mouseY = coords.Y;
+ //Now we check if the mouse is within the bounds of the control
+ if(coords.X >= 0 && coords.Y >= 0 && coords.X <= _w && coords.Y <= _h)
+ {
+ //We're in the local space. Let's fire the MouseMove event.
+ MouseMove?.Invoke(coords);
+ //Also, if the mouse hasn't been in the local space last time it moved, fire MouseEnter.
+ if(_wasMouseInControl == false)
+ {
+ _wasMouseInControl = true;
+ MouseEnter?.Invoke();
+ Invalidate();
+ }
+
+ //Things are going to get a bit complicated.
+ //Firstly, we need to find out if we have any children.
+ bool _requiresMoreWork = true;
+ if(_children.Count > 0)
+ {
+ //We do. We're going to iterate through them all and process the mouse state.
+ foreach(var control in _children)
+ {
+
+ //If the process method returns true, then we do not need to do anything else on our end.
+
+ //We need to first create a new mousestate object with the new coordinates
+
+ var nstate = new MouseState(coords.X, coords.Y, state.ScrollWheelValue, state.LeftButton, state.MiddleButton, state.RightButton, state.XButton1, state.XButton2);
+ //pass that state to the process method, and set the _requiresMoreWork value to the opposite of the return value
+ _requiresMoreWork = !control.ProcessMouseState(nstate);
+ //If it's false, break the loop.
+ if (_requiresMoreWork == false)
+ break;
+ }
+ }
+
+ //If we need to do more work...
+ if(_requiresMoreWork == true)
+ {
+ bool fire = false; //so we know to fire a MouseStateChanged method
+ //Let's get the state values of each button
+ bool ld = state.LeftButton == ButtonState.Pressed;
+ bool md = state.MiddleButton == ButtonState.Pressed;
+ bool rd = state.RightButton == ButtonState.Pressed;
+ if(ld != _leftState || md != _middleState || rd != _rightState)
+ {
+ fire = true;
+ }
+ if (_leftState == true && ld == false)
+ {
+ Click?.Invoke();
+ Invalidate();
+ MouseUp?.Invoke();
+ }
+ if (_leftState == false && ld == true)
+ {
+ var focused = UIManager.FocusedControl;
+ UIManager.FocusedControl = this;
+ focused?.InvalidateTopLevel();
+ InvalidateTopLevel();
+ MouseDown?.Invoke();
+
+ }
+ _leftState = ld;
+ _middleState = md;
+ _rightState = rd;
+ if (fire)
+ MouseStateChanged();
+ }
+ return true;
+ }
+ else
+ {
+ _leftState = false;
+ _rightState = false;
+ _middleState = false;
+ MouseStateChanged();
+ //If the mouse was in local space before, fire MouseLeave
+ if (_wasMouseInControl == true)
+ {
+ if (CaptureMouse == true)
+ {
+ _wasMouseInControl = true;
+ int newX = MathHelper.Clamp(state.X, X, X + Width);
+ int newY = MathHelper.Clamp(state.Y, Y, Y + Height);
+ Mouse.SetPosition(newX, newY);
+
+ }
+ else
+ {
+ _wasMouseInControl = false;
+ MouseLeave?.Invoke();
+ Invalidate();
+ }
+ }
+ }
+ if (CaptureMouse == true)
+ {
+ _mouseX = coords.X;
+ _mouseY = coords.Y;
+ Layout();
+ _wasMouseInControl = true;
+ int newX = MathHelper.Clamp(state.X, X, X + Width);
+ int newY = MathHelper.Clamp(state.Y, Y, Y + Height);
+ Mouse.SetPosition(newX, newY);
+ return true;
+ }
+
+ //Mouse is not in the local space, don't do anything.
+ return false;
+ }
+
+ protected virtual void OnKeyEvent(KeyEvent e)
+ {
+
+ }
+
+ public void ProcessKeyEvent(KeyEvent e)
+ {
+ OnKeyEvent(e);
+ KeyEvent?.Invoke(e);
+ }
+
+ public event Action<Point> MouseMove;
+ public event Action MouseEnter;
+ public event Action MouseLeave;
+ public event Action Click;
+ public event Action<KeyEvent> KeyEvent;
+ public event Action MouseDown;
+ public event Action MouseUp;
+ }
+
+ public struct Point
+ {
+ public Point(int x, int y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+
+ public enum DockStyle
+ {
+ None,
+ Top,
+ Bottom,
+ Left,
+ Right,
+ Fill
+ }
+
+ //Thanks, StackOverflow.
+ public static class BitmapExtensions
+ {
+ public static Image SetOpacity(this Image image, float opacity)
+ {
+ var colorMatrix = new ColorMatrix();
+ colorMatrix.Matrix33 = opacity;
+ var imageAttributes = new ImageAttributes();
+ imageAttributes.SetColorMatrix(
+ colorMatrix,
+ ColorMatrixFlag.Default,
+ ColorAdjustType.Bitmap);
+ var output = new Bitmap(image.Width, image.Height);
+ using (var gfx = Graphics.FromImage(output))
+ {
+ gfx.SmoothingMode = SmoothingMode.AntiAlias;
+ gfx.DrawImage(
+ image,
+ new System.Drawing.Rectangle(0, 0, image.Width, image.Height),
+ 0,
+ 0,
+ image.Width,
+ image.Height,
+ GraphicsUnit.Pixel,
+ imageAttributes);
+ }
+ return output;
+ }
+ }
+
+ [Flags]
+ public enum AnchorStyle
+ {
+ Top,
+ Left,
+ Bottom,
+ Right
+ }
+
+ public class Anchor
+ {
+ public AnchorStyle Style { get; set; }
+ public int Distance { get; set; }
+ }
+}
diff --git a/ShiftOS.Frontend/GUI/ItemGroup.cs b/ShiftOS.Frontend/GUI/ItemGroup.cs
new file mode 100644
index 0000000..e52a17f
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/ItemGroup.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public class ItemGroup : Control
+ {
+ private int _gap = 3;
+ private FlowDirection _flowDir = FlowDirection.LeftToRight;
+ private int _initialgap = 2;
+
+ protected override void OnLayout()
+ {
+ if (AutoSize)
+ {
+ int _highesty = _initialgap;
+ int _xx = _initialgap;
+ foreach(var ctrl in Children)
+ {
+ _xx += ctrl.Width + _gap;
+ if (_highesty < ctrl.Height + _initialgap + _gap)
+ _highesty = ctrl.Height + _initialgap + _gap;
+ }
+ Width = _xx;
+ Height = _highesty;
+ }
+
+ int _x = _initialgap;
+ int _y = _initialgap;
+ int _maxYForRow = 0;
+ foreach (var ctrl in Children)
+ {
+ if (_x + ctrl.Width + _gap > Width)
+ {
+ _x = _initialgap;
+ _y = _maxYForRow;
+ _maxYForRow = 0;
+ if (_maxYForRow < ctrl.Height + _gap)
+ _maxYForRow = ctrl.Height + _gap;
+ }
+ ctrl.X = _x;
+ ctrl.Y = _y;
+ ctrl.Dock = DockStyle.None;
+ ctrl.Layout();
+ _x += ctrl.Width + _gap;
+
+ if (_maxYForRow < ctrl.Height + _gap)
+ _maxYForRow = ctrl.Height + _gap;
+
+ }
+ }
+ }
+
+ public enum FlowDirection
+ {
+ LeftToRight,
+ TopDown,
+ RightToLeft,
+ BottomUp
+ }
+}
diff --git a/ShiftOS.Frontend/GUI/ListBox.cs b/ShiftOS.Frontend/GUI/ListBox.cs
new file mode 100644
index 0000000..f9354e0
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/ListBox.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Xna.Framework;
+using ShiftOS.Frontend.GraphicsSubsystem;
+using static ShiftOS.Engine.SkinEngine;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public class ListBox : GUI.Control
+ {
+ private int fontheight = 0;
+ private List<object> items = new List<object>();
+ private int selectedIndex = -1;
+ private int itemOffset = 0;
+ private int itemsPerPage = 1;
+
+ public int SelectedIndex
+ {
+ get
+ {
+ return MathHelper.Clamp(selectedIndex, 0, items.Count - 1);
+ }
+ set
+ {
+ selectedIndex = MathHelper.Clamp(value, 0, items.Count - 1);
+ RecalculateItemsPerPage();
+ SelectedIndexChanged?.Invoke();
+ }
+ }
+
+ public object SelectedItem
+ {
+ get
+ {
+ try
+ {
+ return items[SelectedIndex];
+ }
+ catch
+ {
+ return "";
+ }
+ }
+ }
+
+ public void ClearItems()
+ {
+ selectedIndex = -1;
+ items.Clear();
+ SelectedIndexChanged?.Invoke();
+ Invalidate();
+ }
+
+ public void AddItem(object item)
+ {
+ items.Add(item);
+ RecalculateItemsPerPage();
+ Invalidate();
+ }
+
+ public void RemoveItem(object item)
+ {
+ items.Remove(item);
+ selectedIndex = -1;
+ RecalculateItemsPerPage();
+ SelectedIndexChanged?.Invoke();
+ Invalidate();
+ }
+
+ public void RecalculateItemsPerPage()
+ {
+ itemsPerPage = 0;
+ while(itemsPerPage * fontheight < Height && itemsPerPage < items.Count - 1)
+ {
+ itemsPerPage++;
+ }
+ //We have the amount of items we can fit on screen.
+ //Now let's calculate the offset based on this, as well
+ //as the currently selected item.
+ //of course, if there IS one.
+ if(selectedIndex > -1)
+ {
+ if(selectedIndex >= items.Count)
+ {
+ selectedIndex = items.Count - 1;
+ }
+ while(this.itemOffset > selectedIndex)
+ {
+ itemOffset--;
+ }
+ while(this.itemOffset + itemsPerPage < selectedIndex)
+ {
+ itemOffset++;
+ }
+ }
+ }
+
+ protected override void OnKeyEvent(KeyEvent e)
+ {
+ if(e.Key== Microsoft.Xna.Framework.Input.Keys.Down)
+ {
+ if(selectedIndex < items.Count - 2)
+ {
+ selectedIndex++;
+ RecalculateItemsPerPage();
+ SelectedIndexChanged?.Invoke();
+ Invalidate();
+ }
+ }
+ else if(e.Key == Microsoft.Xna.Framework.Input.Keys.Up)
+ {
+ if(selectedIndex > 0)
+ {
+ selectedIndex--;
+ RecalculateItemsPerPage();
+ SelectedIndexChanged?.Invoke();
+ Invalidate();
+ }
+ }
+ }
+
+ protected override void OnPaint(GraphicsContext gfx)
+ {
+ gfx.Clear(LoadedSkin.ControlTextColor.ToMonoColor());
+ gfx.DrawRectangle(1, 1, Width - 2, Height - 2, UIManager.SkinTextures["ControlColor"]);
+ for(int i = itemOffset; i < items.Count - 1 && i < itemsPerPage; i++)
+ {
+ int x = 1;
+ int y = fontheight * (i - itemOffset);
+ int width = Width - 2;
+ int height = fontheight;
+ if(i == selectedIndex)
+ {
+ //draw the string as selected
+ gfx.DrawRectangle(x, y, width, height, UIManager.SkinTextures["ControlTextColor"]);
+ gfx.DrawString(items[i].ToString(), x, y, LoadedSkin.ControlColor.ToMonoColor(), LoadedSkin.MainFont);
+ }
+ else
+ {
+ gfx.DrawRectangle(x, y, width, height, UIManager.SkinTextures["ControlColor"]);
+ gfx.DrawString(items[i].ToString(), x, y, LoadedSkin.ControlTextColor.ToMonoColor(), LoadedSkin.MainFont);
+
+ }
+ }
+ }
+
+ protected override void OnLayout()
+ {
+ if(fontheight != LoadedSkin.MainFont.Height)
+ {
+ fontheight = LoadedSkin.MainFont.Height;
+ Invalidate();
+ }
+ base.OnLayout();
+ }
+
+ public event Action SelectedIndexChanged;
+ }
+}
diff --git a/ShiftOS.Frontend/GUI/PictureBox.cs b/ShiftOS.Frontend/GUI/PictureBox.cs
new file mode 100644
index 0000000..6f60b29
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/PictureBox.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ShiftOS.Engine;
+using System.Drawing.Imaging;
+using ShiftOS.Frontend.GraphicsSubsystem;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public class PictureBox : Control
+ {
+ private Texture2D img = null;
+ private ImageLayout _layout = ImageLayout.Fit;
+
+ public ImageLayout ImageLayout
+ {
+ get
+ {
+ return _layout;
+ }
+ set
+ {
+ _layout = value;
+ }
+ }
+
+ public Texture2D Image
+ {
+ get
+ {
+ return img;
+ }
+ set
+ {
+ if (img != null)
+ img.Dispose();
+ img = value;
+ }
+ }
+
+ protected override void OnLayout()
+ {
+ if (AutoSize)
+ {
+ Width = (img == null) ? 0 : img.Width;
+ Height = (img == null) ? 0 : img.Height;
+ }
+ }
+
+ protected override void OnPaint(GraphicsContext gfx)
+ {
+ switch (_layout)
+ {
+ case ImageLayout.Stretch:
+ gfx.DrawRectangle(0, 0, Width, Height, Image);
+ break;
+ case ImageLayout.None:
+ gfx.DrawRectangle(0, 0, Image.Width, Image.Height, Image);
+ break;
+ }
+
+ }
+
+ //Again, thanks StackOverflow
+ static Image FixedSize(Image imgPhoto, int Width, int Height)
+ {
+ int sourceWidth = imgPhoto.Width;
+ int sourceHeight = imgPhoto.Height;
+ int sourceX = 0;
+ int sourceY = 0;
+ int destX = 0;
+ int destY = 0;
+
+ float nPercent = 0;
+ float nPercentW = 0;
+ float nPercentH = 0;
+
+ nPercentW = ((float)Width / (float)sourceWidth);
+ nPercentH = ((float)Height / (float)sourceHeight);
+ if (nPercentH < nPercentW)
+ {
+ nPercent = nPercentH;
+ destX = System.Convert.ToInt16((Width -
+ (sourceWidth * nPercent)) / 2);
+ }
+ else
+ {
+ nPercent = nPercentW;
+ destY = System.Convert.ToInt16((Height -
+ (sourceHeight * nPercent)) / 2);
+ }
+
+ int destWidth = (int)(sourceWidth * nPercent);
+ int destHeight = (int)(sourceHeight * nPercent);
+
+ Bitmap bmPhoto = new Bitmap(Width, Height,
+ PixelFormat.Format24bppRgb);
+ bmPhoto.SetResolution(imgPhoto.HorizontalResolution,
+ imgPhoto.VerticalResolution);
+
+ Graphics grPhoto = Graphics.FromImage(bmPhoto);
+ grPhoto.Clear(SkinEngine.LoadedSkin.ControlColor);
+ grPhoto.InterpolationMode =
+ InterpolationMode.HighQualityBicubic;
+
+ grPhoto.DrawImage(imgPhoto,
+ new Rectangle(destX, destY, destWidth, destHeight),
+ new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight),
+ GraphicsUnit.Pixel);
+
+ grPhoto.Dispose();
+ return bmPhoto;
+ }
+ }
+
+ public enum ImageLayout
+ {
+ None,
+ Stretch,
+ Tile,
+ Fit,
+ }
+}
diff --git a/ShiftOS.Frontend/GUI/ProgressBar.cs b/ShiftOS.Frontend/GUI/ProgressBar.cs
new file mode 100644
index 0000000..a13bbf8
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/ProgressBar.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ShiftOS.Frontend.GraphicsSubsystem;
+using static ShiftOS.Engine.SkinEngine;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public class ProgressBar : Control
+ {
+ private int _maximum = 100;
+ private int _value = 0;
+
+ public int Maximum
+ {
+ get
+ {
+ return _maximum;
+ }
+ set
+ {
+ _maximum = value;
+ }
+ }
+
+ public int Value
+ {
+ get
+ {
+ return _value;
+ }
+ set
+ {
+ _value = value;
+ }
+ }
+
+ protected override void OnPaint(GraphicsContext gfx)
+ {
+ gfx.Clear(LoadedSkin.ProgressBarBackgroundColor.ToMonoColor());
+ int w = (int)linear(_value, 0, _maximum, 0, Width);
+ gfx.DrawRectangle(0, 0, w, Height, LoadedSkin.ProgressColor.ToMonoColor());
+ }
+
+ static public double linear(double x, double x0, double x1, double y0, double y1)
+ {
+ if ((x1 - x0) == 0)
+ {
+ return (y0 + y1) / 2;
+ }
+ return y0 + (x - x0) * (y1 - y0) / (x1 - x0);
+ }
+
+ }
+}
diff --git a/ShiftOS.Frontend/GUI/TextControl.cs b/ShiftOS.Frontend/GUI/TextControl.cs
new file mode 100644
index 0000000..f1bbef1
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/TextControl.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ShiftOS.Frontend.GraphicsSubsystem;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public class TextControl : Control
+ {
+ private string _text = "Text Control";
+ private TextAlign _textAlign = TextAlign.TopLeft;
+ private Font _font = new Font("Tahoma", 9f);
+
+ protected override void OnLayout()
+ {
+ if (AutoSize)
+ {
+ using (var bmp = new Bitmap(1, 1))
+ {
+ using(var gfx = Graphics.FromImage(bmp))
+ {
+ var measure = gfx.MeasureString(_text, _font);
+ Width = (int)measure.Width;
+ Height = (int)measure.Height;
+ }
+ }
+ }
+ }
+
+ public string Text
+ {
+ get { return _text; }
+ set { _text = value; }
+ }
+
+ public Font Font
+ {
+ get
+ {
+ return _font;
+ }
+ set
+ {
+ _font = value;
+ }
+ }
+
+ public TextAlign TextAlign
+ {
+ get { return _textAlign; }
+ set { _textAlign = value; }
+ }
+
+ protected override void OnPaint(GraphicsContext gfx)
+ {
+ var sMeasure = gfx.MeasureString(_text, _font, Width);
+ PointF loc = new PointF(2, 2);
+ float centerH = (Width - sMeasure.X) / 2;
+ float centerV = (Height - sMeasure.Y) / 2;
+ switch (_textAlign)
+ {
+ case TextAlign.TopCenter:
+ loc.X = centerH;
+ break;
+ case TextAlign.TopRight:
+ loc.X = Width - sMeasure.X;
+ break;
+ case TextAlign.MiddleLeft:
+ loc.Y = centerV;
+ break;
+ case TextAlign.MiddleCenter:
+ loc.Y = centerV;
+ loc.X = centerH;
+ break;
+ case TextAlign.MiddleRight:
+ loc.Y = centerV;
+ loc.X = (Width - sMeasure.Y);
+ break;
+ case TextAlign.BottomLeft:
+ loc.Y = (Height - sMeasure.Y);
+ break;
+ case TextAlign.BottomCenter:
+ loc.Y = (Height - sMeasure.Y);
+ loc.X = centerH;
+ break;
+ case TextAlign.BottomRight:
+ loc.Y = (Height - sMeasure.Y);
+ loc.X = (Width - sMeasure.X);
+ break;
+
+ }
+
+ gfx.DrawString(_text, 0, 0, Engine.SkinEngine.LoadedSkin.ControlTextColor.ToMonoColor(), _font, this.Width);
+ }
+ }
+
+ public enum TextAlign
+ {
+ TopLeft,
+ TopCenter,
+ TopRight,
+ MiddleLeft,
+ MiddleCenter,
+ MiddleRight,
+ BottomLeft,
+ BottomCenter,
+ BottomRight
+ }
+}
diff --git a/ShiftOS.Frontend/GUI/TextInput.cs b/ShiftOS.Frontend/GUI/TextInput.cs
new file mode 100644
index 0000000..73954ef
--- /dev/null
+++ b/ShiftOS.Frontend/GUI/TextInput.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Xna.Framework;
+using ShiftOS.Frontend.GraphicsSubsystem;
+using static ShiftOS.Engine.SkinEngine;
+
+namespace ShiftOS.Frontend.GUI
+{
+ public class TextInput : Control
+ {
+ private string _label = "Type here!";
+ private string _text = "";
+ private int _index = 0;
+ private System.Drawing.Font _font = new System.Drawing.Font("Tahoma", 9f);
+
+ public int Index
+ {
+ get
+ {
+ return _index;
+ }
+ set
+ {
+ if (_index == value)
+ return;
+ if(_text.Length == 0)
+ {
+ _index = 0;
+ return;
+ }
+ _index = MathHelper.Clamp(value, 0, _text.Length);
+ Invalidate();
+ }
+ }
+
+ public string Text
+ {
+ get
+ {
+ return _text;
+ }
+ set
+ {
+ if (_text == value)
+ return;
+
+ _text = value;
+ if(_index >= _text.Length)
+ {
+ _index = _text.Length - 1;
+ }
+ Invalidate();
+ }
+ }
+
+ protected override void OnKeyEvent(KeyEvent e)
+ {
+ if(e.Key == Microsoft.Xna.Framework.Input.Keys.Left)
+ {
+ if (_index > 0)
+ _index--;
+
+ }
+ if(e.Key == Microsoft.Xna.Framework.Input.Keys.Back)
+ {
+ if(_index > 0)
+ {
+ _text = _text.Remove(_index - 1, 1);
+ _index--;
+ }
+ }
+ if(e.Key == Microsoft.Xna.Framework.Input.Keys.Delete)
+ {
+ if(_index < _text.Length - 1)
+ {
+ _text = _text.Remove(_index, 1);
+ }
+ }
+ if (e.Key == Microsoft.Xna.Framework.Input.Keys.Right)
+ if (_index < _text.Length)
+ _index++;
+ if (e.KeyChar != '\0') {
+ _text = _text.Insert(_index, e.KeyChar.ToString());
+ _index++;
+ }
+ CalculateVisibleText();
+ Invalidate();
+ base.OnKeyEvent(e);
+ }
+
+ float caretPos = 2f;
+
+ protected void CalculateVisibleText()
+ {
+ using(var gfx = System.Drawing.Graphics.FromImage(new System.Drawing.Bitmap(1, 1)))
+ {
+ string toCaret = _text.Substring(0, _index);
+ var measure = gfx.MeasureString(toCaret, _font);
+ caretPos = 2 + measure.Width;
+ while(caretPos - _textDrawOffset < 0)
+ {
+ _textDrawOffset -= 0.01f;
+ }
+ while(caretPos - _textDrawOffset > Width)
+ {
+ _textDrawOffset += 0.01f;
+ }
+
+ }
+ }
+
+ private float _textDrawOffset = 0;
+
+ protected override void OnPaint(GraphicsContext gfx)
+ {
+ gfx.Clear(LoadedSkin.ControlColor.ToMonoColor());
+ gfx.DrawString(_text, 2 - (int)Math.Floor(_textDrawOffset), 2, LoadedSkin.ControlTextColor.ToMonoColor(), _font);
+ if (IsFocusedControl)
+ {
+ //Draw caret.
+
+
+ gfx.DrawRectangle((int)(Math.Floor(caretPos) - Math.Floor(_textDrawOffset)), 2, 2, Height - 4, LoadedSkin.ControlTextColor.ToMonoColor());
+ }
+ else
+ {
+ if (string.IsNullOrEmpty(_text))
+ {
+ gfx.DrawString(_label, 2, 2, Color.Gray, _font);
+ }
+ }
+ }
+ }
+}