重写CodeStudio

This commit is contained in:
2026-03-29 20:34:20 +08:00
parent 36f467d8f4
commit 9f12c640c7
3 changed files with 273 additions and 132 deletions

View File

@@ -1 +1 @@
2026-03-29 19:34:16 2026-03-29 20:23:53

View File

@@ -1 +1 @@
3a9ff9c 36f467d

View File

@@ -1,25 +1,8 @@
// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS) using CMLeonOS;
// Copyright (C) 2025-present LeonOS 2 Developer Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
using CMLeonOS;
using CMLeonOS.Gui.UILib; using CMLeonOS.Gui.UILib;
using Cosmos.System.Graphics; using Cosmos.System.Graphics;
using System.Drawing;
using System; using System;
using System.Collections.Generic; using System.Drawing;
using System.IO; using System.IO;
using UniLua; using UniLua;
@@ -41,92 +24,163 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
private static byte[] _runBytes; private static byte[] _runBytes;
private static Bitmap runBitmap = new Bitmap(_runBytes); private static Bitmap runBitmap = new Bitmap(_runBytes);
Process process; private readonly Process process;
private readonly WindowManager wm;
WindowManager wm; private AppWindow mainWindow;
private Button runButton;
AppWindow mainWindow; private ShortcutBar shortcutBar;
private CodeEditor editor;
Button runButton; private TextBox problems;
private TextBox output;
CodeEditor editor; private TextBox statusBar;
private Table sideTable;
TextBox problems; private FileBrowser fileBrowser;
TextBox output;
FileBrowser fileBrowser;
private const int headersHeight = 24;
private const int problemsHeight = 128;
private const int outputHeight = 128;
private string? path = null;
private string path = null;
private bool modified = false; private bool modified = false;
private void TextChanged() private static class Layout
{ {
modified = true; internal const int TopBarHeight = 30;
internal const int BottomBarHeight = 24;
UpdateTitle(); internal const int Gap = 6;
} internal const int Side = 8;
internal const int RunWidth = 72;
private void WindowResized() internal const int SidePanelWidth = 190;
{ internal const int PanelTitleHeight = 18;
int editorHeight = mainWindow.Height - headersHeight - problemsHeight - outputHeight - (headersHeight * 3); internal const int BottomPanelHeight = 150;
editor.Move(0, headersHeight);
editor.Resize(mainWindow.Width, editorHeight);
editor.MarkAllLines();
editor.Render();
problems.Move(0, headersHeight + editorHeight + headersHeight);
problems.Resize(mainWindow.Width, problemsHeight + (headersHeight * 2));
problems.MarkAllLines();
problems.Render();
output.Move(0, headersHeight + editorHeight + problemsHeight + (headersHeight * 2));
output.Resize(mainWindow.Width, outputHeight + (headersHeight * 2));
output.MarkAllLines();
output.Render();
mainWindow.Clear(Theme.Background);
mainWindow.DrawString("Problems", Color.White, 0, headersHeight + editorHeight);
mainWindow.DrawString("Output", Color.White, 0, headersHeight + editorHeight + problemsHeight + headersHeight);
} }
private static class Theme private static class Theme
{ {
internal static Color Background = Color.FromArgb(68, 76, 84); internal static readonly Color FrameBg = Color.FromArgb(34, 40, 52);
internal static Color CodeBackground = Color.FromArgb(41, 46, 51); internal static readonly Color ToolbarBg = Color.FromArgb(27, 33, 44);
internal static readonly Color EditorBg = Color.FromArgb(19, 24, 34);
internal static readonly Color PanelBg = Color.FromArgb(23, 29, 39);
internal static readonly Color PanelBorder = Color.FromArgb(66, 78, 97);
internal static readonly Color TitleText = Color.FromArgb(220, 228, 240);
internal static readonly Color HintText = Color.FromArgb(157, 171, 190);
internal static readonly Color OkText = Color.FromArgb(142, 219, 160);
internal static readonly Color ErrorText = Color.FromArgb(255, 153, 164);
} }
private void UpdateTitle() private void UpdateTitle()
{ {
if (path == null) if (path == null)
{ {
mainWindow.Title = "Untitled - Lua CodeStudio"; mainWindow.Title = "Untitled - CodeStudio";
return; return;
} }
if (modified) string fileName = Path.GetFileName(path);
mainWindow.Title = modified ? $"{fileName}* - CodeStudio" : $"{fileName} - CodeStudio";
}
private void SetStatus(string text)
{
statusBar.Text = text;
statusBar.Render();
wm.Update(statusBar);
}
private void TextChanged()
{
modified = true;
UpdateTitle();
SetStatus("Edited");
}
private void RenderChrome()
{
mainWindow.Clear(Theme.FrameBg);
mainWindow.DrawFilledRectangle(0, 0, mainWindow.Width, Layout.TopBarHeight, Theme.ToolbarBg);
mainWindow.DrawRectangle(0, 0, mainWindow.Width, Layout.TopBarHeight, Theme.PanelBorder);
int contentY = Layout.TopBarHeight + Layout.Gap;
int contentH = mainWindow.Height - contentY - Layout.BottomBarHeight - Layout.Gap;
int leftW = Layout.SidePanelWidth;
int rightX = Layout.Side + leftW + Layout.Gap;
int rightW = mainWindow.Width - rightX - Layout.Side;
int editorTop = contentY + Layout.PanelTitleHeight;
int editorH = contentH - Layout.BottomPanelHeight - Layout.Gap - Layout.PanelTitleHeight;
int bottomY = editorTop + editorH + Layout.Gap + Layout.PanelTitleHeight;
int halfW = (rightW - Layout.Gap) / 2;
mainWindow.DrawString("Workspace", Theme.TitleText, Layout.Side + 2, contentY + 2);
mainWindow.DrawString("Editor", Theme.TitleText, rightX + 2, contentY + 2);
mainWindow.DrawString("Problems", Theme.TitleText, rightX + 2, bottomY - Layout.PanelTitleHeight + 2);
mainWindow.DrawString("Output", Theme.TitleText, rightX + halfW + Layout.Gap + 2, bottomY - Layout.PanelTitleHeight + 2);
}
private int GetEditorHeight()
{
int contentY = Layout.TopBarHeight + Layout.Gap;
int contentH = mainWindow.Height - contentY - Layout.BottomBarHeight - Layout.Gap;
int available = contentH - Layout.BottomPanelHeight - Layout.Gap - Layout.PanelTitleHeight;
if (available < 80)
{ {
mainWindow.Title = $"{Path.GetFileName(path)}* - Lua CodeStudio"; return 80;
}
else
{
mainWindow.Title = $"{Path.GetFileName(path)} - Lua CodeStudio";
} }
return available;
}
private void ReLayout()
{
int side = Layout.Side;
int width = mainWindow.Width - (side * 2);
int y = 0;
runButton.MoveAndResize(side, y, Layout.RunWidth, Layout.TopBarHeight, sendWMEvent: false);
shortcutBar.MoveAndResize(side + Layout.RunWidth + Layout.Gap, y, width - Layout.RunWidth - Layout.Gap, Layout.TopBarHeight, sendWMEvent: false);
shortcutBar.Render();
int contentY = Layout.TopBarHeight + Layout.Gap;
int contentH = mainWindow.Height - contentY - Layout.BottomBarHeight - Layout.Gap;
int leftW = Layout.SidePanelWidth;
int rightX = side + leftW + Layout.Gap;
int rightW = mainWindow.Width - rightX - side;
sideTable.MoveAndResize(side, contentY + Layout.PanelTitleHeight, leftW, contentH - Layout.PanelTitleHeight, sendWMEvent: false);
sideTable.Render();
int editorY = contentY + Layout.PanelTitleHeight;
int editorH = GetEditorHeight();
editor.MoveAndResize(rightX, editorY, rightW, editorH, sendWMEvent: false);
editor.MarkAllLines();
editor.Render();
int bottomY = editorY + editorH + Layout.Gap + Layout.PanelTitleHeight;
int halfW = (rightW - Layout.Gap) / 2;
problems.MoveAndResize(rightX, bottomY, halfW, Layout.BottomPanelHeight, sendWMEvent: false);
problems.MarkAllLines();
problems.Render();
output.MoveAndResize(rightX + halfW + Layout.Gap, bottomY, rightW - halfW - Layout.Gap, Layout.BottomPanelHeight, sendWMEvent: false);
output.MarkAllLines();
output.Render();
int statusY = mainWindow.Height - Layout.BottomBarHeight;
statusBar.MoveAndResize(0, statusY, mainWindow.Width, Layout.BottomBarHeight, sendWMEvent: false);
statusBar.Render();
RenderChrome();
wm.Update(mainWindow);
} }
internal void Open(string newPath, bool readFile = true) internal void Open(string newPath, bool readFile = true)
{ {
if (newPath == null) return; if (newPath == null)
{
return;
}
if (readFile && !File.Exists(newPath)) if (readFile && !File.Exists(newPath))
{ {
MessageBox messageBox = new MessageBox(process, "Lua CodeStudio", $"No such file '{Path.GetFileName(newPath)}'."); MessageBox messageBox = new MessageBox(process, "CodeStudio", $"No such file '{Path.GetFileName(newPath)}'.");
messageBox.Show(); messageBox.Show();
return;
} }
path = newPath; path = newPath;
@@ -137,7 +191,6 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
string extension = Path.GetExtension(path).ToLower(); string extension = Path.GetExtension(path).ToLower();
editor.SetSyntaxHighlighting(extension == ".lua", extension); editor.SetSyntaxHighlighting(extension == ".lua", extension);
editor.MarkAllLines(); editor.MarkAllLines();
editor.Render(); editor.Render();
@@ -145,6 +198,7 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
} }
UpdateTitle(); UpdateTitle();
SetStatus("Opened: " + Path.GetFileName(path));
} }
private void OpenFilePrompt() private void OpenFilePrompt()
@@ -181,9 +235,55 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
} }
File.WriteAllText(path, editor.Text); File.WriteAllText(path, editor.Text);
modified = false; modified = false;
UpdateTitle(); UpdateTitle();
SetStatus("Saved: " + Path.GetFileName(path));
}
private void SideCommandSelected(int index)
{
if (index < 0 || index >= sideTable.Cells.Count)
{
return;
}
string cmd = sideTable.Cells[index].Text;
if (cmd == "Open File")
{
OpenFilePrompt();
}
else if (cmd == "Save File")
{
Save();
}
else if (cmd == "Save As")
{
SaveAsPrompt();
}
else if (cmd == "Evaluate")
{
Evaluate();
}
else if (cmd == "Run Script")
{
RunClicked(0, 0);
}
else if (cmd == "Insert: Hello")
{
editor.Text += "\nprint(\"Hello from CodeStudio\")";
editor.MarkAllLines();
editor.Render();
wm.Update(editor);
TextChanged();
}
else if (cmd == "Insert: GUI Demo")
{
editor.Text += "\nlocal w = gui.window(\"Demo\", 100, 100, 260, 120)\n";
editor.MarkAllLines();
editor.Render();
wm.Update(editor);
TextChanged();
}
} }
private void RunClicked(int x, int y) private void RunClicked(int x, int y)
@@ -191,20 +291,23 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
try try
{ {
output.Text = ""; output.Text = "";
ILuaState lua = LuaAPI.NewState(); ILuaState lua = LuaAPI.NewState();
lua.L_OpenLibs(); lua.L_OpenLibs();
LuaGuiLibrary.Initialize(wm, process); LuaGuiLibrary.Initialize(wm, process);
LuaGuiLibrary.Register(lua); LuaGuiLibrary.Register(lua);
lua.PushCSharpFunction(L => lua.PushCSharpFunction(L =>
{ {
int n = L.GetTop(); int n = L.GetTop();
string result = ""; string result = "";
for (int i = 1; i <= n; i++) for (int i = 1; i <= n; i++)
{ {
if (i > 1) result += "\t"; if (i > 1)
{
result += "\t";
}
LuaType type = L.Type(i); LuaType type = L.Type(i);
if (type == LuaType.LUA_TSTRING) if (type == LuaType.LUA_TSTRING)
{ {
@@ -227,37 +330,47 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
return 0; return 0;
}); });
lua.SetGlobal("print"); lua.SetGlobal("print");
UniLua.ThreadStatus loadResult = lua.L_LoadString(editor.Text); UniLua.ThreadStatus loadResult = lua.L_LoadString(editor.Text);
if (loadResult == UniLua.ThreadStatus.LUA_OK) if (loadResult == UniLua.ThreadStatus.LUA_OK)
{ {
UniLua.ThreadStatus callResult = lua.PCall(0, 0, 0); UniLua.ThreadStatus callResult = lua.PCall(0, 0, 0);
if (callResult == UniLua.ThreadStatus.LUA_OK) if (callResult == UniLua.ThreadStatus.LUA_OK)
{ {
problems.Foreground = Color.LimeGreen; problems.Foreground = Theme.OkText;
problems.Text = "Execution successful"; problems.Text = "Execution successful.";
SetStatus("Run succeeded");
} }
else else
{ {
string errorMsg = lua.ToString(-1); string errorMsg = lua.ToString(-1);
problems.Foreground = Color.Pink; problems.Foreground = Theme.ErrorText;
problems.Text = $"Script execution error: {errorMsg}"; problems.Text = "Script execution error: " + errorMsg;
SetStatus("Run failed");
} }
} }
else else
{ {
string errorMsg = lua.ToString(-1); string errorMsg = lua.ToString(-1);
problems.Foreground = Color.Pink; problems.Foreground = Theme.ErrorText;
problems.Text = $"Script load error: {errorMsg}"; problems.Text = "Script load error: " + errorMsg;
SetStatus("Run failed");
} }
} }
catch (Exception e) catch (Exception e)
{ {
problems.Foreground = Color.Pink; problems.Foreground = Theme.ErrorText;
problems.Text = e.Message; problems.Text = e.Message;
SetStatus("Run failed");
} }
problems.MarkAllLines();
problems.Render();
output.MarkAllLines();
output.Render();
wm.Update(problems);
wm.Update(output);
} }
private void Evaluate() private void Evaluate()
@@ -267,58 +380,73 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
ILuaState lua = LuaAPI.NewState(); ILuaState lua = LuaAPI.NewState();
lua.L_OpenLibs(); lua.L_OpenLibs();
UniLua.ThreadStatus loadResult = lua.L_LoadString(editor.Text); UniLua.ThreadStatus loadResult = lua.L_LoadString(editor.Text);
if (loadResult == UniLua.ThreadStatus.LUA_OK) if (loadResult == UniLua.ThreadStatus.LUA_OK)
{ {
problems.Foreground = Color.LimeGreen; problems.Foreground = Theme.OkText;
problems.Text = "No syntax errors"; problems.Text = "No syntax errors.";
SetStatus("Evaluate passed");
} }
else else
{ {
string errorMsg = lua.ToString(-1); string errorMsg = lua.ToString(-1);
problems.Foreground = Theme.ErrorText;
if (string.IsNullOrWhiteSpace(errorMsg)) if (string.IsNullOrWhiteSpace(errorMsg))
{ {
problems.Foreground = Color.Pink;
problems.Text = "Script load error: Unknown error"; problems.Text = "Script load error: Unknown error";
} }
else else
{ {
problems.Foreground = Color.Pink; problems.Text = "Script load error: " + errorMsg;
problems.Text = $"Script load error: {errorMsg}";
} }
SetStatus("Evaluate failed");
} }
} }
catch (Exception e) catch (Exception e)
{ {
problems.Foreground = Color.Pink; problems.Foreground = Theme.ErrorText;
problems.Text = e.Message; problems.Text = e.Message;
SetStatus("Evaluate failed");
} }
problems.MarkAllLines();
problems.Render();
wm.Update(problems);
} }
internal void Start() internal void Start()
{ {
mainWindow = new AppWindow(process, 96, 96, 800, 600); mainWindow = new AppWindow(process, 96, 96, 920, 640);
mainWindow.Icon = iconBitmap; mainWindow.Icon = iconBitmap;
mainWindow.Clear(Theme.Background);
mainWindow.Closing = process.TryStop; mainWindow.Closing = process.TryStop;
mainWindow.CanResize = true; mainWindow.CanResize = true;
mainWindow.UserResized = WindowResized; mainWindow.UserResized = ReLayout;
UpdateTitle();
wm.AddWindow(mainWindow); wm.AddWindow(mainWindow);
UpdateTitle();
runButton = new Button(mainWindow, 0, 0, 60, headersHeight); runButton = new Button(mainWindow, Layout.Side, 0, Layout.RunWidth, Layout.TopBarHeight);
runButton.Background = Theme.Background; runButton.Background = Theme.ToolbarBg;
runButton.Border = Theme.Background; runButton.Border = Theme.PanelBorder;
runButton.Foreground = Color.White; runButton.Foreground = Theme.TitleText;
runButton.Text = "Run"; runButton.Text = "Run";
runButton.Image = runBitmap; runButton.Image = runBitmap;
runButton.ImageLocation = Button.ButtonImageLocation.Left; runButton.ImageLocation = Button.ButtonImageLocation.Left;
runButton.OnClick = RunClicked; runButton.OnClick = RunClicked;
wm.AddWindow(runButton); wm.AddWindow(runButton);
editor = new CodeEditor(mainWindow, 0, headersHeight, mainWindow.Width, mainWindow.Height - headersHeight - problemsHeight - outputHeight - (headersHeight * 3)) shortcutBar = new ShortcutBar(mainWindow, Layout.Side + Layout.RunWidth + Layout.Gap, 0, 300, Layout.TopBarHeight);
shortcutBar.Background = Theme.ToolbarBg;
shortcutBar.Foreground = Theme.TitleText;
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("Evaluate", Evaluate));
shortcutBar.Render();
wm.AddWindow(shortcutBar);
editor = new CodeEditor(mainWindow, Layout.Side, Layout.TopBarHeight + Layout.Gap, 300, 200)
{ {
Background = Theme.CodeBackground, Background = Theme.EditorBg,
Foreground = Color.White, Foreground = Color.White,
Text = "print(\"Hello World!\")", Text = "print(\"Hello World!\")",
Changed = TextChanged, Changed = TextChanged,
@@ -327,42 +455,55 @@ namespace CMLeonOS.Gui.Apps.CodeStudio
editor.SetSyntaxHighlighting(true, ".lua"); editor.SetSyntaxHighlighting(true, ".lua");
wm.AddWindow(editor); wm.AddWindow(editor);
problems = new TextBox(mainWindow, 0, headersHeight + editor.Height + headersHeight, mainWindow.Width, problemsHeight + (headersHeight * 2)) sideTable = new Table(mainWindow, Layout.Side, Layout.TopBarHeight + Layout.Gap + Layout.PanelTitleHeight, Layout.SidePanelWidth, 300);
sideTable.CellHeight = 24;
sideTable.AllowDeselection = false;
sideTable.Background = Theme.PanelBg;
sideTable.Border = Theme.PanelBorder;
sideTable.Foreground = Theme.TitleText;
sideTable.SelectedBackground = Color.FromArgb(54, 72, 103);
sideTable.SelectedBorder = Color.FromArgb(88, 112, 152);
sideTable.SelectedForeground = Color.White;
sideTable.TableCellSelected = SideCommandSelected;
sideTable.Cells.Add(new TableCell("Open File"));
sideTable.Cells.Add(new TableCell("Save File"));
sideTable.Cells.Add(new TableCell("Save As"));
sideTable.Cells.Add(new TableCell("Evaluate"));
sideTable.Cells.Add(new TableCell("Run Script"));
sideTable.Cells.Add(new TableCell("Insert: Hello"));
sideTable.Cells.Add(new TableCell("Insert: GUI Demo"));
wm.AddWindow(sideTable);
problems = new TextBox(mainWindow, Layout.Side, 280, 300, Layout.BottomPanelHeight)
{ {
Background = Theme.CodeBackground, Background = Theme.PanelBg,
Foreground = Color.Gray, Foreground = Theme.HintText,
Text = "Click Evaluate to check your program for syntax errors.", Text = "Click Evaluate to check syntax.",
ReadOnly = true, ReadOnly = true,
MultiLine = true MultiLine = true
}; };
wm.AddWindow(problems); wm.AddWindow(problems);
mainWindow.DrawString("Problems", Color.White, 0, headersHeight + editor.Height); output = new TextBox(mainWindow, Layout.Side, 430, 300, Layout.BottomPanelHeight)
output = new TextBox(mainWindow, 0, headersHeight + editor.Height + problemsHeight + (headersHeight * 2), mainWindow.Width, outputHeight + (headersHeight * 2))
{ {
Background = Theme.CodeBackground, Background = Theme.PanelBg,
Foreground = Color.White, Foreground = Theme.TitleText,
Text = "Output will appear here...", Text = "Output will appear here...",
ReadOnly = true, ReadOnly = true,
MultiLine = true MultiLine = true
}; };
wm.AddWindow(output); wm.AddWindow(output);
mainWindow.DrawString("Output", Color.White, 0, headersHeight + editor.Height + problemsHeight + headersHeight); statusBar = new TextBox(mainWindow, 0, mainWindow.Height - Layout.BottomBarHeight, mainWindow.Width, Layout.BottomBarHeight)
var shortcutBar = new ShortcutBar(mainWindow, runButton.Width, 0, mainWindow.Width - runButton.Width, headersHeight)
{ {
Background = Theme.Background, Background = Theme.ToolbarBg,
Foreground = Color.White Foreground = Theme.HintText,
ReadOnly = true,
Text = "Ready"
}; };
shortcutBar.Cells.Add(new ShortcutBarCell("Open", OpenFilePrompt)); wm.AddWindow(statusBar);
shortcutBar.Cells.Add(new ShortcutBarCell("Save", Save));
shortcutBar.Cells.Add(new ShortcutBarCell("Save As", SaveAsPrompt));
shortcutBar.Cells.Add(new ShortcutBarCell("Evaluate", Evaluate));
shortcutBar.Render();
wm.AddWindow(shortcutBar);
ReLayout();
wm.Update(mainWindow); wm.Update(mainWindow);
} }
} }