mirror of
https://git.alee14.me/shiftos-archive/ShiftOS_TheReturn.git
synced 2025-01-22 18:02:16 +00:00
733 lines
20 KiB
C#
733 lines
20 KiB
C#
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 void BringToFront()
|
|
{
|
|
if(_parent != null)
|
|
{
|
|
_parent._children.Remove(this);
|
|
_parent.AddControl(this);
|
|
}
|
|
else
|
|
{
|
|
UIManager.BringToFront(this);
|
|
}
|
|
}
|
|
|
|
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();
|
|
UIManager.FocusedControl = ctrl;
|
|
}
|
|
}
|
|
|
|
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 void RemoveControl(Control ctrl)
|
|
{
|
|
if(_children.Contains(ctrl))
|
|
{
|
|
_children.Remove(ctrl);
|
|
ctrl._parent = null;
|
|
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(GameTime gameTime)
|
|
{
|
|
//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(gameTime);
|
|
foreach (var child in _children)
|
|
child.Layout(gameTime);
|
|
}
|
|
|
|
protected virtual void OnLayout(GameTime gameTime)
|
|
{
|
|
//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, double lastLeftClickMS)
|
|
{
|
|
//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, lastLeftClickMS);
|
|
//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)
|
|
{
|
|
if (lastLeftClickMS <= 500 & lastLeftClickMS > 0)
|
|
DoubleClick?.Invoke();
|
|
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;
|
|
_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 DoubleClick;
|
|
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; }
|
|
}
|
|
}
|