From 29a68b4ca9800e7973fd4ed4c938688a8ee1c421 Mon Sep 17 00:00:00 2001 From: Leonmmcoset Date: Mon, 2 Mar 2026 21:42:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=93=E5=BC=80=E6=96=87=E4=BB=B6=E5=8F=AF?= =?UTF-8?q?=E8=A7=86=E5=8C=96=E7=AA=97=E5=8F=A3+CodeStudio=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BuildTime.txt | 2 +- GitCommit.txt | 2 +- Gui/Apps/CodeStudio/CodeEditor.cs | 50 ++++ Gui/Apps/CodeStudio/Ide.cs | 36 ++- Gui/Apps/CodeStudio/LuaSyntaxHighlighter.cs | 150 ++++++++++ Gui/Apps/Notepad.cs | 36 +-- Gui/UILib/FileBrowser.cs | 313 ++++++++++++++++++++ Gui/UILib/TextBox.cs | 8 +- 8 files changed, 550 insertions(+), 47 deletions(-) create mode 100644 Gui/Apps/CodeStudio/CodeEditor.cs create mode 100644 Gui/Apps/CodeStudio/LuaSyntaxHighlighter.cs create mode 100644 Gui/UILib/FileBrowser.cs diff --git a/BuildTime.txt b/BuildTime.txt index bc27331..1416272 100644 --- a/BuildTime.txt +++ b/BuildTime.txt @@ -1 +1 @@ -2026-03-01 21:19:06 \ No newline at end of file +2026-03-02 21:39:15 \ No newline at end of file diff --git a/GitCommit.txt b/GitCommit.txt index bcef3a3..f6723d1 100644 --- a/GitCommit.txt +++ b/GitCommit.txt @@ -1 +1 @@ -6a9d39a \ No newline at end of file +5c952e7 \ No newline at end of file diff --git a/Gui/Apps/CodeStudio/CodeEditor.cs b/Gui/Apps/CodeStudio/CodeEditor.cs new file mode 100644 index 0000000..9426b7f --- /dev/null +++ b/Gui/Apps/CodeStudio/CodeEditor.cs @@ -0,0 +1,50 @@ +using CMLeonOS.Gui.UILib; +using Cosmos.System.Graphics; +using System.Drawing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CMLeonOS.Gui.Apps.CodeStudio +{ + internal class CodeEditor : TextBox + { + private bool enableSyntaxHighlighting = false; + private string fileExtension = ""; + + public CodeEditor(Window parent, int x, int y, int width, int height) : base(parent, x, y, width, height) + { + } + + internal void SetSyntaxHighlighting(bool enable, string extension = "") + { + enableSyntaxHighlighting = enable; + fileExtension = extension.ToLower(); + MarkAllLines(); + Render(); + } + + internal override void RenderLine(int lineIndex, string lineText, int lineY, int xOffset) + { + if (enableSyntaxHighlighting && fileExtension == ".lua") + { + var tokens = LuaSyntaxHighlighter.HighlightLine(lineText); + int currentXOffset = xOffset; + + foreach (var token in tokens) + { + if (currentXOffset + token.Text.Length * 8 > 0 && currentXOffset < Width) + { + DrawString(token.Text, token.Color, currentXOffset, lineY); + } + currentXOffset += token.Text.Length * 8; + } + } + else + { + base.RenderLine(lineIndex, lineText, lineY, xOffset); + } + } + } +} diff --git a/Gui/Apps/CodeStudio/Ide.cs b/Gui/Apps/CodeStudio/Ide.cs index fb69dbb..84d9f07 100644 --- a/Gui/Apps/CodeStudio/Ide.cs +++ b/Gui/Apps/CodeStudio/Ide.cs @@ -29,12 +29,14 @@ namespace CMLeonOS.Gui.Apps.CodeStudio Button runButton; - TextBox editor; + CodeEditor editor; TextBox problems; TextBox output; + FileBrowser fileBrowser; + private const int headersHeight = 24; private const int problemsHeight = 128; private const int outputHeight = 128; @@ -90,6 +92,9 @@ namespace CMLeonOS.Gui.Apps.CodeStudio { editor.Text = File.ReadAllText(path); + string extension = Path.GetExtension(path).ToLower(); + editor.SetSyntaxHighlighting(extension == ".lua", extension); + editor.MarkAllLines(); editor.Render(); @@ -101,35 +106,27 @@ namespace CMLeonOS.Gui.Apps.CodeStudio private void OpenFilePrompt() { - PromptBox prompt = new PromptBox(process, "Open File", "Enter the path to open.", "Path", (string newPath) => + fileBrowser = new FileBrowser(process, wm, (string selectedPath) => { - if (!newPath.Contains(':')) + if (selectedPath != null) { - newPath = $@"0:\{newPath}"; + Open(selectedPath); } - Open(newPath); }); - prompt.Show(); + fileBrowser.Show(); } private void SaveAsPrompt() { - PromptBox prompt = new PromptBox(process, "Save As", "Enter the path to save to.", "Path", (string newPath) => + fileBrowser = new FileBrowser(process, wm, (string selectedPath) => { - if (!newPath.Contains(':')) - { - newPath = $@"0:\{newPath}"; - } - - Open(newPath, readFile: false); - - // Check if open succeeded. - if (path != null) + if (selectedPath != null) { + path = selectedPath; Save(); } - }); - prompt.Show(); + }, selectDirectoryOnly: true); + fileBrowser.Show(); } private void Save() @@ -270,7 +267,7 @@ namespace CMLeonOS.Gui.Apps.CodeStudio runButton.OnClick = RunClicked; wm.AddWindow(runButton); - editor = new TextBox(mainWindow, 0, headersHeight, mainWindow.Width, mainWindow.Height - headersHeight - problemsHeight - outputHeight - (headersHeight * 3)) + editor = new CodeEditor(mainWindow, 0, headersHeight, mainWindow.Width, mainWindow.Height - headersHeight - problemsHeight - outputHeight - (headersHeight * 3)) { Background = Theme.CodeBackground, Foreground = Color.White, @@ -278,6 +275,7 @@ namespace CMLeonOS.Gui.Apps.CodeStudio Changed = TextChanged, MultiLine = true }; + editor.SetSyntaxHighlighting(true, ".lua"); wm.AddWindow(editor); problems = new TextBox(mainWindow, 0, headersHeight + editor.Height + headersHeight, mainWindow.Width, problemsHeight + (headersHeight * 2)) diff --git a/Gui/Apps/CodeStudio/LuaSyntaxHighlighter.cs b/Gui/Apps/CodeStudio/LuaSyntaxHighlighter.cs new file mode 100644 index 0000000..4375c35 --- /dev/null +++ b/Gui/Apps/CodeStudio/LuaSyntaxHighlighter.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using System.Drawing; + +namespace CMLeonOS.Gui.Apps.CodeStudio +{ + internal static class LuaSyntaxHighlighter + { + private static readonly List Keywords = new List + { + "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" + }; + + private static readonly List Builtins = new List + { + "print", "type", "tostring", "tonumber", "ipairs", "pairs", "table", "string", "math", "io", "os", "coroutine", "debug", "package", "utf8", "bit32" + }; + + private static readonly List Operators = new List + { + "+", "-", "*", "/", "%", "^", "#", "==", "~=", "<=", ">=", "<", ">", "=", "(", ")", "[", "]", "{", "}", ";", ":", ",", "..", "...", "." + }; + + internal static class Colors + { + internal static Color Keyword = Color.FromArgb(255, 0, 128); + internal static Color Builtin = Color.FromArgb(0, 128, 0); + internal static Color String = Color.FromArgb(163, 21, 21); + internal static Color Number = Color.FromArgb(0, 0, 255); + internal static Color Comment = Color.FromArgb(128, 128, 128); + internal static Color Operator = Color.FromArgb(0, 0, 0); + internal static Color Default = Color.White; + } + + internal class HighlightedToken + { + internal string Text; + internal Color Color; + + internal HighlightedToken(string text, Color color) + { + Text = text; + Color = color; + } + } + + internal static List HighlightLine(string line) + { + var tokens = new List(); + int i = 0; + + while (i < line.Length) + { + char c = line[i]; + + if (char.IsWhiteSpace(c)) + { + int start = i; + while (i < line.Length && char.IsWhiteSpace(line[i])) + { + i++; + } + tokens.Add(new HighlightedToken(line.Substring(start, i - start), Colors.Default)); + continue; + } + + if (c == '-' && i + 1 < line.Length && line[i + 1] == '-') + { + int start = i; + i += 2; + while (i < line.Length && line[i] != '\n') + { + i++; + } + tokens.Add(new HighlightedToken(line.Substring(start, i - start), Colors.Comment)); + continue; + } + + if (c == '"' || c == '\'') + { + int start = i; + i++; + while (i < line.Length && line[i] != c) + { + if (line[i] == '\\' && i + 1 < line.Length) + { + i += 2; + } + else + { + i++; + } + } + if (i < line.Length) + { + i++; + } + tokens.Add(new HighlightedToken(line.Substring(start, i - start), Colors.String)); + continue; + } + + if (char.IsDigit(c)) + { + int start = i; + while (i < line.Length && (char.IsDigit(line[i]) || line[i] == '.')) + { + i++; + } + tokens.Add(new HighlightedToken(line.Substring(start, i - start), Colors.Number)); + continue; + } + + if (char.IsLetter(c) || c == '_') + { + int start = i; + while (i < line.Length && (char.IsLetterOrDigit(line[i]) || line[i] == '_')) + { + i++; + } + string word = line.Substring(start, i - start); + + if (Keywords.Contains(word)) + { + tokens.Add(new HighlightedToken(word, Colors.Keyword)); + } + else if (Builtins.Contains(word)) + { + tokens.Add(new HighlightedToken(word, Colors.Builtin)); + } + else + { + tokens.Add(new HighlightedToken(word, Colors.Default)); + } + continue; + } + + if (Operators.Contains(c.ToString())) + { + tokens.Add(new HighlightedToken(c.ToString(), Colors.Operator)); + i++; + continue; + } + + tokens.Add(new HighlightedToken(c.ToString(), Colors.Default)); + i++; + } + + return tokens; + } + } +} diff --git a/Gui/Apps/Notepad.cs b/Gui/Apps/Notepad.cs index febf1a2..c11d5bd 100644 --- a/Gui/Apps/Notepad.cs +++ b/Gui/Apps/Notepad.cs @@ -19,13 +19,13 @@ namespace CMLeonOS.Gui.Apps AppWindow window; WindowManager wm = ProcessManager.GetProcess(); - SettingsService settingsService = ProcessManager.GetProcess(); - TextBox textBox; ShortcutBar shortcutBar; + FileBrowser fileBrowser; + private string? path = null; private bool modified = false; @@ -70,12 +70,6 @@ namespace CMLeonOS.Gui.Apps { 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)}'."); @@ -99,35 +93,27 @@ namespace CMLeonOS.Gui.Apps private void OpenFilePrompt() { - PromptBox prompt = new PromptBox(this, "Open File", "Enter the path to open.", "Path", (string newPath) => + fileBrowser = new FileBrowser(this, wm, (string selectedPath) => { - if (!newPath.Contains(':')) + if (selectedPath != null) { - newPath = $@"0:\{newPath}"; + Open(selectedPath); } - Open(newPath); }); - prompt.Show(); + fileBrowser.Show(); } private void SaveAsPrompt() { - PromptBox prompt = new PromptBox(this, "Save As", "Enter the path to save to.", "Path", (string newPath) => + fileBrowser = new FileBrowser(this, wm, (string selectedPath) => { - if (!newPath.Contains(':')) - { - newPath = $@"0:\{newPath}"; - } - - Open(newPath, readFile: false); - - // Check if open succeeded. - if (path != null) + if (selectedPath != null) { + path = selectedPath; Save(); } - }); - prompt.Show(); + }, selectDirectoryOnly: true); + fileBrowser.Show(); } private void Save() diff --git a/Gui/UILib/FileBrowser.cs b/Gui/UILib/FileBrowser.cs new file mode 100644 index 0000000..e2e51e7 --- /dev/null +++ b/Gui/UILib/FileBrowser.cs @@ -0,0 +1,313 @@ +using CMLeonOS; +using CMLeonOS.Gui.UILib; +using Cosmos.System.Graphics; +using System.Drawing; +using System; +using System.Collections.Generic; +using System.IO; + +namespace CMLeonOS.Gui.UILib +{ + internal class FileBrowser + { + private Process process; + private WindowManager wm; + private Window window; + private Table fileTable; + private TextBox pathBox; + private TextBox fileNameBox; + private Button upButton; + private Button selectButton; + private Button cancelButton; + + private string currentPath = @"0:\"; + private string selectedPath = null; + private Action onFileSelected; + private bool selectDirectoryOnly = false; + + private const int headerHeight = 32; + private const int buttonHeight = 28; + private const int buttonWidth = 80; + private const int fileNameBoxHeight = 24; + + internal FileBrowser(Process process, WindowManager wm, Action onFileSelected, bool selectDirectoryOnly = false) + { + this.process = process; + this.wm = wm; + this.onFileSelected = onFileSelected; + this.selectDirectoryOnly = selectDirectoryOnly; + } + + 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_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.Up.bmp")] + private static byte[] _iconBytes_Up; + internal static Bitmap Icon_Up = new Bitmap(_iconBytes_Up); + } + + private Bitmap GetFileIcon(string path) + { + string extension = Path.GetExtension(path).ToLower(); + + return extension switch + { + ".txt" or ".md" or ".log" or ".lua" or ".rs" => Icons.Icon_File_Text, + _ => Icons.Icon_File + }; + } + + private void PopulateFileTable() + { + fileTable.Cells.Clear(); + fileTable.SelectedCellIndex = -1; + + bool empty = true; + + foreach (string path in Directory.GetDirectories(currentPath)) + { + fileTable.Cells.Add(new TableCell(Icons.Icon_Directory, Path.GetFileName(path), tag: "Directory")); + empty = false; + } + + foreach (string path in Directory.GetFiles(currentPath)) + { + fileTable.Cells.Add(new TableCell(GetFileIcon(path), Path.GetFileName(path), tag: "File")); + empty = false; + } + + if (empty) + { + fileTable.Clear(fileTable.Background); + fileTable.DrawString("This folder is empty.", Color.Black, (fileTable.Width / 2) - 80, 12); + wm.Update(fileTable); + } + else + { + fileTable.Render(); + } + } + + private void PathBoxChanged() + { + string inputPath = pathBox.Text; + + if (string.IsNullOrWhiteSpace(inputPath)) + { + return; + } + + if (!inputPath.Contains(':')) + { + inputPath = $@"0:\{inputPath}"; + } + + if (Directory.Exists(inputPath)) + { + currentPath = inputPath; + PopulateFileTable(); + } + else if (File.Exists(inputPath)) + { + currentPath = Path.GetDirectoryName(inputPath); + PopulateFileTable(); + } + } + + private void PathBoxSubmitted() + { + string inputPath = pathBox.Text; + + if (string.IsNullOrWhiteSpace(inputPath)) + { + return; + } + + if (!inputPath.Contains(':')) + { + inputPath = $@"0:\{inputPath}"; + } + + if (File.Exists(inputPath) || (selectDirectoryOnly && Directory.Exists(inputPath))) + { + selectedPath = inputPath; + onFileSelected?.Invoke(selectedPath); + Close(); + } + else if (Directory.Exists(inputPath)) + { + currentPath = inputPath; + PopulateFileTable(); + } + } + + private void UpClicked(int x, int y) + { + if (currentPath.Length > 3) + { + string parentPath = Path.GetDirectoryName(currentPath); + if (!string.IsNullOrEmpty(parentPath)) + { + currentPath = @"0:\"; + } + else + { + currentPath = parentPath; + } + PopulateFileTable(); + } + } + + private void SelectClicked(int x, int y) + { + if (fileTable.SelectedCellIndex >= 0) + { + TableCell selectedCell = fileTable.Cells[fileTable.SelectedCellIndex]; + string selectedName = selectedCell.Text; + string fullPath = Path.Combine(currentPath, selectedName); + + string tag = selectedCell.Tag as string; + if (tag == "Directory") + { + currentPath = fullPath; + PopulateFileTable(); + } + else if (tag == "File") + { + if (selectDirectoryOnly) + { + string fileName = fileNameBox.Text; + if (string.IsNullOrWhiteSpace(fileName)) + { + fileName = selectedName; + } + selectedPath = Path.Combine(currentPath, fileName); + } + else + { + selectedPath = fullPath; + } + onFileSelected?.Invoke(selectedPath); + Close(); + } + } + else if (selectDirectoryOnly) + { + string fileName = fileNameBox.Text; + if (!string.IsNullOrWhiteSpace(fileName)) + { + selectedPath = Path.Combine(currentPath, fileName); + onFileSelected?.Invoke(selectedPath); + Close(); + } + } + } + + private void FileTableSelected(int index) + { + SelectClicked(0, 0); + } + + private void CancelClicked(int x, int y) + { + selectedPath = null; + onFileSelected?.Invoke(null); + Close(); + } + + private void FileTableDoubleClicked(int index) + { + SelectClicked(0, 0); + } + + internal void Show() + { + window = new Window(process, 100, 100, 500, 400); + wm.AddWindow(window); + + window.DrawString(selectDirectoryOnly ? "Save As" : "Open File", Color.DarkBlue, 12, 12); + + pathBox = new TextBox(window, 8, 40, window.Width - 16, 24) + { + Background = Color.White, + Foreground = Color.Black, + Text = currentPath, + ReadOnly = false + }; + pathBox.Changed = PathBoxChanged; + pathBox.Submitted = PathBoxSubmitted; + wm.AddWindow(pathBox); + + if (selectDirectoryOnly) + { + window.DrawString("File name:", Color.Black, 8, 72); + + fileNameBox = new TextBox(window, 8, 96, window.Width - 16, fileNameBoxHeight) + { + Background = Color.White, + Foreground = Color.Black, + Text = "", + ReadOnly = false + }; + wm.AddWindow(fileNameBox); + } + + upButton = new Button(window, 8, selectDirectoryOnly ? 128 : 72, 60, buttonHeight) + { + Text = "Up", + Image = Icons.Icon_Up, + ImageLocation = Button.ButtonImageLocation.Left + }; + upButton.OnClick = UpClicked; + wm.AddWindow(upButton); + + int tableY = selectDirectoryOnly ? 128 + buttonHeight + 8 : 72 + buttonHeight + 8; + int tableHeight = window.Height - tableY - buttonHeight - 8 - buttonHeight - 16; + + fileTable = new Table(window, 8, tableY, window.Width - 16, tableHeight) + { + AllowDeselection = false + }; + fileTable.TableCellSelected = FileTableSelected; + PopulateFileTable(); + wm.AddWindow(fileTable); + + int buttonY = window.Height - buttonHeight - 8; + selectButton = new Button(window, window.Width - buttonWidth - 8, buttonY, buttonWidth, buttonHeight) + { + Text = selectDirectoryOnly ? "Save" : "Open" + }; + selectButton.OnClick = SelectClicked; + wm.AddWindow(selectButton); + + cancelButton = new Button(window, window.Width - buttonWidth * 2 - 16, buttonY, buttonWidth, buttonHeight) + { + Text = "Cancel" + }; + cancelButton.OnClick = CancelClicked; + wm.AddWindow(cancelButton); + + wm.Update(window); + } + + internal void Close() + { + if (window != null) + { + wm.RemoveWindow(window); + window = null; + } + } + } +} diff --git a/Gui/UILib/TextBox.cs b/Gui/UILib/TextBox.cs index 314db52..b6f20ba 100644 --- a/Gui/UILib/TextBox.cs +++ b/Gui/UILib/TextBox.cs @@ -19,6 +19,11 @@ namespace CMLeonOS.Gui.UILib internal Action Submitted; internal Action Changed; + internal virtual void RenderLine(int lineIndex, string lineText, int lineY, int xOffset) + { + DrawString(lineText, Foreground, xOffset, lineY); + } + internal string Text { get @@ -355,7 +360,8 @@ namespace CMLeonOS.Gui.UILib if (i < lines.Count) { - DrawString(Shield ? new string('*', lines[i].Length) : lines[i], Foreground, -scrollX, lineY); + string lineText = Shield ? new string('*', lines[i].Length) : lines[i]; + RenderLine(i, lineText, lineY, -scrollX); if (caretLine == i) {