163 lines
3.7 KiB
Zig
163 lines
3.7 KiB
Zig
const std = @import("std");
|
|
|
|
const Self = @This();
|
|
|
|
pub fn init() Self {
|
|
var self = Self{};
|
|
self.clear();
|
|
return self;
|
|
}
|
|
|
|
const vga_width = 80;
|
|
const vga_height = 25;
|
|
const vga_size = vga_width * vga_height;
|
|
|
|
pub const ConsoleColor = enum(u8) {
|
|
black = 0,
|
|
blue = 1,
|
|
green = 2,
|
|
cyan = 3,
|
|
red = 4,
|
|
magenta = 5,
|
|
brown = 6,
|
|
light_gray = 7,
|
|
dark_gray = 8,
|
|
light_blue = 9,
|
|
light_green = 10,
|
|
light_cyan = 11,
|
|
light_red = 12,
|
|
light_magenta = 13,
|
|
light_brown = 14,
|
|
white = 15,
|
|
};
|
|
|
|
const default_color = vgaEntryColor(ConsoleColor.light_gray, ConsoleColor.black);
|
|
|
|
column: usize = 0,
|
|
color: u8 = default_color,
|
|
buffer: [*]volatile u16 = @ptrFromInt(0xb8000),
|
|
|
|
fn vgaEntryColor(fg: ConsoleColor, bg: ConsoleColor) u8 {
|
|
return @intFromEnum(fg) | (@intFromEnum(bg) << 4);
|
|
}
|
|
|
|
fn vgaEntry(uc: u8, new_color: u8) u16 {
|
|
const c: u16 = new_color;
|
|
return uc | (c << 8);
|
|
}
|
|
|
|
fn index(x: usize, y: usize) usize {
|
|
return y * vga_width + x;
|
|
}
|
|
|
|
pub fn setColor(self: *Self, fg: ConsoleColor, bg: ConsoleColor) void {
|
|
self.color = vgaEntryColor(fg, bg);
|
|
}
|
|
|
|
pub fn resetColor(self: *Self) void {
|
|
self.color = default_color;
|
|
}
|
|
|
|
pub fn clear(self: *Self) void {
|
|
@memset(self.buffer[0..vga_size], vgaEntry(' ', self.color));
|
|
}
|
|
|
|
fn clearRow(self: *Self, y: usize) void {
|
|
for (0..vga_width) |x| {
|
|
const i = index(x, y);
|
|
self.buffer[i] = vgaEntry(' ', self.color);
|
|
}
|
|
}
|
|
|
|
fn newLine(self: *Self) void {
|
|
for (1..vga_height) |y| {
|
|
for (0..vga_width) |x| {
|
|
const entry = self.getEntryAt(x, y);
|
|
self.putEntryAt(entry, x, y - 1);
|
|
}
|
|
}
|
|
self.clearRow(vga_height - 1);
|
|
self.column = 0;
|
|
}
|
|
|
|
pub fn putCharAt(self: *Self, c: u8, new_color: u8, x: usize, y: usize) void {
|
|
self.putEntryAt(vgaEntry(c, new_color), x, y);
|
|
}
|
|
|
|
fn putEntryAt(self: *Self, entry: u16, x: usize, y: usize) void {
|
|
const i = index(x, y);
|
|
self.buffer[i] = entry;
|
|
}
|
|
|
|
fn getEntryAt(self: *Self, x: usize, y: usize) u16 {
|
|
const i = index(x, y);
|
|
return self.buffer[i];
|
|
}
|
|
|
|
pub fn putChar(self: *Self, c: u8) void {
|
|
switch (c) {
|
|
'\n' => self.newLine(),
|
|
else => {
|
|
if (self.column >= vga_width) {
|
|
self.newLine();
|
|
}
|
|
const y = vga_height - 1;
|
|
const x = self.column;
|
|
self.putCharAt(c, self.color, x, y);
|
|
self.column += 1;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn puts(self: *Self, data: []const u8) void {
|
|
for (data) |c| {
|
|
self.putChar(c);
|
|
}
|
|
}
|
|
|
|
const Writer = struct {
|
|
console: *Self,
|
|
interface: std.Io.Writer,
|
|
|
|
fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
|
|
const self: *@This() = @fieldParentPtr("interface", io_w);
|
|
|
|
try self.puts(io_w.buffered());
|
|
io_w.end = 0;
|
|
|
|
for (data[0..data.len - 1]) |bytes| {
|
|
try self.puts(bytes);
|
|
}
|
|
const pattern = data[data.len - 1];
|
|
for (0..splat) |_| {
|
|
try self.puts(pattern);
|
|
}
|
|
|
|
return std.Io.Writer.countSplat(data, splat);
|
|
}
|
|
|
|
fn puts(self: *@This(), data: []const u8) std.Io.Writer.Error!void {
|
|
self.console.puts(data);
|
|
}
|
|
};
|
|
|
|
pub fn writer(self: *Self, buffer: []u8) Writer {
|
|
return .{
|
|
.console = self,
|
|
.interface = .{
|
|
.buffer = buffer,
|
|
.vtable = &.{
|
|
.drain = Writer.drain,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn printf(self: *Self, comptime format: []const u8, args: anytype) void {
|
|
var buffer: [1024]u8 = undefined;
|
|
var w = self.writer(&buffer);
|
|
const io_w = &w.interface;
|
|
io_w.print(format, args) catch unreachable;
|
|
io_w.flush() catch unreachable;
|
|
}
|
|
|