Cosmos 大型光标/GUI 教程






4.84/5 (9投票s)
如何使用 Cosmos 开发一个高级操作系统 GUI。
欢迎来到我的第一个 Cosmos 教程(实际上也是我的第一篇文章)。今天我们将学习如何在 Cosmos 中绘制鼠标。我在网上找到了一些关于如何做这个的教程,但它们都没有解决主要问题。例如,如果只有一个像素,你再添加一个就会导致延迟。此外,它们往往会在屏幕上留下像素,并影响屏幕上的其他绘制。我编写了一个全屏绘制管理器,解决了所有这些问题,并允许您轻松绘制其他内容,如任务栏或窗口。
要真正理解我们在做什么,我们必须首先了解 Cosmos 如何绘制到屏幕。我将先给出一个代码示例,然后进行解释。
首先,我们创建一个变量 VGAScreen。
public static VGAScreen VScreen = new VGAScreen();
现在我们将设置它。
Console.WriteLine("Vga Driver Booting");
VScreen.SetGraphicsMode(VGAScreen.ScreenSize320x200, VGAScreen.ColorDepth.BitDepth8);
VScreen.Clear(0);
Console.WriteLine("Vga Driver Booted");
首先,我们打印 "Vga Driver Booting"
。接下来,我们将图形模式更改为 320x200,颜色深度为 8。然后我们用黑色(0)清空屏幕。之后,我们打印驱动已启动。这有点讽刺,因为在设置图形模式后,控制台将不再显示。也许我应该说:“VGA 启动失败”。
现在我们将绘制一个像素。
VScreen.SetPixel320x200x8((uint) x, (uint) y, (uint) c);
现在我们有了一个基本的黑色屏幕和一个不同颜色的像素,我们可以详细了解操作系统是如何设置像素的。归根结底,您的代码会变成汇编,而 CPU 运行的是汇编,所以要完全理解,我们必须回到原始硬件。VGA 的工作原理很简单,屏幕上的像素存储在内存中,这些内存通常在显示设备上。通过改变内存中的值,我们可以改变屏幕上的像素。唯一的问题是,分辨率越高,需要的空间就越大。考虑到内存大小总是固定的,实现更高分辨率的想法很困难,解决这个问题的方法是使用更少的空间来存储像素,这就是为什么更高分辨率的颜色深度更小(希望这有道理)。如果屏幕尺寸是 320x200,颜色深度是 8。这意味着每个像素可以是 255 种颜色之一(字节的大小),这意味着屏幕内存大小为 64000 字节。记住这一点很重要,因为当我们编写屏幕缓冲区时,我们将需要理解这一点。
回到像素在内存中实际如何被操作,让我们像这样把屏幕想象成一个字节数组。
public static byte[] SBuffer = new byte[64000];
如果每个字节是一个像素,第一个字节就是 x=0, y=0 的像素。数组中的第二个字节是 x=1, y=0 等等。所以如果我们想使用 x 和 y 坐标来索引数组,我们会使用一个简单的公式,如下所示:
Index = (Screen Width * y) + x
对于我们的屏幕尺寸,它将是:
Index = (320 * y) + x
让我们看看它是如何工作的,然后我将给出 C# 代码。好的,我们知道屏幕上的一行有 320 个像素。这意味着,如果我们想要屏幕上任何一行的第一个像素,我们只需要将宽度乘以行号,然后加上 x,我们就能得到那个像素在数组中的确切数字或索引。计算机也是这样做的。
这是 C# 代码:
public static void SetPixel(int x, int y, int color)
{
SBuffer[(y*320) + x] = (byte)color;
}
让我们看看 Cosmos 屏幕驱动如此缓慢的原因以及如何解决这个问题。它如此缓慢的原因是计算索引需要很长时间,并且内存写入操作在时间上很耗时。为了让事情变得更糟,驱动程序的工作方式使其更慢,但一个成本不高的操作是读取内存,而关键就在于此。基本上,我们解决这个问题的方法是仅在像素发生变化时才绘制它。事实证明,在绘制之前检查像素是否颜色不同比直接绘制它要快。无论如何,这是代码,然后我将解释它。
public static void ReDraw()
{
// VScreen.Clear(0);
int c = 0;
for (int y = 0; y < 200; y++)
{
for (int x = 0; x < 320; x++)
{
uint cl = VScreen.GetPixel320x200x8((uint) x, (uint) y);
if (cl != (uint)SBuffer[c])
{
VScreen.SetPixel320x200x8((uint) x, (uint) y, SBuffer[c]);
}
c++;
}
}
for (int i = 0; i < 64000; i++)
{
SBuffer[i] = 0;
}
}
这个比前面稍微复杂一些,但我相信我们会弄清楚的。这个方法中有三个循环。第一个循环是 y 轴,第二个循环是 x 轴。通过逐行逐列循环,我们仍然会遍历屏幕上的所有 64000 个像素,但我们会得到 x 和 y 坐标。变量 c
跟踪我们在缓冲区中使用的索引。总的来说,了解循环内部发生的事情是一个不错的想法。我们有一个 if 语句,它只是确保我们不会因为没有原因而进行耗时的设置操作。在此之后,我们有第三个循环,它只是用黑色填充缓冲区。恭喜!您现在已经了解了如何双缓冲屏幕。这将消除屏幕上的所有闪烁,屏幕将逐帧重绘,而不是逐像素重绘,这将大大提高整体感觉和性能。它将消除像所有其他博客推荐的那样手动处理上一帧的需要。既然我们可以正确地绘制到屏幕,让我们来绘制鼠标。
首先,我们将创建一个新的鼠标。
public static Mouse m = new Mouse();
现在我们需要启动鼠标,您需要在启动序列的某个地方进行此操作。
public static class BootManager
{
public static void Boot()
{
Screen.Boot();
Desktop.m.Initialize(320, 200);
}
}
现在我们将像这样绘制鼠标。
Screen.SetPixel(m.X,m.Y,40);
Screen.SetPixel(m.X+ 1,m.Y,40);
Screen.SetPixel(m.X + 2, m.Y, 40);
Screen.SetPixel(m.X, m.Y + 1, 40);
Screen.SetPixel(m.X, m.Y + 2, 40);
Screen.SetPixel(m.X+ 1, m.Y + 1, 40);
Screen.SetPixel(m.X + 2, m.Y + 2, 40);
Screen.SetPixel(m.X +3 , m.Y +3, 40);
如果我们运行此代码,我们会得到这个:
好了,在 Cosmos 中,您得到了一个漂亮的鼠标。如果您移动鼠标,它会流畅地移动 - 没有延迟 - 最重要的是,它永远不会留下残影。现在让我们添加一个“任务栏”(您需要将其添加到您的屏幕类中)。
public static void DrawFilledRectangle(uint x0, uint y0, int Width, int Height, int color)
{
for (uint i = 0; i < Width; i++)
{
for (uint h = 0; h < Height; h++)
{
SetPixel((int)(x0 + i), (int)(y0 + h), color);
}
}
}
我们现在要做的就是将此添加到代码中。
Screen.DrawFilledRectangle(0,0,320,25,50);
请记住,在绘制鼠标之前添加它,这样它就会在底部。这是最终结果:
本教程到此结束。我将在下方添加一个 Cosmos 项目的下载链接。