MarkIt查看器桌面版

This commit is contained in:
2026-03-24 22:10:25 +08:00
parent 10e3a9da96
commit a0eaf70304
5 changed files with 449 additions and 4 deletions

View File

@@ -138,6 +138,7 @@ namespace CMLeonOS.Gui
RegisterApp(new AppMetadata("Memory Statistics", () => { return new Apps.MemoryStatistics(); }, Icons.Icon_MemoryStatistics, Color.FromArgb(25, 25, 25)));
RegisterApp(new AppMetadata("CodeStudio", () => { return new Apps.CodeStudio.CodeStudio(); }, Icons.Icon_CodeStudio, Color.FromArgb(14, 59, 76)));
RegisterApp(new AppMetadata("Image Viewer", () => { return new ImageViewer(); }, Icons.Icon_Default, Color.FromArgb(25, 25, 25)));
RegisterApp(new AppMetadata("MarkIt Viewer", () => { return new MarkItViewer(); }, Icons.Icon_Default, Color.FromArgb(25, 25, 25)));
Logger.Logger.Instance.Info("AppManager", $"{AppMetadatas.Count} apps were registered.");

View File

@@ -104,7 +104,7 @@ namespace CMLeonOS.Gui.Apps
return extension switch
{
".txt" or ".md" or ".log" => Icons.Icon_File_Text,
".txt" or ".md" or ".log" or ".mi" => Icons.Icon_File_Text,
".rs" => Icons.Icon_File_Rs,
".ini" or ".cfg" => Icons.Icon_File_Config,
_ => Icons.Icon_File
@@ -246,6 +246,10 @@ namespace CMLeonOS.Gui.Apps
{
ProcessManager.AddProcess(this, new ImageViewer(path)).Start();
}
else if (extension == ".mi")
{
ProcessManager.AddProcess(this, new MarkItViewer(path)).Start();
}
else
{
ShowOpenWithPrompt(path);
@@ -272,6 +276,9 @@ namespace CMLeonOS.Gui.Apps
case "Image Viewer":
ProcessManager.AddProcess(this, new ImageViewer(path)).Start();
break;
case "MarkIt Viewer":
ProcessManager.AddProcess(this, new MarkItViewer(path)).Start();
break;
default:
Logger.Logger.Instance.Warning("Files", $"Unsupported open-with app: {appName}");
break;
@@ -280,7 +287,7 @@ namespace CMLeonOS.Gui.Apps
private void ShowOpenWithPrompt(string path)
{
AppWindow openWithWindow = new AppWindow(this, 320, 240, 300, 176);
AppWindow openWithWindow = new AppWindow(this, 320, 240, 300, 206);
openWithWindow.Title = "Open With";
openWithWindow.Icon = AppManager.DefaultAppIcon;
wm.AddWindow(openWithWindow);
@@ -317,6 +324,15 @@ namespace CMLeonOS.Gui.Apps
};
wm.AddWindow(imageViewerButton);
Button markItButton = new Button(openWithWindow, 12, 98, 84, 24);
markItButton.Text = "MarkIt";
markItButton.OnClick = (_, _) =>
{
wm.RemoveWindow(openWithWindow);
OpenWithApp(path, "MarkIt Viewer");
};
wm.AddWindow(markItButton);
Button cancelButton = new Button(openWithWindow, openWithWindow.Width - 80 - 12, openWithWindow.Height - 20 - 12, 80, 20);
cancelButton.Text = "Cancel";
cancelButton.OnClick = (_, _) =>

428
Gui/Apps/MarkItViewer.cs Normal file
View File

@@ -0,0 +1,428 @@
using CMLeonOS;
using CMLeonOS.Gui.SmoothMono;
using CMLeonOS.Gui.UILib;
using CMLeonOS.Logger;
using CMLeonOS.Utils;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
namespace CMLeonOS.Gui.Apps
{
internal class MarkItViewer : Process
{
internal MarkItViewer() : base("MarkIt Viewer", ProcessType.Application) { }
internal MarkItViewer(string path) : base("MarkIt Viewer", ProcessType.Application)
{
initialPath = path;
}
private struct StyledGlyph
{
internal char Character;
internal Color Foreground;
internal Color Background;
internal bool HasBackground;
}
private class StyledLine
{
internal List<StyledGlyph> Glyphs { get; } = new List<StyledGlyph>();
}
private AppWindow window;
private Window statusHeader;
private Window contentView;
private Button openButton;
private Button upButton;
private Button downButton;
private readonly WindowManager wm = ProcessManager.GetProcess<WindowManager>();
private FileBrowser fileBrowser;
private readonly List<StyledLine> lines = new List<StyledLine>();
private int scrollLine = 0;
private string currentPath = string.Empty;
private string initialPath = string.Empty;
private string statusText = "Open a MarkIt file to preview.";
private const int toolbarHeight = 32;
private const int headerHeight = 50;
private const int padding = 8;
private const int buttonWidth = 64;
private const int buttonHeight = 24;
private const int contentInset = 8;
private static bool MatchesAt(string text, int index, string token)
{
if (index < 0 || token == null || index + token.Length > text.Length)
{
return false;
}
for (int i = 0; i < token.Length; i++)
{
if (text[index + i] != token[i])
{
return false;
}
}
return true;
}
private static Color ParseColor(string colorName)
{
if (string.IsNullOrWhiteSpace(colorName))
{
return UITheme.TextPrimary;
}
string name = colorName.ToLower().Trim();
switch (name)
{
case "black": return Color.Black;
case "darkblue": return Color.DarkBlue;
case "darkgreen": return Color.DarkGreen;
case "darkcyan": return Color.DarkCyan;
case "darkred": return Color.DarkRed;
case "darkmagenta": return Color.DarkMagenta;
case "darkyellow": return Color.FromArgb(112, 96, 0);
case "gray": return Color.Gray;
case "darkgray": return Color.DarkGray;
case "blue": return Color.Blue;
case "green": return Color.Green;
case "cyan": return Color.Cyan;
case "red": return Color.Red;
case "magenta": return Color.Magenta;
case "yellow": return Color.Yellow;
case "white": return Color.White;
default: return UITheme.TextPrimary;
}
}
private void SetStatus(string text)
{
statusText = text;
RenderHeader();
}
private void RenderHeader()
{
statusHeader.Clear(Color.FromArgb(235, 241, 248));
statusHeader.DrawRectangle(0, 0, statusHeader.Width, statusHeader.Height, Color.FromArgb(180, 192, 208));
statusHeader.DrawString("MarkIt Viewer", Color.FromArgb(28, 38, 52), 12, 8);
statusHeader.DrawString(statusText, Color.FromArgb(97, 110, 126), 12, 26);
wm.Update(statusHeader);
}
private void Relayout()
{
openButton.MoveAndResize(window.Width - ((buttonWidth * 3) + (padding * 3)), padding, buttonWidth, buttonHeight);
upButton.MoveAndResize(window.Width - ((buttonWidth * 2) + (padding * 2)), padding, buttonWidth, buttonHeight);
downButton.MoveAndResize(window.Width - (buttonWidth + padding), padding, buttonWidth, buttonHeight);
int contentY = toolbarHeight + headerHeight;
statusHeader.MoveAndResize(0, toolbarHeight, window.Width, headerHeight);
contentView.MoveAndResize(0, contentY, window.Width, window.Height - contentY);
openButton.Render();
upButton.Render();
downButton.Render();
RenderHeader();
RenderContent();
}
private void ResetDocument()
{
lines.Clear();
lines.Add(new StyledLine());
scrollLine = 0;
currentPath = string.Empty;
window.Title = "MarkIt Viewer";
SetStatus("Open a MarkIt file to preview.");
RenderContent();
}
private void ParseDocument(string content)
{
lines.Clear();
lines.Add(new StyledLine());
Color currentForeground = UITheme.TextPrimary;
Color currentBackground = UITheme.Surface;
bool hasBackground = false;
int i = 0;
while (i < content.Length)
{
if (MatchesAt(content, i, "{/color}"))
{
currentForeground = UITheme.TextPrimary;
i += 8;
continue;
}
if (MatchesAt(content, i, "{/bg}"))
{
hasBackground = false;
i += 5;
continue;
}
if (MatchesAt(content, i, "{color:"))
{
int end = content.IndexOf('}', i);
if (end > i + 7)
{
currentForeground = ParseColor(content.Substring(i + 7, end - (i + 7)));
i = end + 1;
continue;
}
}
if (MatchesAt(content, i, "{bg:"))
{
int end = content.IndexOf('}', i);
if (end > i + 4)
{
currentBackground = ParseColor(content.Substring(i + 4, end - (i + 4)));
hasBackground = true;
i = end + 1;
continue;
}
}
char ch = content[i];
if (ch == '\r')
{
i++;
continue;
}
if (ch == '\n')
{
lines.Add(new StyledLine());
i++;
continue;
}
lines[lines.Count - 1].Glyphs.Add(new StyledGlyph
{
Character = ch,
Foreground = currentForeground,
Background = currentBackground,
HasBackground = hasBackground
});
i++;
}
if (lines.Count == 0)
{
lines.Add(new StyledLine());
}
}
private int VisibleLineCount()
{
int visible = (contentView.Height - (contentInset * 2)) / FontData.Height;
return Math.Max(1, visible);
}
private int VisibleColumnCount()
{
int visible = (contentView.Width - (contentInset * 2)) / FontData.Width;
return Math.Max(1, visible);
}
private void ClampScroll()
{
int maxScroll = Math.Max(0, lines.Count - VisibleLineCount());
if (scrollLine < 0)
{
scrollLine = 0;
}
if (scrollLine > maxScroll)
{
scrollLine = maxScroll;
}
}
private void RenderContent()
{
contentView.Clear(Color.FromArgb(252, 254, 255));
contentView.DrawRectangle(0, 0, contentView.Width, contentView.Height, Color.FromArgb(192, 204, 221));
ClampScroll();
int visibleLines = VisibleLineCount();
int visibleColumns = VisibleColumnCount();
int startLine = scrollLine;
int endLineExclusive = Math.Min(lines.Count, startLine + visibleLines);
int drawY = contentInset;
for (int lineIndex = startLine; lineIndex < endLineExclusive; lineIndex++)
{
StyledLine line = lines[lineIndex];
int drawX = contentInset;
int maxColumns = Math.Min(visibleColumns, line.Glyphs.Count);
for (int i = 0; i < maxColumns; i++)
{
StyledGlyph glyph = line.Glyphs[i];
if (glyph.HasBackground)
{
contentView.DrawFilledRectangle(drawX, drawY, FontData.Width, FontData.Height, glyph.Background);
}
contentView.DrawString(glyph.Character.ToString(), glyph.Foreground, drawX, drawY);
drawX += FontData.Width;
}
drawY += FontData.Height;
}
wm.Update(contentView);
}
private void UpdateStatusWithPosition()
{
if (lines.Count == 0)
{
SetStatus("No content.");
return;
}
int visible = VisibleLineCount();
int start = Math.Min(lines.Count, scrollLine + 1);
int end = Math.Min(lines.Count, scrollLine + visible);
if (string.IsNullOrWhiteSpace(currentPath))
{
SetStatus($"Lines {start}-{end} / {lines.Count}");
return;
}
SetStatus($"{Path.GetFileName(currentPath)} | Lines {start}-{end} / {lines.Count}");
}
private void ScrollUp(int x, int y)
{
scrollLine--;
ClampScroll();
RenderContent();
UpdateStatusWithPosition();
}
private void ScrollDown(int x, int y)
{
scrollLine++;
ClampScroll();
RenderContent();
UpdateStatusWithPosition();
}
private bool LoadFile(string path, bool showPopup = true)
{
if (string.IsNullOrWhiteSpace(path))
{
if (showPopup)
{
new MessageBox(this, "MarkIt Viewer", "Path is empty.").Show();
}
return false;
}
string sanitizedPath = PathUtil.Sanitize(path.Trim());
if (!File.Exists(sanitizedPath))
{
if (showPopup)
{
new MessageBox(this, "MarkIt Viewer", "File not found.").Show();
}
return false;
}
try
{
string content = File.ReadAllText(sanitizedPath);
ParseDocument(content);
scrollLine = 0;
currentPath = sanitizedPath;
window.Title = $"MarkIt Viewer - {Path.GetFileName(sanitizedPath)}";
RenderContent();
UpdateStatusWithPosition();
return true;
}
catch (Exception ex)
{
Logger.Logger.Instance.Error("MarkItViewer", $"Failed to open file: {ex.Message}");
if (showPopup)
{
new MessageBox(this, "MarkIt Viewer", "Unable to open this file.").Show();
}
return false;
}
}
private void OpenDialog()
{
fileBrowser = new FileBrowser(this, wm, (string selectedPath) =>
{
if (!string.IsNullOrWhiteSpace(selectedPath))
{
LoadFile(selectedPath);
}
});
fileBrowser.Show();
}
public override void Start()
{
base.Start();
window = new AppWindow(this, 180, 110, 760, 500);
window.Title = "MarkIt Viewer";
window.Icon = AppManager.DefaultAppIcon;
window.CanResize = true;
window.UserResized = Relayout;
window.Closing = TryStop;
wm.AddWindow(window);
openButton = new Button(window, 0, 0, 1, 1);
openButton.Text = "Open";
openButton.OnClick = (_, _) => OpenDialog();
wm.AddWindow(openButton);
upButton = new Button(window, 0, 0, 1, 1);
upButton.Text = "Up";
upButton.OnClick = ScrollUp;
wm.AddWindow(upButton);
downButton = new Button(window, 0, 0, 1, 1);
downButton.Text = "Down";
downButton.OnClick = ScrollDown;
wm.AddWindow(downButton);
statusHeader = new Window(this, window, 0, toolbarHeight, 1, 1);
wm.AddWindow(statusHeader);
contentView = new Window(this, window, 0, toolbarHeight + headerHeight, 1, 1);
wm.AddWindow(contentView);
ResetDocument();
Relayout();
if (!string.IsNullOrWhiteSpace(initialPath))
{
LoadFile(initialPath, showPopup: false);
}
wm.Update(window);
}
public override void Run()
{
}
}
}