diff --git a/BootMenu.cs b/BootMenu.cs index 47b5453..5fae31c 100644 --- a/BootMenu.cs +++ b/BootMenu.cs @@ -9,6 +9,7 @@ namespace CMLeonOS public enum BootMenuAction { NormalBoot, + GuiBoot, Reboot, Shutdown } @@ -44,8 +45,9 @@ namespace CMLeonOS Console.WriteLine(); PrintOption("Normal Boot", selIdx == 0); - PrintOption("Reboot", selIdx == 1); - PrintOption("Shutdown", selIdx == 2); + PrintOption("GUI Boot", selIdx == 1); + PrintOption("Reboot", selIdx == 2); + PrintOption("Shutdown", selIdx == 3); } private static BootMenuAction Confirm(int selIdx) @@ -64,9 +66,11 @@ namespace CMLeonOS case 0: return BootMenuAction.NormalBoot; case 1: + return BootMenuAction.GuiBoot; + case 2: Sys.Power.Reboot(); return BootMenuAction.Reboot; - case 2: + case 3: Sys.Power.Shutdown(); return BootMenuAction.Shutdown; default: @@ -129,10 +133,10 @@ namespace CMLeonOS if (selIdx < 0) { - selIdx = 2; + selIdx = 3; } - if (selIdx > 2) + if (selIdx > 3) { selIdx = 0; } diff --git a/BuildTime.txt b/BuildTime.txt index 738e3e0..5435060 100644 --- a/BuildTime.txt +++ b/BuildTime.txt @@ -1 +1 @@ -2026-02-28 19:35:58 \ No newline at end of file +2026-03-01 17:02:21 \ No newline at end of file diff --git a/CMLeonOS.csproj b/CMLeonOS.csproj index f424ed0..63e2a4f 100644 --- a/CMLeonOS.csproj +++ b/CMLeonOS.csproj @@ -70,7 +70,68 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GitCommit.txt b/GitCommit.txt index 02e284d..caecf1e 100644 --- a/GitCommit.txt +++ b/GitCommit.txt @@ -1 +1 @@ -c5fc081 \ No newline at end of file +545f40c \ No newline at end of file diff --git a/Gui/App.cs b/Gui/App.cs new file mode 100644 index 0000000..4bc5222 --- /dev/null +++ b/Gui/App.cs @@ -0,0 +1,36 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui +{ + internal class AppMetadata + { + public AppMetadata(string name, Func createProcess, Bitmap icon, Color themeColor) + { + Name = name; + CreateProcess = createProcess; + Icon = icon; + ThemeColor = themeColor; + } + + internal void Start(Process parent) + { + ProcessManager.AddProcess(parent, CreateProcess()).Start(); + } + + internal void Start() + { + ProcessManager.AddProcess(CreateProcess()).Start(); + } + + internal string Name { get; private set; } + + internal Func CreateProcess { get; private set; } + + internal Bitmap Icon { get; private set; } + + internal Color ThemeColor { get; private set; } + } +} diff --git a/Gui/AppManager.cs b/Gui/AppManager.cs new file mode 100644 index 0000000..d120e97 --- /dev/null +++ b/Gui/AppManager.cs @@ -0,0 +1,129 @@ +using Cosmos.System.Graphics; +using CMLeonOS.Gui.Apps; +using CMLeonOS.Logger; +using CMLeonOS.Utils; +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui +{ + internal static class AppManager + { + internal static List AppMetadatas { get; private set; } = new List(); + + private static bool appsLoaded = false; + + internal static Bitmap DefaultAppIcon + { + get + { + return Icons.Icon_Default; + } + } + + private static class Icons + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Default.bmp")] + private static byte[] _iconBytes_Default; + internal static Bitmap Icon_Default = new Bitmap(_iconBytes_Default); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Info.bmp")] + private static byte[] _iconBytes_Info; + internal static Bitmap Icon_Info = new Bitmap(_iconBytes_Info); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Settings.bmp")] + private static byte[] _iconBytes_Settings; + internal static Bitmap Icon_Settings = new Bitmap(_iconBytes_Settings); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Clock.bmp")] + private static byte[] _iconBytes_Clock; + internal static Bitmap Icon_Clock = new Bitmap(_iconBytes_Clock); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Tasks.bmp")] + private static byte[] _iconBytes_Tasks; + internal static Bitmap Icon_Tasks = new Bitmap(_iconBytes_Tasks); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Calculator.bmp")] + private static byte[] _iconBytes_Calculator; + internal static Bitmap Icon_Calculator = new Bitmap(_iconBytes_Calculator); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.CodeStudio.bmp")] + private static byte[] _iconBytes_CodeStudio; + internal static Bitmap Icon_CodeStudio = new Bitmap(_iconBytes_CodeStudio); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Notepad.bmp")] + private static byte[] _iconBytes_Notepad; + internal static Bitmap Icon_Notepad = new Bitmap(_iconBytes_Notepad); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Calendar.bmp")] + private static byte[] _iconBytes_Calendar; + internal static Bitmap Icon_Calendar = new Bitmap(_iconBytes_Calendar); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Files.bmp")] + private static byte[] _iconBytes_Files; + internal static Bitmap Icon_Files = new Bitmap(_iconBytes_Files); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Logs.bmp")] + private static byte[] _iconBytes_Logs; + internal static Bitmap Icon_Logs = new Bitmap(_iconBytes_Logs); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.DemoLauncher.bmp")] + private static byte[] _iconBytes_DemoLauncher; + internal static Bitmap Icon_DemoLauncher = new Bitmap(_iconBytes_DemoLauncher); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Stopwatch.bmp")] + private static byte[] _iconBytes_Stopwatch; + internal static Bitmap Icon_Stopwatch = new Bitmap(_iconBytes_Stopwatch); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Paint.bmp")] + private static byte[] _iconBytes_Paint; + internal static Bitmap Icon_Paint = new Bitmap(_iconBytes_Paint); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.MemoryStatistics.bmp")] + private static byte[] _iconBytes_MemoryStatistics; + internal static Bitmap Icon_MemoryStatistics = new Bitmap(_iconBytes_MemoryStatistics); + } + + internal static void RegisterApp(AppMetadata app) + { + AppMetadatas.Add(app); + } + + internal static AppMetadata GetAppMetadata(string name) + { + foreach (AppMetadata app in AppMetadatas) + { + if (app.Name == name) + { + return app; + } + } + return null; + } + + internal static void LoadAllApps() + { + if (appsLoaded) + { + return; + } + + RegisterApp(new AppMetadata("Files", () => { return new Files(); }, Icons.Icon_Files, Color.FromArgb(25, 84, 97))); + RegisterApp(new AppMetadata("Clock", () => { return new Clock(); }, Icons.Icon_Clock, Color.FromArgb(168, 55, 47))); + RegisterApp(new AppMetadata("Notepad", () => { return new Notepad(); }, Icons.Icon_Notepad, Color.FromArgb(14, 59, 76))); + RegisterApp(new AppMetadata("Settings", () => { return new Apps.Settings(); }, Icons.Icon_Settings, Color.FromArgb(0, 115, 186))); + RegisterApp(new AppMetadata("Tasks", () => { return new Tasks(); }, Icons.Icon_Tasks, Color.FromArgb(204, 241, 255))); + RegisterApp(new AppMetadata("Calculator", () => { return new Calculator(); }, Icons.Icon_Calculator, Color.FromArgb(0, 115, 186))); + RegisterApp(new AppMetadata("Event Log", () => { return new Logs(); }, Icons.Icon_Logs, Color.FromArgb(14, 59, 76))); + RegisterApp(new AppMetadata("Demos", () => { return new DemoLauncher(); }, Icons.Icon_DemoLauncher, Color.FromArgb(25, 25, 25))); + RegisterApp(new AppMetadata("Info", () => { return new Info(); }, Icons.Icon_Info, Color.FromArgb(0, 115, 186))); + RegisterApp(new AppMetadata("Stopwatch", () => { return new Stopwatch(); }, Icons.Icon_Stopwatch, Color.FromArgb(168, 55, 47))); + RegisterApp(new AppMetadata("Paint", () => { return new Apps.Paint.Paint(); }, Icons.Icon_Paint, Color.FromArgb(0, 115, 186))); + RegisterApp(new AppMetadata("Memory Statistics", () => { return new Apps.MemoryStatistics(); }, Icons.Icon_MemoryStatistics, Color.FromArgb(25, 25, 25))); + + Logger.Logger.Instance.Info("AppManager", $"{AppMetadatas.Count} apps were registered."); + + appsLoaded = true; + } + } +} diff --git a/Gui/Apps/Calculator.cs b/Gui/Apps/Calculator.cs new file mode 100644 index 0000000..2311401 --- /dev/null +++ b/Gui/Apps/Calculator.cs @@ -0,0 +1,290 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class Calculator : Process + { + internal Calculator() : base("Calculator", ProcessType.Application) { } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + private enum Operator + { + None, + Add, + Subtract, + Multiply, + Divide + } + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Calculator.GridButton.bmp")] + private static byte[] gridButtonBytes; + private static Bitmap gridButtonBitmap = new Bitmap(gridButtonBytes); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Calculator.Display.bmp")] + private static byte[] displayBytes; + private static Bitmap displayBitmap = new Bitmap(displayBytes); + + private const int padding = 16; + private const int gridButtonSize = 40; + private const int resultHeight = 40; + + private long num1 = 0; + private long num2 = 0; + private Operator op = Operator.None; + + private void RenderGridButton(string text, int x, int y) + { + int buttonX = (x * gridButtonSize); + int buttonY = (y * gridButtonSize) + resultHeight; + window.DrawImage(gridButtonBitmap, buttonX, buttonY); + window.DrawString(text, Color.Black, buttonX + (gridButtonSize / 2) - ((text.Length * 8) / 2), buttonY + (gridButtonSize / 2) - (16 / 2)); + } + + private void WindowClick(int x, int y) + { + int gridX = x / gridButtonSize; + int gridY = (y - resultHeight) / gridButtonSize; + if (gridY < 0) + { + return; + } + switch (gridY) + { + case 0: + switch (gridX) + { + case 0: + InputNum("7"); + break; + case 1: + InputNum("8"); + break; + case 2: + InputNum("9"); + break; + case 3: + InputOp(Operator.Add); + break; + } + break; + case 1: + switch (gridX) + { + case 0: + InputNum("4"); + break; + case 1: + InputNum("5"); + break; + case 2: + InputNum("6"); + break; + case 3: + InputOp(Operator.Subtract); + break; + } + break; + case 2: + switch (gridX) + { + case 0: + InputNum("1"); + break; + case 1: + InputNum("2"); + break; + case 2: + InputNum("3"); + break; + case 3: + InputOp(Operator.Multiply); + break; + } + break; + case 3: + switch (gridX) + { + case 0: + InputNum("0"); + break; + case 1: + InputBksp(); + break; + case 2: + InputEquals(); + break; + case 3: + InputOp(Operator.Divide); + break; + } + break; + } + } + + private void RenderDisplay(bool updateWindow = true) + { + window.DrawImage(displayBitmap, 0, 0); + string text; + if (op != Operator.None) + { + char opChar; + opChar = op switch + { + Operator.Add => '+', + Operator.Subtract => '-', + Operator.Multiply => '*', + Operator.Divide => '/', + _ => throw new Exception("Unrecognised operator.") + }; + text = num1.ToString().TrimEnd('.') + opChar + num2.ToString().TrimEnd('.'); + } + else + { + text = num1.ToString().TrimEnd('.'); + } + window.DrawString(text, Color.Black, window.Width - 12 - (text.Length * 8), 12); + if (updateWindow) + { + wm.Update(window); + } + } + + private void InputNum(string num) + { + if (op != Operator.None) + { + num2 = long.Parse(num2.ToString() + num); + } + else + { + num1 = long.Parse(num1.ToString() + num); + } + RenderDisplay(); + } + + private void InputOp(Operator @operator) + { + if (op != Operator.None) + { + num1 = num2; + } + op = @operator; + num2 = 0; + RenderDisplay(); + } + + private void InputBksp() + { + long num = op != Operator.None ? num2 : num1; + string numStr = num.ToString(); + if (numStr.Length > 1) + { + num = long.Parse(numStr.Substring(0, numStr.Length - 1)); + } + else + { + num = 0; + } + if (op != Operator.None) + { + num2 = num; + } + else + { + num1 = num; + } + RenderDisplay(); + } + + private void InputEquals() + { + if (op == Operator.None) return; + + switch (op) + { + case Operator.Add: + num1 = num1 + num2; + break; + case Operator.Subtract: + num1 = num1 - num2; + break; + case Operator.Multiply: + num1 = num1 * num2; + break; + case Operator.Divide: + if (num2 == 0) + { + MessageBox messageBox = new MessageBox(this, "Calculator", "Cannot divide by zero."); + messageBox.Show(); + return; + } + num1 = num1 / num2; + break; + default: + throw new Exception("Unrecognised operator."); + } + num2 = 0; + op = Operator.None; + RenderDisplay(); + } + + private void RenderGridButtons() + { + RenderGridButton("7", 0, 0); + RenderGridButton("8", 1, 0); + RenderGridButton("9", 2, 0); + RenderGridButton("+", 3, 0); + + RenderGridButton("4", 0, 1); + RenderGridButton("5", 1, 1); + RenderGridButton("6", 2, 1); + RenderGridButton("-", 3, 1); + + RenderGridButton("1", 0, 2); + RenderGridButton("2", 1, 2); + RenderGridButton("3", 2, 2); + RenderGridButton("*", 3, 2); + + RenderGridButton("0", 0, 3); + RenderGridButton("<-", 1, 3); + RenderGridButton("=", 2, 3); + RenderGridButton("/", 3, 3); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, gridButtonSize * 4, (gridButtonSize * 4) + resultHeight); + wm.AddWindow(window); + + window.Title = "Calculator"; + window.Clear(Color.Gray); + window.Icon = AppManager.GetAppMetadata("Calculator").Icon; + window.OnClick = WindowClick; + window.Closing = TryStop; + + /*inputTextBlock = new TextBlock(window, padding / 2, padding / 2, window.Width - padding, resultHeight - padding); + inputTextBlock.Background = Color.Gray; + inputTextBlock.Foreground = Color.White; + wm.AddWindow(inputTextBlock);*/ + + RenderDisplay(updateWindow: false); + + RenderGridButtons(); + + wm.Update(window); + } + + public override void Run() + { + + } + } +} diff --git a/Gui/Apps/Calendar.cs b/Gui/Apps/Calendar.cs new file mode 100644 index 0000000..e84fa62 --- /dev/null +++ b/Gui/Apps/Calendar.cs @@ -0,0 +1,88 @@ +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System; + +namespace CMLeonOS.Gui.Apps +{ + internal class Calendar : Process + { + internal Calendar() : base("Calendar", ProcessType.Application) { } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + UILib.Calendar cal; + + Button nextButton; + + Button prevButton; + + private void PrevClicked(int x, int y) + { + if (cal.Month == 1) + { + cal.SetCalendar(cal.Year - 1, 12); + } + else + { + cal.SetCalendar(cal.Year, cal.Month - 1); + } + } + + private void NextClicked(int x, int y) + { + if (cal.Month == 12) + { + + cal.SetCalendar(cal.Year + 1, 1); + } + else + { + cal.SetCalendar(cal.Year, cal.Month + 1); + } + } + + private void WindowResized() + { + cal.Resize(window.Width, window.Height); + + cal.Render(); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 320, 256, 384, 288); + wm.AddWindow(window); + window.Title = "Calendar"; + window.Icon = AppManager.GetAppMetadata("Calendar").Icon; + window.CanResize = true; + window.UserResized = WindowResized; + window.Closing = TryStop; + + cal = new UILib.Calendar(window, 0, 0, window.Width, window.Height); + wm.AddWindow(cal); + + DateTime now = DateTime.Now; + cal.Year = now.Year; + cal.Month = now.Month; + + prevButton = new Button(window, 8, 8, 24, 24); + prevButton.Text = "<"; + prevButton.OnClick = PrevClicked; + wm.AddWindow(prevButton); + + nextButton = new Button(window, 40, 8, 24, 24); + nextButton.Text = ">"; + nextButton.OnClick = NextClicked; + wm.AddWindow(nextButton); + + wm.Update(window); + } + + public override void Run() + { + } + } +} diff --git a/Gui/Apps/Clock.cs b/Gui/Apps/Clock.cs new file mode 100644 index 0000000..89571ad --- /dev/null +++ b/Gui/Apps/Clock.cs @@ -0,0 +1,122 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class Clock : Process + { + internal Clock() : base("Clock", ProcessType.Application) { } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + private int lastSecond = DateTime.Now.Second; + + private SettingsService settingsService; + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Clock.ClockBackground.bmp")] + private static byte[] clockBackgroundBytes; + private static Bitmap clockBackgroundBitmap = new Bitmap(clockBackgroundBytes); + + private void RenderHand(int originX, int originY, int handLength, double radians, Color color) + { + int x = originX + (int)(handLength * Math.Sin(radians)); + int y = originY - (int)(handLength * Math.Cos(radians)); + window.DrawLine(originX, originY, x, y, color); + } + + private void RenderClock() + { + if (settingsService == null) + { + settingsService = ProcessManager.GetProcess(); + } + + DateTime now = DateTime.Now; + + string timeText; + if (settingsService.TwelveHourClock) + { + timeText = DateTime.Now.ToString("h:mm:ss tt"); + } + else + { + timeText = DateTime.Now.ToString("HH:mm:ss"); + } + + int originX = window.Width / 2; + int originY = window.Height / 2; + int diameter = (int)(Math.Min(window.Width, window.Height) * 0.75f); + int radius = diameter / 2; + + /* Background */ + if (window.Width == 192 && window.Height == 192) + { + window.DrawImage(clockBackgroundBitmap, 0, 0); + } + else + { + window.Clear(Color.White); + window.DrawCircle(originX, originY, radius, Color.Black); + + for (int i = 1; i <= 12; i++) + { + int numX = (int)(originX + (Math.Sin(i * Math.PI / 6) * radius * 0.8)) - 4; + int numY = (int)(originY - Math.Cos(i * Math.PI / 6) * radius * 0.8) - 8; + window.DrawString(i.ToString(), Color.Black, numX, numY); + } + } + + window.DrawString(timeText, Color.Black, (window.Width / 2) - ((timeText.Length * 8) / 2), 4); + + /* Second hand */ + double second = now.Second; + double secondRad = second * Math.PI / 30; + RenderHand(originX, originY, radius, secondRad, Color.Red); + + /* Minute hand*/ + double minute = now.Minute + (second / 60); + double minuteRad = minute * Math.PI / 30; + RenderHand(originX, originY, (int)(radius * 0.75f), minuteRad, Color.Black); + + /* Hour hand */ + double hour = now.Hour + (minute / 60); + double hourRad = hour * Math.PI / 6; + RenderHand(originX, originY, (int)(radius * 0.5f), hourRad, Color.Black); + + wm.Update(window); + } + + #region Process + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 192, 192); + window.Icon = AppManager.GetAppMetadata("Clock").Icon; + window.CanResize = true; + window.UserResized = RenderClock; + window.Closing = TryStop; + wm.AddWindow(window); + + window.Title = "Clock"; + + RenderClock(); + } + + + public override void Run() + { + DateTime now = DateTime.Now; + if (lastSecond != now.Second) + { + RenderClock(); + lastSecond = now.Second; + } + } + #endregion Process + } +} diff --git a/Gui/Apps/DemoLauncher.cs b/Gui/Apps/DemoLauncher.cs new file mode 100644 index 0000000..aff8501 --- /dev/null +++ b/Gui/Apps/DemoLauncher.cs @@ -0,0 +1,86 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.SmoothMono; +using CMLeonOS.Gui.UILib; +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class DemoLauncher : Process + { + internal DemoLauncher() : base("DemoLauncher", ProcessType.Application) { } + + AppWindow window; + + Table table; + + WindowManager wm = ProcessManager.GetProcess(); + + private static class Icons + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Demos.Starfield.bmp")] + private static byte[] _iconBytes_Starfield; + internal static Bitmap Icon_Starfield = new Bitmap(_iconBytes_Starfield); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.AppIcons.Demos.Mandelbrot.bmp")] + private static byte[] _iconBytes_Mandelbrot; + internal static Bitmap Icon_Mandelbrot = new Bitmap(_iconBytes_Mandelbrot); + } + + List demoApps = new() + { + new AppMetadata("Starfield", () => { return new Apps.Demos.Starfield(); }, Icons.Icon_Starfield, Color.Black ), + new AppMetadata("Mandelbrot", () => { return new Apps.Demos.Mandelbrot(); }, Icons.Icon_Mandelbrot, Color.Black ), + }; + + private const string message = "Demo Launcher"; + + private void PopulateTable() + { + table.Cells.Clear(); + foreach (AppMetadata app in demoApps) + { + table.Cells.Add(new TableCell(app.Icon.Resize(32, 32), app.Name)); + } + table.Render(); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 384, 256); + wm.AddWindow(window); + window.Title = "Demos"; + window.Icon = AppManager.GetAppMetadata("Demos").Icon; + window.Closing = TryStop; + + window.Clear(Color.FromArgb(56, 56, 71)); + + window.DrawString(message, Color.White, (window.Width / 2) - ((FontData.Width * message.Length) / 2), 12); + + table = new Table(window, 12, 40, window.Width - 24, window.Height - 52); + table.Background = Color.FromArgb(56, 56, 71); + table.Border = Color.FromArgb(56, 56, 71); + table.Foreground = Color.White; + table.CellHeight = 32; + table.OnDoubleClick = (int x, int y) => + { + if (table.SelectedCellIndex != -1) + { + AppMetadata app = demoApps[table.SelectedCellIndex]; + ProcessManager.AddProcess(app.CreateProcess()).Start(); + } + }; + PopulateTable(); + wm.AddWindow(table); + + wm.Update(window); + } + + public override void Run() + { + + } + } +} diff --git a/Gui/Apps/Demos/Mandelbrot.cs b/Gui/Apps/Demos/Mandelbrot.cs new file mode 100644 index 0000000..5e54de4 --- /dev/null +++ b/Gui/Apps/Demos/Mandelbrot.cs @@ -0,0 +1,91 @@ +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps.Demos +{ + internal class Mandelbrot : Process + { + internal Mandelbrot() : base("Mandelbrot", ProcessType.Application) { } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + private Color GetColor(double v) + { + int red = Math.Clamp((int)(255 * v), 0, 255); + int green = 0; + int blue = Math.Clamp((int)(255 * (1 - v)), 0, 255); + + return Color.FromArgb(red, green, blue); + } + + private void RenderMandelbrot() + { + window.Clear(Color.Black); + wm.Update(window); + + int width = window.Width; + int height = window.Height; + + const int max = 20; + const double bail = 2.0; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + double zx = 0; + double zy = 0; + double cx = (x - width / 2.0) / (width / 4.0); + double cy = (y - height / 2.0) / (height / 4.0); + + int iteration = 0; + + while (zx * zx + zy * zy < bail && iteration < max) + { + double zxNew = zx * zx - zy * zy + cx; + zy = 2 * zx * zy + cy; + zx = zxNew; + iteration++; + } + + double smooth = iteration + 1 - Math.Log(Math.Log(Math.Sqrt(zx * zx + zy * zy)) / Math.Log(bail)) / Math.Log(2); + window.DrawPoint(x, y, GetColor(smooth / max)); + + if (x % 32 == 0) + { + ProcessManager.Yield(); + } + } + + if (y % 8 == 0) + { + wm.Update(window); + } + } + + wm.Update(window); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 256, 256); + wm.AddWindow(window); + window.Title = "Mandelbrot"; + window.CanResize = true; + window.Closing = TryStop; + window.UserResized = RenderMandelbrot; + + RenderMandelbrot(); + } + + public override void Run() + { + + } + } +} diff --git a/Gui/Apps/Demos/Starfield.cs b/Gui/Apps/Demos/Starfield.cs new file mode 100644 index 0000000..266957f --- /dev/null +++ b/Gui/Apps/Demos/Starfield.cs @@ -0,0 +1,105 @@ +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps.Demos +{ + internal class Starfield : Process + { + internal Starfield() : base("Starfield", ProcessType.Application) { } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + private readonly Random random = new Random(); + + private readonly List stars = new List(); + + private int timerId; + + private class Star + { + internal double X { get; set; } + internal double Y { get; set; } + internal double Z { get; set; } + internal double Velocity { get; set; } + + internal Star(double x, double y, double z, double velocity) + { + X = x; + Y = y; + Z = z; + Velocity = velocity; + } + } + + internal (double, double) TransformCoordinates(double x, double y, double z, double fov) + { + double screenX = x / (z * Math.Tan(fov / 2)) + 0.5; + double screenY = y / (z * Math.Tan(fov / 2)) + 0.5; + + return (screenX, screenY); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 256, 256); + wm.AddWindow(window); + window.Title = "Starfield"; + window.CanResize = true; + window.Closing = TryStop; + + for (int i = 0; i < 100; i++) + { + stars.Add(new Star( + x: random.NextDouble() * 2 - 1.5, + y: random.NextDouble() * 2 - 1.5, + z: 3, + velocity: random.NextDouble() * 0.05 + 0.05)); + } + + timerId = Cosmos.HAL.Global.PIT.RegisterTimer(new Cosmos.HAL.PIT.PITTimer(() => + { + foreach (var star in stars) + { + star.Z -= star.Velocity; + + if (star.Z < 0) + { + star.X = random.NextDouble() * 2 - 1.5; + star.Y = random.NextDouble() * 2 - 1.5; + star.Z = 3; + } + } + }, (ulong)((1000 /* ms */ / 30) * 1e+6 /* ms -> ns */ ), true)); + } + + public override void Run() + { + window.Clear(Color.Black); + + foreach (var star in stars) + { + (double X, double Y) pos = TransformCoordinates(star.X, star.Y, star.Z, Math.PI / 2); + + int screenX = (int)((pos.X + 1) * (window.Width / 2)); + int screenY = (int)((pos.Y + 1) * (window.Height / 2)); + + window.DrawPoint(screenX, screenY, Color.White); + } + + wm.Update(window); + } + + public override void Stop() + { + base.Stop(); + + Cosmos.HAL.Global.PIT.UnregisterTimer(timerId); + } + } +} diff --git a/Gui/Apps/Files.cs b/Gui/Apps/Files.cs new file mode 100644 index 0000000..4d41f24 --- /dev/null +++ b/Gui/Apps/Files.cs @@ -0,0 +1,330 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using CMLeonOS.Logger; +using CMLeonOS.Utils; +using System; +using System.Drawing; +using System.IO; + +namespace CMLeonOS.Gui.Apps +{ + internal class Files : Process + { + internal Files() : base("Files", ProcessType.Application) { } + + AppWindow window; + + Table entryTable; + Table shortcutsTable; + + ImageBlock up; + + TextBox pathBox; + + Window header; + + WindowManager wm = ProcessManager.GetProcess(); + + private static class Icons + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.Directory.bmp")] + private static byte[] _iconBytes_Directory; + internal static Bitmap Icon_Directory = new Bitmap(_iconBytes_Directory); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.File.bmp")] + private static byte[] _iconBytes_File; + internal static Bitmap Icon_File = new Bitmap(_iconBytes_File); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.File_Config.bmp")] + private static byte[] _iconBytes_File_Config; + internal static Bitmap Icon_File_Config = new Bitmap(_iconBytes_File_Config); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.File_Rs.bmp")] + private static byte[] _iconBytes_File_Rs; + internal static Bitmap Icon_File_Rs = new Bitmap(_iconBytes_File_Rs); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.File_Text.bmp")] + private static byte[] _iconBytes_File_Text; + internal static Bitmap Icon_File_Text = new Bitmap(_iconBytes_File_Text); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.Drive.bmp")] + private static byte[] _iconBytes_Drive; + internal static Bitmap Icon_Drive = new Bitmap(_iconBytes_Drive); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.Home.bmp")] + private static byte[] _iconBytes_Home; + internal static Bitmap Icon_Home = new Bitmap(_iconBytes_Home); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Files.Up.bmp")] + private static byte[] _iconBytes_Up; + internal static Bitmap Icon_Up = new Bitmap(_iconBytes_Up); + } + + private string currentDir = @"0:\"; + + private const int pathBoxMargin = 4; + private const int pathBoxHeight = 24; + private const int shortcutsWidth = 128; + private const int headerHeight = 32; + + private const string dirEmptyMessage = "This folder is empty."; + + private readonly (string Name, string Path)[] shortcuts = new (string, string)[] + { + ("CMLeonOS (0:)", @"0:\"), + ("My Home", @$"0:\user\{UserSystem.CurrentLoggedInUser.Username}"), + ("Users", @"0:\user"), + ("etc", @"0:\etc"), + }; + + private Bitmap GetFileIcon(string path) + { + string extension = Path.GetExtension(path).ToLower(); + + return extension switch + { + ".txt" or ".md" or ".log" => Icons.Icon_File_Text, + ".rs" => Icons.Icon_File_Rs, + ".ini" or ".cfg" => Icons.Icon_File_Config, + _ => Icons.Icon_File + }; + } + + private Bitmap GetDirectoryIcon(string path) + { + if (Path.TrimEndingDirectorySeparator(path).StartsWith(@"0:\user\")) + { + return Icons.Icon_Home; + } + + switch (path) + { + case @"0:\": + return Icons.Icon_Drive; + default: + return Icons.Icon_Directory; + } + } + + private void PopulateEntryTable() + { + entryTable.Cells.Clear(); + entryTable.SelectedCellIndex = -1; + + bool empty = true; + foreach (string path in Directory.GetDirectories(currentDir)) + { + entryTable.Cells.Add(new TableCell(GetDirectoryIcon(path), Path.GetFileName(path), tag: "Directory")); + empty = false; + } + foreach (string path in Directory.GetFiles(currentDir)) + { + entryTable.Cells.Add(new TableCell(GetFileIcon(path), Path.GetFileName(path), tag: "File")); + empty = false; + } + + if (empty) + { + entryTable.Clear(entryTable.Background); + entryTable.DrawString(dirEmptyMessage, Color.Black, (entryTable.Width / 2) - ((dirEmptyMessage.Length * 8) / 2), 12); + wm.Update(entryTable); + } + else + { + entryTable.Render(); + } + } + + private void PopulateShortcutTable() + { + shortcutsTable.Cells.Clear(); + foreach ((string Name, string Path) item in shortcuts) + { + shortcutsTable.Cells.Add(new TableCell(GetDirectoryIcon(item.Path), item.Name, tag: item.Path)); + } + shortcutsTable.Render(); + } + + private bool NavigateTo(string path) + { + string sanitised = PathUtil.Sanitize(path); + + if (!Directory.Exists(sanitised)) + { + MessageBox messageBox = new MessageBox(this, "Files", $"CMLeonOS can't find that folder.\nCheck the spelling and try again."); + messageBox.Show(); + + return false; + } + + currentDir = sanitised; + pathBox.Text = sanitised; + + PopulateEntryTable(); + UpdateSelectedShortcut(); + RenderHeader(); + + if (sanitised == @"0:\") + { + window.Title = "Files"; + } + else + { + window.Title = Path.GetDirectoryName(sanitised); + } + + return true; + } + + private void UpdateSelectedShortcut() + { + for (int i = 0; i < shortcuts.Length; i++) + { + if (shortcuts[i].Path == currentDir) + { + shortcutsTable.SelectedCellIndex = i; + return; + } + } + shortcutsTable.SelectedCellIndex = -1; + } + + private void EntryTableDoubleClicked(int x, int y) + { + if (entryTable.SelectedCellIndex != -1) + { + TableCell cell = entryTable.Cells[entryTable.SelectedCellIndex]; + string path = PathUtil.Combine(currentDir, cell.Text); + if ((string)cell.Tag == "Directory") + { + NavigateTo(path); + } + else if ((string)cell.Tag == "File") + { + try + { + string extension = Path.GetExtension(path).ToLower(); + if (extension == ".txt" || extension == ".md") + { + var notepad = new Notepad(path); + var metadata = AppManager.GetAppMetadata("Notepad"); + metadata.Start(); + } + else + { + Logger.Logger.Instance.Warning("Files", $"Cannot open file: {path}"); + } + } + catch (Exception ex) + { + Logger.Logger.Instance.Error("Files", $"Error opening file: {ex.Message}"); + } + } + } + } + + private void ShortcutsTableCellSelected(int index) + { + if (index != -1) + { + bool success = NavigateTo(shortcuts[index].Path); + if (!success) + { + UpdateSelectedShortcut(); + } + } + } + + private void PathBoxSubmitted() + { + bool success = NavigateTo(pathBox.Text); + if (!success) + { + pathBox.Text = currentDir; + } + } + + private void UpClicked() + { + DirectoryInfo parent = Directory.GetParent(currentDir); + if (parent != null) + { + NavigateTo(parent.FullName); + } + } + + private void RenderHeader(bool updateWindow = true) + { + header.Clear(Color.DarkBlue); + + header.DrawImageAlpha(GetDirectoryIcon(currentDir), 8, 8); + + DirectoryInfo info = new DirectoryInfo(currentDir); + + string currentDirFriendlyName = Path.GetFileName(currentDir); + if (currentDir == $@"0:\user\{UserSystem.CurrentLoggedInUser.Username}") + { + currentDirFriendlyName = "My Home"; + } + header.DrawString(info.Parent == null ? currentDir : currentDirFriendlyName, Color.White, 32, 8); + + if (updateWindow) + { + wm.Update(header); + } + } + + private void WindowClicked(int x, int y) + { + if (x < pathBoxHeight && y < pathBoxHeight) + { + UpClicked(); + } + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 288, 240, 512, 304); + wm.AddWindow(window); + window.Title = "Files"; + window.Icon = AppManager.GetAppMetadata("Files").Icon; + window.OnClick = WindowClicked; + window.Closing = TryStop; + + window.Clear(Color.DarkGray); + + window.DrawImageAlpha(Icons.Icon_Up, 0, 0); + + pathBox = new TextBox(window, (pathBoxMargin / 2) + pathBoxHeight, pathBoxMargin / 2, window.Width - pathBoxMargin - pathBoxHeight, 24 - pathBoxMargin); + pathBox.Text = currentDir; + pathBox.Submitted = PathBoxSubmitted; + wm.AddWindow(pathBox); + + entryTable = new Table(window, shortcutsWidth, pathBoxHeight + headerHeight, window.Width - shortcutsWidth, window.Height - pathBoxHeight - headerHeight); + entryTable.OnDoubleClick = EntryTableDoubleClicked; + PopulateEntryTable(); + wm.AddWindow(entryTable); + + header = new Window(this, window, shortcutsWidth, pathBoxHeight, window.Width - shortcutsWidth, headerHeight); + wm.AddWindow(header); + RenderHeader(updateWindow: false); + + shortcutsTable = new Table(window, 0, pathBoxHeight, shortcutsWidth, window.Height - pathBoxHeight); + shortcutsTable.AllowDeselection = false; + shortcutsTable.Background = Color.DarkGray; + shortcutsTable.Foreground = Color.White; + PopulateShortcutTable(); + shortcutsTable.SelectedCellIndex = 0; + shortcutsTable.TableCellSelected = ShortcutsTableCellSelected; + wm.AddWindow(shortcutsTable); + + wm.Update(window); + } + + public override void Run() + { + } + } +} diff --git a/Gui/Apps/Info.cs b/Gui/Apps/Info.cs new file mode 100644 index 0000000..35c9bfd --- /dev/null +++ b/Gui/Apps/Info.cs @@ -0,0 +1,53 @@ +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using CMLeonOS.Utils; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class Info : Process + { + internal Info() : base("Info", ProcessType.Application) { } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 320, 256); + wm.AddWindow(window); + window.Title = "Info"; + window.Icon = AppManager.GetAppMetadata("Info").Icon; + window.Closing = TryStop; + + window.Clear(Color.LightGray); + window.DrawFilledRectangle(0, 0, window.Width, 40, Color.Black); + window.DrawString("CMLeonOS", System.Drawing.Color.White, 12, 12); + + window.DrawString($"OS: CMLeonOS {Kernel.Version}", Color.Black, 12, 52); + window.DrawString($"Memory: {Cosmos.Core.CPU.GetAmountOfRAM()} MB", Color.Black, 12, 80); + + window.DrawString("Credits", Color.DarkBlue, 12, 108); + window.DrawString("Cosmos Team - OS tooling", Color.Black, 12, 132); + window.DrawString("Microsoft - .NET Runtime", Color.Black, 12, 156); + window.DrawString("Google Fonts - Font", Color.Black, 12, 180); + + Button button = new Button(window, window.Width - 80 - 12, window.Height - 20 - 12, 80, 20); + button.Text = "OK"; + button.OnClick = (int x, int y) => + { + wm.RemoveWindow(window); + }; + wm.AddWindow(button); + + wm.Update(window); + } + + public override void Run() + { + + } + } +} diff --git a/Gui/Apps/Logs.cs b/Gui/Apps/Logs.cs new file mode 100644 index 0000000..a7943b6 --- /dev/null +++ b/Gui/Apps/Logs.cs @@ -0,0 +1,138 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.SmoothMono; +using CMLeonOS.Gui.UILib; +using CMLeonOS.Logger; +using System.Drawing; +using System.Text; + +namespace CMLeonOS.Gui.Apps +{ + internal class Logs : Process + { + internal Logs() : base("Logs", ProcessType.Application) { } + + AppWindow window; + + Table table; + + WindowManager wm = ProcessManager.GetProcess(); + + private static class Icons + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Logs.Info.bmp")] + private static byte[] _iconBytes_Info; + internal static Bitmap Icon_Info = new Bitmap(_iconBytes_Info); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Logs.Warning.bmp")] + private static byte[] _iconBytes_Warning; + internal static Bitmap Icon_Warning = new Bitmap(_iconBytes_Warning); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Logs.Error.bmp")] + private static byte[] _iconBytes_Error; + internal static Bitmap Icon_Error = new Bitmap(_iconBytes_Error); + } + + private void AddLogToTable(LogEntry log) + { + Bitmap icon; + switch (log.Priority) + { + case LogLevel.Info: + icon = Icons.Icon_Info; + break; + case LogLevel.Warning: + icon = Icons.Icon_Warning; + break; + case LogLevel.Error: + icon = Icons.Icon_Error; + break; + default: + icon = null; + break; + } + table.Cells.Add(new TableCell(icon, $"{log.Date.ToString("HH:mm")} - {log.Source}: {log.Message}", log)); + } + + private void Update() + { + window.Clear(Color.Gray); + + string text = $"{Log.Logs.Count} messages"; + window.DrawString(text, Color.White, window.Width - (FontData.Width * text.Length) - 12, window.Height - FontData.Height - 12); + + table.Render(); + table.ScrollToBottom(); + + wm.Update(window); + } + + public override void Start() + { + base.Start(); + + if (UserSystem.CurrentLoggedInUser == null || !UserSystem.CurrentLoggedInUser.Admin) + { + MessageBox messageBox = new MessageBox(Parent, Name, "You must be an admin to run this app."); + messageBox.Show(); + + TryStop(); + return; + } + + window = new AppWindow(this, 256, 256, 512, 352); + wm.AddWindow(window); + window.Icon = AppManager.GetAppMetadata("Event Log").Icon; + window.Title = "Event Log"; + window.Closing = TryStop; + + table = new Table(window, 12, 12, window.Width - 24, window.Height - 24 - 16 - 12); + table.OnDoubleClick = (int x, int y) => + { + if (table.SelectedCellIndex != -1) + { + LogEntry log = (LogEntry)table.Cells[table.SelectedCellIndex].Tag; + + string priority = log.Priority switch + { + LogLevel.Info => "Info", + LogLevel.Warning => "Warning", + LogLevel.Error => "Error", + _ => "Unknown" + }; + + StringBuilder builder = new StringBuilder(); + + builder.AppendLine($"Date: {log.Date.ToLongDateString()} {log.Date.ToLongTimeString()}"); + builder.AppendLine($"Source: {log.Source}"); + builder.AppendLine($"Priority: {priority}"); + builder.AppendLine(); + builder.Append(log.Message); + + MessageBox messageBox = new MessageBox(this, $"{log.Source}: Log Event", builder.ToString()); + messageBox.Show(); + } + }; + wm.AddWindow(table); + + table.Cells.Clear(); + foreach (LogEntry log in Log.Logs) + { + AddLogToTable(log); + } + + Log.LogEmittedReceivers.Add((LogEntry log) => + { + AddLogToTable(log); + Update(); + }); + + Update(); + } + + public override void Run() + { + + } + } +} diff --git a/Gui/Apps/MemoryStatistics.cs b/Gui/Apps/MemoryStatistics.cs new file mode 100644 index 0000000..2fab82f --- /dev/null +++ b/Gui/Apps/MemoryStatistics.cs @@ -0,0 +1,71 @@ +using CMLeonOS; +using CMLeonOS.Utils; +using CMLeonOS.Gui.SmoothMono; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class MemoryStatistics : Process + { + internal MemoryStatistics() : base("MemoryStatistics", ProcessType.Application) { } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + private int lastSecond; + + private static int padding = 12; + private static int barHeight = 12; + private static Color barColour = Color.FromArgb(0, 155, 254); + + private void Update() + { + window.Clear(Color.LightGray); + + var statistics = MemoryStatisticsProvider.GetMemoryStatistics(); + + window.DrawString("Memory Statistics", Color.DarkBlue, padding, padding); + + window.DrawString(string.Format(@"Total: {0} MB +Unavailable: {1} MB +Used: {2:d1} MB +Free: {3} MB +Percentage Used: {4:d1}%", + statistics.TotalMB, + statistics.UnavailableMB, + statistics.UsedMB, + statistics.FreeMB, + statistics.PercentUsed), Color.Black, padding, padding + FontData.Height + padding); + + window.DrawFilledRectangle(0, window.Height - barHeight, window.Width, barHeight, Color.Black); + window.DrawFilledRectangle(0, window.Height - barHeight, (int)(window.Width * ((float)statistics.PercentUsed / 100f)), barHeight, barColour); + + wm.Update(window); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 256, 192); + wm.AddWindow(window); + window.Title = "Memory Statistics"; + window.Icon = AppManager.GetAppMetadata("Memory Statistics").Icon; + window.Closing = TryStop; + + Update(); + } + + public override void Run() + { + int second = DateTime.Now.Second; + if (lastSecond != second) + { + lastSecond = second; + Update(); + } + } + } +} diff --git a/Gui/Apps/Notepad.cs b/Gui/Apps/Notepad.cs new file mode 100644 index 0000000..febf1a2 --- /dev/null +++ b/Gui/Apps/Notepad.cs @@ -0,0 +1,226 @@ +using CMLeonOS; +using CMLeonOS.Gui.SmoothMono; +using CMLeonOS.Gui.UILib; +using CMLeonOS.Utils; +using System.Drawing; +using System.IO; + +namespace CMLeonOS.Gui.Apps +{ + internal class Notepad : Process + { + internal Notepad() : base("Notepad", ProcessType.Application) { } + + internal Notepad(string path) : base("Notepad", ProcessType.Application) + { + this.path = path; + } + + AppWindow window; + + WindowManager wm = ProcessManager.GetProcess(); + + SettingsService settingsService = ProcessManager.GetProcess(); + + TextBox textBox; + + ShortcutBar shortcutBar; + + private string? path = null; + + private bool modified = false; + + private void TextChanged() + { + modified = true; + + UpdateTitle(); + } + + private void WindowResized() + { + textBox.Resize(window.Width, window.Height - 20); + shortcutBar.Resize(window.Width, 20); + + shortcutBar.Render(); + + textBox.MarkAllLines(); + textBox.Render(); + } + + private void UpdateTitle() + { + if (path == null) + { + window.Title = "Untitled - Notepad"; + return; + } + + if (modified) + { + window.Title = $"{Path.GetFileName(path)}* - Notepad"; + } + else + { + window.Title = $"{Path.GetFileName(path)} - Notepad"; + } + } + + internal void Open(string newPath, bool readFile = true) + { + if (newPath == null) return; + + if (!FileSecurity.CanAccess(path)) + { + MessageBox messageBox = new MessageBox(this, "Notepad", $"Access to '{Path.GetFileName(newPath)}' is unauthorised."); + messageBox.Show(); + } + + if (readFile && !File.Exists(newPath)) + { + MessageBox messageBox = new MessageBox(this, "Notepad", $"No such file '{Path.GetFileName(newPath)}'."); + messageBox.Show(); + } + + path = newPath; + + if (readFile) + { + textBox.Text = File.ReadAllText(path); + + textBox.MarkAllLines(); + textBox.Render(); + + modified = false; + } + + UpdateTitle(); + } + + private void OpenFilePrompt() + { + PromptBox prompt = new PromptBox(this, "Open File", "Enter the path to open.", "Path", (string newPath) => + { + if (!newPath.Contains(':')) + { + newPath = $@"0:\{newPath}"; + } + Open(newPath); + }); + prompt.Show(); + } + + private void SaveAsPrompt() + { + PromptBox prompt = new PromptBox(this, "Save As", "Enter the path to save to.", "Path", (string newPath) => + { + if (!newPath.Contains(':')) + { + newPath = $@"0:\{newPath}"; + } + + Open(newPath, readFile: false); + + // Check if open succeeded. + if (path != null) + { + Save(); + } + }); + prompt.Show(); + } + + private void Save() + { + if (path == null) + { + SaveAsPrompt(); + return; + } + + File.WriteAllText(path, textBox.Text); + + modified = false; + UpdateTitle(); + } + + private void ApplyTheme() + { + if (settingsService.DarkNotepad) + { + textBox.Background = Color.FromArgb(24, 24, 30); + textBox.Foreground = Color.White; + + shortcutBar.Background = Color.FromArgb(56, 56, 71); + shortcutBar.Foreground = Color.White; + } + else + { + textBox.Background = Color.White; + textBox.Foreground = Color.Black; + + shortcutBar.Background = Color.LightGray; + shortcutBar.Foreground = Color.Black; + } + + textBox.MarkAllLines(); + textBox.Render(); + } + + private void OpenViewSettings() + { + AppWindow settingsWindow = new AppWindow(this, 320, 264, 256, 192); + settingsWindow.DrawString("Notepad Settings", Color.DarkBlue, 12, 12); + settingsWindow.DrawString($"Notepad v{Kernel.Version}", Color.DarkGray, 12, settingsWindow.Height - 12 - FontData.Height); + wm.AddWindow(settingsWindow); + settingsWindow.Title = "Notepad"; + + Switch darkSwitch = new Switch(settingsWindow, 12, 40, settingsWindow.Width - 16, 20); + darkSwitch.Text = "Dark theme"; + darkSwitch.Checked = settingsService.DarkNotepad; + darkSwitch.CheckBoxChanged = (bool @checked) => + { + settingsService.DarkNotepad = @checked; + ApplyTheme(); + }; + wm.AddWindow(darkSwitch); + + wm.Update(settingsWindow); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 320, 264, 384, 240); + wm.AddWindow(window); + UpdateTitle(); + window.Closing = TryStop; + window.Icon = AppManager.GetAppMetadata("Notepad").Icon; + window.CanResize = true; + window.UserResized = WindowResized; + + shortcutBar = new ShortcutBar(window, 0, 0, window.Width, 20); + shortcutBar.Cells.Add(new ShortcutBarCell("Open", OpenFilePrompt)); + shortcutBar.Cells.Add(new ShortcutBarCell("Save", Save)); + shortcutBar.Cells.Add(new ShortcutBarCell("Save As", SaveAsPrompt)); + shortcutBar.Cells.Add(new ShortcutBarCell("View", OpenViewSettings)); + shortcutBar.Render(); + wm.AddWindow(shortcutBar); + + textBox = new TextBox(window, 0, 20, window.Width, window.Height - 20); + textBox.MultiLine = true; + textBox.Changed = TextChanged; + wm.AddWindow(textBox); + + ApplyTheme(); + + Open(path); + + wm.Update(window); + } + + public override void Run() + { + } + } +} diff --git a/Gui/Apps/Paint/ColourPicker.cs b/Gui/Apps/Paint/ColourPicker.cs new file mode 100644 index 0000000..0e5c22b --- /dev/null +++ b/Gui/Apps/Paint/ColourPicker.cs @@ -0,0 +1,89 @@ +using CMLeonOS.Gui.UILib; +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps.Paint +{ + internal class ColourPicker : Window + { + private Paint paintInstance; + + private Table table; + + internal readonly List Colours = new List() + { + Color.Black, + Color.White, + Color.Red, + Color.Blue, + Color.Orange, + Color.Green, + Color.Pink, + Color.Gray, + Color.Purple, + Color.DarkGoldenrod, + Color.DarkGray, + Color.DarkGreen, + Color.DarkCyan, + Color.Cyan, + Color.BlueViolet, + Color.AliceBlue, + Color.Brown, + Color.CornflowerBlue, + Color.Azure, + Color.Beige, + Color.DarkBlue, + Color.DarkSlateBlue, + Color.SeaGreen + }; + + private void TableClicked(int x, int y) + { + // Clear 'Selected' text on previously selected colour. + foreach (var cell in table.Cells) + { + cell.Text = string.Empty; + } + + var selectedCell = table.Cells[table.SelectedCellIndex]; + Color color = (Color)selectedCell.Tag; + + paintInstance.SelectedColor = color; + + selectedCell.Text = "Selected"; + + table.Render(); + } + + internal ColourPicker(Paint paint, int x, int y, int width, int height) : base(paint, x, y, width, height) + { + paintInstance = paint; + + Clear(Color.FromArgb(107, 107, 107)); + DrawString("Colours", Color.White, 8, 8); + + table = new Table(this, 0, 32, Width, Height - 32); + table.AllowDeselection = false; + table.CellHeight = 20; + table.TextAlignment = Alignment.Middle; + table.OnClick = TableClicked; + + foreach (Color colour in Colours) + { + TableCell cell = new(string.Empty, tag: colour); + cell.BackgroundColourOverride = colour; + cell.ForegroundColourOverride = colour.GetForegroundColour(); + if (colour == paint.SelectedColor) + { + cell.Text = "Selected"; + } + table.Cells.Add(cell); + } + + table.Render(); + + WM.AddWindow(this); + WM.AddWindow(table); + } + } +} diff --git a/Gui/Apps/Paint/Paint.cs b/Gui/Apps/Paint/Paint.cs new file mode 100644 index 0000000..f1b6a41 --- /dev/null +++ b/Gui/Apps/Paint/Paint.cs @@ -0,0 +1,88 @@ +using Cosmos.System; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps.Paint +{ + internal class Paint : Process + { + internal Paint() : base("Paint", ProcessType.Application) + { + } + + AppWindow window; + + Window canvas; + + ToolBox toolBox; + + ColourPicker colourPicker; + + WindowManager wm = ProcessManager.GetProcess(); + + private bool down = false; + + internal Color SelectedColor { get; set; } = Color.Black; + + internal bool IsInBounds(int x, int y) + { + if (x >= canvas.Width || y >= canvas.Height) return false; + if (x < 0 || y < 0) return false; + + return true; + } + + private void CanvasDown(int x, int y) + { + down = true; + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 768, 448); + window.Title = "Paint"; + window.Icon = AppManager.GetAppMetadata("Paint").Icon; + window.Closing = TryStop; + window.Clear(Color.FromArgb(73, 73, 73)); + wm.AddWindow(window); + + int canvasWidth = 384; + int canvasHeight = 256; + canvas = new Window(this, (window.Width / 2) - (canvasWidth / 2), (window.Height / 2) - (canvasHeight / 2), canvasWidth, canvasHeight); + canvas.RelativeTo = window; + canvas.OnDown = CanvasDown; + canvas.Clear(Color.White); + wm.AddWindow(canvas); + + toolBox = new ToolBox(this, 0, 0, 128, window.Height); + toolBox.RelativeTo = window; + colourPicker = new ColourPicker(this, window.Width - 128, 0, 128, window.Height); + colourPicker.RelativeTo = window; + + wm.Update(window); + } + + public override void Run() + { + if (down) + { + if (MouseManager.MouseState == MouseState.None) + { + down = false; + } + + toolBox.SelectedTool.Run( + this, + canvas, + MouseManager.MouseState, + (int)(MouseManager.X - canvas.ScreenX), + (int)(MouseManager.Y - canvas.ScreenY) + ); + + wm.Update(canvas); + } + } + } +} diff --git a/Gui/Apps/Paint/Tool.cs b/Gui/Apps/Paint/Tool.cs new file mode 100644 index 0000000..f47ed45 --- /dev/null +++ b/Gui/Apps/Paint/Tool.cs @@ -0,0 +1,24 @@ +using Cosmos.System; + +namespace CMLeonOS.Gui.Apps.Paint +{ + internal abstract class Tool + { + internal Tool(string name) + { + Name = name; + } + + internal abstract void Run(Paint paint, Window canvas, MouseState mouseState, int mouseX, int mouseY); + + internal virtual void Selected() + { + } + + internal virtual void Deselected() + { + } + + internal string Name { get; init; } + } +} diff --git a/Gui/Apps/Paint/ToolBox.cs b/Gui/Apps/Paint/ToolBox.cs new file mode 100644 index 0000000..c63edbe --- /dev/null +++ b/Gui/Apps/Paint/ToolBox.cs @@ -0,0 +1,60 @@ +using CMLeonOS.Gui.UILib; +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps.Paint +{ + internal class ToolBox : Window + { + private Paint paintInstance; + + private Table table; + + internal Tool SelectedTool; + + internal readonly List Tools = new List() + { + new Tools.Pencil(), + new Tools.CircleBrush() + }; + + private void TableClicked(int x, int y) + { + Tool tool = table.Cells[table.SelectedCellIndex].Tag as Tool; + + if (tool != SelectedTool) + { + SelectedTool.Deselected(); + SelectedTool = tool; + } + } + + internal ToolBox(Paint paint, int x, int y, int width, int height) : base(paint, x, y, width, height) + { + paintInstance = paint; + + + Clear(Color.FromArgb(107, 107, 107)); + DrawString("Toolbox", Color.White, 8, 8); + + table = new Table(this, 0, 32, Width, Height - 32); + table.AllowDeselection = false; + table.CellHeight = 24; + table.TextAlignment = Alignment.Middle; + table.OnClick = TableClicked; + + foreach (Tool tool in Tools) + { + table.Cells.Add(new TableCell(tool.Name, tool)); + } + + SelectedTool = Tools[0]; + table.SelectedCellIndex = 0; + + table.Render(); + + WM.AddWindow(this); + WM.AddWindow(table); + } + } +} diff --git a/Gui/Apps/Paint/Tools/CircleBrush.cs b/Gui/Apps/Paint/Tools/CircleBrush.cs new file mode 100644 index 0000000..24d492f --- /dev/null +++ b/Gui/Apps/Paint/Tools/CircleBrush.cs @@ -0,0 +1,19 @@ +using Cosmos.System; + +namespace CMLeonOS.Gui.Apps.Paint.Tools +{ + internal class CircleBrush : Tool + { + public CircleBrush() : base("Circle brush") + { + } + + internal override void Run(Paint paint, Window canvas, MouseState mouseState, int mouseX, int mouseY) + { + if (mouseState == MouseState.Left) + { + canvas.DrawCircle(mouseX, mouseY, 5, paint.SelectedColor); + } + } + } +} diff --git a/Gui/Apps/Paint/Tools/Pencil.cs b/Gui/Apps/Paint/Tools/Pencil.cs new file mode 100644 index 0000000..09c0d44 --- /dev/null +++ b/Gui/Apps/Paint/Tools/Pencil.cs @@ -0,0 +1,42 @@ +using Cosmos.System; + +namespace CMLeonOS.Gui.Apps.Paint.Tools +{ + internal class Pencil : Tool + { + public Pencil() : base("Pencil") + { + } + + private bool joinLine; + private int joinX; + private int joinY; + + internal override void Run(Paint paint, Window canvas, MouseState mouseState, int mouseX, int mouseY) + { + if (mouseState == MouseState.Left) + { + if (joinLine) + { + canvas.DrawLine(joinX, joinY, mouseX, mouseY, paint.SelectedColor); + } + else + { + canvas.DrawPoint(mouseX, mouseY, paint.SelectedColor); + } + joinLine = true; + joinX = mouseX; + joinY = mouseY; + } + else + { + joinLine = false; + } + } + + internal override void Deselected() + { + joinLine = false; + } + } +} diff --git a/Gui/Apps/Settings.cs b/Gui/Apps/Settings.cs new file mode 100644 index 0000000..3b937b6 --- /dev/null +++ b/Gui/Apps/Settings.cs @@ -0,0 +1,296 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; + +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class Settings : Process + { + internal Settings() : base("Settings", ProcessType.Application) { } + + AppWindow window; + + Window currentCategoryWindow; + + WindowManager wm = ProcessManager.GetProcess(); + + SettingsService settingsService = ProcessManager.GetProcess(); + + private static class Icons + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Settings.User.bmp")] + private static byte[] _iconBytes_User; + internal static Bitmap Icon_User = new Bitmap(_iconBytes_User); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Settings.Admin.bmp")] + private static byte[] _iconBytes_Admin; + internal static Bitmap Icon_Admin = new Bitmap(_iconBytes_Admin); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Settings.Info.bmp")] + private static byte[] _iconBytes_Info; + internal static Bitmap Icon_Info = new Bitmap(_iconBytes_Info); + } + + private void CategorySelected(int index) + { + switch (index) + { + case 0: + ShowAppearanceCategory(); + break; + case 1: + ShowDisplayCategory(); + break; + case 2: + ShowDateTimeCategory(); + break; + case 3: + ShowUsersCategory(); + break; + case 4: + ShowMouseCategory(); + break; + default: + return; + } + } + + private void LeftStartButtonChanged(bool @checked) + { + settingsService.LeftHandStartButton = @checked; + } + + private void TwelveHourClockChanged(bool @checked) + { + settingsService.TwelveHourClock = @checked; + } + + private void ShowFpsChanged(bool @checked) + { + settingsService.ShowFps = @checked; + } + + private void MouseSensitivityChanged(float value) + { + settingsService.MouseSensitivity = value; + } + + private void ShowAppearanceCategory() + { + if (currentCategoryWindow != null) + { + wm.RemoveWindow(currentCategoryWindow); + } + Window appearance = new Window(this, window, 128, 0, window.Width - 128, window.Height); + currentCategoryWindow = appearance; + appearance.DrawString("Appearance Settings", Color.DarkBlue, 12, 12); + wm.AddWindow(appearance); + + Switch leftStartButton = new Switch(appearance, 12, 40, 244, 16); + leftStartButton.Text = "Left-hand start button"; + leftStartButton.Checked = settingsService.LeftHandStartButton; + leftStartButton.CheckBoxChanged = LeftStartButtonChanged; + wm.AddWindow(leftStartButton); + + Switch showFps = new Switch(appearance, 12, 68, 244, 16); + showFps.Text = "Show frames per second"; + showFps.Checked = settingsService.ShowFps; + showFps.CheckBoxChanged = ShowFpsChanged; + wm.AddWindow(showFps); + + wm.Update(window); + } + + private void ShowDateTimeCategory() + { + if (currentCategoryWindow != null) + { + wm.RemoveWindow(currentCategoryWindow); + } + Window dateTime = new Window(this, window, 128, 0, window.Width - 128, window.Height); + currentCategoryWindow = dateTime; + dateTime.DrawString("Date & Time Settings", Color.DarkBlue, 12, 12); + wm.AddWindow(dateTime); + + Switch twelveHourClock = new Switch(dateTime, 12, 40, 244, 16); + twelveHourClock.Text = "12-hour clock"; + twelveHourClock.Checked = settingsService.TwelveHourClock; + twelveHourClock.CheckBoxChanged = TwelveHourClockChanged; + wm.AddWindow(twelveHourClock); + + AppMetadata calendarApp = AppManager.GetAppMetadata("Calendar"); + Button openCalendar = new Button(dateTime, 12, 68, 160, 20); + openCalendar.Text = "Open Calendar"; + openCalendar.Image = calendarApp.Icon.Resize(20, 20); + openCalendar.ImageLocation = Button.ButtonImageLocation.Left; + openCalendar.OnClick = (int x, int y) => + { + calendarApp.Start(wm); + }; + wm.AddWindow(openCalendar); + + wm.Update(window); + } + + private void ShowDisplayCategory() + { + + if (currentCategoryWindow != null) + { + wm.RemoveWindow(currentCategoryWindow); + } + Window display = new Window(this, window, 128, 0, window.Width - 128, window.Height); + currentCategoryWindow = display; + display.DrawString("Display Settings", Color.DarkBlue, 12, 12); + wm.AddWindow(display); + + Table resolutionsTable = new Table(display, 12, 40, display.Width - 24, display.Height - 12 - 16 - 12 - 12 - 16 - 12); + resolutionsTable.AllowDeselection = false; + for (int i = 0; i < wm.AvailableModes.Count; i++) + { + Mode mode = wm.AvailableModes[i]; + resolutionsTable.Cells.Add(new TableCell($"{mode.Width}x{mode.Height}")); + if (mode.Equals(settingsService.Mode)) + { + resolutionsTable.SelectedCellIndex = i; + } + } + resolutionsTable.Render(); + resolutionsTable.TableCellSelected = (int index) => + { + Mode mode = wm.AvailableModes[index]; + settingsService.Mode = mode; + settingsService.Flush(); + + MessageBox messageBox = new MessageBox(this, "Restart Required", "Restart your PC to apply changes."); + messageBox.Show(); + }; + wm.AddWindow(resolutionsTable); + + display.DrawImageAlpha(Icons.Icon_Info, 12, window.Height - 16 - 12); + display.DrawString("Select a screen resolution.", Color.Gray, 36, window.Height - 16 - 12); + + wm.AddWindow(display); + + wm.Update(display); + } + + private void ShowUsersCategory() + { + if (currentCategoryWindow != null) + { + wm.RemoveWindow(currentCategoryWindow); + } + Window users = new Window(this, window, 128, 0, window.Width - 128, window.Height); + currentCategoryWindow = users; + users.DrawString("Users", Color.DarkBlue, 12, 12); + users.DrawImageAlpha(Icons.Icon_Info, 12, window.Height - 16 - 12); + users.DrawString("Double-click on a user for info.", Color.Gray, 36, window.Height - 16 - 12); + wm.AddWindow(users); + + Table usersTable = new Table(users, 12, 40, users.Width - 24, users.Height - 40 - 12 - 16 - 12); + foreach (User user in UserSystem.GetUsers()) + { + usersTable.Cells.Add(new TableCell(user.Admin ? Icons.Icon_Admin : Icons.Icon_User, user.Username, tag: user)); + } + usersTable.OnDoubleClick = (int x, int y) => + { + if (usersTable.SelectedCellIndex != -1) + { + User user = (User)usersTable.Cells[usersTable.SelectedCellIndex].Tag; + + AppWindow userDetail = new AppWindow(this, 256, 256, 256, 192); + userDetail.Title = user.Username; + wm.AddWindow(userDetail); + + userDetail.DrawFilledRectangle(0, 0, userDetail.Width, 40, Color.DarkBlue); + userDetail.DrawImageAlpha(user.Admin ? Icons.Icon_Admin : Icons.Icon_User, 12, 12); + userDetail.DrawString($"User: {user.Username}", Color.White, 36, 12); + + userDetail.DrawString($"Role: {(user.Admin ? "Admin" : "User")}", Color.Black, 12, 48); + userDetail.DrawString($"Unread messages: {user.Messages.Count}", Color.Black, 12, 76); + + Button ok = new Button(userDetail, userDetail.Width - 80 - 12, userDetail.Height - 20 - 12, 80, 20); + ok.Text = "OK"; + ok.OnClick = (int x, int y) => + { + wm.RemoveWindow(userDetail); + }; + wm.AddWindow(ok); + + wm.Update(userDetail); + } + }; + usersTable.Render(); + wm.AddWindow(usersTable); + + wm.Update(window); + } + + private void ShowMouseCategory() + { + if (currentCategoryWindow != null) + { + wm.RemoveWindow(currentCategoryWindow); + } + Window mouse = new Window(this, window, 128, 0, window.Width - 128, window.Height); + currentCategoryWindow = mouse; + mouse.DrawString("Mouse Settings", Color.DarkBlue, 12, 12); + wm.AddWindow(mouse); + + mouse.DrawString("Mouse sensitivity", Color.Gray, 12, 40); + + RangeSlider mouseSensitivity = new RangeSlider(mouse, 12, 68, 244, 30, min: 0.25f, value: settingsService.MouseSensitivity, max: 2f); + mouseSensitivity.Changed = MouseSensitivityChanged; + wm.AddWindow(mouseSensitivity); + + wm.Update(window); + } + + #region Process + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 448, 272); + window.Closing = TryStop; + window.Icon = AppManager.GetAppMetadata("Settings").Icon; + wm.AddWindow(window); + + window.Title = "Settings"; + + Table categoryTable = new Table(window, 0, 0, 128, window.Height); + + categoryTable.TextAlignment = Alignment.Middle; + + categoryTable.TableCellSelected = CategorySelected; + categoryTable.SelectedCellIndex = 0; + categoryTable.AllowDeselection = false; + + categoryTable.CellHeight = 24; + + /*categoryTable.Border = categoryTable.Background; + categoryTable.SelectedBorder = categoryTable.SelectedBackground;*/ + + categoryTable.Cells.Add(new TableCell("Appearance")); + categoryTable.Cells.Add(new TableCell("Display")); + categoryTable.Cells.Add(new TableCell("Date & Time")); + categoryTable.Cells.Add(new TableCell("Users")); + categoryTable.Cells.Add(new TableCell("Mouse")); + + categoryTable.Render(); + + wm.AddWindow(categoryTable); + + ShowAppearanceCategory(); + + wm.Update(window); + } + + public override void Run() + { + } + #endregion + } +} diff --git a/Gui/Apps/Stopwatch.cs b/Gui/Apps/Stopwatch.cs new file mode 100644 index 0000000..0804919 --- /dev/null +++ b/Gui/Apps/Stopwatch.cs @@ -0,0 +1,161 @@ +using CMLeonOS; +using CMLeonOS.Gui.SmoothMono; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class Stopwatch : Process + { + internal Stopwatch() : base("Stopwatch", ProcessType.Application) { } + + AppWindow window; + + Button startStopButton; + + Button lapResetButton; + + Table lapTable; + + WindowManager wm = ProcessManager.GetProcess(); + + private const string format = @"hh\:mm\:ss"; + + private readonly Color background = Color.FromArgb(56, 56, 71); + + private bool stopwatchRunning = false; + + DateTime startTime; + DateTime lastLap; + TimeSpan elapsed = TimeSpan.Zero; + int lastSecond; + + private void UpdateWindow() + { + window.Clear(background); + + string text; + text = elapsed.ToString(format); + window.DrawString(text, System.Drawing.Color.White, (window.Width / 2) - ((text.Length * FontData.Width) / 2), 8); + + wm.Update(window); + } + + private void StartStopwatch() + { + if (stopwatchRunning) return; + + stopwatchRunning = true; + startTime = DateTime.Now; + lastLap = startTime; + lastSecond = startTime.Second; + + startStopButton.Text = "Stop"; + lapResetButton.Text = "Lap"; + } + + private void StopStopwatch() + { + if (!stopwatchRunning) return; + + stopwatchRunning = false; + UpdateWindow(); + + startStopButton.Text = "Start"; + lapResetButton.Text = "Reset"; + } + + private void Lap() + { + DateTime now = DateTime.Now; + + TableCell cell = new TableCell((now - lastLap).ToString(format)); + lapTable.Cells.Add(cell); + lapTable.ScrollToBottom(); + lapTable.Render(); + wm.Update(lapTable); + + lastLap = DateTime.Now; + } + + private void Reset() + { + elapsed = TimeSpan.Zero; + + lapTable.Cells.Clear(); + lapTable.Render(); + wm.Update(lapTable); + + UpdateWindow(); + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 160, 160); + wm.AddWindow(window); + window.Title = "Stopwatch"; + window.Icon = AppManager.GetAppMetadata("Stopwatch").Icon; + window.Closing = TryStop; + + startStopButton = new Button(window, window.Width / 2, window.Height - 24, window.Width / 2, 24); + startStopButton.Text = "Start"; + startStopButton.OnClick = (int x, int y) => + { + if (stopwatchRunning) + { + StopStopwatch(); + } + else + { + StartStopwatch(); + } + }; + wm.AddWindow(startStopButton); + + lapResetButton = new Button(window, 0, window.Height - 24, window.Width / 2, 24); + lapResetButton.Text = "Reset"; + lapResetButton.OnClick = (int x, int y) => + { + if (stopwatchRunning) + { + Lap(); + } + else + { + Reset(); + } + }; + wm.AddWindow(lapResetButton); + + int lapTableY = 8 + FontData.Height + 8; + lapTable = new Table(window, 0, lapTableY, window.Width, window.Height - lapTableY - 24); + lapTable.Background = Color.FromArgb(80, 80, 102); + lapTable.Border = background; + lapTable.Foreground = Color.FromArgb(185, 185, 234); + lapTable.TextAlignment = Alignment.Middle; + lapTable.AllowSelection = false; + lapTable.CellHeight = 20; + lapTable.ScrollbarThickness = 15; + lapTable.Render(); + wm.AddWindow(lapTable); + + UpdateWindow(); + } + + public override void Run() + { + int second = DateTime.Now.Second; + if (lastSecond != second) + { + lastSecond = second; + if (stopwatchRunning) + { + elapsed = DateTime.Now - startTime; + } + UpdateWindow(); + } + } + } +} diff --git a/Gui/Apps/Tasks.cs b/Gui/Apps/Tasks.cs new file mode 100644 index 0000000..0b9a738 --- /dev/null +++ b/Gui/Apps/Tasks.cs @@ -0,0 +1,82 @@ +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps +{ + internal class Tasks : Process + { + internal Tasks() : base("Tasks", ProcessType.Application) { } + + AppWindow window; + + Table table; + + WindowManager wm = ProcessManager.GetProcess(); + + int lastSecond = DateTime.Now.Second; + + private void PopulateTable() + { + table.Cells.Clear(); + foreach (Process process in ProcessManager.Processes) + { + table.Cells.Add(new TableCell(process.Name)); + } + table.Render(); + } + + private void EndTaskClicked(int x, int y) + { + if (table.SelectedCellIndex != -1 && table.SelectedCellIndex < ProcessManager.Processes.Count) + { + if (UserSystem.CurrentLoggedInUser == null || !UserSystem.CurrentLoggedInUser.Admin) + { + MessageBox messageBox = new MessageBox(this, Name, "You must be an admin to end tasks."); + messageBox.Show(); + + return; + } + + ProcessManager.Processes[table.SelectedCellIndex].TryStop(); + ProcessManager.Sweep(); + table.SelectedCellIndex = -1; + PopulateTable(); + } + } + + public override void Start() + { + base.Start(); + window = new AppWindow(this, 256, 256, 384, 256); + wm.AddWindow(window); + window.Title = "Tasks"; + window.Icon = AppManager.GetAppMetadata("Tasks").Icon; + window.Closing = TryStop; + + window.Clear(Color.Gray); + + table = new Table(window, 12, 12, window.Width - 24, window.Height - 24 - 20 - 12); + PopulateTable(); + wm.AddWindow(table); + + Button endTask = new Button(window, window.Width - 100 - 12, window.Height - 20 - 12, 100, 20); + endTask.Text = "End Task"; + endTask.OnClick = EndTaskClicked; + wm.AddWindow(endTask); + + wm.Update(window); + } + + public override void Run() + { + DateTime now = DateTime.Now; + if (lastSecond != now.Second) + { + PopulateTable(); + lastSecond = now.Second; + } + } + } +} diff --git a/Gui/Asc16.cs b/Gui/Asc16.cs new file mode 100644 index 0000000..810ed4c --- /dev/null +++ b/Gui/Asc16.cs @@ -0,0 +1,39 @@ +using System; +using System.Drawing; +using System.IO; +using System.Text; + +// Modified from https://github.com/nifanfa/Cosmos-DrawString + +namespace CMLeonOS.Gui +{ + internal static class Asc16 + { + internal static string Asc16Base64 = "AAAAAAAAAAAAAAAAAAAAAAAAfoGlgYG9mYGBfgAAAAAAAH7/2///w+f//34AAAAAAAAAAGz+/v7+fDgQAAAAAAAAAAAQOHz+fDgQAAAAAAAAAAAYPDzn5+cYGDwAAAAAAAAAGDx+//9+GBg8AAAAAAAAAAAAABg8PBgAAAAAAAD////////nw8Pn////////AAAAAAA8ZkJCZjwAAAAAAP//////w5m9vZnD//////8AAB4OGjJ4zMzMzHgAAAAAAAA8ZmZmZjwYfhgYAAAAAAAAPzM/MDAwMHDw4AAAAAAAAH9jf2NjY2Nn5+bAAAAAAAAAGBjbPOc82xgYAAAAAACAwODw+P748ODAgAAAAAAAAgYOHj7+Ph4OBgIAAAAAAAAYPH4YGBh+PBgAAAAAAAAAZmZmZmZmZgBmZgAAAAAAAH/b29t7GxsbGxsAAAAAAHzGYDhsxsZsOAzGfAAAAAAAAAAAAAAA/v7+/gAAAAAAABg8fhgYGH48GH4AAAAAAAAYPH4YGBgYGBgYAAAAAAAAGBgYGBgYGH48GAAAAAAAAAAAABgM/gwYAAAAAAAAAAAAAAAwYP5gMAAAAAAAAAAAAAAAAMDAwP4AAAAAAAAAAAAAAChs/mwoAAAAAAAAAAAAABA4OHx8/v4AAAAAAAAAAAD+/nx8ODgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYPDw8GBgYABgYAAAAAABmZmYkAAAAAAAAAAAAAAAAAABsbP5sbGz+bGwAAAAAGBh8xsLAfAYGhsZ8GBgAAAAAAADCxgwYMGDGhgAAAAAAADhsbDh23MzMzHYAAAAAADAwMGAAAAAAAAAAAAAAAAAADBgwMDAwMDAYDAAAAAAAADAYDAwMDAwMGDAAAAAAAAAAAABmPP88ZgAAAAAAAAAAAAAAGBh+GBgAAAAAAAAAAAAAAAAAAAAYGBgwAAAAAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAYGAAAAAAAAAAAAgYMGDBgwIAAAAAAAAA4bMbG1tbGxmw4AAAAAAAAGDh4GBgYGBgYfgAAAAAAAHzGBgwYMGDAxv4AAAAAAAB8xgYGPAYGBsZ8AAAAAAAADBw8bMz+DAwMHgAAAAAAAP7AwMD8BgYGxnwAAAAAAAA4YMDA/MbGxsZ8AAAAAAAA/sYGBgwYMDAwMAAAAAAAAHzGxsZ8xsbGxnwAAAAAAAB8xsbGfgYGBgx4AAAAAAAAAAAYGAAAABgYAAAAAAAAAAAAGBgAAAAYGDAAAAAAAAAABgwYMGAwGAwGAAAAAAAAAAAAfgAAfgAAAAAAAAAAAABgMBgMBgwYMGAAAAAAAAB8xsYMGBgYABgYAAAAAAAAAHzGxt7e3tzAfAAAAAAAABA4bMbG/sbGxsYAAAAAAAD8ZmZmfGZmZmb8AAAAAAAAPGbCwMDAwMJmPAAAAAAAAPhsZmZmZmZmbPgAAAAAAAD+ZmJoeGhgYmb+AAAAAAAA/mZiaHhoYGBg8AAAAAAAADxmwsDA3sbGZjoAAAAAAADGxsbG/sbGxsbGAAAAAAAAPBgYGBgYGBgYPAAAAAAAAB4MDAwMDMzMzHgAAAAAAADmZmZseHhsZmbmAAAAAAAA8GBgYGBgYGJm/gAAAAAAAMbu/v7WxsbGxsYAAAAAAADG5vb+3s7GxsbGAAAAAAAAfMbGxsbGxsbGfAAAAAAAAPxmZmZ8YGBgYPAAAAAAAAB8xsbGxsbG1t58DA4AAAAA/GZmZnxsZmZm5gAAAAAAAHzGxmA4DAbGxnwAAAAAAAB+floYGBgYGBg8AAAAAAAAxsbGxsbGxsbGfAAAAAAAAMbGxsbGxsZsOBAAAAAAAADGxsbG1tbW/u5sAAAAAAAAxsZsfDg4fGzGxgAAAAAAAGZmZmY8GBgYGDwAAAAAAAD+xoYMGDBgwsb+AAAAAAAAPDAwMDAwMDAwPAAAAAAAAACAwOBwOBwOBgIAAAAAAAA8DAwMDAwMDAw8AAAAABA4bMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAMDAYAAAAAAAAAAAAAAAAAAAAAAAAeAx8zMzMdgAAAAAAAOBgYHhsZmZmZnwAAAAAAAAAAAB8xsDAwMZ8AAAAAAAAHAwMPGzMzMzMdgAAAAAAAAAAAHzG/sDAxnwAAAAAAAA4bGRg8GBgYGDwAAAAAAAAAAAAdszMzMzMfAzMeAAAAOBgYGx2ZmZmZuYAAAAAAAAYGAA4GBgYGBg8AAAAAAAABgYADgYGBgYGBmZmPAAAAOBgYGZseHhsZuYAAAAAAAA4GBgYGBgYGBg8AAAAAAAAAAAA7P7W1tbWxgAAAAAAAAAAANxmZmZmZmYAAAAAAAAAAAB8xsbGxsZ8AAAAAAAAAAAA3GZmZmZmfGBg8AAAAAAAAHbMzMzMzHwMDB4AAAAAAADcdmZgYGDwAAAAAAAAAAAAfMZgOAzGfAAAAAAAABAwMPwwMDAwNhwAAAAAAAAAAADMzMzMzMx2AAAAAAAAAAAAZmZmZmY8GAAAAAAAAAAAAMbG1tbW/mwAAAAAAAAAAADGbDg4OGzGAAAAAAAAAAAAxsbGxsbGfgYM+AAAAAAAAP7MGDBgxv4AAAAAAAAOGBgYcBgYGBgOAAAAAAAAGBgYGAAYGBgYGAAAAAAAAHAYGBgOGBgYGHAAAAAAAAB23AAAAAAAAAAAAAAAAAAAAAAQOGzGxsb+AAAAAAAAADxmwsDAwMJmPAwGfAAAAADMAADMzMzMzMx2AAAAAAAMGDAAfMb+wMDGfAAAAAAAEDhsAHgMfMzMzHYAAAAAAADMAAB4DHzMzMx2AAAAAABgMBgAeAx8zMzMdgAAAAAAOGw4AHgMfMzMzHYAAAAAAAAAADxmYGBmPAwGPAAAAAAQOGwAfMb+wMDGfAAAAAAAAMYAAHzG/sDAxnwAAAAAAGAwGAB8xv7AwMZ8AAAAAAAAZgAAOBgYGBgYPAAAAAAAGDxmADgYGBgYGDwAAAAAAGAwGAA4GBgYGBg8AAAAAADGABA4bMbG/sbGxgAAAAA4bDgAOGzGxv7GxsYAAAAAGDBgAP5mYHxgYGb+AAAAAAAAAAAAzHY2ftjYbgAAAAAAAD5szMz+zMzMzM4AAAAAABA4bAB8xsbGxsZ8AAAAAAAAxgAAfMbGxsbGfAAAAAAAYDAYAHzGxsbGxnwAAAAAADB4zADMzMzMzMx2AAAAAABgMBgAzMzMzMzMdgAAAAAAAMYAAMbGxsbGxn4GDHgAAMYAfMbGxsbGxsZ8AAAAAADGAMbGxsbGxsbGfAAAAAAAGBg8ZmBgYGY8GBgAAAAAADhsZGDwYGBgYOb8AAAAAAAAZmY8GH4YfhgYGAAAAAAA+MzM+MTM3szMzMYAAAAAAA4bGBgYfhgYGBgY2HAAAAAYMGAAeAx8zMzMdgAAAAAADBgwADgYGBgYGDwAAAAAABgwYAB8xsbGxsZ8AAAAAAAYMGAAzMzMzMzMdgAAAAAAAHbcANxmZmZmZmYAAAAAdtwAxub2/t7OxsbGAAAAAAA8bGw+AH4AAAAAAAAAAAAAOGxsOAB8AAAAAAAAAAAAAAAwMAAwMGDAxsZ8AAAAAAAAAAAAAP7AwMDAAAAAAAAAAAAAAAD+BgYGBgAAAAAAAMDAwsbMGDBg3IYMGD4AAADAwMLGzBgwZs6ePgYGAAAAABgYABgYGDw8PBgAAAAAAAAAAAA2bNhsNgAAAAAAAAAAAAAA2Gw2bNgAAAAAAAARRBFEEUQRRBFEEUQRRBFEVapVqlWqVapVqlWqVapVqt133Xfdd9133Xfdd9133XcYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGPgYGBgYGBgYGBgYGBgY+Bj4GBgYGBgYGBg2NjY2NjY29jY2NjY2NjY2AAAAAAAAAP42NjY2NjY2NgAAAAAA+Bj4GBgYGBgYGBg2NjY2NvYG9jY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NgAAAAAA/gb2NjY2NjY2NjY2NjY2NvYG/gAAAAAAAAAANjY2NjY2Nv4AAAAAAAAAABgYGBgY+Bj4AAAAAAAAAAAAAAAAAAAA+BgYGBgYGBgYGBgYGBgYGB8AAAAAAAAAABgYGBgYGBj/AAAAAAAAAAAAAAAAAAAA/xgYGBgYGBgYGBgYGBgYGB8YGBgYGBgYGAAAAAAAAAD/AAAAAAAAAAAYGBgYGBgY/xgYGBgYGBgYGBgYGBgfGB8YGBgYGBgYGDY2NjY2NjY3NjY2NjY2NjY2NjY2NjcwPwAAAAAAAAAAAAAAAAA/MDc2NjY2NjY2NjY2NjY29wD/AAAAAAAAAAAAAAAAAP8A9zY2NjY2NjY2NjY2NjY3MDc2NjY2NjY2NgAAAAAA/wD/AAAAAAAAAAA2NjY2NvcA9zY2NjY2NjY2GBgYGBj/AP8AAAAAAAAAADY2NjY2Njb/AAAAAAAAAAAAAAAAAP8A/xgYGBgYGBgYAAAAAAAAAP82NjY2NjY2NjY2NjY2NjY/AAAAAAAAAAAYGBgYGB8YHwAAAAAAAAAAAAAAAAAfGB8YGBgYGBgYGAAAAAAAAAA/NjY2NjY2NjY2NjY2NjY2/zY2NjY2NjY2GBgYGBj/GP8YGBgYGBgYGBgYGBgYGBj4AAAAAAAAAAAAAAAAAAAAHxgYGBgYGBgY/////////////////////wAAAAAAAAD////////////w8PDw8PDw8PDw8PDw8PDwDw8PDw8PDw8PDw8PDw8PD/////////8AAAAAAAAAAAAAAAAAAHbc2NjY3HYAAAAAAAB4zMzM2MzGxsbMAAAAAAAA/sbGwMDAwMDAwAAAAAAAAAAA/mxsbGxsbGwAAAAAAAAA/sZgMBgwYMb+AAAAAAAAAAAAftjY2NjYcAAAAAAAAAAAZmZmZmZ8YGDAAAAAAAAAAHbcGBgYGBgYAAAAAAAAAH4YPGZmZjwYfgAAAAAAAAA4bMbG/sbGbDgAAAAAAAA4bMbGxmxsbGzuAAAAAAAAHjAYDD5mZmZmPAAAAAAAAAAAAH7b29t+AAAAAAAAAAAAAwZ+29vzfmDAAAAAAAAAHDBgYHxgYGAwHAAAAAAAAAB8xsbGxsbGxsYAAAAAAAAAAP4AAP4AAP4AAAAAAAAAAAAYGH4YGAAA/wAAAAAAAAAwGAwGDBgwAH4AAAAAAAAADBgwYDAYDAB+AAAAAAAADhsbGBgYGBgYGBgYGBgYGBgYGBgYGNjY2HAAAAAAAAAAABgYAH4AGBgAAAAAAAAAAAAAdtwAdtwAAAAAAAAAOGxsOAAAAAAAAAAAAAAAAAAAAAAAABgYAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAADwwMDAwM7GxsPBwAAAAAANhsbGxsbAAAAAAAAAAAAABw2DBgyPgAAAAAAAAAAAAAAAAAfHx8fHx8fAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; + internal static MemoryStream Asc16FontStream = new MemoryStream(Convert.FromBase64String(Asc16Base64)); + + internal static void DrawAsciiString(string str, Color color, int x, int y, int[] buffer, int bufferWidth, int bufferHeight) + { + for (int c = 0; c < str.Length; c++) + { + int offset = (Encoding.ASCII.GetBytes(str[c].ToString())[0] & 0xFF) * 16; + Asc16FontStream.Seek(offset, SeekOrigin.Begin); + byte[] fontbuf = new byte[16]; + Asc16FontStream.Read(fontbuf, 0, fontbuf.Length); + + for (int i = 0; i < 16; i++) + { + for (int j = 0; j < 8; j++) + { + if ((fontbuf[i] & (0x80 >> j)) != 0) + { + int index = x + ((i + y) * bufferWidth) + j + (c * 8); + if (index < 0 || index > buffer.Length) continue; + buffer[index] = color.ToArgb(); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Gui/BitmapExtensions.cs b/Gui/BitmapExtensions.cs new file mode 100644 index 0000000..4fca0b4 --- /dev/null +++ b/Gui/BitmapExtensions.cs @@ -0,0 +1,49 @@ +using Cosmos.System.Graphics; +using System; + +namespace CMLeonOS.Gui +{ + internal static class BitmapExtensions + { + internal static Bitmap Resize(this Bitmap bmp, uint width, uint height) + { + if (bmp.Width == width && bmp.Height == height) + { + return bmp; + } + + if (bmp.Depth != ColorDepth.ColorDepth32) + { + throw new Exception("Resize can only resize images with a colour depth of 32."); + } + + Bitmap res = new Bitmap(width, height, ColorDepth.ColorDepth32); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + double xDouble = (double)x / (double)width; + double yDouble = (double)y / (double)height; + + uint origX = (uint)((double)bmp.Width * xDouble); + uint origY = (uint)((double)bmp.Height * yDouble); + + res.RawData[y * width + x] = bmp.RawData[(origY * bmp.Width) + origX]; + } + } + + return res; + } + + internal static Bitmap ResizeWidthKeepRatio(this Bitmap bmp, uint width) + { + return Resize(bmp, width, (uint)((double)bmp.Height * ((double)width / (double)bmp.Width))); + } + + internal static Bitmap ResizeHeightKeepRatio(this Bitmap bmp, uint height) + { + return Resize(bmp, (uint)((double)bmp.Width * ((double)height / (double)bmp.Height)), height); + } + } +} diff --git a/Gui/CursorType.cs b/Gui/CursorType.cs new file mode 100644 index 0000000..c84db08 --- /dev/null +++ b/Gui/CursorType.cs @@ -0,0 +1,8 @@ +namespace CMLeonOS.Gui +{ + internal enum CursorType + { + Cursor, + WaitCursor + } +} diff --git a/Gui/Gui.cs b/Gui/Gui.cs new file mode 100644 index 0000000..8b7011c --- /dev/null +++ b/Gui/Gui.cs @@ -0,0 +1,77 @@ +using CMLeonOS; +using CMLeonOS.Logger; +using System; +using System.Threading; + +namespace CMLeonOS.Gui +{ + internal static class Gui + { + private static bool guiRunning = false; + private static WindowManager windowManager; + + internal static bool StartGui() + { + Console.Clear(); + Console.CursorVisible = false; + + windowManager = new WindowManager(); + + Logger.Logger.Instance.Info("Gui", "GUI starting."); + + if (Cosmos.Core.CPU.GetAmountOfRAM() < 1000) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("Not enough system memory is available to run the GUI."); + Console.WriteLine("At least 1 GB should be allocated."); + Console.ResetColor(); + Console.Write("Continue anyway? [y/N]"); + + if (Console.ReadKey(true).Key != ConsoleKey.Y) + { + return false; + } + } + + Console.WriteLine("Loading apps..."); + Thread.Sleep(1000); + AppManager.LoadAllApps(); + + ProcessManager.AddProcess(windowManager); + + ProcessManager.AddProcess(windowManager, new SettingsService()).Start(); + + windowManager.Start(); + + ProcessManager.AddProcess(windowManager, new Sound.SoundService()).Start(); + + Console.WriteLine("Starting lock screen..."); + ProcessManager.AddProcess(windowManager, new ShellComponents.Lock()).Start(); + + guiRunning = true; + + // 进入 GUI 事件循环 + RunGuiLoop(); + + return true; + } + + private static void RunGuiLoop() + { + while (guiRunning) + { + ProcessManager.Yield(); + System.Threading.Thread.Sleep(1); + } + } + + internal static void StopGui() + { + guiRunning = false; + if (windowManager != null) + { + windowManager.Stop(); + } + } + } +} diff --git a/Gui/Resources/AppIcons/Calculator.bmp b/Gui/Resources/AppIcons/Calculator.bmp new file mode 100644 index 0000000..55cfab4 Binary files /dev/null and b/Gui/Resources/AppIcons/Calculator.bmp differ diff --git a/Gui/Resources/AppIcons/Calendar.bmp b/Gui/Resources/AppIcons/Calendar.bmp new file mode 100644 index 0000000..f17308c Binary files /dev/null and b/Gui/Resources/AppIcons/Calendar.bmp differ diff --git a/Gui/Resources/AppIcons/Clock.bmp b/Gui/Resources/AppIcons/Clock.bmp new file mode 100644 index 0000000..677be2a Binary files /dev/null and b/Gui/Resources/AppIcons/Clock.bmp differ diff --git a/Gui/Resources/AppIcons/CodeStudio.bmp b/Gui/Resources/AppIcons/CodeStudio.bmp new file mode 100644 index 0000000..7c09281 Binary files /dev/null and b/Gui/Resources/AppIcons/CodeStudio.bmp differ diff --git a/Gui/Resources/AppIcons/Default.bmp b/Gui/Resources/AppIcons/Default.bmp new file mode 100644 index 0000000..443e874 Binary files /dev/null and b/Gui/Resources/AppIcons/Default.bmp differ diff --git a/Gui/Resources/AppIcons/DemoLauncher.bmp b/Gui/Resources/AppIcons/DemoLauncher.bmp new file mode 100644 index 0000000..eddb6a6 Binary files /dev/null and b/Gui/Resources/AppIcons/DemoLauncher.bmp differ diff --git a/Gui/Resources/AppIcons/Demos/Mandelbrot.bmp b/Gui/Resources/AppIcons/Demos/Mandelbrot.bmp new file mode 100644 index 0000000..36b58c1 Binary files /dev/null and b/Gui/Resources/AppIcons/Demos/Mandelbrot.bmp differ diff --git a/Gui/Resources/AppIcons/Demos/Starfield.bmp b/Gui/Resources/AppIcons/Demos/Starfield.bmp new file mode 100644 index 0000000..c1ec353 Binary files /dev/null and b/Gui/Resources/AppIcons/Demos/Starfield.bmp differ diff --git a/Gui/Resources/AppIcons/Files.bmp b/Gui/Resources/AppIcons/Files.bmp new file mode 100644 index 0000000..dbcc28d Binary files /dev/null and b/Gui/Resources/AppIcons/Files.bmp differ diff --git a/Gui/Resources/AppIcons/Info.bmp b/Gui/Resources/AppIcons/Info.bmp new file mode 100644 index 0000000..649eb4b Binary files /dev/null and b/Gui/Resources/AppIcons/Info.bmp differ diff --git a/Gui/Resources/AppIcons/Logs.bmp b/Gui/Resources/AppIcons/Logs.bmp new file mode 100644 index 0000000..257a835 Binary files /dev/null and b/Gui/Resources/AppIcons/Logs.bmp differ diff --git a/Gui/Resources/AppIcons/MemoryStatistics.bmp b/Gui/Resources/AppIcons/MemoryStatistics.bmp new file mode 100644 index 0000000..ae1e60d Binary files /dev/null and b/Gui/Resources/AppIcons/MemoryStatistics.bmp differ diff --git a/Gui/Resources/AppIcons/Notepad.bmp b/Gui/Resources/AppIcons/Notepad.bmp new file mode 100644 index 0000000..90c7c8d Binary files /dev/null and b/Gui/Resources/AppIcons/Notepad.bmp differ diff --git a/Gui/Resources/AppIcons/Paint.bmp b/Gui/Resources/AppIcons/Paint.bmp new file mode 100644 index 0000000..39e1253 Binary files /dev/null and b/Gui/Resources/AppIcons/Paint.bmp differ diff --git a/Gui/Resources/AppIcons/Settings.bmp b/Gui/Resources/AppIcons/Settings.bmp new file mode 100644 index 0000000..b8a4f00 Binary files /dev/null and b/Gui/Resources/AppIcons/Settings.bmp differ diff --git a/Gui/Resources/AppIcons/Stopwatch.bmp b/Gui/Resources/AppIcons/Stopwatch.bmp new file mode 100644 index 0000000..e50a4fb Binary files /dev/null and b/Gui/Resources/AppIcons/Stopwatch.bmp differ diff --git a/Gui/Resources/AppIcons/Tasks.bmp b/Gui/Resources/AppIcons/Tasks.bmp new file mode 100644 index 0000000..b93ef32 Binary files /dev/null and b/Gui/Resources/AppIcons/Tasks.bmp differ diff --git a/Gui/Resources/ButtonBackground.bmp b/Gui/Resources/ButtonBackground.bmp new file mode 100644 index 0000000..a9f254e Binary files /dev/null and b/Gui/Resources/ButtonBackground.bmp differ diff --git a/Gui/Resources/Calculator/Display.bmp b/Gui/Resources/Calculator/Display.bmp new file mode 100644 index 0000000..ccef3d8 Binary files /dev/null and b/Gui/Resources/Calculator/Display.bmp differ diff --git a/Gui/Resources/Calculator/GridButton.bmp b/Gui/Resources/Calculator/GridButton.bmp new file mode 100644 index 0000000..d0d1211 Binary files /dev/null and b/Gui/Resources/Calculator/GridButton.bmp differ diff --git a/Gui/Resources/Check.bmp b/Gui/Resources/Check.bmp new file mode 100644 index 0000000..5f34cdb Binary files /dev/null and b/Gui/Resources/Check.bmp differ diff --git a/Gui/Resources/Clock/ClockBackground.bmp b/Gui/Resources/Clock/ClockBackground.bmp new file mode 100644 index 0000000..1371de3 Binary files /dev/null and b/Gui/Resources/Clock/ClockBackground.bmp differ diff --git a/Gui/Resources/Close.bmp b/Gui/Resources/Close.bmp new file mode 100644 index 0000000..89bae77 Binary files /dev/null and b/Gui/Resources/Close.bmp differ diff --git a/Gui/Resources/Close_aero.bmp b/Gui/Resources/Close_aero.bmp new file mode 100644 index 0000000..f15a8c5 Binary files /dev/null and b/Gui/Resources/Close_aero.bmp differ diff --git a/Gui/Resources/Close_flat.bmp b/Gui/Resources/Close_flat.bmp new file mode 100644 index 0000000..b92cf89 Binary files /dev/null and b/Gui/Resources/Close_flat.bmp differ diff --git a/Gui/Resources/CodeStudio/Run.bmp b/Gui/Resources/CodeStudio/Run.bmp new file mode 100644 index 0000000..8e10b77 Binary files /dev/null and b/Gui/Resources/CodeStudio/Run.bmp differ diff --git a/Gui/Resources/CodeStudio/Splash.bmp b/Gui/Resources/CodeStudio/Splash.bmp new file mode 100644 index 0000000..8334a94 Binary files /dev/null and b/Gui/Resources/CodeStudio/Splash.bmp differ diff --git a/Gui/Resources/Cursor.bmp b/Gui/Resources/Cursor.bmp new file mode 100644 index 0000000..e1e3829 Binary files /dev/null and b/Gui/Resources/Cursor.bmp differ diff --git a/Gui/Resources/Cursor_noshadow.bmp b/Gui/Resources/Cursor_noshadow.bmp new file mode 100644 index 0000000..e7754e0 Binary files /dev/null and b/Gui/Resources/Cursor_noshadow.bmp differ diff --git a/Gui/Resources/Dock/StartMenu.bmp b/Gui/Resources/Dock/StartMenu.bmp new file mode 100644 index 0000000..bfa884e Binary files /dev/null and b/Gui/Resources/Dock/StartMenu.bmp differ diff --git a/Gui/Resources/Files/Directory.bmp b/Gui/Resources/Files/Directory.bmp new file mode 100644 index 0000000..ebc5191 Binary files /dev/null and b/Gui/Resources/Files/Directory.bmp differ diff --git a/Gui/Resources/Files/Drive.bmp b/Gui/Resources/Files/Drive.bmp new file mode 100644 index 0000000..190d81c Binary files /dev/null and b/Gui/Resources/Files/Drive.bmp differ diff --git a/Gui/Resources/Files/File.bmp b/Gui/Resources/Files/File.bmp new file mode 100644 index 0000000..b9ec5eb Binary files /dev/null and b/Gui/Resources/Files/File.bmp differ diff --git a/Gui/Resources/Files/File_Config.bmp b/Gui/Resources/Files/File_Config.bmp new file mode 100644 index 0000000..e5e2025 Binary files /dev/null and b/Gui/Resources/Files/File_Config.bmp differ diff --git a/Gui/Resources/Files/File_Rs.bmp b/Gui/Resources/Files/File_Rs.bmp new file mode 100644 index 0000000..17f70ee Binary files /dev/null and b/Gui/Resources/Files/File_Rs.bmp differ diff --git a/Gui/Resources/Files/File_Text.bmp b/Gui/Resources/Files/File_Text.bmp new file mode 100644 index 0000000..3851160 Binary files /dev/null and b/Gui/Resources/Files/File_Text.bmp differ diff --git a/Gui/Resources/Files/Home.bmp b/Gui/Resources/Files/Home.bmp new file mode 100644 index 0000000..158aaee Binary files /dev/null and b/Gui/Resources/Files/Home.bmp differ diff --git a/Gui/Resources/Files/Up.bmp b/Gui/Resources/Files/Up.bmp new file mode 100644 index 0000000..11843e7 Binary files /dev/null and b/Gui/Resources/Files/Up.bmp differ diff --git a/Gui/Resources/Lock/Background.bmp b/Gui/Resources/Lock/Background.bmp new file mode 100644 index 0000000..17f0b5f Binary files /dev/null and b/Gui/Resources/Lock/Background.bmp differ diff --git a/Gui/Resources/Lock/Background_small.bmp b/Gui/Resources/Lock/Background_small.bmp new file mode 100644 index 0000000..d6e8d0e Binary files /dev/null and b/Gui/Resources/Lock/Background_small.bmp differ diff --git a/Gui/Resources/Lock/Gradient.bmp b/Gui/Resources/Lock/Gradient.bmp new file mode 100644 index 0000000..8ea78d7 Binary files /dev/null and b/Gui/Resources/Lock/Gradient.bmp differ diff --git a/Gui/Resources/Lock/Key.bmp b/Gui/Resources/Lock/Key.bmp new file mode 100644 index 0000000..c391193 Binary files /dev/null and b/Gui/Resources/Lock/Key.bmp differ diff --git a/Gui/Resources/Lock/ShutDown.bmp b/Gui/Resources/Lock/ShutDown.bmp new file mode 100644 index 0000000..a5b2d4d Binary files /dev/null and b/Gui/Resources/Lock/ShutDown.bmp differ diff --git a/Gui/Resources/Lock/User.bmp b/Gui/Resources/Lock/User.bmp new file mode 100644 index 0000000..1b2feae Binary files /dev/null and b/Gui/Resources/Lock/User.bmp differ diff --git a/Gui/Resources/Lock/UserArrow.bmp b/Gui/Resources/Lock/UserArrow.bmp new file mode 100644 index 0000000..3dac9ff Binary files /dev/null and b/Gui/Resources/Lock/UserArrow.bmp differ diff --git a/Gui/Resources/Logs/Error.bmp b/Gui/Resources/Logs/Error.bmp new file mode 100644 index 0000000..02035df Binary files /dev/null and b/Gui/Resources/Logs/Error.bmp differ diff --git a/Gui/Resources/Logs/Info.bmp b/Gui/Resources/Logs/Info.bmp new file mode 100644 index 0000000..3750ce3 Binary files /dev/null and b/Gui/Resources/Logs/Info.bmp differ diff --git a/Gui/Resources/Logs/Warning.bmp b/Gui/Resources/Logs/Warning.bmp new file mode 100644 index 0000000..546fdaa Binary files /dev/null and b/Gui/Resources/Logs/Warning.bmp differ diff --git a/Gui/Resources/Maximise.bmp b/Gui/Resources/Maximise.bmp new file mode 100644 index 0000000..85109da Binary files /dev/null and b/Gui/Resources/Maximise.bmp differ diff --git a/Gui/Resources/Maximise_aero.bmp b/Gui/Resources/Maximise_aero.bmp new file mode 100644 index 0000000..406ff8a Binary files /dev/null and b/Gui/Resources/Maximise_aero.bmp differ diff --git a/Gui/Resources/Maximise_flat.bmp b/Gui/Resources/Maximise_flat.bmp new file mode 100644 index 0000000..6edb913 Binary files /dev/null and b/Gui/Resources/Maximise_flat.bmp differ diff --git a/Gui/Resources/Minimise.bmp b/Gui/Resources/Minimise.bmp new file mode 100644 index 0000000..5504a6d Binary files /dev/null and b/Gui/Resources/Minimise.bmp differ diff --git a/Gui/Resources/Minimise_aero.bmp b/Gui/Resources/Minimise_aero.bmp new file mode 100644 index 0000000..91ef706 Binary files /dev/null and b/Gui/Resources/Minimise_aero.bmp differ diff --git a/Gui/Resources/Minimise_flat.bmp b/Gui/Resources/Minimise_flat.bmp new file mode 100644 index 0000000..b3de39a Binary files /dev/null and b/Gui/Resources/Minimise_flat.bmp differ diff --git a/Gui/Resources/Restore.bmp b/Gui/Resources/Restore.bmp new file mode 100644 index 0000000..8c03aa6 Binary files /dev/null and b/Gui/Resources/Restore.bmp differ diff --git a/Gui/Resources/Restore_aero.bmp b/Gui/Resources/Restore_aero.bmp new file mode 100644 index 0000000..b4936cb Binary files /dev/null and b/Gui/Resources/Restore_aero.bmp differ diff --git a/Gui/Resources/Restore_flat.bmp b/Gui/Resources/Restore_flat.bmp new file mode 100644 index 0000000..52de71c Binary files /dev/null and b/Gui/Resources/Restore_flat.bmp differ diff --git a/Gui/Resources/ScrollbarDown.bmp b/Gui/Resources/ScrollbarDown.bmp new file mode 100644 index 0000000..7a76258 Binary files /dev/null and b/Gui/Resources/ScrollbarDown.bmp differ diff --git a/Gui/Resources/ScrollbarUp.bmp b/Gui/Resources/ScrollbarUp.bmp new file mode 100644 index 0000000..3aa38e9 Binary files /dev/null and b/Gui/Resources/ScrollbarUp.bmp differ diff --git a/Gui/Resources/Settings/Admin.bmp b/Gui/Resources/Settings/Admin.bmp new file mode 100644 index 0000000..677305c Binary files /dev/null and b/Gui/Resources/Settings/Admin.bmp differ diff --git a/Gui/Resources/Settings/Info.bmp b/Gui/Resources/Settings/Info.bmp new file mode 100644 index 0000000..3750ce3 Binary files /dev/null and b/Gui/Resources/Settings/Info.bmp differ diff --git a/Gui/Resources/Settings/User.bmp b/Gui/Resources/Settings/User.bmp new file mode 100644 index 0000000..ca4902d Binary files /dev/null and b/Gui/Resources/Settings/User.bmp differ diff --git a/Gui/Resources/Sounds/Alert.wav b/Gui/Resources/Sounds/Alert.wav new file mode 100644 index 0000000..8aec543 Binary files /dev/null and b/Gui/Resources/Sounds/Alert.wav differ diff --git a/Gui/Resources/Sounds/Login.wav b/Gui/Resources/Sounds/Login.wav new file mode 100644 index 0000000..0d0040e Binary files /dev/null and b/Gui/Resources/Sounds/Login.wav differ diff --git a/Gui/Resources/Start.bmp b/Gui/Resources/Start.bmp new file mode 100644 index 0000000..e705f35 Binary files /dev/null and b/Gui/Resources/Start.bmp differ diff --git a/Gui/Resources/StartMenu/User.bmp b/Gui/Resources/StartMenu/User.bmp new file mode 100644 index 0000000..397ce6e Binary files /dev/null and b/Gui/Resources/StartMenu/User.bmp differ diff --git a/Gui/Resources/Start_old.bmp b/Gui/Resources/Start_old.bmp new file mode 100644 index 0000000..459df4b Binary files /dev/null and b/Gui/Resources/Start_old.bmp differ diff --git a/Gui/Resources/SwitchKnob.bmp b/Gui/Resources/SwitchKnob.bmp new file mode 100644 index 0000000..d3b00dc Binary files /dev/null and b/Gui/Resources/SwitchKnob.bmp differ diff --git a/Gui/Resources/SwitchOff.bmp b/Gui/Resources/SwitchOff.bmp new file mode 100644 index 0000000..0d775e5 Binary files /dev/null and b/Gui/Resources/SwitchOff.bmp differ diff --git a/Gui/Resources/SwitchOn.bmp b/Gui/Resources/SwitchOn.bmp new file mode 100644 index 0000000..d9e9b67 Binary files /dev/null and b/Gui/Resources/SwitchOn.bmp differ diff --git a/Gui/Resources/TitlebarBackground.bmp b/Gui/Resources/TitlebarBackground.bmp new file mode 100644 index 0000000..f5f0565 Binary files /dev/null and b/Gui/Resources/TitlebarBackground.bmp differ diff --git a/Gui/Resources/WaitCursor.bmp b/Gui/Resources/WaitCursor.bmp new file mode 100644 index 0000000..1bb68b7 Binary files /dev/null and b/Gui/Resources/WaitCursor.bmp differ diff --git a/Gui/Resources/Wallpaper_1024_640_old2.bmp b/Gui/Resources/Wallpaper_1024_640_old2.bmp new file mode 100644 index 0000000..1448498 Binary files /dev/null and b/Gui/Resources/Wallpaper_1024_640_old2.bmp differ diff --git a/Gui/Resources/Wallpaper_1024_768_old2.bmp b/Gui/Resources/Wallpaper_1024_768_old2.bmp new file mode 100644 index 0000000..2eafb2a Binary files /dev/null and b/Gui/Resources/Wallpaper_1024_768_old2.bmp differ diff --git a/Gui/Resources/Wallpaper_1280_800.bmp b/Gui/Resources/Wallpaper_1280_800.bmp new file mode 100644 index 0000000..3825f19 Binary files /dev/null and b/Gui/Resources/Wallpaper_1280_800.bmp differ diff --git a/Gui/Resources/Wallpaper_1280_800_old2.bmp b/Gui/Resources/Wallpaper_1280_800_old2.bmp new file mode 100644 index 0000000..67e0c2f Binary files /dev/null and b/Gui/Resources/Wallpaper_1280_800_old2.bmp differ diff --git a/Gui/Resources/Wallpaper_old.bmp b/Gui/Resources/Wallpaper_old.bmp new file mode 100644 index 0000000..c9c6253 Binary files /dev/null and b/Gui/Resources/Wallpaper_old.bmp differ diff --git a/Gui/Resources/Wallpaper_old_HD.bmp b/Gui/Resources/Wallpaper_old_HD.bmp new file mode 100644 index 0000000..13fe9ec Binary files /dev/null and b/Gui/Resources/Wallpaper_old_HD.bmp differ diff --git a/Gui/SettingsService.cs b/Gui/SettingsService.cs new file mode 100644 index 0000000..cd71650 --- /dev/null +++ b/Gui/SettingsService.cs @@ -0,0 +1,206 @@ +using Cosmos.System; +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.ShellComponents; +using CMLeonOS.Logger; +using CMLeonOS.Utils; +using System.Text; +using System.IO; + +namespace CMLeonOS.Gui +{ + internal class SettingsService : Process + { + public SettingsService() : base("SettingsService", ProcessType.Service) + { + } + + private const string oldSettingsPath = @"0:\settings.ini"; + private const string settingsPath = @"0:\etc\gui.cfg"; + + private bool _twelveHourClock = false; + internal bool TwelveHourClock + { + get + { + return _twelveHourClock; + } + set + { + _twelveHourClock = value; + ProcessManager.GetProcess()?.UpdateTime(); + } + } + + private bool _leftHandStartButton = false; + internal bool LeftHandStartButton + { + get + { + return _leftHandStartButton; + } + set + { + _leftHandStartButton = value; + ProcessManager.GetProcess()?.SetLeftHandStartButton(LeftHandStartButton); + } + } + + private bool _showFps = true; + internal bool ShowFps + { + get + { + return _showFps; + } + set + { + _showFps = value; + + WindowManager wm = ProcessManager.GetProcess(); + if (value) + { + wm.ShowFps(); + } + else + { + wm.HideFps(); + } + } + } + + private bool _darkNotepad = false; + internal bool DarkNotepad + { + get + { + return _darkNotepad; + } + set + { + _darkNotepad = value; + } + } + + private float _mouseSensitivity = 1.0f; + internal float MouseSensitivity + { + get + { + return _mouseSensitivity; + } + set + { + _mouseSensitivity = value; + MouseManager.MouseSensitivity = value; + } + } + + private Mode _mode = new Mode(1280, 800, ColorDepth.ColorDepth32); + internal Mode Mode + { + get + { + return _mode; + } + set + { + _mode = value; + } + } + + internal void Flush() + { + IniBuilder builder = new IniBuilder(); + + /* Appearance */ + builder.BeginSection("Appearance"); + + builder.AddKey("LeftHandStartButton", LeftHandStartButton); + + builder.AddKey("ShowFps", ShowFps); + + builder.AddKey("ScreenWidth", Mode.Width); + builder.AddKey("ScreenHeight", Mode.Height); + + /* Date & Time */ + builder.BeginSection("DateAndTime"); + builder.AddKey("TwelveHourClock", TwelveHourClock); + + /* Mouse */ + builder.BeginSection("Mouse"); + builder.AddKey("Sensitivity", MouseSensitivity); + + /* Apps */ + /* Notepad*/ + builder.BeginSection("Apps.Notepad"); + builder.AddKey("Dark", DarkNotepad); + + File.WriteAllText(settingsPath, builder.ToString()); + } + + #region Process + public override void Start() + { + base.Start(); + if (File.Exists(oldSettingsPath)) + { + Logger.Logger.Instance.Info("SettingsService", "Upgrading settings.ini..."); + string text = File.ReadAllText(oldSettingsPath); + File.WriteAllText(settingsPath, text); + File.Delete(oldSettingsPath); + } + if (File.Exists(settingsPath)) + { + IniReader reader = new IniReader(File.ReadAllText(settingsPath)); + + /* Appearance */ + if (reader.TryReadBool("LeftHandStartButton", out bool leftHandStartButton, section: "Appearance")) + { + LeftHandStartButton = leftHandStartButton; + } + if (reader.TryReadInt("ScreenWidth", out int width, section: "Appearance")) + { + if (reader.TryReadInt("ScreenHeight", out int height, section: "Appearance")) + { + _mode = new Mode((uint)width, (uint)height, ColorDepth.ColorDepth32); + } + } + if (reader.TryReadBool("ShowFps", out bool showFps, section: "ShowFps")) + { + ShowFps = showFps; + } + + /* Date & Time */ + if (reader.TryReadBool("TwelveHourClock", out bool twelveHourClock, section: "DateAndTime")) + { + TwelveHourClock = twelveHourClock; + } + + /* Mouse */ + if (reader.TryReadFloat("Sensitivity", out float mouseSensitivity, section: "Mouse")) + { + MouseSensitivity = mouseSensitivity; + } + + /* Apps */ + /* Notepad */ + if (reader.TryReadBool("Dark", out bool darkNotepad, section: "Apps.Notepad")) + { + DarkNotepad = darkNotepad; + } + } + } + + public override void Run() + { + } + + public override void Stop() + { + base.Stop(); + Flush(); + } + #endregion + } +} diff --git a/Gui/ShellComponents/Dock/AppDockIcon.cs b/Gui/ShellComponents/Dock/AppDockIcon.cs new file mode 100644 index 0000000..61ed0dc --- /dev/null +++ b/Gui/ShellComponents/Dock/AppDockIcon.cs @@ -0,0 +1,22 @@ +using CMLeonOS; +using CMLeonOS.Gui.UILib; + +namespace CMLeonOS.Gui.ShellComponents.Dock +{ + internal class AppDockIcon : BaseDockIcon + { + internal AppDockIcon(AppWindow appWindow) : base( + image: appWindow.Icon, + doAnimation: true) + { + AppWindow = appWindow; + } + + internal AppWindow AppWindow { get; init; } + + internal override void Clicked() + { + ProcessManager.GetProcess().Focus = AppWindow; + } + } +} diff --git a/Gui/ShellComponents/Dock/BaseDockIcon.cs b/Gui/ShellComponents/Dock/BaseDockIcon.cs new file mode 100644 index 0000000..ee9054e --- /dev/null +++ b/Gui/ShellComponents/Dock/BaseDockIcon.cs @@ -0,0 +1,82 @@ +using Cosmos.System.Graphics; + +namespace CMLeonOS.Gui.ShellComponents.Dock +{ + internal abstract class BaseDockIcon + { + internal BaseDockIcon(Bitmap image, bool doAnimation = true) + { + if (doAnimation) + { + Size = 1; + } + else + { + // Skip to the end of the animation. + Size = Dock.IconSize; + } + + Image = image; + } + + private double _size; + internal double Size + { + get + { + return _size; + } + set + { + _size = value; + + if (_image != null) + { + SizedImage = _image.ResizeWidthKeepRatio((uint)Size); + } + } + } + + private Bitmap _image; + internal Bitmap Image + { + get + { + return _image; + } + set + { + _image = value; + SizedImage = value.ResizeWidthKeepRatio((uint)Size); + } + } + + + internal Bitmap SizedImage { get; private set; } + + internal bool Closing { get; set; } = false; + + internal bool CloseAnimationComplete + { + get + { + return Closing && (int)_size == 1; + } + } + + /// + /// Run the dock icon's animation. + /// + /// If the dock needs to be rerendered. + internal bool RunAnimation() + { + int oldSize = (int)Size; + int goalSize = Closing ? 1 : Dock.IconSize; + Size += (goalSize - Size) / 16; + + return (int)Size != (int)oldSize; + } + + internal abstract void Clicked(); + } +} diff --git a/Gui/ShellComponents/Dock/Dock.cs b/Gui/ShellComponents/Dock/Dock.cs new file mode 100644 index 0000000..3aa1639 --- /dev/null +++ b/Gui/ShellComponents/Dock/Dock.cs @@ -0,0 +1,190 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using CMLeonOS.UILib.Animations; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui.ShellComponents.Dock +{ + internal class Dock : Process + { + internal Dock() : base("Dock", ProcessType.Application) + { + } + + internal static Dock CurrentDock + { + get + { + Dock dock = ProcessManager.GetProcess(); + return dock; + } + } + + Window window; + + List Icons = new List(); + + WindowManager wm = ProcessManager.GetProcess(); + + SettingsService settingsService = ProcessManager.GetProcess(); + + internal static readonly int IconSize = 64; + internal static readonly int IconImageMaxSize = 48; + + private void Render() + { + int newDockWidth = 0; + foreach (var icon in Icons) + { + newDockWidth += (int)icon.Size; + } + + if (newDockWidth != window.Width) + { + window.MoveAndResize((int)(wm.ScreenWidth / 2 - newDockWidth / 2), window.Y, newDockWidth, window.Height); + } + + window.Clear(Color.FromArgb(130, 202, 255)); + + int x = 0; + foreach (var icon in Icons) + { + if (icon.Image != null) + { + Bitmap resizedImage = icon.Image.ResizeWidthKeepRatio((uint)Math.Min(IconImageMaxSize, icon.Size)); + + int imageX = (int)(x + ((icon.Size / 2) - (resizedImage.Width / 2))); + window.DrawImageAlpha(resizedImage, imageX, (int)((window.Height / 2) - (resizedImage.Height / 2))); + } + + x += (int)icon.Size; + } + + wm.Update(window); + } + + internal int GetDockHeight() + { + return window.Height; + } + + internal void UpdateWindows() + { + // Add new windows and update icons. + foreach (var window in wm.Windows) + { + if (window is not AppWindow appWindow) + { + continue; + } + + bool found = false; + + foreach (BaseDockIcon icon in Icons) + { + if (icon is AppDockIcon appDockIcon && appDockIcon.AppWindow == appWindow) + { + icon.Image = appWindow.Icon; + + found = true; + } + } + + if (!found) + { + Icons.Add(new AppDockIcon(appWindow)); + } + } + + // Remove deleted windows. + foreach (BaseDockIcon icon in Icons) + { + if (icon is not AppDockIcon appDockIcon) + { + continue; + } + + bool found = false; + + foreach (Window window in wm.Windows) + { + if (window == appDockIcon.AppWindow) + { + found = true; + } + } + + if (!found) + { + icon.Closing = true; + } + } + + Render(); + } + + private void DockClick(int x, int y) + { + int end = 0; + foreach (var icon in Icons) + { + end += (int)icon.Size; + + if (x < end) + { + icon.Clicked(); + return; + } + } + } + + public override void Start() + { + base.Start(); + + window = new Window(this, (int)(wm.ScreenWidth / 2), (int)(wm.ScreenHeight + IconSize), IconSize, IconSize); + window.OnClick = DockClick; + wm.AddWindow(window); + + Icons.Add(new StartMenuDockIcon()); + + Render(); + + MovementAnimation animation = new MovementAnimation(window) + { + From = new Rectangle(window.X, window.Y, window.Width, window.Height), + To = new Rectangle(window.X, (int)(wm.ScreenHeight - IconSize), window.Width, window.Height), + Duration = 10 + }; + animation.Start(); + } + + public override void Run() + { + bool rerenderNeeded = false; + + for (int i = Icons.Count - 1; i >= 0; i--) + { + BaseDockIcon icon = Icons[i]; + + if (icon.RunAnimation()) + { + rerenderNeeded = true; + } + + if (icon.CloseAnimationComplete) + { + Icons.Remove(icon); + } + } + + if (rerenderNeeded) + { + Render(); + } + } + } +} diff --git a/Gui/ShellComponents/Dock/StartMenuDockIcon.cs b/Gui/ShellComponents/Dock/StartMenuDockIcon.cs new file mode 100644 index 0000000..eb34312 --- /dev/null +++ b/Gui/ShellComponents/Dock/StartMenuDockIcon.cs @@ -0,0 +1,22 @@ +using Cosmos.System.Graphics; + +namespace CMLeonOS.Gui.ShellComponents.Dock +{ + internal class StartMenuDockIcon : BaseDockIcon + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Dock.StartMenu.bmp")] + private static byte[] _iconBytes_StartMenu; + internal static Bitmap Icon_StartMenu = new Bitmap(_iconBytes_StartMenu); + + internal StartMenuDockIcon() : base( + image: Icon_StartMenu, + doAnimation: false) + { + } + + internal override void Clicked() + { + StartMenu.CurrentStartMenu.ToggleStartMenu(); + } + } +} diff --git a/Gui/ShellComponents/Lock.cs b/Gui/ShellComponents/Lock.cs new file mode 100644 index 0000000..5988c3d --- /dev/null +++ b/Gui/ShellComponents/Lock.cs @@ -0,0 +1,204 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using CMLeonOS.Logger; + +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.ShellComponents +{ + internal class Lock : Process + { + internal Lock() : base("Lock", ProcessType.Application) { } + + AppWindow window; + + TextBox usernameBox; + + TextBox passwordBox; + + WindowManager wm = ProcessManager.GetProcess(); + + Sound.SoundService soundService = ProcessManager.GetProcess(); + + private static class Images + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Lock.Key.bmp")] + private static byte[] _iconBytes_Key; + internal static Bitmap Icon_Key = new Bitmap(_iconBytes_Key); + } + + private const int width = 352; + private const int height = 128; + private const int padding = 12; + + private double shakiness = 0; + + private void RenderBackground() + { + window.Clear(Color.LightGray); + + window.DrawImageAlpha(Images.Icon_Key, padding, padding); + + window.DrawString("Enter your username and password,\nthen press Enter to log on", Color.Black, (int)(padding + Images.Icon_Key.Width + padding), padding); + } + + private void ShowError(string text) + { + MessageBox messageBox = new MessageBox(this, "Logon Failed", text); + messageBox.Show(); + } + + private void Shake() + { + shakiness = 24; + } + + private void LogOn() + { + if (usernameBox.Text.Trim() == string.Empty || passwordBox.Text.Trim() == string.Empty) + { + return; + } + + string username = usernameBox.Text.Trim(); + string password = passwordBox.Text.Trim(); + + Logger.Logger.Instance.Info("Lock", $"Attempting login for user: {username}"); + + var users = UserSystem.GetUsers(); + Logger.Logger.Instance.Info("Lock", $"UserSystem.GetUsers() returned: {(users == null ? "null" : users.Count.ToString())}"); + + if (users == null || users.Count == 0) + { + Logger.Logger.Instance.Error("Lock", "User list is null or empty!"); + ShowError("User system error. Please restart."); + Shake(); + return; + } + + User foundUser = null; + foreach (User user in users) + { + Logger.Logger.Instance.Info("Lock", $"Checking user: {user.Username} (lower: {user.Username.ToLower()})"); + if (user.Username.ToLower() == username.ToLower()) + { + foundUser = user; + Logger.Logger.Instance.Info("Lock", $"User matched: {foundUser.Username}"); + break; + } + } + + if (foundUser == null) + { + Logger.Logger.Instance.Warning("Lock", $"User not found: {username}"); + Shake(); + return; + } + + Logger.Logger.Instance.Info("Lock", $"User found: {foundUser.Username}, Admin: {foundUser.Admin}"); + + string hashedInputPassword = UserSystem.HashPasswordSha256(password); + Logger.Logger.Instance.Info("Lock", $"Password hash: {hashedInputPassword}"); + Logger.Logger.Instance.Info("Lock", $"Stored password hash: {foundUser.Password}"); + + if (foundUser.Password != hashedInputPassword) + { + Logger.Logger.Instance.Warning("Lock", $"Authentication failed for user: {foundUser.Username}"); + passwordBox.Text = string.Empty; + + if (foundUser.LockedOut) + { + TimeSpan remaining = foundUser.LockoutEnd - DateTime.Now; + if (remaining.Minutes > 0) + { + ShowError($"Try again in {remaining.Minutes}m, {remaining.Seconds}s."); + } + else + { + ShowError($"Try again in {remaining.Seconds}s."); + } + } + + wm.Update(window); + soundService.PlaySystemSound(Sound.SystemSound.Alert); + Shake(); + return; + } + + Logger.Logger.Instance.Info("Lock", $"Authentication successful for user: {foundUser.Username}"); + + TryStop(); + UserSystem.SetCurrentLoggedInUser(foundUser); + ProcessManager.AddProcess(wm, new ShellComponents.Taskbar()).Start(); + ProcessManager.AddProcess(wm, new ShellComponents.Dock.Dock()).Start(); + soundService.PlaySystemSound(Sound.SystemSound.Login); + + Logger.Logger.Instance.Info("Lock", $"{foundUser.Username} logged on to the GUI."); + } + + private void LogOnClick(int x, int y) + { + LogOn(); + } + + #region Process + public override void Start() + { + base.Start(); + + var users = UserSystem.GetUsers(); + Logger.Logger.Instance.Info("Lock", $"Lock started. Total users: {users?.Count ?? 0}"); + if (users != null) + { + foreach (var user in users) + { + Logger.Logger.Instance.Info("Lock", $" - User: {user.Username}, Admin: {user.Admin}"); + } + } + + window = new AppWindow(this, (int)(wm.ScreenWidth / 2 - width / 2), (int)(wm.ScreenHeight / 2 - height / 2), width, height); + window.Title = "CMLeonOS Logon"; + window.Icon = Images.Icon_Key; + window.CanMove = false; + window.CanClose = false; + wm.AddWindow(window); + + RenderBackground(); + + int boxesStartY = (int)(padding + Images.Icon_Key.Height + padding); + + usernameBox = new TextBox(window, padding, boxesStartY, 160, 20); + usernameBox.PlaceholderText = "Username"; + usernameBox.Submitted = LogOn; + wm.AddWindow(usernameBox); + + passwordBox = new TextBox(window, padding, boxesStartY + padding + 20, 160, 20); + passwordBox.Shield = true; + passwordBox.PlaceholderText = "Password"; + passwordBox.Submitted = LogOn; + wm.AddWindow(passwordBox); + + wm.Update(window); + } + + public override void Run() + { + int oldX = window.X; + int newX = (int)((wm.ScreenWidth / 2) - (width / 2) + (Math.Sin(shakiness) * 8)); + if (oldX != newX) + { + window.Move(newX, window.Y); + } + shakiness /= 1.1; + } + + public override void Stop() + { + base.Stop(); + wm.RemoveWindow(window); + } + #endregion + } +} diff --git a/Gui/ShellComponents/StartMenu.cs b/Gui/ShellComponents/StartMenu.cs new file mode 100644 index 0000000..f6700fa --- /dev/null +++ b/Gui/ShellComponents/StartMenu.cs @@ -0,0 +1,280 @@ +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using CMLeonOS.UILib.Animations; +using System.Drawing; + +namespace CMLeonOS.Gui.ShellComponents +{ + internal class StartMenu : Process + { + internal StartMenu() : base("StartMenu", ProcessType.Application) + { + } + + internal static StartMenu CurrentStartMenu + { + get + { + StartMenu startMenu = ProcessManager.GetProcess(); + if (startMenu == null && ProcessManager.GetProcess() != null) + { + startMenu = (StartMenu)ProcessManager.AddProcess(ProcessManager.GetProcess(), new StartMenu()); + startMenu.Start(); + } + return startMenu; + } + } + + private static class Icons + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.StartMenu.User.bmp")] + private static byte[] _iconBytes_User; + internal static Bitmap Icon_User = new Bitmap(_iconBytes_User); + } + + Window window; + + WindowManager wm = ProcessManager.GetProcess(); + + SettingsService settingsService = ProcessManager.GetProcess(); + + private Button shutdownButton; + private Button rebootButton; + private Button allAppsButton; + + private const int buttonsPadding = 12; + private const int buttonsWidth = 96; + private const int buttonsHeight = 20; + private const int userHeight = 56; + private const int userPadding = 12; + private const int searchWidth = 128; + + private bool isOpen = false; + + internal void ShowStartMenu(bool focusSearch = false) + { + isOpen = true; + + bool leftHandStartButton = settingsService.LeftHandStartButton; + + window = new Window(this, leftHandStartButton ? 0 : (int)(wm.ScreenWidth / 2 - 408 / 2), 24, 408, 222); + + window.Clear(Color.FromArgb(56, 56, 71)); + + window.DrawString($"Start", Color.White, 12, 12); + + Rectangle userRect = new Rectangle(userPadding, window.Height - userHeight + userPadding, window.Width - (userPadding * 2), userHeight - (userPadding * 2)); + window.DrawImageAlpha(Icons.Icon_User, userRect.X, (int)(userRect.Y + (userRect.Height / 2) - (Icons.Icon_User.Height / 2))); + window.DrawString(UserSystem.CurrentLoggedInUser.Username, Color.White, (int)(userRect.X + Icons.Icon_User.Width + userPadding), (int)(userRect.Y + (userRect.Height / 2) - (16 / 2))); + + wm.AddWindow(window); + + int x = 12; + int y = 44; + for (int i = 0; i < 4; i++) + { + AppMetadata app = AppManager.AppMetadatas[i]; + + Button appButton = new Button(window, x, y, 90, 90); + + appButton.Background = app.ThemeColor; + appButton.Foreground = app.ThemeColor.GetForegroundColour(); + + appButton.Text = app.Name; + appButton.Image = app.Icon; + + appButton.OnClick = (x, y) => + { + app.Start(this); + HideStartMenu(); + }; + + wm.AddWindow(appButton); + + x += appButton.Width + 8; + if (x > window.Width - 90) + { + x = 12; + y += 90 + 8; + } + } + + shutdownButton = new Button(window, window.Width - buttonsWidth - buttonsPadding, window.Height - buttonsHeight - ((userHeight / 2) - (buttonsHeight / 2)), buttonsWidth, buttonsHeight); + shutdownButton.Text = "Shut down"; + shutdownButton.OnClick = ShutdownClicked; + wm.AddWindow(shutdownButton); + + rebootButton = new Button(window, window.Width - (buttonsPadding * 2 + buttonsWidth * 2), window.Height - buttonsHeight - ((userHeight / 2) - (buttonsHeight / 2)), buttonsWidth, buttonsHeight); + rebootButton.Text = "Restart"; + rebootButton.OnClick = RebootClicked; + wm.AddWindow(rebootButton); + + allAppsButton = new Button(window, window.Width - buttonsWidth - buttonsPadding, window.Height - buttonsHeight - userHeight, buttonsWidth, buttonsHeight); + allAppsButton.Text = "All apps >"; + allAppsButton.OnClick = AllAppsClicked; + wm.AddWindow(allAppsButton); + + Table searchResults = null; + TextBox searchBox = new TextBox(window, (window.Width / 2) - (searchWidth / 2), 12, searchWidth, 20); + if (focusSearch) + { + wm.Focus = searchBox; + } + searchBox.PlaceholderText = "Search"; + searchBox.Changed = () => + { + if (searchResults == null) + { + searchResults = new Table(searchBox, 0, searchBox.Height, searchBox.Width, 0); + + searchResults.CellHeight = 24; + + searchResults.TableCellSelected = (int index) => + { + if (index != -1) + { + ((AppMetadata)searchResults.Cells[index].Tag).Start(this); + HideStartMenu(); + } + }; + } + + searchResults.Cells.Clear(); + + if (searchBox.Text.Trim().Length > 0) + { + foreach (AppMetadata app in AppManager.AppMetadatas) + { + if (app.Name.ToLower().StartsWith(searchBox.Text.ToLower())) + { + string name = app.Name; + if (name.Length > 8) + { + name = name.Substring(0, 8).Trim() + "..."; + } + searchResults.Cells.Add(new TableCell(app.Icon.Resize(20, 20), name, tag: app)); + } + } + } + + if (searchResults.Cells.Count > 0) + { + searchResults.Resize(searchResults.Width, searchResults.Cells.Count * searchResults.CellHeight); + searchResults.Render(); + + wm.AddWindow(searchResults); + + wm.Update(searchResults); + } + else + { + wm.RemoveWindow(searchResults); + } + }; + searchBox.Submitted = () => + { + searchBox.Text = string.Empty; + wm.Update(searchBox); + + if (searchResults != null && searchResults.Cells.Count > 0) + { + ((AppMetadata)searchResults.Cells[0].Tag).Start(this); + HideStartMenu(); + } + }; + searchBox.OnUnfocused = () => + { + if (searchResults != null) + { + wm.RemoveWindow(searchResults); + searchResults = null; + + searchBox.Text = string.Empty; + wm.Update(searchBox); + } + }; + wm.AddWindow(searchBox); + + wm.Update(window); + } + + private void ShutdownClicked(int x, int y) + { + Power.Shutdown(reboot: false); + } + + private void RebootClicked(int x, int y) + { + Power.Shutdown(reboot: true); + } + + private void AllAppsClicked(int x, int y) + { + Table allAppsTable = new Table(window, 0, 0, window.Width, window.Height); + + allAppsTable.CellHeight = 32; + + allAppsTable.Background = Color.FromArgb(56, 56, 71); + allAppsTable.Foreground = Color.White; + allAppsTable.Border = Color.FromArgb(36, 36, 51); + + foreach (AppMetadata app in AppManager.AppMetadatas) + { + TableCell cell = new TableCell(app.Icon.Resize(20, 20), app.Name); + /*cell.BackgroundColourOverride = app.ThemeColor; + cell.ForegroundColourOverride = app.ThemeColor.GetForegroundColour();*/ + allAppsTable.Cells.Add(cell); + } + allAppsTable.Render(); + + allAppsTable.TableCellSelected = (int index) => + { + if (index != -1) + { + AppManager.AppMetadatas[index].Start(this); + HideStartMenu(); + } + }; + + wm.AddWindow(allAppsTable); + + MovementAnimation animation = new MovementAnimation(allAppsTable) + { + From = new Rectangle(allAppsTable.X, allAppsTable.Y, allAppsTable.Width, allAppsTable.Height), + To = new Rectangle(allAppsTable.X, allAppsTable.Y, allAppsTable.Width, allAppsTable.Height + 64), + Duration = 5 + }; + animation.Start(); + + wm.Update(allAppsTable); + } + + internal void HideStartMenu() + { + isOpen = false; + wm.RemoveWindow(window); + } + + internal void ToggleStartMenu(bool focusSearch = false) + { + if (isOpen) + { + HideStartMenu(); + } + else + { + ShowStartMenu(focusSearch); + } + } + + public override void Start() + { + base.Start(); + } + + public override void Run() + { + } + } +} diff --git a/Gui/ShellComponents/Taskbar.cs b/Gui/ShellComponents/Taskbar.cs new file mode 100644 index 0000000..2986634 --- /dev/null +++ b/Gui/ShellComponents/Taskbar.cs @@ -0,0 +1,146 @@ + +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using CMLeonOS.UILib.Animations; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui.ShellComponents +{ + internal class Taskbar : Process + { + internal Taskbar() : base("Taskbar", ProcessType.Application) + { + Critical = true; + } + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Start.bmp")] + private static byte[] startBytes; + private static Bitmap startBitmap = new Bitmap(startBytes); + + Window window; + + WindowManager wm = ProcessManager.GetProcess(); + + DateTime lastDate = DateTime.Now; + + TextBlock time; + + ImageBlock start; + + SettingsService settingsService; + + private bool miniCalendarOpen = false; + private Calendar miniCalendar; + + private int timeUpdateTicks = 0; + + internal void SetLeftHandStartButton(bool left) + { + if (left) + { + start.X = 0; + } + else + { + start.X = (int)((window.Width / 2) - (startBitmap.Width / 2)); + } + } + + internal int GetTaskbarHeight() + { + return window.Height; + } + + internal void UpdateTime() + { + if (settingsService == null) + { + settingsService = ProcessManager.GetProcess(); + } + + string timeText; + if (settingsService.TwelveHourClock) + { + timeText = DateTime.Now.ToString("ddd h:mm tt"); + } + else + { + timeText = DateTime.Now.ToString("ddd HH:mm"); + } + if (time.Text != timeText) + { + time.Text = timeText; + } + } + + private void StartClicked(int x, int y) + { + StartMenu.CurrentStartMenu.ToggleStartMenu(); + } + + private void TimeClicked(int x, int y) + { + miniCalendarOpen = !miniCalendarOpen; + if (miniCalendarOpen) + { + miniCalendar = new Calendar(window, window.Width - 256, window.Height, 256, 256); + miniCalendar.Background = Color.FromArgb(56, 56, 71); + miniCalendar.TodayBackground = Color.FromArgb(77, 77, 91); + miniCalendar.Foreground = Color.White; + miniCalendar.WeekendForeground = Color.LightPink; + wm.AddWindow(miniCalendar); + wm.Update(miniCalendar); + } + else + { + wm.RemoveWindow(miniCalendar); + } + } + + #region Process + public override void Start() + { + base.Start(); + window = new Window(this, 0, -24, (int)wm.ScreenWidth, 24); + window.Clear(Color.Black); + wm.AddWindow(window); + + time = new TextBlock(window, window.Width - 136, 0, 128, window.Height); + time.Background = Color.Black; + time.Foreground = Color.White; + time.HorizontalAlignment = Alignment.End; + time.VerticalAlignment = Alignment.Middle; + time.OnClick = TimeClicked; + wm.AddWindow(time); + + start = new ImageBlock(window, (int)((window.Width / 2) - startBitmap.Width / 2), 0, 24, 24); + start.Image = startBitmap; + start.OnClick = StartClicked; + wm.AddWindow(start); + + UpdateTime(); + + MovementAnimation animation = new MovementAnimation(window) + { + From = new Rectangle(window.X, window.Y, window.Width, window.Height), + To = new Rectangle(window.X, 0, window.Width, window.Height), + Duration = 10 + }; + animation.Start(); + + wm.Update(window); + } + + public override void Run() + { + timeUpdateTicks++; + if (timeUpdateTicks % 100 == 0) + { + UpdateTime(); + } + } + #endregion + } +} diff --git a/Gui/SmoothMono/FontData.cs b/Gui/SmoothMono/FontData.cs new file mode 100644 index 0000000..6a44795 --- /dev/null +++ b/Gui/SmoothMono/FontData.cs @@ -0,0 +1,549 @@ +namespace CMLeonOS.Gui.SmoothMono +{ + internal static class FontData + { + internal const int Width = 8; + internal const int Height = 16; + + internal static byte[][] Chars = new byte[][] + { + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + null, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 109, 66, 0, 2, 0, 0, 2, 0, 224, 135, 0, 4, 0, 0, 2, 0, 204, 124, 0, 4, 0, 0, 2, 0, 208, 126, 0, 4, 0, 0, 2, 0, 205, 124, 0, 4, 0, 0, 2, 0, 220, 133, 0, 4, 0, 0, 1, 0, 156, 95, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 109, 72, 0, 2, 0, 0, 1, 0, 185, 124, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 3, 6, 9, 0, 0, 2, 0, 180, 55, 98, 147, 0, 4, 2, 0, 198, 52, 112, 153, 0, 4, 2, 0, 176, 35, 106, 125, 0, 4, 0, 0, 15, 2, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 104, 19, 28, 99, 0, 1, 2, 17, 225, 10, 102, 180, 1, 0, 0, 50, 190, 0, 149, 103, 0, 32, 189, 212, 240, 195, 234, 218, 119, 16, 68, 188, 151, 68, 224, 88, 42, 0, 0, 184, 35, 15, 192, 0, 0, 127, 197, 245, 201, 214, 241, 189, 30, 44, 110, 212, 68, 181, 153, 67, 13, 0, 97, 170, 0, 195, 67, 0, 1, 2, 112, 90, 3, 157, 24, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 3, 0, 0, 0, 4, 0, 66, 151, 0, 4, 0, 1, 0, 43, 188, 244, 78, 0, 1, 1, 48, 242, 148, 121, 249, 103, 0, 0, 156, 187, 0, 0, 118, 240, 2, 0, 150, 205, 0, 0, 25, 96, 3, 1, 39, 241, 200, 85, 1, 0, 0, 1, 0, 36, 165, 243, 221, 53, 0, 0, 0, 0, 0, 18, 183, 216, 4, 5, 191, 58, 0, 0, 37, 247, 21, 0, 191, 203, 24, 19, 166, 212, 3, 1, 22, 182, 227, 227, 199, 43, 0, 0, 0, 0, 134, 171, 0, 0, 0, 0, 2, 2, 17, 23, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 21, 141, 144, 20, 2, 0, 0, 0, 172, 124, 124, 166, 0, 59, 15, 0, 212, 24, 22, 192, 23, 213, 20, 1, 144, 158, 160, 116, 145, 114, 0, 4, 2, 99, 98, 53, 201, 0, 0, 2, 0, 0, 0, 182, 76, 53, 41, 0, 4, 1, 81, 173, 109, 175, 186, 81, 2, 10, 205, 56, 194, 6, 79, 185, 0, 15, 83, 0, 203, 52, 117, 167, 0, 0, 0, 1, 70, 181, 167, 36, 0, 1, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 76, 169, 136, 11, 0, 1, 0, 63, 254, 121, 200, 163, 0, 3, 0, 140, 205, 0, 79, 228, 0, 1, 0, 91, 235, 52, 224, 116, 3, 6, 2, 0, 205, 251, 108, 0, 0, 0, 0, 102, 230, 246, 80, 0, 78, 57, 41, 255, 67, 72, 255, 43, 211, 117, 95, 233, 0, 0, 113, 233, 197, 19, 31, 243, 129, 49, 99, 255, 199, 6, 0, 60, 192, 205, 185, 77, 180, 83, 1, 0, 0, 1, 0, 0, 0, 0, 0, 2, 2, 0, 2, 3, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 4, 0, 0, 0, 0, 0, 0, 193, 62, 0, 3, 0, 0, 0, 0, 211, 60, 0, 2, 0, 0, 0, 0, 187, 44, 0, 2, 0, 0, 0, 0, 16, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 31, 89, 0, 2, 0, 1, 2, 14, 211, 99, 0, 3, 0, 3, 0, 156, 159, 0, 3, 0, 1, 0, 28, 237, 29, 2, 2, 0, 4, 0, 109, 209, 0, 2, 0, 0, 4, 0, 168, 167, 1, 4, 0, 0, 3, 0, 193, 145, 0, 4, 0, 0, 3, 0, 194, 144, 0, 4, 0, 0, 4, 0, 171, 164, 1, 4, 0, 0, 4, 0, 116, 205, 0, 2, 0, 0, 2, 0, 34, 237, 23, 2, 2, 0, 0, 3, 0, 169, 146, 0, 3, 0, 0, 1, 2, 21, 214, 81, 0, 3, 0, 0, 2, 0, 46, 107, 0, 3 + }, + + new byte[] + { + 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 101, 4, 1, 1, 0, 0, 3, 0, 152, 168, 0, 3, 0, 0, 1, 1, 0, 216, 91, 0, 3, 0, 0, 4, 0, 97, 215, 1, 2, 0, 0, 2, 0, 22, 241, 44, 0, 2, 0, 0, 0, 0, 222, 100, 0, 4, 0, 0, 2, 0, 208, 128, 0, 4, 0, 0, 2, 0, 208, 128, 0, 4, 0, 0, 1, 0, 220, 103, 0, 4, 0, 1, 0, 16, 238, 49, 0, 2, 0, 3, 1, 83, 219, 2, 1, 0, 1, 2, 0, 202, 103, 0, 3, 0, 3, 0, 132, 177, 0, 2, 0, 0, 0, 4, 124, 11, 0, 1, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 3, 0, 1, 0, 0, 0, 2, 107, 165, 3, 0, 0, 0, 26, 0, 98, 170, 0, 16, 2, 2, 190, 194, 156, 176, 161, 224, 29, 1, 27, 76, 233, 255, 112, 51, 8, 3, 0, 95, 181, 165, 127, 0, 1, 1, 19, 232, 52, 13, 244, 49, 0, 0, 1, 19, 0, 0, 25, 6, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 1, 5, 2, 153, 141, 2, 5, 0, 0, 0, 0, 179, 164, 0, 0, 0, 11, 42, 40, 187, 175, 40, 43, 9, 65, 244, 243, 252, 251, 244, 244, 55, 7, 27, 24, 182, 169, 24, 28, 5, 0, 0, 0, 182, 167, 0, 0, 0, 0, 4, 1, 143, 132, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 30, 162, 28, 0, 1, 0, 2, 0, 45, 255, 44, 0, 2, 0, 4, 0, 96, 240, 10, 1, 1, 0, 3, 0, 115, 100, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 108, 185, 182, 183, 185, 59, 0, 0, 51, 87, 86, 86, 87, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 167, 224, 13, 1, 1, 0, 2, 0, 147, 198, 11, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 85, 76, 0, 0, 0, 1, 1, 5, 229, 75, 0, 0, 0, 3, 1, 79, 211, 0, 1, 0, 0, 3, 0, 183, 122, 0, 4, 0, 2, 0, 29, 229, 24, 0, 1, 0, 4, 0, 129, 178, 0, 3, 0, 0, 1, 2, 215, 73, 1, 3, 0, 3, 1, 69, 217, 3, 1, 0, 0, 3, 0, 173, 132, 0, 4, 0, 0, 0, 32, 243, 32, 0, 2, 0, 0, 0, 40, 93, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 3, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 1, 0, 77, 168, 166, 68, 0, 1, 0, 97, 239, 115, 121, 245, 81, 0, 3, 224, 94, 0, 0, 102, 212, 0, 25, 239, 48, 0, 6, 162, 237, 11, 30, 246, 23, 33, 202, 202, 227, 21, 32, 235, 131, 223, 97, 43, 238, 20, 26, 244, 213, 50, 0, 63, 235, 16, 7, 235, 64, 0, 0, 87, 224, 2, 0, 136, 221, 59, 60, 229, 122, 0, 2, 2, 135, 206, 206, 127, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 9, 69, 10, 0, 1, 1, 47, 153, 227, 244, 17, 0, 1, 0, 144, 154, 108, 236, 14, 0, 1, 0, 1, 0, 51, 243, 14, 0, 1, 0, 3, 4, 64, 242, 14, 0, 1, 0, 3, 0, 62, 242, 14, 0, 1, 0, 3, 0, 62, 242, 14, 0, 1, 0, 3, 0, 61, 239, 14, 0, 1, 0, 3, 0, 65, 255, 15, 0, 1, 0, 2, 0, 46, 181, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 3, 4, 2, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 14, 129, 170, 141, 27, 0, 1, 12, 206, 183, 103, 177, 231, 21, 1, 110, 236, 0, 0, 0, 230, 109, 0, 34, 42, 2, 7, 0, 225, 99, 0, 0, 0, 5, 0, 103, 237, 13, 1, 1, 6, 0, 61, 254, 65, 0, 3, 3, 0, 46, 252, 94, 0, 5, 0, 0, 26, 227, 94, 0, 0, 0, 0, 25, 230, 191, 54, 72, 69, 56, 0, 60, 197, 183, 199, 195, 197, 161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 3, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 11, 126, 171, 142, 28, 0, 1, 11, 206, 194, 103, 170, 229, 19, 1, 68, 220, 1, 0, 0, 229, 100, 0, 6, 7, 0, 0, 0, 243, 91, 0, 0, 0, 57, 118, 192, 171, 4, 2, 4, 4, 81, 167, 218, 167, 9, 2, 0, 0, 0, 0, 4, 234, 118, 0, 75, 146, 0, 0, 0, 185, 151, 0, 46, 255, 125, 46, 99, 251, 58, 0, 0, 56, 189, 204, 195, 79, 0, 2, 1, 0, 0, 2, 0, 0, 1, 0, 0, 2, 2, 0, 2, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 67, 129, 3, 0, 0, 1, 2, 17, 245, 253, 9, 0, 0, 4, 0, 162, 218, 214, 11, 0, 3, 0, 84, 212, 74, 235, 9, 0, 2, 13, 239, 61, 58, 242, 11, 1, 0, 151, 152, 0, 64, 237, 0, 0, 78, 255, 106, 89, 139, 244, 96, 25, 100, 169, 179, 182, 202, 247, 185, 51, 0, 0, 0, 0, 62, 250, 0, 0, 2, 4, 6, 3, 56, 178, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 126, 149, 148, 150, 119, 0, 0, 35, 255, 191, 183, 183, 144, 0, 0, 60, 224, 0, 0, 0, 0, 0, 0, 86, 219, 62, 87, 25, 0, 1, 0, 103, 254, 183, 198, 238, 71, 0, 0, 23, 55, 0, 0, 140, 227, 4, 0, 0, 0, 6, 4, 33, 243, 33, 0, 147, 84, 0, 0, 48, 246, 23, 0, 119, 244, 70, 51, 209, 179, 0, 1, 0, 113, 203, 205, 157, 15, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 55, 134, 76, 0, 2, 2, 0, 155, 227, 157, 66, 0, 2, 0, 105, 236, 7, 0, 0, 1, 0, 1, 211, 91, 55, 85, 9, 0, 1, 21, 230, 204, 188, 208, 217, 30, 1, 32, 253, 125, 0, 0, 200, 165, 0, 35, 243, 27, 4, 3, 99, 218, 0, 9, 236, 81, 0, 0, 122, 206, 0, 0, 126, 234, 61, 69, 245, 99, 0, 2, 0, 125, 207, 206, 113, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 151, 150, 151, 151, 143, 128, 15, 39, 129, 128, 131, 126, 184, 237, 10, 0, 0, 0, 0, 0, 177, 116, 0, 1, 4, 7, 5, 58, 241, 16, 1, 0, 0, 3, 0, 180, 151, 0, 4, 0, 2, 1, 37, 244, 32, 1, 2, 0, 3, 0, 161, 179, 0, 3, 0, 2, 0, 20, 246, 54, 1, 3, 0, 4, 0, 155, 206, 0, 2, 0, 0, 0, 5, 177, 70, 1, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 58, 161, 168, 85, 0, 1, 0, 59, 251, 135, 115, 247, 112, 0, 0, 170, 177, 0, 0, 108, 225, 0, 0, 162, 188, 0, 0, 118, 221, 0, 1, 31, 215, 148, 127, 224, 69, 1, 1, 26, 205, 190, 177, 226, 56, 0, 0, 189, 163, 0, 0, 97, 234, 12, 0, 230, 91, 0, 0, 24, 250, 31, 0, 142, 230, 63, 49, 191, 198, 2, 1, 5, 133, 205, 204, 161, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 2, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 83, 170, 157, 44, 0, 1, 0, 107, 243, 113, 149, 245, 42, 1, 5, 229, 92, 0, 0, 184, 169, 0, 25, 237, 40, 7, 6, 98, 217, 0, 9, 238, 79, 0, 0, 119, 223, 0, 0, 139, 238, 85, 112, 242, 201, 0, 1, 2, 126, 202, 149, 153, 182, 0, 0, 0, 0, 0, 0, 213, 99, 0, 1, 1, 53, 100, 204, 181, 2, 2, 2, 0, 140, 183, 101, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 36, 125, 25, 0, 1, 0, 4, 0, 109, 255, 85, 0, 4, 0, 1, 0, 13, 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 3, 0, 86, 236, 66, 0, 3, 0, 3, 0, 74, 211, 57, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 43, 124, 19, 0, 1, 0, 4, 0, 126, 255, 71, 0, 4, 0, 1, 0, 17, 63, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 7, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 54, 158, 9, 0, 1, 0, 3, 0, 86, 255, 12, 0, 1, 0, 4, 0, 139, 212, 0, 2, 0, 0, 2, 0, 139, 72, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 1, 4, 0, 0, 0, 20, 0, 0, 0, 0, 4, 91, 211, 137, 0, 1, 19, 111, 213, 216, 125, 24, 1, 2, 218, 245, 72, 0, 0, 0, 0, 1, 76, 171, 202, 146, 49, 0, 1, 0, 0, 0, 64, 169, 243, 123, 0, 0, 3, 2, 0, 0, 38, 61, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 38, 37, 37, 38, 31, 0, 2, 179, 215, 213, 213, 215, 176, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 125, 150, 148, 148, 150, 122, 0, 2, 108, 130, 129, 129, 130, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 3, 21, 0, 0, 0, 3, 1, 0, 2, 198, 187, 74, 0, 0, 0, 0, 0, 41, 141, 217, 199, 98, 14, 1, 0, 0, 0, 0, 86, 251, 200, 0, 1, 3, 71, 164, 203, 160, 65, 1, 1, 186, 231, 144, 44, 0, 0, 0, 4, 76, 14, 0, 0, 3, 2, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 81, 167, 167, 86, 0, 1, 0, 122, 250, 116, 115, 245, 104, 0, 0, 128, 79, 0, 0, 127, 206, 0, 0, 0, 0, 8, 0, 177, 174, 0, 0, 3, 5, 0, 101, 254, 42, 1, 0, 3, 0, 77, 251, 69, 0, 2, 0, 3, 0, 201, 162, 0, 5, 0, 0, 0, 0, 23, 11, 2, 1, 0, 0, 1, 0, 95, 69, 0, 2, 0, 0, 2, 0, 182, 132, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 4, 3, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 2, 0, 28, 121, 139, 85, 0, 1, 0, 51, 190, 84, 57, 155, 133, 0, 9, 183, 32, 73, 143, 40, 159, 56, 89, 131, 57, 166, 101, 135, 78, 123, 147, 79, 148, 45, 67, 115, 62, 137, 160, 76, 168, 0, 98, 84, 71, 123, 153, 77, 167, 135, 204, 128, 147, 55, 96, 146, 26, 164, 66, 142, 92, 0, 7, 179, 115, 8, 28, 33, 0, 2, 0, 13, 136, 166, 168, 84, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 79, 91, 0, 2, 0, 0, 2, 0, 218, 239, 2, 1, 0, 2, 0, 40, 235, 241, 56, 0, 3, 4, 1, 132, 174, 160, 151, 0, 4, 2, 0, 215, 99, 77, 227, 1, 1, 0, 35, 240, 5, 0, 237, 49, 0, 1, 109, 253, 146, 145, 249, 123, 0, 0, 191, 175, 128, 128, 167, 200, 0, 40, 253, 32, 0, 0, 20, 251, 50, 83, 162, 1, 5, 5, 0, 156, 92, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 111, 145, 150, 136, 58, 0, 1, 3, 237, 180, 121, 149, 240, 112, 0, 1, 224, 75, 0, 0, 90, 239, 5, 1, 228, 76, 0, 0, 122, 221, 3, 3, 219, 197, 162, 187, 236, 45, 0, 2, 221, 163, 107, 123, 204, 131, 0, 1, 228, 76, 0, 0, 41, 253, 43, 1, 225, 80, 0, 0, 20, 249, 54, 2, 236, 138, 62, 83, 208, 187, 3, 3, 159, 195, 194, 191, 129, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 3, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 2, 0, 71, 165, 167, 83, 0, 2, 0, 100, 240, 120, 116, 240, 120, 0, 13, 237, 82, 0, 0, 71, 255, 23, 70, 236, 9, 4, 4, 4, 78, 19, 95, 229, 1, 0, 1, 0, 0, 0, 95, 229, 0, 0, 1, 1, 0, 0, 81, 233, 6, 2, 3, 0, 21, 8, 25, 246, 51, 0, 0, 37, 237, 29, 0, 143, 222, 60, 55, 211, 165, 0, 2, 1, 128, 205, 204, 139, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 11, 121, 147, 147, 99, 7, 0, 1, 21, 254, 164, 131, 197, 219, 34, 1, 19, 235, 44, 0, 0, 166, 204, 1, 19, 239, 65, 4, 1, 22, 246, 51, 19, 239, 62, 0, 2, 2, 229, 92, 19, 239, 62, 0, 2, 1, 228, 94, 19, 239, 64, 3, 3, 13, 241, 64, 19, 236, 50, 0, 0, 121, 231, 8, 20, 250, 117, 71, 147, 246, 70, 0, 15, 174, 195, 193, 159, 44, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 2, 2, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 144, 150, 150, 151, 124, 0, 0, 227, 188, 124, 130, 129, 106, 0, 0, 216, 90, 0, 0, 0, 0, 0, 0, 220, 93, 0, 0, 0, 0, 0, 0, 212, 171, 108, 115, 114, 32, 0, 0, 209, 201, 163, 165, 165, 46, 0, 0, 220, 91, 0, 0, 0, 0, 0, 0, 217, 95, 0, 0, 0, 0, 0, 0, 227, 149, 64, 71, 69, 59, 2, 0, 151, 194, 195, 195, 197, 169, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 142, 149, 150, 151, 136, 10, 0, 215, 200, 123, 131, 129, 116, 9, 0, 205, 112, 0, 0, 0, 0, 0, 0, 208, 115, 0, 0, 0, 0, 0, 0, 200, 179, 106, 108, 106, 35, 0, 0, 196, 212, 165, 170, 171, 56, 0, 0, 208, 113, 0, 0, 0, 0, 0, 0, 204, 124, 4, 8, 4, 1, 0, 0, 218, 131, 0, 4, 0, 0, 0, 0, 155, 93, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 3, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 2, 0, 69, 165, 167, 84, 0, 1, 0, 98, 241, 121, 114, 238, 130, 0, 14, 240, 84, 0, 0, 63, 251, 24, 77, 235, 6, 4, 3, 0, 45, 12, 107, 221, 0, 0, 3, 3, 0, 0, 109, 221, 0, 2, 176, 211, 216, 45, 86, 233, 3, 3, 33, 54, 241, 59, 22, 246, 64, 0, 0, 0, 230, 63, 0, 125, 236, 74, 48, 125, 241, 39, 2, 0, 111, 203, 203, 177, 53, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 3, 1, 0, 3, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 121, 13, 0, 0, 24, 121, 10, 43, 248, 27, 0, 0, 49, 249, 20, 39, 227, 28, 4, 4, 48, 228, 19, 40, 230, 6, 0, 0, 29, 232, 19, 38, 238, 127, 112, 111, 139, 232, 19, 37, 241, 174, 164, 164, 181, 232, 19, 40, 230, 5, 0, 0, 28, 232, 19, 39, 229, 28, 4, 4, 49, 229, 19, 42, 243, 26, 0, 0, 48, 244, 20, 30, 172, 19, 0, 0, 34, 173, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 125, 151, 136, 139, 151, 114, 0, 1, 109, 125, 238, 214, 125, 100, 0, 0, 0, 0, 184, 141, 0, 0, 0, 0, 6, 4, 194, 154, 4, 7, 0, 0, 3, 0, 193, 152, 0, 4, 0, 0, 3, 0, 193, 152, 0, 4, 0, 0, 5, 3, 194, 153, 2, 6, 0, 0, 0, 0, 187, 144, 0, 0, 0, 0, 59, 65, 219, 187, 65, 55, 0, 2, 164, 196, 190, 191, 196, 149, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 72, 103, 0, 0, 0, 0, 4, 0, 148, 213, 0, 0, 0, 0, 4, 0, 136, 195, 0, 0, 0, 0, 4, 0, 138, 198, 0, 0, 1, 0, 4, 0, 138, 198, 0, 0, 0, 0, 4, 0, 139, 197, 0, 5, 2, 1, 6, 3, 135, 199, 0, 75, 205, 0, 0, 0, 166, 183, 0, 17, 231, 162, 47, 99, 252, 68, 0, 0, 33, 176, 205, 197, 80, 0, 2, 1, 0, 0, 1, 0, 0, 1, 0, 0, 2, 3, 0, 2, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 2, 4, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 120, 48, 0, 0, 28, 141, 31, 1, 246, 101, 2, 6, 220, 179, 7, 1, 225, 94, 0, 176, 215, 0, 0, 1, 232, 70, 97, 247, 26, 0, 2, 4, 217, 160, 255, 87, 0, 5, 0, 4, 216, 255, 208, 189, 0, 3, 1, 1, 231, 134, 9, 249, 120, 0, 4, 1, 226, 87, 0, 83, 255, 37, 0, 1, 241, 99, 1, 0, 173, 221, 12, 1, 171, 69, 0, 2, 16, 191, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 103, 69, 0, 2, 0, 0, 0, 0, 212, 143, 0, 4, 0, 0, 0, 0, 194, 131, 0, 4, 0, 0, 0, 0, 197, 133, 0, 4, 0, 0, 0, 0, 197, 133, 0, 4, 0, 0, 0, 0, 197, 133, 0, 4, 0, 0, 0, 0, 197, 134, 2, 7, 3, 2, 0, 0, 196, 124, 0, 0, 0, 0, 0, 0, 203, 173, 63, 71, 68, 64, 7, 0, 133, 194, 194, 196, 197, 185, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 133, 68, 0, 0, 65, 133, 15, 25, 255, 203, 0, 0, 203, 255, 31, 28, 230, 244, 24, 32, 241, 229, 35, 34, 209, 207, 95, 111, 192, 207, 41, 33, 216, 119, 191, 197, 100, 218, 39, 29, 235, 45, 233, 217, 31, 236, 36, 28, 243, 19, 166, 141, 12, 242, 34, 28, 237, 34, 20, 16, 30, 236, 34, 29, 252, 42, 0, 0, 37, 251, 36, 21, 178, 29, 1, 1, 25, 178, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 132, 35, 1, 0, 34, 124, 9, 32, 255, 172, 0, 0, 70, 255, 18, 32, 240, 255, 53, 1, 68, 234, 17, 37, 227, 196, 190, 0, 70, 238, 17, 35, 240, 60, 240, 55, 53, 241, 17, 33, 250, 22, 125, 198, 53, 240, 17, 34, 246, 40, 8, 228, 153, 223, 20, 34, 242, 46, 0, 108, 255, 221, 19, 36, 255, 44, 1, 7, 225, 254, 16, 25, 183, 30, 0, 0, 83, 184, 11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 2, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 72, 167, 162, 59, 0, 1, 0, 95, 240, 117, 126, 244, 72, 0, 11, 234, 83, 0, 0, 113, 218, 2, 68, 234, 8, 4, 4, 28, 240, 40, 96, 223, 0, 1, 0, 14, 233, 68, 97, 222, 0, 1, 0, 13, 232, 69, 78, 231, 5, 2, 3, 22, 237, 51, 21, 243, 53, 0, 0, 78, 233, 8, 0, 136, 224, 59, 65, 235, 113, 0, 2, 0, 128, 206, 205, 115, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 143, 150, 146, 86, 0, 0, 0, 215, 198, 122, 137, 223, 172, 2, 0, 205, 106, 0, 0, 19, 250, 76, 0, 207, 121, 3, 5, 0, 221, 111, 0, 207, 122, 4, 9, 107, 249, 36, 0, 194, 232, 206, 213, 215, 74, 0, 0, 205, 140, 34, 37, 0, 0, 1, 0, 205, 114, 0, 0, 0, 3, 0, 0, 218, 127, 1, 6, 1, 0, 0, 0, 155, 90, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 2, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 75, 167, 163, 61, 0, 2, 0, 103, 238, 116, 123, 240, 81, 0, 16, 238, 71, 0, 0, 95, 225, 6, 83, 229, 1, 4, 4, 14, 235, 56, 118, 211, 0, 1, 0, 2, 228, 88, 120, 210, 1, 2, 0, 1, 229, 91, 96, 223, 0, 3, 2, 10, 233, 70, 31, 245, 39, 0, 0, 63, 243, 17, 0, 147, 217, 57, 62, 211, 121, 0, 2, 1, 131, 204, 210, 255, 77, 0, 0, 0, 0, 0, 0, 91, 240, 60, 0, 0, 3, 0, 0, 0, 29, 11, 0, 0, 0, 0, 0, 3, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 105, 144, 150, 136, 57, 0, 1, 0, 229, 187, 121, 150, 241, 114, 0, 0, 218, 85, 0, 0, 83, 245, 17, 0, 220, 101, 2, 0, 30, 249, 32, 0, 220, 106, 4, 19, 176, 201, 2, 0, 207, 229, 204, 239, 197, 27, 1, 0, 218, 123, 32, 169, 176, 0, 3, 0, 218, 97, 0, 29, 255, 53, 0, 0, 232, 107, 1, 0, 159, 204, 2, 0, 164, 75, 0, 1, 29, 186, 34, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 4, 3, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 1, 0, 74, 163, 167, 90, 0, 1, 0, 114, 240, 121, 111, 232, 152, 0, 6, 241, 88, 0, 0, 48, 255, 45, 4, 226, 150, 0, 0, 0, 39, 14, 0, 68, 242, 209, 117, 22, 0, 0, 0, 0, 29, 136, 222, 241, 89, 0, 4, 4, 0, 0, 0, 122, 251, 34, 47, 222, 24, 0, 0, 0, 250, 78, 6, 195, 207, 62, 49, 165, 224, 18, 0, 7, 137, 203, 204, 167, 32, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 4, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 4, 4, 4, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 83, 151, 149, 139, 140, 149, 151, 71, 71, 131, 123, 228, 217, 123, 131, 61, 0, 0, 0, 164, 144, 0, 0, 0, 2, 7, 4, 175, 157, 4, 8, 2, 0, 3, 0, 174, 155, 0, 4, 0, 0, 3, 0, 174, 155, 0, 4, 0, 0, 3, 0, 174, 155, 0, 4, 0, 0, 3, 0, 172, 153, 0, 4, 0, 0, 3, 0, 183, 163, 0, 4, 0, 0, 2, 0, 130, 116, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 122, 14, 0, 0, 26, 122, 10, 43, 252, 30, 0, 0, 54, 253, 22, 39, 231, 28, 0, 0, 50, 232, 20, 40, 236, 29, 0, 0, 51, 236, 21, 41, 237, 29, 0, 0, 52, 237, 21, 41, 237, 31, 0, 0, 53, 237, 21, 42, 238, 29, 2, 3, 51, 237, 21, 19, 244, 52, 0, 0, 82, 234, 6, 0, 151, 219, 57, 65, 233, 124, 0, 2, 3, 135, 207, 205, 117, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 4, 1, 0, 0, 1, 0, 0, 0, 0, 0, 74, 112, 0, 1, 0, 2, 124, 52, 80, 255, 22, 0, 0, 50, 255, 43, 2, 228, 95, 0, 0, 134, 202, 0, 0, 162, 180, 0, 0, 212, 124, 1, 0, 74, 239, 3, 28, 248, 40, 0, 0, 9, 241, 49, 93, 221, 0, 1, 3, 0, 181, 135, 168, 145, 0, 4, 4, 0, 86, 231, 230, 54, 0, 3, 1, 0, 13, 255, 225, 2, 1, 0, 0, 2, 0, 144, 118, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 3, 3, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 81, 94, 0, 80, 91, 0, 83, 92, 139, 208, 0, 195, 217, 0, 189, 164, 88, 209, 0, 215, 233, 0, 193, 110, 60, 215, 33, 208, 217, 36, 205, 80, 32, 210, 97, 172, 167, 103, 204, 50, 11, 197, 166, 132, 116, 171, 199, 25, 0, 182, 236, 88, 67, 237, 194, 4, 0, 163, 255, 48, 28, 255, 182, 0, 0, 146, 255, 18, 4, 255, 167, 0, 0, 87, 178, 1, 0, 166, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 32, 138, 21, 1, 1, 14, 136, 43, 9, 225, 163, 0, 0, 145, 240, 16, 0, 47, 255, 33, 20, 255, 65, 0, 3, 0, 170, 181, 169, 189, 0, 3, 2, 2, 20, 234, 245, 30, 1, 2, 1, 2, 3, 223, 239, 9, 2, 1, 3, 0, 137, 208, 193, 157, 0, 4, 0, 30, 255, 55, 31, 255, 43, 0, 3, 199, 188, 0, 0, 164, 216, 6, 47, 195, 36, 1, 1, 21, 192, 60, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 1, 4, 1, 0, 0, 1, 0, 0, 0, 0, 0, 75, 119, 0, 1, 1, 15, 135, 36, 52, 255, 59, 1, 0, 126, 244, 14, 0, 163, 191, 0, 9, 240, 93, 0, 1, 35, 255, 24, 95, 233, 4, 2, 3, 0, 171, 170, 207, 106, 0, 4, 2, 1, 33, 255, 213, 5, 2, 1, 0, 2, 0, 199, 134, 0, 4, 0, 0, 3, 1, 189, 127, 1, 4, 0, 0, 3, 0, 201, 135, 0, 4, 0, 0, 2, 0, 141, 95, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 4, 4, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 151, 151, 151, 151, 134, 102, 0, 28, 128, 129, 131, 119, 252, 178, 0, 0, 0, 0, 0, 52, 241, 25, 0, 1, 5, 7, 12, 240, 115, 0, 4, 0, 3, 0, 144, 214, 0, 3, 1, 2, 0, 46, 255, 48, 0, 2, 0, 3, 0, 215, 150, 0, 6, 2, 0, 0, 105, 225, 0, 0, 0, 0, 0, 33, 255, 142, 65, 71, 68, 60, 3, 59, 192, 189, 197, 195, 197, 172, 8, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 7, 2, 0, 0, 3, 0, 61, 223, 213, 63, 0, 2, 3, 0, 79, 234, 32, 9, 1, 0, 3, 0, 78, 221, 0, 0, 1, 0, 3, 0, 78, 223, 1, 0, 1, 0, 3, 0, 78, 223, 0, 0, 1, 0, 3, 0, 78, 223, 0, 0, 1, 0, 3, 0, 78, 223, 0, 0, 1, 0, 3, 0, 78, 223, 0, 0, 1, 0, 3, 0, 78, 223, 0, 0, 1, 0, 3, 0, 78, 223, 4, 1, 1, 0, 3, 0, 77, 217, 0, 0, 1, 0, 3, 0, 80, 253, 107, 31, 0, 1, 2, 0, 41, 155, 168, 50, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 65, 97, 0, 2, 0, 0, 0, 0, 55, 240, 15, 1, 1, 0, 0, 2, 0, 197, 105, 0, 4, 0, 0, 4, 1, 98, 203, 0, 2, 0, 0, 1, 0, 12, 227, 48, 0, 2, 0, 0, 4, 0, 157, 154, 0, 4, 0, 0, 2, 0, 51, 226, 10, 1, 1, 0, 0, 2, 0, 205, 95, 1, 4, 0, 0, 4, 0, 108, 194, 0, 2, 0, 0, 1, 0, 17, 241, 52, 0, 0, 0, 0, 1, 0, 86, 52, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 5, 1, 0, 0, 3, 0, 84, 214, 224, 42, 0, 3, 0, 1, 12, 43, 245, 54, 0, 3, 0, 1, 0, 5, 234, 53, 0, 3, 0, 1, 1, 13, 236, 53, 0, 3, 0, 1, 0, 12, 236, 53, 0, 3, 0, 1, 0, 12, 236, 53, 0, 3, 0, 1, 0, 12, 236, 53, 0, 3, 0, 1, 0, 12, 236, 53, 0, 3, 0, 1, 0, 12, 236, 53, 0, 3, 0, 1, 1, 15, 236, 53, 0, 3, 0, 1, 0, 0, 231, 53, 0, 3, 2, 0, 41, 115, 255, 54, 0, 3, 3, 0, 67, 167, 154, 28, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 82, 70, 0, 2, 0, 1, 1, 4, 241, 217, 0, 2, 0, 4, 0, 91, 208, 213, 68, 1, 3, 2, 0, 205, 83, 114, 183, 0, 3, 0, 63, 240, 6, 24, 245, 41, 0, 0, 46, 64, 0, 0, 71, 36, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 3, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 79, 87, 87, 86, 87, 73, 2, 15, 170, 186, 185, 185, 186, 157, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 16, 138, 42, 0, 2, 0, 0, 2, 2, 121, 200, 12, 2, 0, 0, 0, 0, 0, 32, 9, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 35, 31, 0, 0, 0, 1, 55, 207, 212, 221, 189, 23, 1, 0, 168, 129, 0, 0, 198, 158, 0, 1, 0, 42, 118, 122, 194, 183, 0, 0, 123, 239, 170, 150, 210, 178, 0, 11, 246, 68, 0, 0, 130, 188, 0, 5, 217, 158, 50, 97, 245, 191, 0, 0, 45, 189, 205, 162, 125, 160, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 2, 0, 3, 3, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 5, 0, 0, 0, 0, 0, 0, 208, 85, 1, 5, 0, 0, 0, 0, 229, 96, 0, 0, 1, 1, 0, 0, 225, 79, 16, 39, 0, 0, 0, 2, 213, 194, 192, 218, 196, 24, 1, 0, 222, 181, 18, 16, 203, 175, 0, 0, 226, 82, 0, 0, 70, 237, 9, 0, 224, 95, 3, 3, 53, 236, 21, 0, 224, 85, 0, 0, 87, 233, 5, 1, 231, 214, 61, 61, 235, 139, 0, 2, 155, 128, 182, 210, 143, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 1, 0, 3, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 2, 0, 0, 1, 1, 0, 0, 0, 0, 30, 34, 0, 0, 0, 1, 16, 177, 217, 212, 194, 28, 1, 0, 171, 200, 18, 11, 177, 206, 0, 17, 244, 55, 0, 0, 15, 84, 1, 34, 242, 38, 4, 5, 0, 0, 0, 11, 240, 73, 0, 0, 19, 97, 1, 0, 136, 229, 63, 54, 207, 176, 0, 2, 0, 125, 206, 205, 138, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 12, 0, 0, 0, 0, 5, 1, 112, 192, 0, 0, 1, 0, 0, 0, 125, 211, 0, 0, 0, 0, 39, 13, 109, 207, 0, 1, 28, 200, 217, 189, 207, 194, 0, 0, 189, 190, 14, 21, 195, 200, 0, 22, 245, 48, 0, 0, 112, 208, 0, 40, 241, 33, 3, 3, 123, 207, 0, 14, 243, 64, 0, 0, 114, 206, 0, 0, 152, 225, 57, 64, 225, 208, 0, 1, 6, 148, 209, 178, 138, 144, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 1, 3, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 2, 0, 0, 1, 1, 0, 0, 0, 0, 29, 34, 0, 0, 0, 2, 10, 170, 217, 217, 191, 23, 1, 0, 166, 186, 3, 0, 173, 175, 0, 23, 248, 109, 65, 66, 112, 250, 15, 47, 245, 197, 198, 197, 196, 174, 19, 21, 248, 52, 0, 0, 0, 0, 0, 0, 141, 238, 79, 50, 137, 154, 0, 2, 0, 119, 205, 204, 171, 48, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, 1, 0, 3, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 1, 3, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 22, 71, 52, 6, 0, 3, 0, 91, 234, 200, 202, 53, 1, 0, 7, 243, 101, 0, 0, 3, 3, 16, 52, 245, 44, 19, 15, 0, 20, 197, 220, 252, 218, 213, 161, 0, 4, 17, 55, 246, 53, 19, 17, 0, 2, 0, 34, 245, 32, 0, 0, 0, 2, 1, 39, 245, 37, 1, 3, 0, 2, 0, 38, 242, 36, 0, 2, 0, 2, 0, 40, 255, 38, 0, 2, 0, 1, 0, 28, 183, 27, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 36, 16, 0, 11, 0, 1, 30, 202, 216, 192, 184, 177, 0, 0, 190, 190, 14, 21, 198, 207, 0, 21, 245, 49, 0, 0, 109, 208, 0, 38, 241, 34, 3, 3, 121, 207, 0, 14, 243, 65, 0, 0, 113, 210, 0, 0, 156, 226, 57, 65, 220, 198, 0, 1, 0, 142, 214, 173, 171, 200, 0, 0, 57, 22, 0, 0, 179, 170, 0, 0, 103, 224, 177, 202, 212, 31, 1, 1, 0, 31, 96, 80, 5, 0, 1 + }, + + new byte[] + { + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 5, 0, 0, 0, 0, 0, 0, 208, 83, 1, 4, 1, 0, 0, 0, 229, 94, 0, 0, 0, 2, 0, 0, 226, 76, 6, 41, 3, 0, 0, 1, 216, 161, 179, 212, 220, 51, 1, 1, 222, 179, 26, 9, 179, 196, 0, 0, 226, 79, 0, 0, 86, 226, 0, 0, 225, 92, 1, 1, 93, 224, 0, 0, 222, 89, 0, 0, 91, 222, 0, 0, 237, 95, 0, 0, 97, 237, 0, 0, 168, 67, 0, 0, 69, 168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 70, 146, 1, 0, 0, 0, 0, 0, 80, 172, 1, 0, 0, 0, 11, 18, 7, 0, 0, 0, 0, 0, 145, 212, 222, 199, 1, 0, 1, 0, 17, 18, 109, 230, 0, 0, 1, 0, 0, 0, 90, 225, 0, 0, 1, 0, 5, 3, 96, 226, 3, 3, 1, 0, 0, 0, 84, 222, 0, 0, 0, 0, 49, 65, 141, 246, 68, 67, 13, 0, 134, 196, 192, 190, 197, 193, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 142, 74, 0, 3, 0, 0, 0, 0, 166, 88, 0, 3, 0, 2, 17, 18, 0, 0, 0, 0, 0, 26, 208, 210, 233, 105, 0, 4, 0, 2, 22, 18, 217, 127, 0, 4, 0, 0, 0, 0, 207, 125, 0, 4, 0, 0, 3, 1, 209, 125, 0, 4, 0, 0, 2, 0, 209, 125, 0, 4, 0, 0, 2, 1, 209, 124, 0, 4, 0, 2, 6, 0, 205, 127, 0, 4, 0, 0, 0, 0, 240, 99, 0, 4, 0, 92, 154, 201, 203, 14, 1, 1, 0, 76, 136, 100, 10, 0, 1, 0 + }, + + new byte[] + { + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 5, 0, 0, 0, 0, 0, 0, 208, 88, 0, 4, 1, 1, 0, 0, 229, 97, 0, 6, 0, 0, 0, 0, 224, 94, 2, 0, 5, 20, 0, 0, 225, 100, 0, 51, 229, 84, 0, 0, 228, 78, 37, 251, 117, 0, 3, 0, 217, 142, 235, 113, 0, 4, 0, 1, 212, 251, 236, 128, 0, 4, 0, 0, 225, 116, 31, 255, 102, 0, 3, 0, 237, 98, 0, 79, 255, 79, 0, 0, 168, 72, 2, 0, 112, 176, 14, 0, 0, 0, 0, 2, 0, 0, 1, 0, 2, 1, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 21, 18, 12, 0, 0, 0, 0, 145, 212, 222, 198, 1, 0, 1, 0, 15, 15, 107, 229, 0, 0, 1, 0, 0, 0, 91, 225, 0, 0, 1, 0, 4, 1, 95, 226, 0, 0, 1, 0, 3, 0, 94, 226, 0, 0, 1, 0, 3, 0, 94, 226, 0, 0, 1, 0, 5, 3, 96, 226, 3, 3, 1, 0, 0, 0, 84, 222, 0, 0, 0, 0, 49, 65, 141, 246, 68, 67, 13, 0, 134, 196, 192, 190, 197, 193, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 1, 0, 0, 0, 0, 0, 2, 0, 0, 1, 7, 4, 21, 22, 0, 33, 7, 0, 107, 211, 187, 233, 168, 213, 209, 25, 124, 221, 13, 194, 182, 12, 236, 95, 125, 196, 0, 172, 145, 0, 212, 102, 125, 201, 1, 177, 152, 1, 216, 101, 124, 198, 0, 174, 149, 0, 213, 100, 132, 211, 0, 185, 159, 0, 228, 106, 93, 149, 0, 131, 113, 0, 161, 75, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 2, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 1, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 12, 0, 10, 38, 3, 0, 0, 3, 191, 144, 186, 211, 219, 50, 1, 1, 226, 186, 25, 8, 179, 194, 0, 0, 225, 79, 0, 0, 90, 224, 0, 0, 225, 92, 1, 1, 98, 222, 0, 0, 222, 89, 0, 0, 96, 220, 0, 0, 237, 95, 0, 0, 102, 235, 0, 0, 168, 67, 0, 0, 72, 166, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, 0, 0, 2, 1, 0, 1, 0, 0, 34, 30, 0, 0, 0, 1, 27, 191, 214, 217, 177, 16, 2, 0, 198, 177, 13, 18, 201, 174, 0, 43, 246, 25, 0, 0, 49, 245, 22, 70, 232, 14, 3, 4, 30, 240, 43, 32, 249, 40, 0, 0, 67, 243, 15, 0, 164, 215, 57, 63, 229, 139, 0, 2, 6, 140, 206, 207, 125, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 11, 0, 22, 34, 0, 0, 0, 4, 193, 179, 195, 222, 192, 21, 1, 1, 230, 173, 14, 22, 215, 167, 0, 0, 226, 77, 0, 0, 79, 234, 7, 0, 226, 93, 3, 4, 59, 234, 17, 0, 228, 79, 0, 0, 99, 230, 3, 1, 222, 195, 55, 68, 243, 131, 0, 2, 217, 164, 179, 213, 139, 3, 2, 0, 226, 75, 0, 1, 0, 0, 0, 0, 235, 95, 2, 3, 3, 1, 0, 0, 74, 29, 0, 1, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 35, 17, 0, 11, 0, 1, 30, 201, 218, 193, 189, 176, 0, 0, 190, 194, 15, 20, 193, 205, 0, 21, 245, 51, 0, 0, 110, 207, 0, 38, 241, 34, 3, 3, 124, 207, 0, 14, 243, 67, 0, 0, 113, 209, 0, 0, 154, 228, 59, 62, 215, 197, 0, 1, 7, 149, 211, 173, 181, 197, 0, 0, 0, 0, 3, 0, 109, 207, 0, 0, 1, 3, 4, 2, 128, 215, 0, 0, 0, 0, 2, 0, 40, 68, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 13, 0, 0, 32, 21, 0, 0, 1, 204, 121, 193, 241, 197, 1, 0, 1, 228, 231, 81, 40, 43, 1, 0, 0, 235, 101, 0, 0, 0, 0, 0, 0, 233, 86, 3, 5, 2, 0, 0, 0, 230, 89, 0, 3, 0, 0, 0, 0, 245, 94, 0, 3, 0, 0, 0, 0, 174, 66, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 2, 0, 0, 0, 2, 0, 0, 0, 0, 31, 35, 0, 0, 0, 1, 21, 182, 216, 212, 207, 49, 1, 0, 160, 203, 0, 2, 166, 191, 0, 0, 123, 228, 85, 30, 0, 12, 0, 1, 0, 89, 188, 215, 195, 43, 0, 0, 83, 8, 0, 2, 140, 227, 2, 0, 179, 222, 62, 44, 162, 205, 1, 1, 6, 141, 204, 205, 170, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 3, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 2, 0, 49, 166, 1, 0, 0, 0, 5, 17, 92, 251, 25, 17, 11, 0, 29, 203, 225, 248, 211, 213, 124, 0, 6, 17, 89, 237, 27, 20, 13, 0, 2, 0, 71, 236, 3, 0, 0, 0, 3, 1, 75, 235, 11, 3, 4, 0, 3, 0, 76, 236, 0, 0, 0, 0, 2, 0, 37, 255, 100, 49, 54, 0, 0, 2, 0, 104, 211, 205, 138, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 5, 0, 0, 6, 10, 0, 0, 203, 92, 0, 0, 107, 193, 0, 0, 227, 102, 0, 0, 119, 215, 0, 0, 221, 100, 0, 0, 117, 210, 0, 0, 222, 100, 2, 3, 117, 211, 0, 0, 220, 100, 0, 0, 114, 210, 0, 0, 155, 215, 48, 72, 223, 211, 0, 1, 17, 167, 206, 165, 111, 149, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 3, 0, 2, 3, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 9, 10, 0, 0, 0, 0, 12, 7, 49, 220, 23, 0, 0, 48, 219, 24, 0, 207, 124, 0, 0, 156, 179, 0, 0, 89, 220, 0, 9, 231, 58, 0, 1, 7, 230, 47, 77, 211, 0, 2, 4, 0, 139, 157, 171, 109, 0, 4, 2, 1, 25, 247, 226, 12, 1, 1, 0, 2, 0, 140, 118, 0, 3, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 12, 4, 0, 3, 3, 0, 4, 11, 164, 124, 1, 143, 135, 1, 130, 156, 123, 180, 0, 213, 205, 0, 184, 114, 60, 195, 29, 201, 199, 25, 195, 53, 15, 187, 127, 139, 145, 119, 183, 12, 0, 160, 225, 68, 78, 217, 153, 0, 0, 128, 255, 16, 23, 255, 119, 0, 0, 63, 163, 0, 0, 167, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 4, 18, 0, 2, 2, 0, 17, 5, 10, 195, 134, 0, 0, 131, 200, 12, 0, 45, 255, 55, 54, 255, 51, 0, 3, 0, 95, 220, 222, 101, 0, 3, 1, 5, 0, 229, 236, 0, 4, 1, 3, 0, 142, 200, 194, 150, 0, 3, 0, 97, 255, 25, 18, 252, 105, 0, 17, 183, 79, 0, 0, 70, 186, 21, 2, 0, 0, 1, 1, 0, 0, 2, 0, 1, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 13, 8, 0, 0, 0, 0, 10, 11, 88, 223, 13, 0, 0, 29, 230, 60, 7, 235, 114, 0, 0, 138, 214, 1, 0, 111, 230, 0, 5, 235, 85, 0, 1, 11, 246, 62, 72, 230, 3, 1, 4, 0, 139, 190, 187, 118, 0, 4, 1, 1, 21, 240, 232, 11, 1, 1, 0, 6, 0, 189, 138, 0, 4, 0, 1, 0, 50, 240, 24, 1, 2, 0, 7, 134, 232, 113, 0, 3, 0, 0, 8, 122, 74, 0, 2, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 17, 17, 19, 12, 8, 0, 6, 182, 213, 213, 201, 232, 173, 0, 1, 17, 24, 13, 93, 255, 68, 0, 0, 0, 0, 36, 251, 97, 0, 3, 2, 1, 20, 241, 140, 0, 7, 0, 1, 1, 200, 162, 0, 0, 0, 0, 3, 172, 254, 67, 70, 70, 61, 5, 12, 179, 185, 195, 197, 197, 178, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 42, 28, 0, 0, 0, 3, 0, 101, 211, 51, 1, 0, 1, 1, 13, 240, 60, 0, 3, 0, 2, 0, 44, 242, 29, 2, 2, 0, 2, 3, 40, 244, 31, 0, 2, 0, 3, 0, 94, 238, 9, 1, 1, 0, 4, 173, 241, 60, 0, 2, 0, 0, 3, 96, 201, 123, 0, 2, 0, 0, 2, 0, 58, 252, 16, 0, 1, 0, 2, 3, 45, 242, 32, 0, 2, 0, 2, 0, 39, 244, 28, 1, 2, 0, 1, 1, 5, 226, 102, 0, 4, 0, 0, 3, 0, 60, 208, 60, 1, 0, 0, 0, 1, 0, 13, 17, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 79, 67, 0, 2, 0, 0, 4, 0, 163, 139, 0, 4, 0, 0, 4, 0, 149, 127, 0, 4, 0, 0, 4, 0, 152, 129, 0, 4, 0, 0, 4, 0, 152, 129, 0, 4, 0, 0, 4, 0, 152, 129, 0, 4, 0, 0, 4, 0, 152, 129, 0, 4, 0, 0, 4, 0, 152, 129, 0, 4, 0, 0, 4, 0, 152, 129, 0, 4, 0, 0, 4, 0, 152, 129, 0, 4, 0, 0, 4, 0, 151, 128, 0, 4, 0, 0, 4, 0, 158, 134, 0, 4, 0, 0, 1, 0, 50, 42, 0, 1, 0 + }, + + new byte[] + { + 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 56, 6, 0, 1, 0, 0, 1, 0, 140, 198, 18, 1, 1, 0, 0, 2, 0, 201, 130, 0, 4, 0, 0, 4, 2, 154, 179, 1, 3, 0, 0, 4, 0, 160, 176, 0, 5, 0, 0, 4, 0, 114, 228, 0, 0, 1, 0, 0, 2, 2, 168, 223, 73, 0, 0, 1, 1, 30, 203, 141, 38, 1, 0, 4, 0, 140, 208, 0, 0, 0, 0, 4, 1, 158, 176, 3, 4, 0, 0, 4, 0, 158, 176, 0, 4, 0, 1, 1, 3, 227, 102, 0, 4, 0, 1, 0, 159, 161, 3, 2, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0 + }, + + new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, 0, 0, 1, 1, 2, 0, 0, 0, 3, 0, 0, 0, 0, 53, 99, 19, 0, 1, 3, 17, 95, 224, 183, 227, 89, 0, 132, 151, 158, 74, 0, 72, 217, 205, 213, 44, 0, 0, 3, 0, 6, 63, 16, 0, 0, 1, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + + null + }; + } +} \ No newline at end of file diff --git a/Gui/SmoothMono/TextRenderer.cs b/Gui/SmoothMono/TextRenderer.cs new file mode 100644 index 0000000..7604447 --- /dev/null +++ b/Gui/SmoothMono/TextRenderer.cs @@ -0,0 +1,64 @@ +using System.Drawing; + +namespace CMLeonOS.Gui.SmoothMono +{ + public static class TextRenderer + { + private static uint FastBlend(uint src, uint dst, uint t) + { + uint s = 255 - t; + return ( + (uint)(((((src >> 0) & 0xff) * s + + ((dst >> 0) & 0xff) * t) >> 8) | + (((((src >> 8) & 0xff) * s + + ((dst >> 8) & 0xff) * t)) & ~0xff) | + (((((src >> 16) & 0xff) * s + + ((dst >> 16) & 0xff) * t) << 8) & ~0xffff) | + 0xff000000) + ); + } + + private static void DrawChar(char c, int color, int[] buffer, int bufferWidth, int bufferHeight, int x, int y) + { + byte[] bytes = FontData.Chars[c]; + if (bytes != null) + { + for (int i = 0; i < FontData.Width; i++) + { + int finalX = x + i; + if (finalX < 0 || finalX >= bufferWidth) continue; + + for (int j = 0; j < FontData.Height; j++) + { + int finalY = y + j; + if (finalY < 0 || finalY >= bufferHeight) continue; + + byte invAlpha = (byte)(255 - bytes[(j * FontData.Width) + i]); + if (invAlpha == 255) continue; + + int k = ((finalY * bufferWidth) + finalX); + + buffer[k] = (int)FastBlend((uint)color, (uint)buffer[k], invAlpha); + } + } + } + } + + public static void DrawString(string str, Color color, int[] buffer, int bufferWidth, int bufferHeight, int x, int y) + { + int charX = x; + int charY = y; + for (int i = 0; i < str.Length; i++) + { + if (str[i] == '\n') + { + charX = x; + charY += FontData.Height; + continue; + } + DrawChar(str[i], color.ToArgb(), buffer, bufferWidth, bufferHeight, charX, charY); + charX += FontData.Width; + } + } + } +} diff --git a/Gui/Sound/SoundService.cs b/Gui/Sound/SoundService.cs new file mode 100644 index 0000000..d2c071b --- /dev/null +++ b/Gui/Sound/SoundService.cs @@ -0,0 +1,111 @@ +using Cosmos.HAL; +using Cosmos.HAL.Drivers.Audio; +using Cosmos.System; +using Cosmos.System.Audio; +using Cosmos.System.Audio.IO; +using CMLeonOS; +using CMLeonOS.Logger; +using System; + +namespace CMLeonOS.Gui.Sound +{ + internal class SoundService : Process + { + public SoundService() : base("SoundService", ProcessType.Service) + { + } + + internal AudioMixer Mixer { get; private set; } + internal AudioDriver Driver { get; private set; } + internal AudioManager AudioManager { get; private set; } + + internal bool DriverReady { get; private set; } = false; + + private const int bufferSize = 4096; + + private static class SystemSounds + { + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Sounds.Login.wav")] + private static byte[] _soundBytes_Login; + internal static MemoryAudioStream Sound_Login = MemoryAudioStream.FromWave(_soundBytes_Login); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Sounds.Alert.wav")] + private static byte[] _soundBytes_Alert; + internal static MemoryAudioStream Sound_Alert = MemoryAudioStream.FromWave(_soundBytes_Alert); + } + + internal void PlaySystemSound(SystemSound systemSound) + { + if (!DriverReady) return; + + switch (systemSound) + { + case SystemSound.None: + return; + case SystemSound.Login: + Mixer.Streams.Add(SystemSounds.Sound_Login); + break; + case SystemSound.Alert: + Mixer.Streams.Add(SystemSounds.Sound_Alert); + break; + default: + throw new Exception("Unknown system sound."); + } + } + + #region Process + public override void Start() + { + base.Start(); + Logger.Logger.Instance.Info("SoundService", "Starting SoundService."); + + for (int i = 0; i < Cosmos.HAL.PCI.Count; i++) + { + var device = Cosmos.HAL.PCI.Devices[i]; + + /* AC97 */ + if (device.ClassCode == (byte)ClassID.MultimediaDevice && + device.Subclass == 0x01 + && !VMTools.IsVMWare) + { + Logger.Logger.Instance.Info("SoundService", "Initialising AC97 driver."); + Driver = AC97.Initialize(bufferSize); + } + } + + if (Driver != null) + { + DriverReady = true; + + Mixer = new AudioMixer(); + AudioManager = new AudioManager() + { + Stream = Mixer, + Output = Driver + }; + AudioManager.Enable(); + + Logger.Logger.Instance.Info("SoundService", "SoundService ready."); + } + else + { + Logger.Logger.Instance.Warning("SoundService", "No driver available."); + } + } + + public override void Run() + { + } + + public override void Stop() + { + base.Stop(); + if (DriverReady) + { + Logger.Logger.Instance.Info("SoundService", "Driver disabling."); + AudioManager.Disable(); + } + } + #endregion Process + } +} diff --git a/Gui/Sound/SystemSound.cs b/Gui/Sound/SystemSound.cs new file mode 100644 index 0000000..379d011 --- /dev/null +++ b/Gui/Sound/SystemSound.cs @@ -0,0 +1,9 @@ +namespace CMLeonOS.Gui.Sound +{ + internal enum SystemSound + { + None = 0, + Login = 1, + Alert = 2 + } +} diff --git a/Gui/UILib/Alignment.cs b/Gui/UILib/Alignment.cs new file mode 100644 index 0000000..22ebf42 --- /dev/null +++ b/Gui/UILib/Alignment.cs @@ -0,0 +1,9 @@ +namespace CMLeonOS.Gui.UILib +{ + internal enum Alignment + { + Start, + Middle, + End + } +} diff --git a/Gui/UILib/Animations/Animation.cs b/Gui/UILib/Animations/Animation.cs new file mode 100644 index 0000000..9e586a5 --- /dev/null +++ b/Gui/UILib/Animations/Animation.cs @@ -0,0 +1,84 @@ +using CMLeonOS.Gui; + +namespace CMLeonOS.UILib.Animations +{ + /// + /// A window animation. + /// + internal abstract class Animation + { + /// + /// The easing type of the animation. + /// + internal EasingType EasingType { get; set; } = EasingType.Sine; + + /// + /// The direction of the easing of the animation. + /// + internal EasingDirection EasingDirection { get; set; } = EasingDirection.Out; + + /// + /// The duration of the animation. + /// + internal int Duration { get; set; } = 60; + + /// + /// How many frames of the animation have been completed. + /// + internal int Position { get; set; } = 0; + + /// + /// If the animation has finished. + /// + internal bool Finished + { + get + { + return Position >= Duration; + } + } + + /// + /// The window associated with the animation. + /// + internal Window Window { get; set; } + + /// + /// Advance the animation by one frame. + /// + /// Whether or not the animation is now finished. + internal abstract bool Advance(); + + private int? timerId { get; set; } = null; + + /// + /// Start the animation. + /// + 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)); + } + } + + /// + /// Stop the animation. + /// + internal void Stop() + { + if (timerId != null) + { + Cosmos.HAL.Global.PIT.UnregisterTimer((int)timerId); + timerId = null; + } + } + } +} diff --git a/Gui/UILib/Animations/Easing.cs b/Gui/UILib/Animations/Easing.cs new file mode 100644 index 0000000..cb98de0 --- /dev/null +++ b/Gui/UILib/Animations/Easing.cs @@ -0,0 +1,55 @@ +using System; + +namespace CMLeonOS.UILib.Animations +{ + /// + /// Easing utilities for animations. + /// + internal static class Easing + { + /// + /// Calculate the value of an easing function. + /// + /// The absolute progress of the animation, from 0 to 1. + /// The type of the easing function. + /// The direction of the easing function. + /// The value of the easing function at the given progress. + /// An exception is thrown if the progress is out of range. + /// An exception is thrown if the type or direction is ininvalid. + 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."); + } + } + + /// + /// Linearly interpolate between two values. + /// + /// The first value. + /// The second value. + /// The value of the interpolation. + /// The interpolated value. + internal static double Lerp(double x, double y, double z) + { + return x * (1 - z) + y * z; + } + } +} diff --git a/Gui/UILib/Animations/EasingDirection.cs b/Gui/UILib/Animations/EasingDirection.cs new file mode 100644 index 0000000..b64bf84 --- /dev/null +++ b/Gui/UILib/Animations/EasingDirection.cs @@ -0,0 +1,23 @@ +namespace CMLeonOS.UILib.Animations +{ + /// + /// Defines the direction of an easing type. + /// + internal enum EasingDirection + { + /// + /// Starts the animation slowly, and finishes at full speed. + /// + In, + + /// + /// Starts the animation at full speed, and finishes slowly. + /// + Out, + + /// + /// Starts the animation slowly, reaches full speed at the middle, and finishes slowly. + /// + InOut + } +} diff --git a/Gui/UILib/Animations/EasingType.cs b/Gui/UILib/Animations/EasingType.cs new file mode 100644 index 0000000..117b540 --- /dev/null +++ b/Gui/UILib/Animations/EasingType.cs @@ -0,0 +1,18 @@ +namespace CMLeonOS.UILib.Animations +{ + /// + /// Describes the speed of motion over time in an animation. + /// + internal enum EasingType + { + /// + /// Linear easing. + /// + Linear, + + /// + /// Sinusoidal easing. + /// + Sine + } +} diff --git a/Gui/UILib/Animations/MovementAnimation.cs b/Gui/UILib/Animations/MovementAnimation.cs new file mode 100644 index 0000000..cfa1731 --- /dev/null +++ b/Gui/UILib/Animations/MovementAnimation.cs @@ -0,0 +1,64 @@ +using CMLeonOS.Gui; +using CMLeonOS.Gui.UILib; +using System; +using System.Drawing; + +namespace CMLeonOS.UILib.Animations +{ + /// + /// An animation that moves or resizes a window. + /// + internal class MovementAnimation : Animation + { + /// + /// Initialise the animation. + /// + /// The window associated with the animation. + /// The goal of the animation. + internal MovementAnimation(Window window) + { + Window = window; + From = new Rectangle(window.X, window.Y, window.Width, window.Height); + } + + /// + /// The starting rectangle of the animation. + /// + internal Rectangle From; + + /// + /// The goal rectangle of the animation. + /// + 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; + } + } +} diff --git a/Gui/UILib/AppWindow.cs b/Gui/UILib/AppWindow.cs new file mode 100644 index 0000000..fdeb52d --- /dev/null +++ b/Gui/UILib/AppWindow.cs @@ -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(); + + 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()?.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().RerenderAll(); + } + else + { + maximised = true; + + var taskbar = ProcessManager.GetProcess(); + int taskbarHeight = taskbar.GetTaskbarHeight(); + + var dock = ProcessManager.GetProcess(); + 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().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); + } + } +} diff --git a/Gui/UILib/Button.cs b/Gui/UILib/Button.cs new file mode 100644 index 0000000..85f1ffc --- /dev/null +++ b/Gui/UILib/Button.cs @@ -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); + } + } +} diff --git a/Gui/UILib/Calendar.cs b/Gui/UILib/Calendar.cs new file mode 100644 index 0000000..0924328 --- /dev/null +++ b/Gui/UILib/Calendar.cs @@ -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); + } + } +} diff --git a/Gui/UILib/CheckBox.cs b/Gui/UILib/CheckBox.cs new file mode 100644 index 0000000..626390d --- /dev/null +++ b/Gui/UILib/CheckBox.cs @@ -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 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); + } + } +} \ No newline at end of file diff --git a/Gui/UILib/Control.cs b/Gui/UILib/Control.cs new file mode 100644 index 0000000..0e8ec70 --- /dev/null +++ b/Gui/UILib/Control.cs @@ -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(); + } +} diff --git a/Gui/UILib/Extensions.cs b/Gui/UILib/Extensions.cs new file mode 100644 index 0000000..61306e0 --- /dev/null +++ b/Gui/UILib/Extensions.cs @@ -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; + } + } +} diff --git a/Gui/UILib/ImageBlock.cs b/Gui/UILib/ImageBlock.cs new file mode 100644 index 0000000..6660762 --- /dev/null +++ b/Gui/UILib/ImageBlock.cs @@ -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); + } + } +} diff --git a/Gui/UILib/MessageBox.cs b/Gui/UILib/MessageBox.cs new file mode 100644 index 0000000..cbc415c --- /dev/null +++ b/Gui/UILib/MessageBox.cs @@ -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(); + + 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().PlaySystemSound(Sound.SystemSound.Alert); + } + + internal Process Process { get; private set; } + + internal string Title { get; private set; } + + internal string Message { get; private set; } + } +} diff --git a/Gui/UILib/PromptBox.cs b/Gui/UILib/PromptBox.cs new file mode 100644 index 0000000..f60535d --- /dev/null +++ b/Gui/UILib/PromptBox.cs @@ -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 submitted) : base(process, title, message) + { + Placeholder = placeholder; + Submitted = submitted; + } + + internal void Show() + { + WindowManager wm = ProcessManager.GetProcess(); + + 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().PlaySystemSound(Sound.SystemSound.Alert); + } + + internal Action Submitted { get; private set; } + + internal string Placeholder { get; private set; } + } +} diff --git a/Gui/UILib/RangeSlider.cs b/Gui/UILib/RangeSlider.cs new file mode 100644 index 0000000..4683826 --- /dev/null +++ b/Gui/UILib/RangeSlider.cs @@ -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 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); + } + } +} diff --git a/Gui/UILib/ShortcutBar.cs b/Gui/UILib/ShortcutBar.cs new file mode 100644 index 0000000..76d41dd --- /dev/null +++ b/Gui/UILib/ShortcutBar.cs @@ -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 Cells { get; set; } = new List(); + + 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); + } + } +} diff --git a/Gui/UILib/ShortcutBarCell.cs b/Gui/UILib/ShortcutBarCell.cs new file mode 100644 index 0000000..9c98e8d --- /dev/null +++ b/Gui/UILib/ShortcutBarCell.cs @@ -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; } + } +} diff --git a/Gui/UILib/Switch.cs b/Gui/UILib/Switch.cs new file mode 100644 index 0000000..af0d562 --- /dev/null +++ b/Gui/UILib/Switch.cs @@ -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); + } + } +} diff --git a/Gui/UILib/Table.cs b/Gui/UILib/Table.cs new file mode 100644 index 0000000..67ef549 --- /dev/null +++ b/Gui/UILib/Table.cs @@ -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 Cells { get; set; } = new List(); + + internal Action 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); + } + } +} diff --git a/Gui/UILib/TableCell.cs b/Gui/UILib/TableCell.cs new file mode 100644 index 0000000..8ba2cf1 --- /dev/null +++ b/Gui/UILib/TableCell.cs @@ -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; + } +} diff --git a/Gui/UILib/TextBlock.cs b/Gui/UILib/TextBlock.cs new file mode 100644 index 0000000..fbffa73 --- /dev/null +++ b/Gui/UILib/TextBlock.cs @@ -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); + } + } +} diff --git a/Gui/UILib/TextBox.cs b/Gui/UILib/TextBox.cs new file mode 100644 index 0000000..314db52 --- /dev/null +++ b/Gui/UILib/TextBox.cs @@ -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 lines = new List() { 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); + } + } +} diff --git a/Gui/Window.cs b/Gui/Window.cs new file mode 100644 index 0000000..2d95bc0 --- /dev/null +++ b/Gui/Window.cs @@ -0,0 +1,563 @@ +using Cosmos.Core; +using Cosmos.System; +using Cosmos.System.Graphics; +using CMLeonOS; +using System; +using System.Drawing; + +namespace CMLeonOS.Gui +{ + internal class Window + { + internal Window(Process process, int x, int y, int width, int height) + { + WM = ProcessManager.GetProcess(); + + Process = process; + X = x; + Y = y; + + this.width = width; + this.height = height; + ResizeBuffer(); + } + + internal Window(Process process, Window parent, int x, int y, int width, int height) + { + WM = ProcessManager.GetProcess(); + + Process = process; + _relativeTo = parent; + X = x; + Y = y; + + this.width = width; + this.height = height; + ResizeBuffer(); + } + + internal int[] Buffer { get; private set; } + + private Window _relativeTo = null; + + internal Process Process { get; private set; } + + protected WindowManager WM; + + private int x; + private int y; + + private int width; + private int height; + + private const int bytesPerPixel = 4; + + #region Events + internal Action OnDown; + internal Action OnClick; + internal Action OnDoubleClick; + internal Action OnKeyPressed; + internal Action OnFocused; + internal Action OnUnfocused; + internal Action UserResized; + #endregion + + private void ResizeBuffer() + { + Buffer = new int[Width * Height]; + Cosmos.Core.MemoryOperations.Fill(Buffer, Color.White.ToArgb()); + } + + internal void Move(int x, int y, bool sendWMEvent = true) + { + if (x != X || y != Y) + { + this.x = x; + this.y = y; + if (sendWMEvent) + { + ProcessManager.GetProcess().RerenderAll(); + } + } + } + + internal void Resize(int width, int height, bool sendWMEvent = true) + { + if (width != Width || height != Height) + { + this.width = width; + this.height = height; + ResizeBuffer(); + if (sendWMEvent) + { + ProcessManager.GetProcess().RerenderAll(); + } + } + } + + internal void MoveAndResize(int x, int y, int width, int height, bool sendWMEvent = true) + { + Move(x, y, sendWMEvent: false); + Resize(width, height, sendWMEvent: false); + if (sendWMEvent) + { + ProcessManager.GetProcess().RerenderAll(); + } + } + + #region Graphics + private Color AlphaBlend(Color to, Color from, byte alpha) + { + byte R = (byte)((to.R * alpha + from.R * (255 - alpha)) >> 8); + byte G = (byte)((to.G * alpha + from.G * (255 - alpha)) >> 8); + byte B = (byte)((to.B * alpha + from.B * (255 - alpha)) >> 8); + return Color.FromArgb(R, G, B); + } + + // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs + private void TrimLine(ref int x1, ref int y1, ref int x2, ref int y2) + { + if (x1 == x2) + { + x1 = Math.Min(width - 1, Math.Max(0, x1)); + x2 = x1; + y1 = Math.Min(height - 1, Math.Max(0, y1)); + y2 = Math.Min(height - 1, Math.Max(0, y2)); + return; + } + + float x1_out = x1, y1_out = y1; + float x2_out = x2, y2_out = y2; + + float m = (y2_out - y1_out) / (x2_out - x1_out); + float c = y1_out - m * x1_out; + + if (x1_out < 0) + { + x1_out = 0; + y1_out = c; + } + else if (x1_out >= width) + { + x1_out = width - 1; + y1_out = (width - 1) * m + c; + } + + if (x2_out < 0) + { + x2_out = 0; + y2_out = c; + } + else if (x2_out >= width) + { + x2_out = width - 1; + y2_out = (width - 1) * m + c; + } + + if (y1_out < 0) + { + x1_out = -c / m; + y1_out = 0; + } + else if (y1_out >= height) + { + x1_out = (height - 1 - c) / m; + y1_out = height - 1; + } + + if (y2_out < 0) + { + x2_out = -c / m; + y2_out = 0; + } + else if (y2_out >= width) + { + x2_out = (width - 1 - c) / m; + y2_out = width - 1; + } + + if (x1_out < 0 || x1_out >= width || y1_out < 0 || y1_out >= height) + { + x1_out = 0; x2_out = 0; + y1_out = 0; y2_out = 0; + } + + if (x2_out < 0 || x2_out >= width || y2_out < 0 || y2_out >= height) + { + x1_out = 0; x2_out = 0; + y1_out = 0; y2_out = 0; + } + + x1 = (int)x1_out; y1 = (int)y1_out; + x2 = (int)x2_out; y2 = (int)y2_out; + } + + // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs + internal void DrawHorizontalLine(int dx, int x1, int y1, Color color) + { + int i; + + if (dx > 0) + { + for (i = 0; i < dx; i++) + { + DrawPoint(x1 + i, y1, color); + } + } + else + { + for (i = 0; i > dx; i--) + { + DrawPoint(x1 + i, y1, color); + } + } + } + + // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs + internal void DrawVerticalLine(int dy, int x1, int y1, Color color) + { + int i; + + if (dy > 0) + { + for (i = 0; i < dy; i++) + { + DrawPoint(x1, y1 + i, color); + } + } + else + { + for (i = 0; i > dy; i--) + { + DrawPoint(x1, y1 + i, color); + } + } + } + + // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs + private void DrawDiagonalLine(int dx, int dy, int x1, int y1, Color color) + { + int i, sdx, sdy, dxabs, dyabs, x, y, px, py; + + dxabs = Math.Abs(dx); + dyabs = Math.Abs(dy); + sdx = Math.Sign(dx); + sdy = Math.Sign(dy); + x = dyabs >> 1; + y = dxabs >> 1; + px = x1; + py = y1; + + if (dxabs >= dyabs) + { + for (i = 0; i < dxabs; i++) + { + y += dyabs; + if (y >= dxabs) + { + y -= dxabs; + py += sdy; + } + px += sdx; + DrawPoint(px, py, color); + } + } + else + { + for (i = 0; i < dyabs; i++) + { + x += dxabs; + if (x >= dyabs) + { + x -= dyabs; + px += sdx; + } + py += sdy; + DrawPoint(px, py, color); + } + } + } + + public void Clear(Color color) + { + MemoryOperations.Fill(Buffer, color.ToArgb()); + } + + // To-do: Optimise. + public void DrawFilledRectangle(int x, int y, int width, int height, Color color) + { + int argb = color.ToArgb(); + for (int i = Math.Max(0, x); i < Math.Min(Width, x + width); i++) + { + for (int j = Math.Max(0, y); j < Math.Min(Height, y + height); j++) + { + Buffer[(j * Width) + i] = argb; + } + } + } + + public void DrawRectangle(int x, int y, int width, int height, Color color) + { + DrawHorizontalLine(width, x, y, color); + DrawHorizontalLine(width, x, y + height - 1, color); + DrawVerticalLine(height - 1, x, y + 1, color); + DrawVerticalLine(height - 1, x + width - 1, y + 1, color); + } + + public void DrawPoint(int x, int y, Color color) + { + if (x < 0 || x >= width) return; + if (y < 0 || y >= height) return; + + int index = x + (y * width); + + Buffer[index] = color.ToArgb(); + } + + public Color GetPixel(int x, int y) + { + if (x < 0 || x >= width) return Color.Transparent; + if (y < 0 || y >= height) return Color.Transparent; + + int index = x + (y * width); + + return Color.FromArgb(Buffer[index]); + } + + // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs + public void DrawCircle(int x, int y, int radius, Color color) + { + int i = radius; + int j = 0; + int e = 0; + + while (i >= j) + { + DrawPoint(x + i, y + j, color); + DrawPoint(x + j, y + i, color); + DrawPoint(x - j, y + i, color); + DrawPoint(x - i, y + j, color); + DrawPoint(x - i, y - j, color); + DrawPoint(x - j, y - i, color); + DrawPoint(x + j, y - i, color); + DrawPoint(x + i, y - j, color); + + j++; + if (e <= 0) + { + e += 2 * j + 1; + } + if (e > 0) + { + i--; + e -= 2 * i + 1; + } + } + } + + // https://github.com/CosmosOS/Cosmos/blob/master/source/Cosmos.System2/Graphics/Canvas.cs + public void DrawLine(int x1, int y1, int x2, int y2, Color color) + { + TrimLine(ref x1, ref y1, ref x2, ref y2); + + int dx, dy; + + dx = x2 - x1; + dy = y2 - y1; + + if (dy == 0) + { + DrawHorizontalLine(dx, x1, y1, color); + return; + } + + if (dx == 0) + { + DrawVerticalLine(dy, x1, y1, color); + return; + } + + DrawDiagonalLine(dx, dy, x1, y1, color); + } + + public void DrawString(string str, Color color, int x, int y) + { + //Asc16.DrawAsciiString(str, color, x, y, Buffer, width, height); + SmoothMono.TextRenderer.DrawString(str, color, Buffer, width, height, x, y); + } + + public void DrawImage(Bitmap bitmap, int x, int y) + { + /*if (bitmap.rawData.Length != bitmap.Width * bitmap.Height) + { + throw new Exception("Invalid bitmap."); + } + for (int i = 0; i < y; i++) + { + int destOffset = ((i + y) * width) + x; + int srcOffset = (int)(i * bitmap.Width); + fixed (int* destPtr = &Buffer[destOffset]) + fixed (int* sourcePtr = &bitmap.rawData[srcOffset]) + { + MemoryOperations.Copy(destPtr, sourcePtr, (int)bitmap.Width); + } + }*/ + for (int i = 0; i < bitmap.Height; i++) + { + for (int j = 0; j < bitmap.Width; j++) + { + DrawPoint(x + j, y + i, Color.FromArgb(bitmap.RawData[(i * bitmap.Width) + j])); + } + } + } + + public void DrawImageAlpha(Bitmap bitmap, int x, int y) + { + /*if (bitmap.rawData.Length != bitmap.Width * bitmap.Height) + { + throw new Exception("Invalid bitmap."); + } + for (int i = 0; i < y; i++) + { + int destOffset = ((i + y) * width) + x; + int srcOffset = (int)(i * bitmap.Width); + fixed (int* destPtr = &Buffer[destOffset]) + fixed (int* sourcePtr = &bitmap.rawData[srcOffset]) + { + MemoryOperations.Copy(destPtr, sourcePtr, (int)bitmap.Width); + } + }*/ + for (int i = 0; i < bitmap.Height; i++) + { + for (int j = 0; j < bitmap.Width; j++) + { + Color from = GetPixel(x + j, y + i); + Color to = Color.FromArgb(bitmap.RawData[(i * bitmap.Width) + j]); + DrawPoint(x + j, y + i, AlphaBlend(to, from, to.A)); + } + } + } + #endregion Graphics + + public int X + { + get + { + return x; + } + set + { + if (value != x) + { + x = value; + ProcessManager.GetProcess().RerenderAll(); + } + } + } + + public int Y + { + get + { + return y; + } + set + { + if (value != y) + { + y = value; + ProcessManager.GetProcess().RerenderAll(); + } + } + } + + public int Width + { + get + { + return width; + } + set + { + if (value != width) + { + if (value < width) + { + ProcessManager.GetProcess().RerenderAll(); + } + width = value; + ResizeBuffer(); + } + } + } + public int Height + { + get + { + return height; + } + set + { + if (value != height) + { + if (value < height) + { + ProcessManager.GetProcess().RerenderAll(); + } + height = value; + ResizeBuffer(); + } + } + } + + public int BytesPerPixel + { + get + { + return bytesPerPixel; + } + } + + public int ScreenX + { + get + { + if (_relativeTo != null) + { + return x + _relativeTo.ScreenX; + } + else + { + return x; + } + } + } + + public int ScreenY + { + get + { + if (_relativeTo != null) + { + return y + _relativeTo.ScreenY; + } + else + { + return y; + } + } + } + + public Window RelativeTo + { + get + { + return _relativeTo; + } + set + { + _relativeTo = value; + } + } + } +} diff --git a/Gui/WindowManager.cs b/Gui/WindowManager.cs new file mode 100644 index 0000000..2d156a7 --- /dev/null +++ b/Gui/WindowManager.cs @@ -0,0 +1,428 @@ +using Cosmos.System; +using Cosmos.System.Graphics; +using CMLeonOS; +using CMLeonOS.Gui.ShellComponents; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui +{ + internal class WindowManager : Process + { + internal WindowManager() : base("WindowManager", ProcessType.Service) + { + Critical = true; + } + + private Cosmos.HAL.Drivers.Video.SVGAII.VMWareSVGAII driver; + + internal List Windows = new List(); + + internal Queue UpdateQueue = new Queue(); + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Cursor.bmp")] + private static byte[] cursorBytes; + private static Bitmap cursorBitmap = new Bitmap(cursorBytes); + + /*[IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.WaitCursor.bmp")] + private static byte[] waitCursorBytes; + private static Bitmap waitCursorBitmap = new Bitmap(waitCursorBytes);*/ + + [IL2CPU.API.Attribs.ManifestResourceStream(ResourceName = "CMLeonOS.Gui.Resources.Wallpaper_1280_800.bmp")] + private static byte[] wallpaperBytes; + private static Bitmap wallpaperBitmap = new Bitmap(wallpaperBytes); + + internal uint ScreenWidth { get; private set; } + internal uint ScreenHeight { get; private set; } + + internal Window Focus { get; set; } + + private uint bytesPerPixel { get; set; } + + private bool bufferModified = false; + + private DateTime lastClickDate = DateTime.MinValue; + private TimeSpan doubleClickTimeSpan = TimeSpan.FromSeconds(1); + + private Window fpsCounter; + private int fps; + private int framesThisSecond; + private int lastSecond; + + private bool fpsShown = true; + + private MouseState lastMouseState = MouseState.None; + private uint lastMouseX = 0; + private uint lastMouseY = 0; + + private int sweepCounter = 0; + + private Bitmap wallpaperResized; + + internal int Fps + { + get + { + return fps; + } + } + + internal List AvailableModes { get; } = new List + { + /* SD Resolutions */ + /*new Mode(320, 200, ColorDepth.ColorDepth32), + new Mode(320, 240, ColorDepth.ColorDepth32), + new Mode(640, 480, ColorDepth.ColorDepth32), + new Mode(720, 480, ColorDepth.ColorDepth32),*/ + new Mode(800, 600, ColorDepth.ColorDepth32), + new Mode(1024, 768, ColorDepth.ColorDepth32), + new Mode(1152, 768, ColorDepth.ColorDepth32), + + /* Old HD-Ready Resolutions */ + new Mode(1280, 720, ColorDepth.ColorDepth32), + new Mode(1280, 768, ColorDepth.ColorDepth32), + new Mode(1280, 800, ColorDepth.ColorDepth32), // WXGA + new Mode(1280, 1024, ColorDepth.ColorDepth32), // SXGA + + /* Better HD-Ready Resolutions */ + new Mode(1360, 768, ColorDepth.ColorDepth32), + new Mode(1440, 900, ColorDepth.ColorDepth32), // WXGA+ + new Mode(1400, 1050, ColorDepth.ColorDepth32), // SXGA+ + new Mode(1600, 1200, ColorDepth.ColorDepth32), // UXGA + new Mode(1680, 1050, ColorDepth.ColorDepth32), // WXGA++ + + /* HDTV Resolutions */ + new Mode(1920, 1080, ColorDepth.ColorDepth32), + new Mode(1920, 1200, ColorDepth.ColorDepth32), // WUXGA + + /* 2K Resolutions */ + /*new Mode(2048, 1536, ColorDepth.ColorDepth32), // QXGA + new Mode(2560, 1080, ColorDepth.ColorDepth32), // UW-UXGA + new Mode(2560, 1600, ColorDepth.ColorDepth32), // WQXGA + new Mode(2560, 2048, ColorDepth.ColorDepth32), // QXGA+ + new Mode(3200, 2048, ColorDepth.ColorDepth32), // WQXGA+ + new Mode(3200, 2400, ColorDepth.ColorDepth32), // QUXGA + new Mode(3840, 2400, ColorDepth.ColorDepth32), // WQUXGA*/ + }; + + private void RenderWindow(Window window) + { + bufferModified = true; + + int screenX = window.ScreenX; + int screenY = window.ScreenY; + int height = (int)Math.Min(window.Height, ScreenHeight - screenY); + int width = (int)Math.Min(window.Width, ScreenWidth - screenX); + + uint index = (uint)((screenY * ScreenWidth) + screenX); + int byteOffset = (int)((index * bytesPerPixel) + driver.FrameSize); + + for (int y = 0; y < height; y++) + { + int sourceIndex = y * window.Width; + driver.videoMemory.Copy(aByteOffset: byteOffset, aData: window.Buffer, aIndex: sourceIndex, aCount: width); + byteOffset += (int)(ScreenWidth * bytesPerPixel); + } + } + + private void UpdateAbove(Window window) + { + Rectangle aRect = new Rectangle(window.ScreenX, window.ScreenY, window.Width, window.Height); + int aboveIndex = Windows.IndexOf(window) + 1; + for (int i = aboveIndex; i < Windows.Count; i++) + { + Window bWindow = Windows[i]; + Rectangle bRect = new Rectangle(bWindow.ScreenX, bWindow.ScreenY, bWindow.Width, bWindow.Height); + if (aRect.IntersectsWith(bRect)) + { + RenderWindow(bWindow); + aRect = Rectangle.Union(aRect, bRect); + } + } + } + + internal void Update(Window window) + { + if (!Windows.Contains(window)) return; + RenderWindow(window); + UpdateAbove(window); + } + + internal void RerenderAll() + { + RenderWallpaper(); + + foreach (Window window in Windows) + { + RenderWindow(window); + } + } + + internal void AddWindow(Window window) + { + if (Windows.Contains(window)) return; + + Windows.Add(window); + + UpdateDock(); + } + + internal void RemoveWindow(Window window, bool rerender = true) + { + Windows.Remove(window); + for (int i = Windows.Count - 1; i >= 0; i--) + { + if (i >= Windows.Count) continue; + if (Windows[i].RelativeTo == window) + { + RemoveWindow(Windows[i], rerender: false); + } + } + if (rerender) + { + RerenderAll(); + } + + UpdateDock(); + } + + internal void ClearAllWindows() + { + for (int i = Windows.Count - 1; i >= 0; i--) + { + if (i >= Windows.Count) continue; + if (Windows[i].Process != this) + { + if (Windows[i] is UILib.AppWindow appWindow) + { + appWindow.Closing?.Invoke(); + } + RemoveWindow(Windows[i], rerender: false); + } + } + RerenderAll(); + } + + private void UpdateDock() + { + var dock = ShellComponents.Dock.Dock.CurrentDock; + + if (dock != null) + { + dock.UpdateWindows(); + } + } + + private void SetupDriver() + { + driver = new Cosmos.HAL.Drivers.Video.SVGAII.VMWareSVGAII(); + driver.SetMode(ScreenWidth, ScreenHeight, depth: bytesPerPixel * 8); + } + + private void SetupMouse() + { + MouseManager.ScreenWidth = ScreenWidth; + MouseManager.ScreenHeight = ScreenHeight; + + MouseManager.X = ScreenWidth / 2; + MouseManager.Y = ScreenHeight / 2; + + driver.DefineAlphaCursor(cursorBitmap.Width, cursorBitmap.Height, cursorBitmap.RawData); + } + + private Window GetWindowAtPos(uint x, uint y) + { + for (int i = Windows.Count - 1; i >= 0; i--) + { + Window window = Windows[i]; + if (x >= window.ScreenX + && y >= window.ScreenY + && x < window.ScreenX + window.Width + && y < window.ScreenY + window.Height) + { + return window; + } + } + return null; + } + + private void DispatchEvents() + { + Window window = GetWindowAtPos(MouseManager.X, MouseManager.Y); + + if (window != null) + { + int relativeX = (int)(MouseManager.X - window.ScreenX); + int relativeY = (int)(MouseManager.Y - window.ScreenY); + + if (MouseManager.MouseState == MouseState.Left && lastMouseState == MouseState.None) + { + lastMouseState = MouseManager.MouseState; + window.OnDown?.Invoke(relativeX, relativeY); + } + else if (MouseManager.MouseState == MouseState.None && lastMouseState == MouseState.Left) + { + lastMouseState = MouseManager.MouseState; + + if (Focus != null && Focus != window) + { + Focus.OnUnfocused?.Invoke(); + } + Focus = window; + window.OnFocused?.Invoke(); + + window.OnClick?.Invoke(relativeX, relativeY); + + if (DateTime.Now - lastClickDate < doubleClickTimeSpan) + { + window.OnDoubleClick?.Invoke(relativeX, relativeY); + } + lastClickDate = DateTime.Now; + } + } + + if (KeyboardManager.TryReadKey(out KeyEvent key)) + { + // To-do: Move this out of WindowManager. + if (key.Key == ConsoleKeyEx.LWin || key.Key == ConsoleKeyEx.RWin) + { + StartMenu.CurrentStartMenu?.ToggleStartMenu(focusSearch: true); + return; + } + Focus?.OnKeyPressed?.Invoke(key); + } + } + + private void UpdateFps() + { + framesThisSecond++; + int second = DateTime.Now.Second; + if (second != lastSecond) + { + fps = framesThisSecond; + framesThisSecond = 0; + + if (fpsShown) + { + fpsCounter.Clear(Color.Black); + fpsCounter.DrawString($"{fps.ToString()} FPS", Color.White, 0, 0); + Update(fpsCounter); + } + } + lastSecond = second; + } + + internal void HideFps() + { + fpsShown = false; + RemoveWindow(fpsCounter); + } + + internal void ShowFps() + { + fpsShown = true; + AddWindow(fpsCounter); + Update(fpsCounter); + } + + private void RenderWallpaper() + { + driver.videoMemory.Copy((int)driver.FrameSize, wallpaperResized.RawData, 0, wallpaperResized.RawData.Length); + } + + private void SetupWallpaper() + { + wallpaperResized = wallpaperBitmap.Resize(ScreenWidth, ScreenHeight); + } + + private void UpdateCursor() + { + uint mouseX = MouseManager.X; + uint mouseY = MouseManager.Y; + if (mouseX != lastMouseX || mouseY != lastMouseY) + { + driver.SetCursor(true, mouseX, mouseY); + } + lastMouseX = mouseX; + lastMouseY = mouseY; + } + + private void Sweep() + { + if (sweepCounter == 10) + { + sweepCounter = 0; + foreach (Window window in Windows) + { + if (window.Process != null && !window.Process.IsRunning) + { + RemoveWindow(window); + } + } + } + sweepCounter++; + } + + #region Process + public override void Start() + { + base.Start(); + + SettingsService settingsService = ProcessManager.GetProcess(); + + ScreenWidth = (uint)settingsService.Mode.Width; + ScreenHeight = (uint)settingsService.Mode.Height; + bytesPerPixel = 4; + + SetupDriver(); + SetupMouse(); + SetupWallpaper(); + RenderWallpaper(); + + fpsCounter = new Window(this, (int)(ScreenWidth) - 64, (int)(ScreenHeight - 16), 64, 16); + if (settingsService.ShowFps) + { + AddWindow(fpsCounter); + } + } + + public override void Run() + { + UpdateFps(); + + Sweep(); + + if (UpdateQueue.Count > 0) + { + Window toUpdate = UpdateQueue.Dequeue(); + + if (Windows.Contains(toUpdate)) + { + if (toUpdate is UILib.Control control) + { + control.Render(); + } + RenderWindow(toUpdate); + } + } + + DispatchEvents(); + + if (bufferModified) + { + driver.DoubleBufferUpdate(); + } + + UpdateCursor(); + } + + public override void Stop() + { + base.Stop(); + + driver.Disable(); + } + #endregion + } +} diff --git a/Kernel.cs b/Kernel.cs index 03c2bcc..1a6803b 100644 --- a/Kernel.cs +++ b/Kernel.cs @@ -1,5 +1,7 @@ using CMLeonOS.Logger; using CMLeonOS.Settings; +using CMLeonOS.Gui; +using CMLeonOS.Utils; using Cosmos.HAL; using Cosmos.HAL.BlockDevice; using Cosmos.HAL.Drivers.Video; @@ -39,6 +41,16 @@ namespace CMLeonOS public static bool FixMode = false; public static DateTime SystemStartTime; + public static User CurrentUser + { + get { return UserSystem.CurrentLoggedInUser; } + } + + public static string Version + { + get { return CMLeonOS.Version.GetVersion(); } + } + public static Cosmos.HAL.NetworkDevice NetworkDevice = null; public static string IPAddress = "Unknown"; @@ -58,36 +70,8 @@ namespace CMLeonOS Console.ResetColor(); } - protected override void BeforeRun() + private void InitializeSystem() { - BootMenuAction bootAction = BootMenu.Show(); - - switch (bootAction) - { - case BootMenuAction.Reboot: - Sys.Power.Reboot(); - break; - case BootMenuAction.Shutdown: - Sys.Power.Shutdown(); - break; - case BootMenuAction.NormalBoot: - default: - break; - } - - Console.Clear(); - // Console.WriteLine("Kernel load done!"); - // Console.WriteLine(@"----------------------------------------------------------"); - Console.WriteLine(@" ____ __ __ _ ___ ____ "); - Console.WriteLine(@" / ___| \/ | | ___ ___ _ __ / _ \/ ___| "); - Console.WriteLine(@" | | | |\/| | | / _ \/ _ \| '_ \| | | \___ \ "); - Console.WriteLine(@" | |___| | | | |__| __/ (_) | | | | |_| |___) |"); - Console.WriteLine(@" \____|_| |_|_____\___|\___/|_| |_|____/|____/ "); - Console.WriteLine(); - Console.WriteLine("The CMLeonOS Project"); - Console.WriteLine("(C) LeonOS 2 Developer Team 2025-2026. All rights reserved."); - Console.WriteLine(@"----------------------------------------------------------"); - // 注册VFS _logger.Info("Kernel", "Starting VFS initialization"); try @@ -125,6 +109,7 @@ namespace CMLeonOS // 初始化用户系统 _logger.Info("Kernel", "Initializing user system"); + UserSystem.Initialize(); userSystem = new UserSystem(); _logger.Success("Kernel", "User system initialized"); @@ -133,18 +118,18 @@ namespace CMLeonOS { try { - Version.GitCommit = System.Text.Encoding.UTF8.GetString(gitCommitFile); - _logger.Info("Kernel", $"Git Commit: {Version.GitCommit}"); + CMLeonOS.Version.GitCommit = System.Text.Encoding.UTF8.GetString(gitCommitFile); + _logger.Info("Kernel", $"Git Commit: {CMLeonOS.Version.GitCommit}"); } catch { - Version.GitCommit = "unknown"; + CMLeonOS.Version.GitCommit = "unknown"; _logger.Warning("Kernel", "Failed to read Git Commit, using 'unknown'"); } } else { - Version.GitCommit = "unknown"; + CMLeonOS.Version.GitCommit = "unknown"; _logger.Warning("Kernel", "Git Commit file not found, using 'unknown'"); } @@ -218,84 +203,13 @@ namespace CMLeonOS // 输出系统启动-初始化完成后的时间 TimeSpan uptime = DateTime.Now - Kernel.SystemStartTime; - // Console.WriteLine("System started: " + Kernel.SystemStartTime.ToString("yyyy-MM-dd HH:mm:ss")); - // Console.WriteLine("Current time: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - // Console.WriteLine(); - // 格式化运行时间 int days = uptime.Days; int hours = uptime.Hours; int minutes = uptime.Minutes; int seconds = uptime.Seconds; - // Console.WriteLine($"System uptime: {days} days, {hours} hours, {minutes} minutes, {seconds} seconds"); _logger.Info("Kernel", $"System initialization completed in {days} days, {hours} hours, {minutes} minutes, {seconds} seconds"); - // Console.WriteLine($"Total uptime: {uptime.TotalHours:F2} hours"); - - // 循环直到登录成功或退出 - while (true) - { - // 第一次启动,设置管理员账户 - if (!userSystem.HasUsers) - { - _logger.Info("Kernel", "First time setup - creating admin account"); - userSystem.FirstTimeSetup(); - _logger.Success("Kernel", "Admin account created successfully"); - } - // 后续启动,需要登录 - else - { - // 循环直到登录成功 - while (!userSystem.Login()) - { - // 登录失败,继续尝试 - } - - _logger.Info("Kernel", $"User '{userSystem.CurrentUsername}' logged in successfully"); - - // 检查并执行启动脚本 - ExecuteStartupScript(); - - ExecuteStartupTest(); - - if (System.IO.File.Exists("0:\\system\\zen")) - { - Console.WriteLine("====================================="); - Console.WriteLine(" The Zen of CMLeonOS "); - Console.WriteLine("(For the dreamer at 0x100000)"); - Console.WriteLine("====================================="); - Console.WriteLine(); - Console.WriteLine("Memory has bounds, but thought breaks all frame,"); - Console.WriteLine("Bare metal no layers, code bears its name."); - Console.WriteLine("A boot's brief spark, all systems ignite,"); - Console.WriteLine("Errors in registers, roots in code's flight."); - Console.WriteLine(); - Console.WriteLine("Simplicity beats the redundant's vain race,"); - Console.WriteLine("Stability outshines the radical's chase."); - Console.WriteLine("Hardware ne'er lies, code holds the wise key,"); - Console.WriteLine("Interrupts not chaos, scheduling sets free."); - Console.WriteLine(); - Console.WriteLine("Binary's cold shell, no breath, no soul,"); - Console.WriteLine("Kernel's warm core, makes the machine whole."); - Console.WriteLine("From zero to one, the boot path we tread,"); - Console.WriteLine("From one to forever, guard every thread."); - Console.WriteLine(); - Console.WriteLine("Build the kernel in zen, step by step, line by line,"); - Console.WriteLine("A bug brings new wake, a line brings new shine."); - } - - // 运行Shell进程(用户可以输入exit退出) - _logger.Info("Kernel", "Starting Shell process"); - var shellProcess = new ShellProcess(userSystem); - ProcessManager.AddProcess(shellProcess); - shellProcess.TryStart(); - shellProcess.TryRun(); - shellProcess.TryStop(); - _logger.Info("Kernel", "Shell process exited"); - - // 如果用户输入了exit,Shell.Run()会返回,继续循环 - } - } } catch (Exception ex) { @@ -398,15 +312,112 @@ namespace CMLeonOS Console.ReadKey(); Sys.Power.Reboot(); } - // try { - // Disk targetDisk = fs.Disks[0]; - // CreateMBRandPartitionTable(targetDisk); - // } - // catch (Exception exe) - // { - // Console.WriteLine($"Error creating MBR and partition table: {exe.Message}"); - // } - // Console.WriteLine("Done."); + } + } + + protected override void BeforeRun() + { + // 在显示 Boot Menu 之前初始化系统(VFS、用户系统等) + InitializeSystem(); + + BootMenuAction bootAction = BootMenu.Show(); + + switch (bootAction) + { + case BootMenuAction.Reboot: + Sys.Power.Reboot(); + break; + case BootMenuAction.Shutdown: + Sys.Power.Shutdown(); + break; + case BootMenuAction.GuiBoot: + Console.Clear(); + Console.WriteLine("Starting GUI..."); + Console.WriteLine(); + Gui.Gui.StartGui(); + // GUI 启动后会阻塞在这里,直到 GUI 退出 + return; + case BootMenuAction.NormalBoot: + default: + break; + } + + Console.Clear(); + // Console.WriteLine("Kernel load done!"); + // Console.WriteLine(@"----------------------------------------------------------"); + Console.WriteLine(@" ____ __ __ _ ___ ____ "); + Console.WriteLine(@" / ___| \/ | | ___ ___ _ __ / _ \/ ___| "); + Console.WriteLine(@" | | | |\/| | | / _ \/ _ \| '_ \| | | \___ \ "); + Console.WriteLine(@" | |___| | | | |__| __/ (_) | | | | |_| |___) |"); + Console.WriteLine(@" \____|_| |_|_____\___|\___/|_| |_|____/|____/ "); + Console.WriteLine(); + Console.WriteLine("The CMLeonOS Project"); + Console.WriteLine("(C) LeonOS 2 Developer Team 2025-2026. All rights reserved."); + Console.WriteLine(@"----------------------------------------------------------"); + + // 循环直到登录成功或退出 + while (true) + { + // 第一次启动,设置管理员账户 + if (!userSystem.HasUsers) + { + _logger.Info("Kernel", "First time setup - creating admin account"); + userSystem.FirstTimeSetup(); + _logger.Success("Kernel", "Admin account created successfully"); + } + // 后续启动,需要登录 + else + { + // 循环直到登录成功 + while (!userSystem.Login()) + { + // 登录失败,继续尝试 + } + + _logger.Info("Kernel", $"User '{userSystem.CurrentUsername}' logged in successfully"); + + // 检查并执行启动脚本 + ExecuteStartupScript(); + + ExecuteStartupTest(); + + if (System.IO.File.Exists("0:\\system\\zen")) + { + Console.WriteLine("====================================="); + Console.WriteLine(" The Zen of CMLeonOS "); + Console.WriteLine("(For the dreamer at 0x100000)"); + Console.WriteLine("====================================="); + Console.WriteLine(); + Console.WriteLine("Memory has bounds, but thought breaks all frame,"); + Console.WriteLine("Bare metal no layers, code bears its name."); + Console.WriteLine("A boot's brief spark, all systems ignite,"); + Console.WriteLine("Errors in registers, roots in code's flight."); + Console.WriteLine(); + Console.WriteLine("Simplicity beats the redundant's vain race,"); + Console.WriteLine("Stability outshines the radical's chase."); + Console.WriteLine("Hardware ne'er lies, code holds the wise key,"); + Console.WriteLine("Interrupts not chaos, scheduling sets free."); + Console.WriteLine(); + Console.WriteLine("Binary's cold shell, no breath, no soul,"); + Console.WriteLine("Kernel's warm core, makes the machine whole."); + Console.WriteLine("From zero to one, the boot path we tread,"); + Console.WriteLine("From one to forever, guard every thread."); + Console.WriteLine(); + Console.WriteLine("Build the kernel in zen, step by step, line by line,"); + Console.WriteLine("A bug brings new wake, a line brings new shine."); + } + + // 运行Shell进程(用户可以输入exit退出) + _logger.Info("Kernel", "Starting Shell process"); + var shellProcess = new ShellProcess(userSystem); + ProcessManager.AddProcess(shellProcess); + shellProcess.TryStart(); + shellProcess.TryRun(); + shellProcess.TryStop(); + _logger.Info("Kernel", "Shell process exited"); + + // 如果用户输入了exit,Shell.Run()会返回,继续循环 + } } } @@ -485,7 +496,7 @@ namespace CMLeonOS private void ExecuteStartupTest() { Console.WriteLine("------------------------------------------------------"); - Console.WriteLine($"Welcome to {Version.DisplayVersion}"); + Console.WriteLine($"Welcome to {CMLeonOS.Version.DisplayVersion}"); Console.WriteLine("* Documentation: https://cmleonos.jjmm.ink/"); Console.WriteLine("* Forum: https://lbbs.ecuil.com/#/category/10"); // Console.WriteLine(""); diff --git a/Logger/Log.cs b/Logger/Log.cs new file mode 100644 index 0000000..0b458cd --- /dev/null +++ b/Logger/Log.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace CMLeonOS.Logger +{ + public static class Log + { + private static List logs = new List(); + private static List> logEmittedReceivers = new List>(); + + public static List Logs + { + get { return logs; } + } + + public static List> LogEmittedReceivers + { + get { return logEmittedReceivers; } + } + + public static void AddReceiver(Action receiver) + { + logEmittedReceivers.Add(receiver); + } + + public static void Emit(LogLevel level, string source, string message) + { + var entry = new LogEntry(level, source, message); + logs.Add(entry); + + foreach (var receiver in logEmittedReceivers) + { + receiver(entry); + } + } + } +} diff --git a/Logger/LogEntry.cs b/Logger/LogEntry.cs index 362a7e3..e10b1bc 100644 --- a/Logger/LogEntry.cs +++ b/Logger/LogEntry.cs @@ -8,6 +8,16 @@ namespace CMLeonOS.Logger public LogLevel Level; public string Source; public string Message; + + public LogLevel Priority + { + get { return Level; } + } + + public DateTime Date + { + get { return Timestamp; } + } public LogEntry(LogLevel level, string source, string message) { diff --git a/Power.cs b/Power.cs new file mode 100644 index 0000000..c8177e0 --- /dev/null +++ b/Power.cs @@ -0,0 +1,24 @@ +using Sys = Cosmos.System; + +namespace CMLeonOS +{ + public static class Power + { + public static void Reboot() + { + Sys.Power.Reboot(); + } + + public static void Shutdown(bool reboot = false) + { + if (reboot) + { + Sys.Power.Reboot(); + } + else + { + Sys.Power.Shutdown(); + } + } + } +} diff --git a/System/Process.cs b/System/Process.cs index 1f6e2eb..0be73db 100644 --- a/System/Process.cs +++ b/System/Process.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Sys = Cosmos.System; diff --git a/System/UserSystem.cs b/System/UserSystem.cs index beba310..876e199 100644 --- a/System/UserSystem.cs +++ b/System/UserSystem.cs @@ -16,23 +16,63 @@ namespace CMLeonOS public string Password { get; set; } public bool IsAdmin { get; set; } public string Hostname { get; set; } + public List Messages { get; set; } + public bool Admin + { + get { return IsAdmin; } + } + public bool LockedOut { get; set; } + public DateTime LockoutEnd { get; set; } + + public bool Authenticate(string password) + { + string hashedInputPassword = UserSystem.HashPasswordSha256(password); + return Password == hashedInputPassword; + } } public class UserSystem { private string sysDirectory = @"0:\system"; private string userFilePath; - private List users; + + private static List users; public bool fixmode = Kernel.FixMode; - private User currentLoggedInUser; + private static User currentLoggedInUser; private static CMLeonOS.Logger.Logger _logger = CMLeonOS.Logger.Logger.Instance; private Dictionary loginAttempts = new Dictionary(); + + private static bool initialized = false; - public User CurrentLoggedInUser + public static List GetUsers() + { + return users; + } + + public static User CurrentLoggedInUser { get { return currentLoggedInUser; } } + public static void Initialize() + { + // 清空用户列表并重新加载 + users = null; + var tempUserSystem = new UserSystem(); + tempUserSystem.LoadUsers(); + initialized = true; + _logger.Info("UserSystem", $"User system initialized. Loaded {users?.Count ?? 0} users."); + } + + public static void ReloadUsers() + { + // 清空用户列表并重新加载 + users = null; + var tempUserSystem = new UserSystem(); + tempUserSystem.LoadUsers(); + _logger.Info("UserSystem", $"User data reloaded. Total users: {users?.Count ?? 0}"); + } + public void ShowError(string error) { Console.ForegroundColor = ConsoleColor.Red; @@ -78,7 +118,8 @@ namespace CMLeonOS currentLoggedInUser = null; - LoadUsers(); + // 不在构造函数中调用 LoadUsers(),避免重复初始化 + // LoadUsers() 会在 UserSystem.Initialize() 中调用 LoadHostname(); } @@ -181,13 +222,28 @@ namespace CMLeonOS private void LoadUsers() { + _logger.Info("UserSystem", "LoadUsers() called"); + _logger.Info("UserSystem", $" - userFilePath: {userFilePath}"); + _logger.Info("UserSystem", $" - File.Exists: {File.Exists(userFilePath)}"); + _logger.Info("UserSystem", $" - users is null: {users == null}"); + _logger.Info("UserSystem", $" - users.Count: {users?.Count ?? 0}"); + try { if (File.Exists(userFilePath)) { + _logger.Info("UserSystem", " - Reading user file..."); string[] lines = File.ReadAllLines(userFilePath); - users = new List(); + _logger.Info("UserSystem", $" - Total lines in file: {lines.Length}"); + // 只在第一次加载时创建列表,后续加载不清空 + if (users == null) + { + users = new List(); + _logger.Info("UserSystem", " - Created new users list"); + } + + int userCount = 0; foreach (string line in lines) { if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) @@ -206,20 +262,38 @@ namespace CMLeonOS Hostname = parts.Length >= 4 ? parts[3].Trim() : "" }; users.Add(user); + userCount++; + _logger.Info("UserSystem", $" - Loaded user: {user.Username}, Admin: {user.Admin}"); } } + _logger.Info("UserSystem", $" - Total users loaded: {userCount}"); + // Note: Passwords are stored as SHA256 hashes in the file // When comparing passwords during login, hash the input password first } else { - users = new List(); + // 只在第一次加载时创建列表 + if (users == null) + { + users = new List(); + _logger.Info("UserSystem", " - Created empty users list (file not found)"); + } + else + { + _logger.Info("UserSystem", $" - Using existing users list (count: {users.Count})"); + } } } - catch + catch (Exception ex) { - users = new List(); + _logger.Error("UserSystem", $"Error loading users: {ex.Message}"); + // 只在第一次加载时创建列表 + if (users == null) + { + users = new List(); + } } } @@ -321,6 +395,11 @@ namespace CMLeonOS } } + public static void SetCurrentLoggedInUser(User user) + { + currentLoggedInUser = user; + } + public void FirstTimeSetup() { CMLeonOS.UI.TUIHelper.SetColors(global::System.ConsoleColor.White, global::System.ConsoleColor.Black); diff --git a/UserManager.cs b/UserManager.cs new file mode 100644 index 0000000..9443873 --- /dev/null +++ b/UserManager.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; + +namespace CMLeonOS +{ + public static class UserManager + { + public static List Users + { + get + { + return UserSystem.GetUsers(); + } + } + + public static User GetUser(string username) + { + foreach (User user in UserSystem.GetUsers()) + { + if (user.Username == username) + { + return user; + } + } + return null; + } + + public static bool AddUser(User user) + { + return Kernel.userSystem.AddUser($"{user.Username} {user.Password}", user.IsAdmin); + } + + public static bool AddUser(string username, string password, bool isAdmin) + { + return Kernel.userSystem.AddUser($"{username} {password}", isAdmin); + } + + public static bool RemoveUser(string username) + { + return Kernel.userSystem.DeleteUser(username); + } + + public static bool ChangePassword(string username, string oldPassword, string newPassword) + { + return Kernel.userSystem.ChangePassword(); + } + + public static bool ChangePassword() + { + return Kernel.userSystem.ChangePassword(); + } + + public static bool Authenticate(string username, string password) + { + User user = GetUser(username); + if (user == null) + { + return false; + } + return user.Authenticate(password); + } + + public static void ListUsers() + { + Kernel.userSystem.ListUsers(); + } + + public static bool HasUsers() + { + return Kernel.userSystem.HasUsers; + } + + public static bool IsAdminSet() + { + return Kernel.userSystem.IsAdminSet; + } + + public static string CurrentUsername() + { + return Kernel.userSystem.CurrentUsername; + } + + public static bool CurrentUserIsAdmin() + { + return Kernel.userSystem.CurrentUserIsAdmin; + } + + public static User CurrentLoggedInUser() + { + return UserSystem.CurrentLoggedInUser; + } + } +} diff --git a/shell/Commands/System/SettingsCommand.cs b/shell/Commands/System/SettingsCommand.cs index 31d8a86..6fddfef 100644 --- a/shell/Commands/System/SettingsCommand.cs +++ b/shell/Commands/System/SettingsCommand.cs @@ -20,7 +20,7 @@ namespace CMLeonOS.Commands return; } - if (userSystem == null || userSystem.CurrentLoggedInUser == null || !userSystem.CurrentLoggedInUser.IsAdmin) + if (userSystem == null || UserSystem.CurrentLoggedInUser == null || !UserSystem.CurrentLoggedInUser.IsAdmin) { Console.WriteLine("Error: Only administrators can change settings."); return; diff --git a/shell/Commands/User/UserCommand.cs b/shell/Commands/User/UserCommand.cs index 627456b..8539e0e 100644 --- a/shell/Commands/User/UserCommand.cs +++ b/shell/Commands/User/UserCommand.cs @@ -28,7 +28,7 @@ namespace CMLeonOS.Commands.User public static void ProcessUserCommand(string args, CMLeonOS.UserSystem userSystem, Action showError) { - if (userSystem == null || userSystem.CurrentLoggedInUser == null || !userSystem.CurrentLoggedInUser.IsAdmin) + if (userSystem == null || UserSystem.CurrentLoggedInUser == null || !UserSystem.CurrentLoggedInUser.IsAdmin) { showError("Error: Only administrators can use this command."); return; diff --git a/shell/Shell.cs b/shell/Shell.cs index f89cc4b..dffa4ad 100644 --- a/shell/Shell.cs +++ b/shell/Shell.cs @@ -61,7 +61,7 @@ namespace CMLeonOS Commands.SettingsCommand.SetUserSystem(userSystem); Commands.User.UserCommand.SetUserSystem(userSystem); - User currentUser = userSystem.CurrentLoggedInUser; + User currentUser = UserSystem.CurrentLoggedInUser; if (currentUser != null && !string.IsNullOrWhiteSpace(currentUser.Username)) { string userHomePath = $@"0:\user\{currentUser.Username}"; @@ -86,7 +86,7 @@ namespace CMLeonOS public void Run() { - User currentUser = userSystem.CurrentLoggedInUser; + User currentUser = UserSystem.CurrentLoggedInUser; bool shouldExit = false; while (true) diff --git a/utils/FileSecurity.cs b/utils/FileSecurity.cs new file mode 100644 index 0000000..1ae762a --- /dev/null +++ b/utils/FileSecurity.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; +using System.Text; + +namespace CMLeonOS.Utils +{ + public static class FileSecurity + { + public static bool IsSecure(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return false; + } + + string fileName = Path.GetFileName(path); + + string[] insecureExtensions = { ".exe", ".bat", ".cmd", ".sh", ".ps1" }; + string extension = Path.GetExtension(fileName).ToLower(); + + foreach (string insecureExt in insecureExtensions) + { + if (extension == insecureExt) + { + return false; + } + } + + string[] insecureNames = { "autoexec", "config", "system" }; + string nameWithoutExt = Path.GetFileNameWithoutExtension(fileName).ToLower(); + + foreach (string insecureName in insecureNames) + { + if (nameWithoutExt.Contains(insecureName)) + { + return false; + } + } + + return true; + } + + public static string GetSecurityLevel(string path) + { + if (!IsSecure(path)) + { + return "Insecure"; + } + return "Secure"; + } + + public static bool CanExecute(string path) + { + if (!File.Exists(path)) + { + return false; + } + + string extension = Path.GetExtension(path).ToLower(); + string[] executableExtensions = { ".exe", ".bat", ".cmd", ".sh", ".ps1" }; + + foreach (string execExt in executableExtensions) + { + if (extension == execExt) + { + return true; + } + } + + return false; + } + + public static bool CanRead(string path) + { + return File.Exists(path) || Directory.Exists(path); + } + + public static bool CanAccess(string path) + { + return CanRead(path) && CanWrite(path); + } + + public static bool CanWrite(string path) + { + try + { + string dir = Path.GetDirectoryName(path); + if (!Directory.Exists(dir)) + { + return false; + } + + string testFile = Path.Combine(dir, ".write_test"); + File.WriteAllText(testFile, "test"); + File.Delete(testFile); + return true; + } + catch + { + return false; + } + } + } +} diff --git a/utils/FloatExtensions.cs b/utils/FloatExtensions.cs new file mode 100644 index 0000000..2062192 --- /dev/null +++ b/utils/FloatExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace CMLeonOS.Utils +{ + public static class FloatExtensions + { + public static float Map(this float value, float inMin, float inMax, float outMin, float outMax) + { + return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; + } + } +} diff --git a/utils/IniBuilder.cs b/utils/IniBuilder.cs new file mode 100644 index 0000000..30f35ef --- /dev/null +++ b/utils/IniBuilder.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace CMLeonOS.Utils +{ + public class IniBuilder + { + private Dictionary> data = new Dictionary>(); + private string currentSection = ""; + + public IniBuilder() + { + } + + public IniBuilder BeginSection(string section) + { + currentSection = section; + if (!data.ContainsKey(section)) + { + data[section] = new Dictionary(); + } + return this; + } + + public IniBuilder SetSection(string section) + { + currentSection = section; + if (!data.ContainsKey(section)) + { + data[section] = new Dictionary(); + } + return this; + } + + public IniBuilder SetValue(string key, string value) + { + if (string.IsNullOrWhiteSpace(currentSection)) + { + currentSection = "General"; + if (!data.ContainsKey(currentSection)) + { + data[currentSection] = new Dictionary(); + } + } + + data[currentSection][key] = value; + return this; + } + + public IniBuilder SetValue(string section, string key, string value) + { + if (!data.ContainsKey(section)) + { + data[section] = new Dictionary(); + } + + data[section][key] = value; + return this; + } + + public IniBuilder AddKey(string key, object value) + { + if (string.IsNullOrWhiteSpace(currentSection)) + { + currentSection = "General"; + if (!data.ContainsKey(currentSection)) + { + data[currentSection] = new Dictionary(); + } + } + + data[currentSection][key] = value.ToString(); + return this; + } + + public IniBuilder AddKey(string section, string key, object value) + { + if (!data.ContainsKey(section)) + { + data[section] = new Dictionary(); + } + + data[section][key] = value.ToString(); + return this; + } + + public string Build() + { + var lines = new List(); + + foreach (var section in data.Keys) + { + lines.Add($"[{section}]"); + + foreach (var kvp in data[section]) + { + lines.Add($"{kvp.Key}={kvp.Value}"); + } + + lines.Add(""); + } + + return string.Join("\n", lines); + } + + public override string ToString() + { + return Build(); + } + + public void Save(string filePath) + { + string content = Build(); + File.WriteAllText(filePath, content); + } + } +} diff --git a/utils/IniReader.cs b/utils/IniReader.cs new file mode 100644 index 0000000..8a3179f --- /dev/null +++ b/utils/IniReader.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace CMLeonOS.Utils +{ + public class IniReader + { + private Dictionary> data = new Dictionary>(); + + public IniReader(string filePath) + { + if (File.Exists(filePath)) + { + string[] lines = File.ReadAllLines(filePath); + string currentSection = ""; + + foreach (string line in lines) + { + string trimmedLine = line.Trim(); + + if (trimmedLine.StartsWith("[") && trimmedLine.EndsWith("]")) + { + currentSection = trimmedLine.Substring(1, trimmedLine.Length - 2); + if (!data.ContainsKey(currentSection)) + { + data[currentSection] = new Dictionary(); + } + } + else if (trimmedLine.Contains("=") && !string.IsNullOrWhiteSpace(currentSection)) + { + string[] parts = trimmedLine.Split(new[] { '=' }, 2); + if (parts.Length == 2) + { + string key = parts[0].Trim(); + string value = parts[1].Trim(); + data[currentSection][key] = value; + } + } + } + } + } + + public string GetValue(string section, string key, string defaultValue = "") + { + if (data.ContainsKey(section) && data[section].ContainsKey(key)) + { + return data[section][key]; + } + return defaultValue; + } + + public int GetInt(string section, string key, int defaultValue = 0) + { + string value = GetValue(section, key, defaultValue.ToString()); + if (int.TryParse(value, out int result)) + { + return result; + } + return defaultValue; + } + + public bool GetBool(string section, string key, bool defaultValue = false) + { + string value = GetValue(section, key, defaultValue.ToString()).ToLower(); + return value == "true" || value == "1" || value == "yes"; + } + + public List GetSections() + { + return new List(data.Keys); + } + + public List GetKeys(string section) + { + if (data.ContainsKey(section)) + { + return new List(data[section].Keys); + } + return new List(); + } + + public bool TryReadBool(string key, out bool value, string section = "") + { + string sectionToUse = string.IsNullOrWhiteSpace(section) ? "General" : section; + string stringValue = GetValue(sectionToUse, key, "false"); + value = stringValue.ToLower() == "true" || stringValue == "1" || stringValue == "yes"; + return true; + } + + public bool TryReadInt(string key, out int value, string section = "") + { + string sectionToUse = string.IsNullOrWhiteSpace(section) ? "General" : section; + string stringValue = GetValue(sectionToUse, key, "0"); + return int.TryParse(stringValue, out value); + } + + public bool TryReadFloat(string key, out float value, string section = "") + { + string sectionToUse = string.IsNullOrWhiteSpace(section) ? "General" : section; + string stringValue = GetValue(sectionToUse, key, "0"); + return float.TryParse(stringValue, out value); + } + } +} diff --git a/utils/MemoryStatisticsProvider.cs b/utils/MemoryStatisticsProvider.cs new file mode 100644 index 0000000..482d429 --- /dev/null +++ b/utils/MemoryStatisticsProvider.cs @@ -0,0 +1,108 @@ +using System; + +namespace CMLeonOS.Utils +{ + public static class MemoryStatisticsProvider + { + public static ulong GetAvailableMemory() + { + return Cosmos.Core.CPU.GetAmountOfRAM() * 1024 * 1024; + } + + public static ulong GetUsedMemory() + { + ulong totalMemory = GetAvailableMemory(); + ulong freeMemory = GetFreeMemory(); + return totalMemory - freeMemory; + } + + public static ulong GetFreeMemory() + { + ulong totalMemory = GetAvailableMemory(); + return totalMemory / 4; + } + + public static ulong GetTotalMemory() + { + return Cosmos.Core.CPU.GetAmountOfRAM() * 1024 * 1024; + } + + public static string FormatBytes(ulong bytes) + { + string[] sizes = { "B", "KB", "MB", "GB", "TB" }; + int order = 0; + double size = bytes; + + while (size >= 1024 && order < sizes.Length - 1) + { + order++; + size = size / 1024; + } + + return $"{size:0.##} {sizes[order]}"; + } + + public static string GetMemoryUsagePercentage() + { + ulong used = GetUsedMemory(); + ulong total = GetTotalMemory(); + double percentage = (double)used / total * 100; + return $"{percentage:0.1}%"; + } + + public static MemoryStatistics GetMemoryStatistics() + { + return new MemoryStatistics + { + TotalMemory = GetTotalMemory(), + UsedMemory = GetUsedMemory(), + FreeMemory = GetFreeMemory(), + UsagePercentage = GetMemoryUsagePercentage() + }; + } + } + + public class MemoryStatistics + { + public ulong TotalMemory { get; set; } + public ulong UsedMemory { get; set; } + public ulong FreeMemory { get; set; } + public string UsagePercentage { get; set; } + + public ulong TotalMB + { + get { return TotalMemory / (1024 * 1024); } + } + + public ulong UnavailableMB + { + get { return 0; } + } + + public ulong UsedMB + { + get { return UsedMemory / (1024 * 1024); } + } + + public ulong FreeMB + { + get { return FreeMemory / (1024 * 1024); } + } + + public int PercentUsed + { + get + { + if (UsagePercentage.EndsWith("%")) + { + string percentage = UsagePercentage.TrimEnd('%'); + if (int.TryParse(percentage, out int result)) + { + return result; + } + } + return 0; + } + } + } +} diff --git a/utils/PathUtil.cs b/utils/PathUtil.cs new file mode 100644 index 0000000..c2c6671 --- /dev/null +++ b/utils/PathUtil.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Text; + +namespace CMLeonOS.Utils +{ + public static class PathUtil + { + public static string Sanitize(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return path; + } + + string sanitized = path; + + sanitized = sanitized.Replace('\\', '/'); + sanitized = sanitized.Replace(':', '_'); + sanitized = sanitized.Replace('*', '_'); + sanitized = sanitized.Replace('?', '_'); + sanitized = sanitized.Replace('"', '_'); + sanitized = sanitized.Replace('<', '_'); + sanitized = sanitized.Replace('>', '_'); + sanitized = sanitized.Replace('|', '_'); + + sanitized = sanitized.Trim('/', '\\'); + + return sanitized; + } + + public static string Combine(string path1, string path2) + { + return Path.Combine(path1, path2); + } + + public static string GetExtension(string path) + { + return Path.GetExtension(path); + } + + public static string GetFileName(string path) + { + return Path.GetFileName(path); + } + + public static string GetDirectoryName(string path) + { + return Path.GetDirectoryName(path); + } + + public static bool Exists(string path) + { + return File.Exists(path) || Directory.Exists(path); + } + + public static string Normalize(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return path; + } + + string normalized = path.Replace('\\', '/'); + + while (normalized.Contains("//")) + { + normalized = normalized.Replace("//", "/"); + } + + return normalized; + } + } +} diff --git a/utils/Version.cs b/utils/Version.cs index 549b348..f2c5789 100644 --- a/utils/Version.cs +++ b/utils/Version.cs @@ -29,5 +29,10 @@ namespace CMLeonOS { get { return $"CMLeonOS v{ShortVersion} ({VersionType}) - Git: {GitCommit}"; } } + + public static string GetVersion() + { + return ShortVersion; + } } }