mirror of
https://github.com/Leonmmcoset/CMLeonOS.git
synced 2026-03-03 15:30:27 +00:00
GUI桌面环境
This commit is contained in:
9
Gui/UILib/Alignment.cs
Normal file
9
Gui/UILib/Alignment.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal enum Alignment
|
||||
{
|
||||
Start,
|
||||
Middle,
|
||||
End
|
||||
}
|
||||
}
|
||||
84
Gui/UILib/Animations/Animation.cs
Normal file
84
Gui/UILib/Animations/Animation.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using CMLeonOS.Gui;
|
||||
|
||||
namespace CMLeonOS.UILib.Animations
|
||||
{
|
||||
/// <summary>
|
||||
/// A window animation.
|
||||
/// </summary>
|
||||
internal abstract class Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// The easing type of the animation.
|
||||
/// </summary>
|
||||
internal EasingType EasingType { get; set; } = EasingType.Sine;
|
||||
|
||||
/// <summary>
|
||||
/// The direction of the easing of the animation.
|
||||
/// </summary>
|
||||
internal EasingDirection EasingDirection { get; set; } = EasingDirection.Out;
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the animation.
|
||||
/// </summary>
|
||||
internal int Duration { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// How many frames of the animation have been completed.
|
||||
/// </summary>
|
||||
internal int Position { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// If the animation has finished.
|
||||
/// </summary>
|
||||
internal bool Finished
|
||||
{
|
||||
get
|
||||
{
|
||||
return Position >= Duration;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The window associated with the animation.
|
||||
/// </summary>
|
||||
internal Window Window { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Advance the animation by one frame.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the animation is now finished.</returns>
|
||||
internal abstract bool Advance();
|
||||
|
||||
private int? timerId { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Start the animation.
|
||||
/// </summary>
|
||||
internal void Start()
|
||||
{
|
||||
if (timerId == null)
|
||||
{
|
||||
timerId = Cosmos.HAL.Global.PIT.RegisterTimer(new Cosmos.HAL.PIT.PITTimer(() =>
|
||||
{
|
||||
Advance();
|
||||
if (Finished)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}, (ulong)((1000d /* ms */ / 60d) * 1e+6d /* ms -> ns */ ), true));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the animation.
|
||||
/// </summary>
|
||||
internal void Stop()
|
||||
{
|
||||
if (timerId != null)
|
||||
{
|
||||
Cosmos.HAL.Global.PIT.UnregisterTimer((int)timerId);
|
||||
timerId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Gui/UILib/Animations/Easing.cs
Normal file
55
Gui/UILib/Animations/Easing.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace CMLeonOS.UILib.Animations
|
||||
{
|
||||
/// <summary>
|
||||
/// Easing utilities for animations.
|
||||
/// </summary>
|
||||
internal static class Easing
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculate the value of an easing function.
|
||||
/// </summary>
|
||||
/// <param name="t">The absolute progress of the animation, from 0 to 1.</param>
|
||||
/// <param name="type">The type of the easing function.</param>
|
||||
/// <param name="direction">The direction of the easing function.</param>
|
||||
/// <returns>The value of the easing function at the given progress.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">An exception is thrown if the progress is out of range.</exception>
|
||||
/// <exception cref="ArgumentException">An exception is thrown if the type or direction is ininvalid.</exception>
|
||||
internal static double Ease(double t, EasingType type, EasingDirection direction)
|
||||
{
|
||||
if (t < 0 || t > 1) throw new ArgumentOutOfRangeException();
|
||||
switch (type)
|
||||
{
|
||||
case EasingType.Linear:
|
||||
return t;
|
||||
case EasingType.Sine:
|
||||
switch (direction)
|
||||
{
|
||||
case EasingDirection.In:
|
||||
return 1 - Math.Cos(t * Math.PI / 2);
|
||||
case EasingDirection.Out:
|
||||
return Math.Sin(t * Math.PI / 2);
|
||||
case EasingDirection.InOut:
|
||||
return -0.5 * (Math.Cos(Math.PI * t) - 1);
|
||||
default:
|
||||
throw new ArgumentException("Unknown easing direction.");
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException("Unknown easing type.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolate between two values.
|
||||
/// </summary>
|
||||
/// <param name="x">The first value.</param>
|
||||
/// <param name="y">The second value.</param>
|
||||
/// <param name="z">The value of the interpolation.</param>
|
||||
/// <returns>The interpolated value.</returns>
|
||||
internal static double Lerp(double x, double y, double z)
|
||||
{
|
||||
return x * (1 - z) + y * z;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Gui/UILib/Animations/EasingDirection.cs
Normal file
23
Gui/UILib/Animations/EasingDirection.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace CMLeonOS.UILib.Animations
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the direction of an easing type.
|
||||
/// </summary>
|
||||
internal enum EasingDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the animation slowly, and finishes at full speed.
|
||||
/// </summary>
|
||||
In,
|
||||
|
||||
/// <summary>
|
||||
/// Starts the animation at full speed, and finishes slowly.
|
||||
/// </summary>
|
||||
Out,
|
||||
|
||||
/// <summary>
|
||||
/// Starts the animation slowly, reaches full speed at the middle, and finishes slowly.
|
||||
/// </summary>
|
||||
InOut
|
||||
}
|
||||
}
|
||||
18
Gui/UILib/Animations/EasingType.cs
Normal file
18
Gui/UILib/Animations/EasingType.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace CMLeonOS.UILib.Animations
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the speed of motion over time in an animation.
|
||||
/// </summary>
|
||||
internal enum EasingType
|
||||
{
|
||||
/// <summary>
|
||||
/// Linear easing.
|
||||
/// </summary>
|
||||
Linear,
|
||||
|
||||
/// <summary>
|
||||
/// Sinusoidal easing.
|
||||
/// </summary>
|
||||
Sine
|
||||
}
|
||||
}
|
||||
64
Gui/UILib/Animations/MovementAnimation.cs
Normal file
64
Gui/UILib/Animations/MovementAnimation.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using CMLeonOS.Gui;
|
||||
using CMLeonOS.Gui.UILib;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.UILib.Animations
|
||||
{
|
||||
/// <summary>
|
||||
/// An animation that moves or resizes a window.
|
||||
/// </summary>
|
||||
internal class MovementAnimation : Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialise the animation.
|
||||
/// </summary>
|
||||
/// <param name="window">The window associated with the animation.</param>
|
||||
/// <param name="to">The goal of the animation.</param>
|
||||
internal MovementAnimation(Window window)
|
||||
{
|
||||
Window = window;
|
||||
From = new Rectangle(window.X, window.Y, window.Width, window.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The starting rectangle of the animation.
|
||||
/// </summary>
|
||||
internal Rectangle From;
|
||||
|
||||
/// <summary>
|
||||
/// The goal rectangle of the animation.
|
||||
/// </summary>
|
||||
internal Rectangle To;
|
||||
|
||||
internal override bool Advance()
|
||||
{
|
||||
if (From.IsEmpty || To.IsEmpty) throw new Exception("The From or To value of this MovementAnimation is empty.");
|
||||
Position++;
|
||||
if (Position == Duration)
|
||||
{
|
||||
Window.MoveAndResize(To.X, To.Y, To.Width, To.Height);
|
||||
if (Window is Control control)
|
||||
{
|
||||
control.Render();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double t = Easing.Ease(Position / (double)Duration, EasingType, EasingDirection);
|
||||
Rectangle current = new Rectangle(
|
||||
(int)Easing.Lerp(From.X, To.X, t),
|
||||
(int)Easing.Lerp(From.Y, To.Y, t),
|
||||
(int)Easing.Lerp(From.Width, To.Width, t),
|
||||
(int)Easing.Lerp(From.Height, To.Height, t)
|
||||
);
|
||||
Window.MoveAndResize(current.X, current.Y, current.Width, current.Height);
|
||||
if (Window is Control control)
|
||||
{
|
||||
control.Render();
|
||||
}
|
||||
}
|
||||
return Finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Gui/UILib/AppWindow.cs
Normal file
243
Gui/UILib/AppWindow.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using Cosmos.System;
|
||||
using Cosmos.System.Graphics;
|
||||
using CMLeonOS;
|
||||
using CMLeonOS.Gui.ShellComponents.Dock;
|
||||
using CMLeonOS.Gui.SmoothMono;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class AppWindow : Window
|
||||
{
|
||||
internal AppWindow(Process process, int x, int y, int width, int height) : base(process, x, y, width, height)
|
||||
{
|
||||
wm = ProcessManager.GetProcess<WindowManager>();
|
||||
|
||||
decorationWindow = new Window(process, 0, -titlebarHeight, width, titlebarHeight);
|
||||
wm.AddWindow(decorationWindow);
|
||||
|
||||
decorationWindow.RelativeTo = this;
|
||||
|
||||
decorationWindow.OnClick = DecorationClicked;
|
||||
decorationWindow.OnDown = DecorationDown;
|
||||
|
||||
Icon = defaultAppIconBitmap;
|
||||
|
||||
RenderDecoration();
|
||||
}
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Close.bmp")]
|
||||
private static byte[] closeBytes;
|
||||
private static Bitmap closeBitmap = new Bitmap(closeBytes);
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Maximise.bmp")]
|
||||
private static byte[] maximiseBytes;
|
||||
private static Bitmap maximiseBitmap = new Bitmap(maximiseBytes);
|
||||
|
||||
/*[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Minimise.bmp")]
|
||||
private static byte[] minimiseBytes;
|
||||
private static Bitmap minimiseBitmap = new Bitmap(minimiseBytes);*/
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Restore.bmp")]
|
||||
private static byte[] restoreBytes;
|
||||
private static Bitmap restoreBitmap = new Bitmap(restoreBytes);
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Default.bmp")]
|
||||
private static byte[] defaultAppIconBytes;
|
||||
private static Bitmap defaultAppIconBitmap = new Bitmap(defaultAppIconBytes);
|
||||
|
||||
internal Action Closing;
|
||||
|
||||
private Bitmap _icon;
|
||||
private Bitmap _smallIcon;
|
||||
internal Bitmap Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return _icon;
|
||||
}
|
||||
set
|
||||
{
|
||||
_icon = value;
|
||||
_smallIcon = _icon.Resize(20, 20);
|
||||
|
||||
RenderDecoration();
|
||||
|
||||
ProcessManager.GetProcess<Dock>()?.UpdateWindows();
|
||||
}
|
||||
}
|
||||
|
||||
private string _title = "Window";
|
||||
internal string Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
set
|
||||
{
|
||||
_title = value;
|
||||
RenderDecoration();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _canResize = false;
|
||||
internal bool CanResize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _canResize;
|
||||
}
|
||||
set
|
||||
{
|
||||
_canResize = value;
|
||||
RenderDecoration();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _canClose = true;
|
||||
internal bool CanClose
|
||||
{
|
||||
get
|
||||
{
|
||||
return _canClose;
|
||||
}
|
||||
set
|
||||
{
|
||||
_canClose = value;
|
||||
RenderDecoration();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _canMove = true;
|
||||
internal bool CanMove
|
||||
{
|
||||
get
|
||||
{
|
||||
return _canMove;
|
||||
}
|
||||
set
|
||||
{
|
||||
_canMove = value;
|
||||
RenderDecoration();
|
||||
}
|
||||
}
|
||||
|
||||
private const int titlebarHeight = 24;
|
||||
|
||||
private Window decorationWindow;
|
||||
|
||||
private WindowManager wm;
|
||||
|
||||
private bool maximised = false;
|
||||
private int originalX;
|
||||
private int originalY;
|
||||
private int originalWidth;
|
||||
private int originalHeight;
|
||||
|
||||
private void DecorationClicked(int x, int y)
|
||||
{
|
||||
if (x >= Width - titlebarHeight && _canClose)
|
||||
{
|
||||
// Close.
|
||||
Closing?.Invoke();
|
||||
wm.RemoveWindow(this);
|
||||
}
|
||||
else if (x >= Width - (titlebarHeight * (_canClose ? 2 : 1)) && _canResize)
|
||||
{
|
||||
// Maximise / restore.
|
||||
if (maximised)
|
||||
{
|
||||
maximised = false;
|
||||
|
||||
MoveAndResize(originalX, originalY, originalWidth, originalHeight, sendWMEvent: false);
|
||||
|
||||
decorationWindow.Resize(originalWidth, titlebarHeight, sendWMEvent: false);
|
||||
|
||||
UserResized?.Invoke();
|
||||
ProcessManager.GetProcess<WindowManager>().RerenderAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
maximised = true;
|
||||
|
||||
var taskbar = ProcessManager.GetProcess<ShellComponents.Taskbar>();
|
||||
int taskbarHeight = taskbar.GetTaskbarHeight();
|
||||
|
||||
var dock = ProcessManager.GetProcess<ShellComponents.Dock.Dock>();
|
||||
int dockHeight = dock.GetDockHeight();
|
||||
|
||||
originalX = X;
|
||||
originalY = Y;
|
||||
originalWidth = Width;
|
||||
originalHeight = Height;
|
||||
|
||||
MoveAndResize(
|
||||
0,
|
||||
taskbarHeight + titlebarHeight,
|
||||
(int)wm.ScreenWidth,
|
||||
(int)wm.ScreenHeight - titlebarHeight - taskbarHeight - dockHeight,
|
||||
sendWMEvent: false
|
||||
);
|
||||
|
||||
decorationWindow.Resize((int)wm.ScreenWidth, titlebarHeight, sendWMEvent: false);
|
||||
|
||||
UserResized?.Invoke();
|
||||
ProcessManager.GetProcess<WindowManager>().RerenderAll();
|
||||
}
|
||||
RenderDecoration();
|
||||
}
|
||||
}
|
||||
|
||||
private void DecorationDown(int x, int y)
|
||||
{
|
||||
int buttonSpace = 0;
|
||||
if (_canClose)
|
||||
{
|
||||
buttonSpace += titlebarHeight;
|
||||
}
|
||||
if (_canResize)
|
||||
{
|
||||
buttonSpace += titlebarHeight;
|
||||
}
|
||||
if (x >= Width - buttonSpace || maximised || !_canMove) return;
|
||||
|
||||
uint startMouseX = MouseManager.X;
|
||||
uint startMouseY = MouseManager.Y;
|
||||
|
||||
int startWindowX = X;
|
||||
int startWindowY = Y;
|
||||
|
||||
while (MouseManager.MouseState == MouseState.Left)
|
||||
{
|
||||
X = (int)(startWindowX + (MouseManager.X - startMouseX));
|
||||
Y = (int)(startWindowY + (MouseManager.Y - startMouseY));
|
||||
|
||||
ProcessManager.Yield();
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderDecoration()
|
||||
{
|
||||
decorationWindow.Clear(Color.FromArgb(56, 56, 71));
|
||||
|
||||
if (_smallIcon != null)
|
||||
{
|
||||
decorationWindow.DrawImageAlpha(_smallIcon, 2, 2);
|
||||
}
|
||||
|
||||
decorationWindow.DrawString(Title, Color.White, (Width / 2) - ((FontData.Width * Title.Length) / 2), 4);
|
||||
|
||||
if (_canClose)
|
||||
{
|
||||
decorationWindow.DrawImageAlpha(closeBitmap, Width - titlebarHeight, 0);
|
||||
}
|
||||
if (_canResize)
|
||||
{
|
||||
decorationWindow.DrawImageAlpha(maximised ? restoreBitmap : maximiseBitmap, Width - (titlebarHeight * (_canClose ? 2 : 1)), 0);
|
||||
}
|
||||
wm.Update(decorationWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Gui/UILib/Button.cs
Normal file
133
Gui/UILib/Button.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Cosmos.System.Graphics;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class Button : Control
|
||||
{
|
||||
public Button(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
internal enum ButtonImageLocation
|
||||
{
|
||||
AboveText,
|
||||
Left
|
||||
}
|
||||
|
||||
private string _text = "Button";
|
||||
internal string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
set
|
||||
{
|
||||
_text = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private ButtonImageLocation _imageLocation = ButtonImageLocation.AboveText;
|
||||
internal ButtonImageLocation ImageLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return _imageLocation;
|
||||
}
|
||||
set
|
||||
{
|
||||
_imageLocation = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _background = Color.FromArgb(48, 48, 48);
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.White;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _border = Color.Black;
|
||||
internal Color Border
|
||||
{
|
||||
get
|
||||
{
|
||||
return _border;
|
||||
}
|
||||
set
|
||||
{
|
||||
_border = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap _image;
|
||||
internal Bitmap Image
|
||||
{
|
||||
get
|
||||
{
|
||||
return _image;
|
||||
}
|
||||
set
|
||||
{
|
||||
_image = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
Clear(Background);
|
||||
|
||||
if (_image != null)
|
||||
{
|
||||
switch (_imageLocation)
|
||||
{
|
||||
case ButtonImageLocation.Left:
|
||||
DrawImageAlpha(_image, (int)((Width / 2) - ((8 / 2) * Text.Length) - 8 - _image.Width), (int)((Height / 2) - (_image.Height / 2)));
|
||||
DrawString(Text, Foreground, (Width / 2) - ((8 / 2) * Text.Length), (Height / 2) - (16 / 2));
|
||||
break;
|
||||
case ButtonImageLocation.AboveText:
|
||||
DrawImageAlpha(_image, (int)((Width / 2) - (_image.Width / 2)), (int)((Height / 2) - (_image.Height / 2)));
|
||||
DrawString(Text, Foreground, (Width / 2) - (4 * Text.Length), Height - 16);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unrecognised image location in button.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawString(Text, Foreground, (Width / 2) - (4 * Text.Length), (Height / 2) - 8);
|
||||
}
|
||||
|
||||
DrawRectangle(0, 0, Width, Height, Border);
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
204
Gui/UILib/Calendar.cs
Normal file
204
Gui/UILib/Calendar.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class Calendar : Control
|
||||
{
|
||||
public Calendar(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
internal void SetCalendar(int year, int month)
|
||||
{
|
||||
_year = year;
|
||||
_month = month;
|
||||
Render();
|
||||
}
|
||||
|
||||
private int _year = DateTime.Now.Year;
|
||||
internal int Year
|
||||
{
|
||||
get
|
||||
{
|
||||
return _year;
|
||||
}
|
||||
set
|
||||
{
|
||||
_year = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private int _month = DateTime.Now.Month;
|
||||
internal int Month
|
||||
{
|
||||
get
|
||||
{
|
||||
return _month;
|
||||
}
|
||||
set
|
||||
{
|
||||
_month = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _background = Color.White;
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.Black;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _weekendForeground = Color.Red;
|
||||
internal Color WeekendForeground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _weekendForeground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_weekendForeground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _todayBackground = Color.LightGray;
|
||||
internal Color TodayBackground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _todayBackground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_todayBackground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly string[] weekdaysShort = new string[]
|
||||
{
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun"
|
||||
};
|
||||
|
||||
private readonly string[] monthsLong = new string[]
|
||||
{
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
};
|
||||
|
||||
private int GetWeekdayIndex(DayOfWeek dayOfWeek)
|
||||
{
|
||||
return dayOfWeek switch
|
||||
{
|
||||
DayOfWeek.Monday => 0,
|
||||
DayOfWeek.Tuesday => 1,
|
||||
DayOfWeek.Wednesday => 2,
|
||||
DayOfWeek.Thursday => 3,
|
||||
DayOfWeek.Friday => 4,
|
||||
DayOfWeek.Saturday => 5,
|
||||
DayOfWeek.Sunday => 6,
|
||||
_ => throw new Exception("Invalid DayOfWeek.")
|
||||
};
|
||||
}
|
||||
|
||||
private const int cellPadding = 4;
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
Clear(_background);
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
int daysInMonth = DateTime.DaysInMonth(_year, _month);
|
||||
int startingWeekday = GetWeekdayIndex(new DateTime(_year, _month, 1).DayOfWeek);
|
||||
|
||||
int headerHeight = 68;
|
||||
|
||||
Rectangle availableSpace = new Rectangle(0, headerHeight, Width, Height - headerHeight);
|
||||
|
||||
int cellWidth = availableSpace.Width / 7;
|
||||
int cellHeight = availableSpace.Height / 5;
|
||||
|
||||
/* Header */
|
||||
string title = $"{monthsLong[_month - 1]} {_year}";
|
||||
DrawString(title, _foreground, (Width / 2) - ((title.Length * 8) / 2), 12);
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
string weekday = weekdaysShort[i];
|
||||
DrawString(weekday, _foreground, (i * cellWidth) + ((cellWidth / 2) - weekday.Length * (8 / 2) / 2), 40);
|
||||
}
|
||||
|
||||
/* Days */
|
||||
int cellX = startingWeekday;
|
||||
int cellY = 0;
|
||||
for (int i = 1; i <= daysInMonth; i++)
|
||||
{
|
||||
if (cellX > 6)
|
||||
{
|
||||
cellX = 0;
|
||||
cellY++;
|
||||
}
|
||||
|
||||
string str = i.ToString();
|
||||
bool weekend = cellX >= 5;
|
||||
|
||||
int cellWindowX = availableSpace.X + (cellX * cellWidth);
|
||||
int cellWindowY = availableSpace.Y + (cellY * cellHeight);
|
||||
|
||||
int textWindowX = (cellWindowX + cellWidth) - (8 * str.Length) - cellPadding;
|
||||
int textWindowY = cellWindowY + cellPadding;
|
||||
|
||||
if (_year == now.Year && _month == now.Month && i == now.Day)
|
||||
{
|
||||
DrawFilledRectangle(cellWindowX, cellWindowY, cellWidth, cellHeight, _todayBackground);
|
||||
}
|
||||
|
||||
DrawString(str, weekend ? _weekendForeground : _foreground, textWindowX, textWindowY);
|
||||
|
||||
cellX++;
|
||||
}
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Gui/UILib/CheckBox.cs
Normal file
115
Gui/UILib/CheckBox.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Cosmos.System.Graphics;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class CheckBox : Control
|
||||
{
|
||||
public CheckBox(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
OnClick = CheckBoxClicked;
|
||||
}
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Check.bmp")]
|
||||
private static byte[] checkBytes;
|
||||
private static Bitmap checkBitmap = new Bitmap(checkBytes);
|
||||
|
||||
internal Action CheckBoxChecked;
|
||||
internal Action CheckBoxUnchecked;
|
||||
internal Action<bool> CheckBoxChanged;
|
||||
|
||||
private const int iconSize = 16;
|
||||
|
||||
private bool _checked = false;
|
||||
internal bool Checked
|
||||
{
|
||||
get
|
||||
{
|
||||
return _checked;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checked = value;
|
||||
if (_checked)
|
||||
{
|
||||
CheckBoxChecked?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckBoxUnchecked?.Invoke();
|
||||
}
|
||||
CheckBoxChanged?.Invoke(_checked);
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private string _text = "CheckBox";
|
||||
internal string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
set
|
||||
{
|
||||
_text = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _background = Color.White;
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.Black;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckBoxClicked(int x, int y)
|
||||
{
|
||||
Checked = !Checked;
|
||||
}
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
Clear(Background);
|
||||
|
||||
int iconX = 0;
|
||||
int iconY = (Height / 2) - (iconSize / 2);
|
||||
|
||||
int textX = iconSize + 8;
|
||||
int textY = (Height / 2) - (16 / 2);
|
||||
|
||||
DrawFilledRectangle(iconX, iconY, iconSize, iconSize, Color.LightGray);
|
||||
if (_checked)
|
||||
{
|
||||
DrawImageAlpha(checkBitmap, iconX, iconY);
|
||||
}
|
||||
|
||||
DrawString(Text, _foreground, textX, textY);
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Gui/UILib/Control.cs
Normal file
14
Gui/UILib/Control.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal abstract class Control : Window
|
||||
{
|
||||
internal Control(Window parent, int x, int y, int width, int height) : base(parent.Process, x, y, width, height)
|
||||
{
|
||||
RelativeTo = parent;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
internal abstract void Render();
|
||||
}
|
||||
}
|
||||
17
Gui/UILib/Extensions.cs
Normal file
17
Gui/UILib/Extensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
internal static float GetLuminance(this Color color)
|
||||
{
|
||||
return (float)((color.R * 0.2126) + (color.G * 0.7152) + (color.B * 0.0722));
|
||||
}
|
||||
|
||||
internal static Color GetForegroundColour(this Color color)
|
||||
{
|
||||
return color.GetLuminance() < 140 ? Color.White : Color.Black;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Gui/UILib/ImageBlock.cs
Normal file
61
Gui/UILib/ImageBlock.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Cosmos.System.Graphics;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class ImageBlock : Control
|
||||
{
|
||||
public ImageBlock(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
private Bitmap _image;
|
||||
internal Bitmap Image
|
||||
{
|
||||
get
|
||||
{
|
||||
return _image;
|
||||
}
|
||||
set
|
||||
{
|
||||
_image = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _alpha = false;
|
||||
internal bool Alpha
|
||||
{
|
||||
get
|
||||
{
|
||||
return _alpha;
|
||||
}
|
||||
set
|
||||
{
|
||||
_alpha = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
if (_image == null)
|
||||
{
|
||||
Clear(Color.Gray);
|
||||
WM.Update(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_alpha)
|
||||
{
|
||||
DrawImageAlpha(_image, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawImage(_image, 0, 0);
|
||||
}
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Gui/UILib/MessageBox.cs
Normal file
58
Gui/UILib/MessageBox.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using CMLeonOS;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class MessageBox
|
||||
{
|
||||
internal MessageBox(Process process, string title, string message)
|
||||
{
|
||||
this.Process = process;
|
||||
Title = title;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
protected const int Padding = 12;
|
||||
|
||||
internal void Show()
|
||||
{
|
||||
WindowManager wm = ProcessManager.GetProcess<WindowManager>();
|
||||
|
||||
int longestLineLength = 0;
|
||||
foreach (string line in Message.Split('\n'))
|
||||
{
|
||||
longestLineLength = Math.Max(longestLineLength, line.Length);
|
||||
}
|
||||
|
||||
int width = Math.Max(192, (Padding * 2) + (8 * longestLineLength));
|
||||
int height = 128 + ((Message.Split('\n').Length - 1) * 16);
|
||||
|
||||
AppWindow window = new AppWindow(Process, (int)((wm.ScreenWidth / 2) - (height / 2)), (int)((wm.ScreenWidth / 2) - (width / 2)), width, height);
|
||||
window.Title = Title;
|
||||
wm.AddWindow(window);
|
||||
|
||||
window.Clear(Color.LightGray);
|
||||
window.DrawFilledRectangle(0, window.Height - (Padding * 2) - 20, window.Width, (Padding * 2) + 20, Color.Gray);
|
||||
window.DrawString(Message, Color.Black, Padding, Padding);
|
||||
|
||||
Button ok = new Button(window, window.Width - 80 - Padding, window.Height - 20 - Padding, 80, 20);
|
||||
ok.Text = "OK";
|
||||
ok.OnClick = (int x, int y) =>
|
||||
{
|
||||
wm.RemoveWindow(window);
|
||||
};
|
||||
wm.AddWindow(ok);
|
||||
|
||||
wm.Update(window);
|
||||
|
||||
ProcessManager.GetProcess<Sound.SoundService>().PlaySystemSound(Sound.SystemSound.Alert);
|
||||
}
|
||||
|
||||
internal Process Process { get; private set; }
|
||||
|
||||
internal string Title { get; private set; }
|
||||
|
||||
internal string Message { get; private set; }
|
||||
}
|
||||
}
|
||||
60
Gui/UILib/PromptBox.cs
Normal file
60
Gui/UILib/PromptBox.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using CMLeonOS;
|
||||
using CMLeonOS.Gui.SmoothMono;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class PromptBox : MessageBox
|
||||
{
|
||||
internal PromptBox(Process process, string title, string message, string placeholder, Action<string> submitted) : base(process, title, message)
|
||||
{
|
||||
Placeholder = placeholder;
|
||||
Submitted = submitted;
|
||||
}
|
||||
|
||||
internal void Show()
|
||||
{
|
||||
WindowManager wm = ProcessManager.GetProcess<WindowManager>();
|
||||
|
||||
int longestLineLength = 0;
|
||||
foreach (string line in Message.Split('\n'))
|
||||
{
|
||||
longestLineLength = Math.Max(longestLineLength, line.Length);
|
||||
}
|
||||
|
||||
int width = Math.Max(256, (Padding * 2) + (8 * longestLineLength));
|
||||
int height = 128 + ((Message.Split('\n').Length - 1) * 16);
|
||||
|
||||
AppWindow window = new AppWindow(Process, (int)((wm.ScreenWidth / 2) - (height / 2)), (int)((wm.ScreenWidth / 2) - (width / 2)), width, height);
|
||||
window.Title = Title;
|
||||
wm.AddWindow(window);
|
||||
|
||||
window.Clear(Color.LightGray);
|
||||
window.DrawFilledRectangle(0, window.Height - (Padding * 2) - 20, window.Width, (Padding * 2) + 20, Color.Gray);
|
||||
window.DrawString(Message, Color.Black, Padding, Padding);
|
||||
|
||||
TextBox textBox = new TextBox(window, Padding, Padding + FontData.Height + 8, 192, 20);
|
||||
textBox.PlaceholderText = Placeholder;
|
||||
wm.AddWindow(textBox);
|
||||
|
||||
Button ok = new Button(window, window.Width - 80 - Padding, window.Height - 20 - Padding, 80, 20);
|
||||
ok.Text = "OK";
|
||||
ok.OnClick = (int x, int y) =>
|
||||
{
|
||||
wm.RemoveWindow(window);
|
||||
|
||||
Submitted.Invoke(textBox.Text);
|
||||
};
|
||||
wm.AddWindow(ok);
|
||||
|
||||
wm.Update(window);
|
||||
|
||||
ProcessManager.GetProcess<Sound.SoundService>().PlaySystemSound(Sound.SystemSound.Alert);
|
||||
}
|
||||
|
||||
internal Action<string> Submitted { get; private set; }
|
||||
|
||||
internal string Placeholder { get; private set; }
|
||||
}
|
||||
}
|
||||
195
Gui/UILib/RangeSlider.cs
Normal file
195
Gui/UILib/RangeSlider.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using Cosmos.System;
|
||||
using CMLeonOS;
|
||||
using CMLeonOS.Gui.SmoothMono;
|
||||
using CMLeonOS.Utils;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class RangeSlider : Control
|
||||
{
|
||||
public RangeSlider(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
OnDown = RangeSliderDown;
|
||||
}
|
||||
|
||||
public RangeSlider(Window parent, int x, int y, int width, int height, float min, float value, float max) : base(parent, x, y, width, height)
|
||||
{
|
||||
OnDown = RangeSliderDown;
|
||||
|
||||
_minimum = min;
|
||||
_value = value;
|
||||
_maximum = max;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
private Color _background = Color.White;
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.Gray;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private float _minimum = 0;
|
||||
internal float Minimum
|
||||
{
|
||||
get
|
||||
{
|
||||
return _minimum;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_minimum != value)
|
||||
{
|
||||
_minimum = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float _value = 50;
|
||||
internal float Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_value != value)
|
||||
{
|
||||
_value = value;
|
||||
Render();
|
||||
|
||||
Changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float _maximum = 100;
|
||||
internal float Maximum
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maximum;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_maximum != value)
|
||||
{
|
||||
_maximum = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _rangeLabels = true;
|
||||
internal bool RangeLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
return _rangeLabels;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_rangeLabels != value)
|
||||
{
|
||||
_rangeLabels = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Action<float> Changed { get; set; }
|
||||
|
||||
private bool held = false;
|
||||
|
||||
private static int slotHeight = 3;
|
||||
private static int sliderHeight = 15;
|
||||
private static int sliderWidth = 5;
|
||||
|
||||
private void RangeSliderDown(int x, int y)
|
||||
{
|
||||
held = true;
|
||||
Render();
|
||||
}
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
if (held && MouseManager.MouseState != MouseState.Left)
|
||||
{
|
||||
held = false;
|
||||
}
|
||||
|
||||
if (held)
|
||||
{
|
||||
float relativeX = (float)(MouseManager.X - ScreenX);
|
||||
float clamped = Math.Clamp(relativeX, 0, Width - sliderWidth);
|
||||
//DrawString(clamped.ToString(), Color.Red, 0, 0);
|
||||
Value = (float)clamped.Map(0, Width - sliderWidth, (float)_minimum, (float)_maximum);
|
||||
|
||||
WM.UpdateQueue.Enqueue(this);
|
||||
}
|
||||
|
||||
Clear(Background);
|
||||
|
||||
int slotY;
|
||||
int sliderY;
|
||||
|
||||
if (_rangeLabels)
|
||||
{
|
||||
slotY = (sliderHeight / 2) - (slotHeight / 2);
|
||||
sliderY = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
slotY = (Height / 2) - (slotHeight / 2);
|
||||
sliderY = (Height / 2) - (sliderHeight / 2);
|
||||
}
|
||||
|
||||
// Slot
|
||||
DrawFilledRectangle(0, slotY, Width, slotHeight, Color.FromArgb(168, 168, 168));
|
||||
|
||||
// Slider
|
||||
DrawFilledRectangle(
|
||||
(int)(_value.Map((float)_minimum, (float)_maximum, 0, Width - sliderWidth)),
|
||||
sliderY,
|
||||
sliderWidth,
|
||||
sliderHeight,
|
||||
held ? Color.FromArgb(0, 71, 112) : Color.FromArgb(0, 115, 186)
|
||||
);
|
||||
|
||||
if (_rangeLabels)
|
||||
{
|
||||
DrawString(_minimum.ToString(), Foreground, 0, Height - FontData.Height);
|
||||
|
||||
DrawString(_maximum.ToString(), Foreground, Width - (FontData.Width * _maximum.ToString().Length), Height - FontData.Height);
|
||||
}
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Gui/UILib/ShortcutBar.cs
Normal file
93
Gui/UILib/ShortcutBar.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using CMLeonOS.Gui.SmoothMono;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class ShortcutBar : Control
|
||||
{
|
||||
public ShortcutBar(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
OnClick = ShortcutBarClick;
|
||||
}
|
||||
|
||||
internal List<ShortcutBarCell> Cells { get; set; } = new List<ShortcutBarCell>();
|
||||
|
||||
private Color _background = Color.LightGray;
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.Black;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private int _cellPadding = 10;
|
||||
internal int CellPadding
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cellPadding;
|
||||
}
|
||||
set
|
||||
{
|
||||
_cellPadding = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShortcutBarClick(int x, int y)
|
||||
{
|
||||
int cellEndX = 0;
|
||||
foreach (var cell in Cells)
|
||||
{
|
||||
cellEndX += (_cellPadding * 2) + (FontData.Width * cell.Text.Length);
|
||||
if (x < cellEndX)
|
||||
{
|
||||
cell.OnClick?.Invoke();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
Clear(Background);
|
||||
|
||||
int cellX = 0;
|
||||
for (int i = 0; i < Cells.Count; i++)
|
||||
{
|
||||
ShortcutBarCell cell = Cells[i];
|
||||
Rectangle cellRect = new Rectangle(cellX, 0, Width, Height);
|
||||
|
||||
int textX = cellRect.X + _cellPadding;
|
||||
int textY = cellRect.Y + (cellRect.Height / 2) - (FontData.Height / 2);
|
||||
|
||||
DrawString(cell.Text, Foreground, textX, textY);
|
||||
|
||||
cellX += (_cellPadding * 2) + (FontData.Width * cell.Text.Length);
|
||||
}
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Gui/UILib/ShortcutBarCell.cs
Normal file
17
Gui/UILib/ShortcutBarCell.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class ShortcutBarCell
|
||||
{
|
||||
internal ShortcutBarCell(string text, Action onClick)
|
||||
{
|
||||
Text = text;
|
||||
OnClick = onClick;
|
||||
}
|
||||
|
||||
internal string Text { get; set; } = string.Empty;
|
||||
|
||||
internal Action OnClick { get; set; }
|
||||
}
|
||||
}
|
||||
118
Gui/UILib/Switch.cs
Normal file
118
Gui/UILib/Switch.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using Cosmos.System;
|
||||
using Cosmos.System.Graphics;
|
||||
using System;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class Switch : CheckBox
|
||||
{
|
||||
public Switch(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
OnDown = SwitchDown;
|
||||
OnClick = null;
|
||||
}
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.SwitchOff.bmp")]
|
||||
private static byte[] offBytes;
|
||||
private static Bitmap offBitmap = new Bitmap(offBytes);
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.SwitchOn.bmp")]
|
||||
private static byte[] onBytes;
|
||||
private static Bitmap onBitmap = new Bitmap(onBytes);
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.SwitchKnob.bmp")]
|
||||
private static byte[] knobBytes;
|
||||
private static Bitmap knobBitmap = new Bitmap(knobBytes);
|
||||
|
||||
private const int maximumToggleDrag = 4;
|
||||
|
||||
private int lastMouseX = 0;
|
||||
private int totalDragged = 0;
|
||||
private bool held = false;
|
||||
|
||||
private void SwitchDown(int x, int y)
|
||||
{
|
||||
lastMouseX = (int)MouseManager.X;
|
||||
totalDragged = 0;
|
||||
held = true;
|
||||
Render();
|
||||
}
|
||||
|
||||
private void Release()
|
||||
{
|
||||
held = false;
|
||||
if (totalDragged <= maximumToggleDrag)
|
||||
{
|
||||
// Interpret as a toggle.
|
||||
Checked = !Checked;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interpret as a drag rather than a toggle,
|
||||
// setting the Checked state based on where
|
||||
// the switch knob is.
|
||||
Checked = knobX >= (offBitmap.Width / 2) - (knobBitmap.Width / 2);
|
||||
}
|
||||
}
|
||||
|
||||
private double knobX = -1;
|
||||
private double knobGoal = 0;
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
knobGoal = (int)(Checked ? offBitmap.Width - knobBitmap.Width : 0);
|
||||
|
||||
if (held && MouseManager.MouseState != MouseState.Left)
|
||||
{
|
||||
Release();
|
||||
}
|
||||
|
||||
if (held)
|
||||
{
|
||||
int diff = (int)(MouseManager.X - lastMouseX);
|
||||
lastMouseX = (int)MouseManager.X;
|
||||
totalDragged += Math.Abs(diff);
|
||||
knobX = Math.Clamp(knobX + diff, 0, offBitmap.Width - knobBitmap.Width);
|
||||
|
||||
WM.UpdateQueue.Enqueue(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
double oldKnobX = knobX;
|
||||
if (knobX == -1)
|
||||
{
|
||||
knobX = knobGoal;
|
||||
}
|
||||
else
|
||||
{
|
||||
double diff = knobGoal - knobX;
|
||||
double move = diff / 8d;
|
||||
knobX += move;
|
||||
}
|
||||
if (Math.Abs(knobX - oldKnobX) < 0.25)
|
||||
{
|
||||
knobX = knobGoal;
|
||||
}
|
||||
else
|
||||
{
|
||||
WM.UpdateQueue.Enqueue(this);
|
||||
}
|
||||
}
|
||||
|
||||
Clear(Background);
|
||||
|
||||
int switchX = 0;
|
||||
int switchY = (Height / 2) - ((int)offBitmap.Height / 2);
|
||||
|
||||
int textX = (int)(offBitmap.Width + 8);
|
||||
int textY = (Height / 2) - (16 / 2);
|
||||
|
||||
DrawImageAlpha(Checked ? onBitmap : offBitmap, switchX, switchY);
|
||||
DrawImageAlpha(knobBitmap, (int)knobX, switchY);
|
||||
|
||||
DrawString(Text, Foreground, textX, textY);
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
393
Gui/UILib/Table.cs
Normal file
393
Gui/UILib/Table.cs
Normal file
@@ -0,0 +1,393 @@
|
||||
using Cosmos.System;
|
||||
using Cosmos.System.Graphics;
|
||||
using CMLeonOS.Gui.SmoothMono;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class Table : Control
|
||||
{
|
||||
public Table(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
OnDown = TableDown;
|
||||
}
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.ScrollbarUp.bmp")]
|
||||
private static byte[] scrollbarUpBytes;
|
||||
private static Bitmap scrollbarUpBitmap = new Bitmap(scrollbarUpBytes);
|
||||
|
||||
[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.ScrollbarDown.bmp")]
|
||||
private static byte[] scrollbarDownBytes;
|
||||
private static Bitmap scrollbarDownBitmap = new Bitmap(scrollbarDownBytes);
|
||||
|
||||
internal List<TableCell> Cells { get; set; } = new List<TableCell>();
|
||||
|
||||
internal Action<int> TableCellSelected { get; set; }
|
||||
|
||||
internal bool AllowDeselection { get; set; } = true;
|
||||
|
||||
internal bool AllowSelection { get; set; } = true;
|
||||
|
||||
private double scrollY = 0;
|
||||
|
||||
private bool dragging = false;
|
||||
private int lastDragY = 0;
|
||||
|
||||
private int _selectedCellIndex = -1;
|
||||
internal int SelectedCellIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedCellIndex;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_selectedCellIndex != value)
|
||||
{
|
||||
_selectedCellIndex = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _scrollbarThickness = 20;
|
||||
internal int ScrollbarThickness
|
||||
{
|
||||
get
|
||||
{
|
||||
return _scrollbarThickness;
|
||||
}
|
||||
set
|
||||
{
|
||||
_scrollbarThickness = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _background = Color.LightGray;
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.Black;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _border = Color.Gray;
|
||||
internal Color Border
|
||||
{
|
||||
get
|
||||
{
|
||||
return _border;
|
||||
}
|
||||
set
|
||||
{
|
||||
_border = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _selectedBackground = Color.FromArgb(221, 246, 255);
|
||||
internal Color SelectedBackground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedBackground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_selectedBackground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _selectedForeground = Color.Black;
|
||||
internal Color SelectedForeground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedForeground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_selectedForeground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _selectedBorder = Color.FromArgb(126, 205, 234);
|
||||
internal Color SelectedBorder
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedBorder;
|
||||
}
|
||||
set
|
||||
{
|
||||
_selectedBorder = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private int _cellHeight = 20;
|
||||
internal int CellHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cellHeight;
|
||||
}
|
||||
set
|
||||
{
|
||||
_cellHeight = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Alignment _textAlignment = Alignment.Start;
|
||||
internal Alignment TextAlignment
|
||||
{
|
||||
get
|
||||
{
|
||||
return _textAlignment;
|
||||
}
|
||||
set
|
||||
{
|
||||
_textAlignment = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private void TableDown(int x, int y)
|
||||
{
|
||||
if ((CanScrollUp || CanScrollDown) && x >= (Width - _scrollbarThickness))
|
||||
{
|
||||
int allCellsHeight = Cells.Count * CellHeight;
|
||||
if (y < _scrollbarThickness && CanScrollUp)
|
||||
{
|
||||
scrollY = Math.Max(0, scrollY - CellHeight);
|
||||
Render();
|
||||
}
|
||||
if (y >= Height - _scrollbarThickness && CanScrollDown)
|
||||
{
|
||||
scrollY = Math.Min(allCellsHeight - Height, scrollY + CellHeight);
|
||||
Render();
|
||||
}
|
||||
if (y < Height - _scrollbarThickness && y >= _scrollbarThickness)
|
||||
{
|
||||
dragging = true;
|
||||
lastDragY = (int)MouseManager.Y;
|
||||
Render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int scrollAdjustedY = (int)(y + scrollY);
|
||||
if (scrollAdjustedY < 0 || scrollAdjustedY > _cellHeight * Cells.Count)
|
||||
{
|
||||
if (AllowDeselection)
|
||||
{
|
||||
SelectedCellIndex = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (AllowSelection)
|
||||
{
|
||||
SelectedCellIndex = scrollAdjustedY / _cellHeight;
|
||||
TableCellSelected?.Invoke(_selectedCellIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanScrollUp
|
||||
{
|
||||
get
|
||||
{
|
||||
return scrollY > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanScrollDown
|
||||
{
|
||||
get
|
||||
{
|
||||
int allCellsHeight = Cells.Count * CellHeight;
|
||||
return (scrollY < 0) || ((allCellsHeight > Height) && (scrollY < (allCellsHeight - Height)));
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderScrollbar()
|
||||
{
|
||||
if (CanScrollUp || CanScrollDown)
|
||||
{
|
||||
/* Background */
|
||||
DrawFilledRectangle(Width - _scrollbarThickness, 0, _scrollbarThickness, Height, _border);
|
||||
|
||||
/* Track */
|
||||
int trackAvailableHeight = Height - (ScrollbarThickness * 2);
|
||||
double trackSize = (double)Height / (double)(Cells.Count * CellHeight);
|
||||
double trackProgress = (double)scrollY / (double)((Cells.Count * CellHeight) - Height);
|
||||
int trackY = (int)(_scrollbarThickness + (((double)trackAvailableHeight - ((double)trackAvailableHeight * trackSize)) * trackProgress));
|
||||
// Border
|
||||
DrawFilledRectangle(Width - _scrollbarThickness, 0, _scrollbarThickness, Height, _border);
|
||||
// Background
|
||||
DrawFilledRectangle(Width - _scrollbarThickness + 1, trackY + 1, _scrollbarThickness - 2, (int)(trackSize * trackAvailableHeight) - 2, _background);
|
||||
|
||||
/* Up arrow */
|
||||
// Border
|
||||
DrawFilledRectangle(Width - _scrollbarThickness, 0, _scrollbarThickness, _scrollbarThickness, _border);
|
||||
// Background
|
||||
DrawFilledRectangle(Width - _scrollbarThickness + 1, 1, _scrollbarThickness - 2, _scrollbarThickness - 2, CanScrollUp ? _background : _border);
|
||||
DrawImageAlpha(scrollbarUpBitmap, (int)((Width - _scrollbarThickness) + ((_scrollbarThickness / 2) - (scrollbarUpBitmap.Width / 2))), (int)((_scrollbarThickness / 2) - (scrollbarUpBitmap.Height / 2)));
|
||||
|
||||
/* Down arrow */
|
||||
// Border
|
||||
DrawFilledRectangle(Width - _scrollbarThickness, Height - _scrollbarThickness, _scrollbarThickness, _scrollbarThickness, _border);
|
||||
// Background
|
||||
DrawFilledRectangle(Width - _scrollbarThickness + 1, Height - _scrollbarThickness + 1, _scrollbarThickness - 2, _scrollbarThickness - 2, CanScrollDown ? _background : _border);
|
||||
DrawImageAlpha(scrollbarDownBitmap, (int)((Width - _scrollbarThickness) + ((_scrollbarThickness / 2) - (scrollbarUpBitmap.Width / 2))), (int)((Height - _scrollbarThickness) + ((_scrollbarThickness / 2) - (scrollbarUpBitmap.Height / 2))));
|
||||
}
|
||||
}
|
||||
|
||||
internal void ScrollToTop()
|
||||
{
|
||||
scrollY = 0;
|
||||
Render();
|
||||
}
|
||||
|
||||
internal void ScrollToBottom()
|
||||
{
|
||||
int allCellsHeight = Cells.Count * CellHeight;
|
||||
if (allCellsHeight > Height)
|
||||
{
|
||||
scrollY = allCellsHeight - Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollY = 0;
|
||||
}
|
||||
Render();
|
||||
}
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
int scrollMax = (Cells.Count * CellHeight) - Height;
|
||||
if (dragging)
|
||||
{
|
||||
scrollY += (int)(MouseManager.Y - lastDragY);
|
||||
lastDragY = (int)MouseManager.Y;
|
||||
if (MouseManager.MouseState != MouseState.Left)
|
||||
{
|
||||
dragging = false;
|
||||
}
|
||||
WM.UpdateQueue.Enqueue(this);
|
||||
}
|
||||
else if (scrollY < 0 || scrollY > scrollMax)
|
||||
{
|
||||
double oldScrollY = scrollY;
|
||||
double move;
|
||||
if (scrollY > 0)
|
||||
{
|
||||
move = (scrollMax - scrollY) / 8d;
|
||||
}
|
||||
else
|
||||
{
|
||||
move = (-scrollY) / 8d;
|
||||
}
|
||||
scrollY += move;
|
||||
if (Math.Abs(scrollY - oldScrollY) > 0.05)
|
||||
{
|
||||
WM.UpdateQueue.Enqueue(this);
|
||||
}
|
||||
}
|
||||
|
||||
Clear(Background);
|
||||
|
||||
for (int i = 0; i < Cells.Count; i++)
|
||||
{
|
||||
TableCell cell = Cells[i];
|
||||
bool selected = _selectedCellIndex == i;
|
||||
Rectangle cellRect = new Rectangle(0, (int)((i * _cellHeight) - scrollY), Width, _cellHeight);
|
||||
|
||||
if (cellRect.Y < -cellRect.Height || cellRect.Y > Height)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cell.BackgroundColourOverride != null)
|
||||
{
|
||||
DrawFilledRectangle(cellRect.X, cellRect.Y, cellRect.Width, cellRect.Height, (Color)cell.BackgroundColourOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Border.
|
||||
DrawFilledRectangle(cellRect.X, cellRect.Y, cellRect.Width, cellRect.Height, selected ? _selectedBorder : _border);
|
||||
|
||||
// Background.
|
||||
DrawFilledRectangle(cellRect.X + 1, cellRect.Y + 1, cellRect.Width - 2, cellRect.Height - 2, selected ? _selectedBackground : _background);
|
||||
}
|
||||
|
||||
int textX;
|
||||
switch (_textAlignment)
|
||||
{
|
||||
case Alignment.Start:
|
||||
textX = cellRect.X + (cell.Image != null ? (CellHeight - FontData.Height) / 2 : 0);
|
||||
break;
|
||||
case Alignment.Middle:
|
||||
textX = cellRect.X + (cellRect.Width / 2) - (cell.Text.Length * FontData.Width / 2);
|
||||
break;
|
||||
case Alignment.End:
|
||||
textX = cellRect.X + cellRect.Width - (cell.Text.Length * FontData.Width);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid Table alignment!");
|
||||
}
|
||||
|
||||
|
||||
int textY = cellRect.Y + (cellRect.Height / 2) - (16 / 2);
|
||||
|
||||
if (cell.Image != null)
|
||||
{
|
||||
textX += (int)cell.Image.Width;
|
||||
DrawImageAlpha(cell.Image, cellRect.X, (int)(cellRect.Y + (cellRect.Height / 2) - (cell.Image.Height / 2)));
|
||||
}
|
||||
|
||||
if (cell.ForegroundColourOverride != null)
|
||||
{
|
||||
DrawString(cell.Text, (Color)cell.ForegroundColourOverride, textX, textY);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawString(cell.Text, selected ? SelectedForeground : Foreground, textX, textY);
|
||||
}
|
||||
}
|
||||
|
||||
//DrawString($"{scrollY.ToString()} {dragging.ToString()} {scrollMax.ToString()}", Color.Red, 0, 0);
|
||||
|
||||
RenderScrollbar();
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Gui/UILib/TableCell.cs
Normal file
42
Gui/UILib/TableCell.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Cosmos.System.Graphics;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class TableCell
|
||||
{
|
||||
internal TableCell(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
internal TableCell(string text, object tag)
|
||||
{
|
||||
Text = text;
|
||||
Tag = tag;
|
||||
}
|
||||
|
||||
internal TableCell(Bitmap image, string text)
|
||||
{
|
||||
Image = image;
|
||||
Text = text;
|
||||
}
|
||||
|
||||
internal TableCell(Bitmap image, string text, object tag)
|
||||
{
|
||||
Image = image;
|
||||
Text = text;
|
||||
Tag = tag;
|
||||
}
|
||||
|
||||
internal Bitmap Image { get; set; }
|
||||
|
||||
internal string Text { get; set; } = string.Empty;
|
||||
|
||||
internal object Tag { get; set; }
|
||||
|
||||
internal Color? BackgroundColourOverride { get; set; } = null;
|
||||
|
||||
internal Color? ForegroundColourOverride { get; set; } = null;
|
||||
}
|
||||
}
|
||||
124
Gui/UILib/TextBlock.cs
Normal file
124
Gui/UILib/TextBlock.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class TextBlock : Control
|
||||
{
|
||||
public TextBlock(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
private string _text = "TextBlock";
|
||||
internal string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return _text;
|
||||
}
|
||||
set
|
||||
{
|
||||
_text = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _background = Color.White;
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.Black;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Alignment _horizontalAlignment = Alignment.Start;
|
||||
internal Alignment HorizontalAlignment
|
||||
{
|
||||
get
|
||||
{
|
||||
return _horizontalAlignment;
|
||||
}
|
||||
set
|
||||
{
|
||||
_horizontalAlignment = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Alignment _verticalAlignment = Alignment.Start;
|
||||
internal Alignment VerticalAlignment
|
||||
{
|
||||
get
|
||||
{
|
||||
return _verticalAlignment;
|
||||
}
|
||||
set
|
||||
{
|
||||
_verticalAlignment = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
Clear(Background);
|
||||
|
||||
int textX;
|
||||
int textY;
|
||||
|
||||
switch (HorizontalAlignment)
|
||||
{
|
||||
case Alignment.Start:
|
||||
textX = 0;
|
||||
break;
|
||||
case Alignment.Middle:
|
||||
textX = (Width / 2) - (8 * Text.Length / 2);
|
||||
break;
|
||||
case Alignment.End:
|
||||
textX = Width - (8 * Text.Length);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid horizontal alignment.");
|
||||
}
|
||||
|
||||
switch (VerticalAlignment)
|
||||
{
|
||||
case Alignment.Start:
|
||||
textY = 0;
|
||||
break;
|
||||
case Alignment.Middle:
|
||||
textY = (Height / 2) - (16 / 2);
|
||||
break;
|
||||
case Alignment.End:
|
||||
textY = Height - 16;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Invalid vertical alignment.");
|
||||
}
|
||||
|
||||
DrawString(Text, Foreground, textX, textY);
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
375
Gui/UILib/TextBox.cs
Normal file
375
Gui/UILib/TextBox.cs
Normal file
@@ -0,0 +1,375 @@
|
||||
using Cosmos.System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace CMLeonOS.Gui.UILib
|
||||
{
|
||||
internal class TextBox : Control
|
||||
{
|
||||
public TextBox(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height)
|
||||
{
|
||||
OnDown = TextBoxDown;
|
||||
OnKeyPressed = TextBoxKeyPressed;
|
||||
OnUnfocused = TextBoxUnfocused;
|
||||
}
|
||||
|
||||
internal Action Submitted;
|
||||
internal Action Changed;
|
||||
|
||||
internal string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
builder.Append(lines[i]);
|
||||
if (i != lines.Count - 1)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
lines = value.Split('\n').ToList();
|
||||
|
||||
caretLine = -1;
|
||||
caretCol = 0;
|
||||
|
||||
MarkAllLines();
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private string _placeholderText = string.Empty;
|
||||
internal string PlaceholderText
|
||||
{
|
||||
get
|
||||
{
|
||||
return _placeholderText;
|
||||
}
|
||||
set
|
||||
{
|
||||
_placeholderText = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool ReadOnly { get; set; } = false;
|
||||
|
||||
internal bool MultiLine { get; set; } = false;
|
||||
|
||||
internal bool Shield { get; set; } = false;
|
||||
|
||||
private Color _background = Color.White;
|
||||
internal Color Background
|
||||
{
|
||||
get
|
||||
{
|
||||
return _background;
|
||||
}
|
||||
set
|
||||
{
|
||||
_background = value;
|
||||
Clear(_background);
|
||||
MarkAllLines();
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _foreground = Color.Black;
|
||||
internal Color Foreground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _foreground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_foreground = value;
|
||||
MarkAllLines();
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private Color _placeholderForeground = Color.Gray;
|
||||
internal Color PlaceholderForeground
|
||||
{
|
||||
get
|
||||
{
|
||||
return _placeholderForeground;
|
||||
}
|
||||
set
|
||||
{
|
||||
_placeholderForeground = value;
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveCaret(int line, int col)
|
||||
{
|
||||
if (caretLine == line && caretCol == col) return;
|
||||
MarkLine(caretLine);
|
||||
caretLine = Math.Clamp(line, 0, lines.Count - 1);
|
||||
caretCol = Math.Clamp(col, 0, lines[caretLine].Length);
|
||||
MarkLine(caretLine);
|
||||
Render();
|
||||
}
|
||||
|
||||
private void TextBoxDown(int x, int y)
|
||||
{
|
||||
MoveCaret((y + scrollY) / fontHeight, ((x + scrollX) + (fontWidth / 2)) / fontWidth);
|
||||
}
|
||||
|
||||
private void TextBoxUnfocused()
|
||||
{
|
||||
MarkLine(caretLine);
|
||||
|
||||
caretLine = -1;
|
||||
caretCol = 0;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
private void AutoScroll()
|
||||
{
|
||||
if (caretLine == -1) return;
|
||||
|
||||
if (scrollY + Height < (caretLine + 1) * fontHeight)
|
||||
{
|
||||
// Scroll up.
|
||||
scrollY = ((caretLine + 1) * fontHeight) - Height;
|
||||
MarkAllLines();
|
||||
}
|
||||
if (caretLine * fontHeight < scrollY)
|
||||
{
|
||||
// Scroll down.
|
||||
scrollY = caretLine * fontHeight;
|
||||
MarkAllLines();
|
||||
}
|
||||
|
||||
if (scrollX + Width < (caretCol + 1) * fontWidth)
|
||||
{
|
||||
// Scroll right.
|
||||
scrollX = ((caretCol + 1) * fontWidth) - Width;
|
||||
MarkAllLines();
|
||||
}
|
||||
if (caretCol * fontWidth < scrollX)
|
||||
{
|
||||
// Scroll left.
|
||||
scrollX = caretCol * fontWidth;
|
||||
MarkAllLines();
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBoxKeyPressed(KeyEvent key)
|
||||
{
|
||||
if (caretLine == -1 || ReadOnly) return;
|
||||
switch (key.Key)
|
||||
{
|
||||
case ConsoleKeyEx.LeftArrow:
|
||||
if (caretCol == 0)
|
||||
{
|
||||
if (caretLine == 0) return;
|
||||
caretLine--;
|
||||
caretCol = lines[caretLine].Length;
|
||||
MarkLine(caretLine);
|
||||
MarkLine(caretLine + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
caretCol--;
|
||||
MarkLine(caretLine);
|
||||
}
|
||||
break;
|
||||
case ConsoleKeyEx.RightArrow:
|
||||
if (caretCol == lines[caretLine].Length)
|
||||
{
|
||||
if (caretLine == lines.Count - 1) return;
|
||||
caretLine++;
|
||||
|
||||
caretCol = 0;
|
||||
MarkLine(caretLine - 1);
|
||||
MarkLine(caretLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
caretCol++;
|
||||
MarkLine(caretLine);
|
||||
}
|
||||
break;
|
||||
case ConsoleKeyEx.UpArrow:
|
||||
if (caretLine == 0) return;
|
||||
|
||||
caretLine--;
|
||||
caretCol = Math.Min(lines[caretLine].Length, caretCol);
|
||||
|
||||
MarkLine(caretLine);
|
||||
MarkLine(caretLine + 1);
|
||||
break;
|
||||
case ConsoleKeyEx.DownArrow:
|
||||
if (caretLine == lines.Count - 1) return;
|
||||
|
||||
caretLine++;
|
||||
caretCol = Math.Min(lines[caretLine].Length, caretCol);
|
||||
|
||||
MarkLine(caretLine - 1);
|
||||
MarkLine(caretLine);
|
||||
break;
|
||||
case ConsoleKeyEx.Enter:
|
||||
if (!MultiLine)
|
||||
{
|
||||
Submitted?.Invoke();
|
||||
|
||||
caretLine = -1;
|
||||
caretCol = 0;
|
||||
|
||||
MarkAllLines();
|
||||
break;
|
||||
}
|
||||
|
||||
lines.Insert(caretLine + 1, lines[caretLine].Substring(caretCol));
|
||||
lines[caretLine] = lines[caretLine].Substring(0, caretCol);
|
||||
|
||||
caretLine++;
|
||||
caretCol = 0;
|
||||
|
||||
MarkLine(caretLine - 1);
|
||||
MarkLine(caretLine);
|
||||
|
||||
Changed?.Invoke();
|
||||
break;
|
||||
case ConsoleKeyEx.Backspace:
|
||||
if (caretCol == 0)
|
||||
{
|
||||
if (caretLine == 0) return;
|
||||
|
||||
caretLine--;
|
||||
caretCol = lines[caretLine].Length;
|
||||
|
||||
lines[caretLine] += lines[caretLine + 1];
|
||||
lines.RemoveAt(caretLine + 1);
|
||||
|
||||
MarkLine(caretLine);
|
||||
MarkLine(caretLine + 1);
|
||||
|
||||
Changed?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
lines[caretLine] = lines[caretLine].Remove(caretCol - 1, 1);
|
||||
caretCol--;
|
||||
MarkLine(caretLine);
|
||||
|
||||
Changed?.Invoke();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
lines[caretLine] = lines[caretLine].Insert(caretCol, key.KeyChar.ToString());
|
||||
caretCol++;
|
||||
MarkLine(caretLine);
|
||||
|
||||
Changed?.Invoke();
|
||||
break;
|
||||
}
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
private void MarkLine(int lineNum)
|
||||
{
|
||||
if (markedLinesBegin == -1)
|
||||
{
|
||||
markedLinesBegin = lineNum;
|
||||
}
|
||||
else
|
||||
{
|
||||
markedLinesBegin = Math.Min(markedLinesBegin, lineNum);
|
||||
}
|
||||
if (markedLinesEnd == -1)
|
||||
{
|
||||
markedLinesEnd = lineNum;
|
||||
}
|
||||
else
|
||||
{
|
||||
markedLinesEnd = Math.Max(markedLinesEnd, lineNum);
|
||||
}
|
||||
}
|
||||
|
||||
internal void MarkAllLines()
|
||||
{
|
||||
markedLinesBegin = 0;
|
||||
markedLinesEnd = lines.Count - 1;
|
||||
}
|
||||
|
||||
private List<string> lines = new List<string>() { string.Empty };
|
||||
|
||||
private int markedLinesBegin = 0;
|
||||
private int markedLinesEnd = 0;
|
||||
|
||||
private const int fontWidth = 8;
|
||||
private const int fontHeight = 16;
|
||||
|
||||
private int caretLine = -1;
|
||||
private int caretCol = 0;
|
||||
|
||||
private int scrollX = 0;
|
||||
private int scrollY = 0;
|
||||
|
||||
internal override void Render()
|
||||
{
|
||||
if (Text == string.Empty)
|
||||
{
|
||||
Clear(_background);
|
||||
|
||||
DrawRectangle(0, 0, Width, Height, Color.Gray);
|
||||
DrawString(PlaceholderText, PlaceholderForeground, 0, 0);
|
||||
|
||||
if (caretLine == 0)
|
||||
{
|
||||
DrawVerticalLine(fontHeight, 1, 0, Foreground);
|
||||
}
|
||||
|
||||
WM.Update(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (markedLinesBegin == -1 || markedLinesEnd == -1) return;
|
||||
|
||||
AutoScroll();
|
||||
|
||||
for (int i = markedLinesBegin; i <= markedLinesEnd; i++)
|
||||
{
|
||||
int lineY = (i * fontHeight) - scrollY;
|
||||
|
||||
if (lineY < 0) continue;
|
||||
if (lineY > Height) break;
|
||||
|
||||
DrawFilledRectangle(0, lineY, Width, fontHeight, Background);
|
||||
|
||||
if (i < lines.Count)
|
||||
{
|
||||
DrawString(Shield ? new string('*', lines[i].Length) : lines[i], Foreground, -scrollX, lineY);
|
||||
|
||||
if (caretLine == i)
|
||||
{
|
||||
DrawVerticalLine(fontHeight, ((caretCol * fontWidth) - scrollX) + 1, (caretLine * fontHeight) - scrollY, Foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
markedLinesBegin = -1;
|
||||
markedLinesEnd = -1;
|
||||
|
||||
DrawRectangle(0, 0, Width, Height, Color.Gray);
|
||||
|
||||
WM.Update(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user