2 Commits

Author SHA1 Message Date
Leonmmcoset
6c514df84e 修复设置bug 2026-03-03 21:25:01 +08:00
Leonmmcoset
29a68b4ca9 打开文件可视化窗口+CodeStudio代码高亮 2026-03-02 21:42:44 +08:00
12 changed files with 616 additions and 56 deletions

View File

@@ -1 +1 @@
2026-03-01 21:19:06
2026-03-03 21:19:17

View File

@@ -1 +1 @@
6a9d39a
29a68b4

View File

@@ -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);
}
}
}
}

View File

@@ -1,4 +1,4 @@
using CMLeonOS;
using CMLeonOS;
using CMLeonOS.Gui.UILib;
using Cosmos.System.Graphics;
using System.Drawing;
@@ -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))

View File

@@ -0,0 +1,180 @@
using System.Collections.Generic;
using System.Drawing;
namespace CMLeonOS.Gui.Apps.CodeStudio
{
internal static class LuaSyntaxHighlighter
{
private static readonly List<string> Keywords = new List<string>
{
"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while",
"goto", "self"
};
private static readonly List<string> Builtins = new List<string>
{
"print", "type", "tostring", "tonumber", "ipairs", "pairs", "table", "string", "math", "io", "os", "coroutine", "debug", "package", "utf8", "bit32",
"assert", "collectgarbage", "dofile", "error", "getmetatable", "load", "loadfile", "next", "pcall", "rawequal", "rawget", "rawlen", "rawset", "require", "select", "setmetatable", "xpcall",
"byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", "reverse", "sub", "upper",
"abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "cosh", "deg", "exp", "floor", "fmod", "frexp", "huge", "ldexp", "log", "log10", "max", "min", "modf", "pi", "pow", "rad", "random", "randomseed", "sin", "sinh", "sqrt", "tan", "tanh",
"close", "flush", "input", "lines", "open", "output", "popen", "read", "tmpfile", "type", "write",
"clock", "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname",
"create", "resume", "running", "status", "wrap", "yield",
"concat", "insert", "maxn", "remove", "sort",
"getinfo", "getlocal", "getmetatable", "setmetatable", "setlocal", "traceback",
"loadlib", "seeall", "loaded", "loaders", "path", "cpath", "preload"
};
private static readonly List<string> Operators = new List<string>
{
"+", "-", "*", "/", "%", "^", "#", "==", "~=", "<=", ">=", "<", ">", "=", "(", ")", "[", "]", "{", "}", ";", ":", ",", "..", "...", "."
};
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(255, 128, 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<HighlightedToken> HighlightLine(string line)
{
var tokens = new List<HighlightedToken>();
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 (i + 3 < line.Length && line.Substring(i, 4) == "--[[")
{
int start = i;
i += 4;
int endPos = line.IndexOf("--]]", i);
if (endPos != -1)
{
i = endPos + 4;
}
else
{
i = line.Length;
}
tokens.Add(new HighlightedToken(line.Substring(start, i - start), Colors.Comment));
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) || (c == '0' && i + 1 < line.Length && (line[i + 1] == 'x' || line[i + 1] == 'X')))
{
int start = i;
i++;
while (i < line.Length && (char.IsDigit(line[i]) || line[i] == '.' || line[i] == 'e' || line[i] == 'E' || line[i] == 'x' || line[i] == 'X' || line[i] == 'a' || line[i] == 'A' || line[i] == 'b' || line[i] == 'B' || line[i] == 'c' || line[i] == 'C' || line[i] == 'd' || line[i] == 'D' || line[i] == 'f' || line[i] == 'F'))
{
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;
}
}
}

View File

@@ -19,13 +19,13 @@ namespace CMLeonOS.Gui.Apps
AppWindow window;
WindowManager wm = ProcessManager.GetProcess<WindowManager>();
SettingsService settingsService = ProcessManager.GetProcess<SettingsService>();
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()

View File

@@ -253,7 +253,11 @@ namespace CMLeonOS.Gui.Apps
{
base.Start();
window = new AppWindow(this, 256, 256, 448, 272);
window.Closing = TryStop;
window.Closing = () =>
{
SettingsManager.FlushSettings();
TryStop();
};
window.Icon = AppManager.GetAppMetadata("Settings").Icon;
wm.AddWindow(window);

View File

@@ -3,6 +3,7 @@ using Cosmos.System.Graphics;
using CMLeonOS;
using CMLeonOS.Gui.UILib;
using CMLeonOS.UILib.Animations;
using CMLeonOS.Settings;
using System;
using System.Drawing;
@@ -55,13 +56,8 @@ namespace CMLeonOS.Gui.ShellComponents
internal void UpdateTime()
{
if (settingsService == null)
{
settingsService = ProcessManager.GetProcess<SettingsService>();
}
string timeText;
if (settingsService.TwelveHourClock)
if (SettingsManager.GUI_TwelveHourClock)
{
timeText = DateTime.Now.ToString("ddd h:mm tt");
}
@@ -120,6 +116,8 @@ namespace CMLeonOS.Gui.ShellComponents
start.OnClick = StartClicked;
wm.AddWindow(start);
SetLeftHandStartButton(SettingsManager.GUI_LeftHandStartButton);
UpdateTime();
MovementAnimation animation = new MovementAnimation(window)

313
Gui/UILib/FileBrowser.cs Normal file
View File

@@ -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<string> 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<string> 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;
}
}
}
}

View File

@@ -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)
{

View File

@@ -2,6 +2,7 @@ using Cosmos.System;
using Cosmos.System.Graphics;
using CMLeonOS;
using CMLeonOS.Gui.ShellComponents;
using CMLeonOS.Settings;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -381,7 +382,7 @@ namespace CMLeonOS.Gui
RenderWallpaper();
fpsCounter = new Window(this, (int)(ScreenWidth) - 64, (int)(ScreenHeight - 16), 64, 16);
if (settingsService.ShowFps)
if (SettingsManager.GUI_ShowFps)
{
AddWindow(fpsCounter);
}

View File

@@ -8,6 +8,9 @@ namespace CMLeonOS.Settings
{
private static string settingsFilePath = @"0:\system\settings.dat";
private static Dictionary<string, string> settings = new Dictionary<string, string>();
private static bool savePending = false;
private static int pendingSaveCount = 0;
private const int MAX_PENDING_SAVES = 5;
private static Dictionary<string, string> defaultSettings = new Dictionary<string, string>
{
@@ -226,6 +229,8 @@ namespace CMLeonOS.Settings
}
SaveSettings();
}
FlushSettings();
}
catch (Exception e)
{
@@ -235,6 +240,22 @@ namespace CMLeonOS.Settings
public static void SaveSettings()
{
savePending = true;
pendingSaveCount++;
if (pendingSaveCount >= MAX_PENDING_SAVES)
{
FlushSettings();
}
}
public static void FlushSettings()
{
if (!savePending)
{
return;
}
try
{
Directory.CreateDirectory(Path.GetDirectoryName(settingsFilePath));
@@ -250,6 +271,9 @@ namespace CMLeonOS.Settings
writer.WriteLine($"{setting.Key}={setting.Value}");
}
}
savePending = false;
pendingSaveCount = 0;
}
catch (Exception e)
{