diff --git a/README.md b/README.md index 4cba96f..66879cb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ ## Overview -Pygame GUI library, named after the creator of Python, Guido van Rossum, believing that this library will be the first to implement a GUI completely compatible with pygame. Included is a "mini-OS" to show off all the elements. +Pygame GUI library, named after the creator of Python, Guido van Rossum, believing that this library will be the first to implement a GUI completely compatible with pygame. Included is a "mini-OS" to show off all the elements. That "mini-OS" is called *Winnux 58* (**Win**dows 9**5** + **Win**dows 9**8** + Li**nux**). + +~~i kinda hate this name now~~ ## Features @@ -20,9 +22,32 @@ Please check our [wiki](https://github.com/The-UltimateGamer/GUIdo/wiki) for mor ## NEVER TRY TO MOUNT `files.img` ## Directory structure: -/home/ -- test.txt -(Note: this is created for the testing of the `cat` command.) -/sys/ -- kernel.py -(This is where our OS boots.) +- `/home/` + + - test.txt + (Note: this is created for the testing of the `cat` command.) + +- `/sys/` + + - kernel.py + (This is where our OS boots. **DO NOT TRY TO DELETE IT.**) + +## License Notice + +Winnux 58 is brought to you under the [GNU AGPL-3.0 License](https://www.gnu.org/licenses/agpl-3.0.en.html). + +OS emulator using pygame. +Copyright (C) 2020 The-UltimateGamer & pythonleo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . \ No newline at end of file diff --git a/README.pdf b/README.pdf new file mode 100644 index 0000000..2b72003 Binary files /dev/null and b/README.pdf differ diff --git a/files.img b/files.img index eafb9f1..4696092 100644 --- a/files.img +++ b/files.img @@ -575,3 +575,12 @@ If you're seeing this, then the `cat` command is working! !LOC=/ !FNAME=home/ + + + +!LOC=/ +!FNAME=a.txt + + + + diff --git a/kernel.py b/kernel.py new file mode 100644 index 0000000..af62be5 --- /dev/null +++ b/kernel.py @@ -0,0 +1,563 @@ +# kernel.py +# Core of our OS + +import pygame, sys, random, time, io, os +from math import * +from contextlib import redirect_stdout +from enum import Enum + +width, height = 1024, 768 + +def pwd(working_dir, args): + print(working_dir, end='\r') + +def ls(working_dir, args, flag=True): + files = open("files.img", 'r+', encoding="ISO-8859-1") + lines = files.read().strip('\0').split('\n') + + result = [] + for i, line in enumerate(lines): + if line == "!LOC=%s" % working_dir: + if flag: print(lines[i + 1].strip('!FNAME='), end='\r') + result.append(lines[i + 1].strip('!FNAME=')) + return result + +def cat(working_dir, args): + if not args: + print("Missing argument.\rUsage: cat ", end='\r') + return + for target in args: + files = open("files.img", 'r+', encoding="ISO-8859-1") + lines = files.read().strip('\0').split('\n') + contents = [] + for i, line in enumerate(lines): + if line == '!LOC=%s' % working_dir \ + and lines[i + 1] == '!FNAME=%s' % target: + for i in range(i+2, len(lines)): + if lines[i] and lines[i][0] == '!': break + contents.append(lines[i]) + break + else: + print("cat: %s: no such file" % target, end = '\r') + print('\r'.join(contents)) + files.close() + +def rm(working_dir, args): + files = open("files.img", 'r+', encoding="ISO-8859-1") + to_be_written = [] + target = args[0] + lines = files.read().strip('\0').split('\n') + write_or_not = True + for i, line in enumerate(lines): + if write_or_not: + if line == '!FNAME=%s' % target \ + and lines[i-1] == '!LOC=%s' % working_dir: + write_or_not = False + to_be_written.pop() + else: + if line and line[0] == '!': + write_or_not = True + if write_or_not: + to_be_written.append(line) + files.close() + fw = open("files.img", 'w+', encoding="ISO-8859-1") + fw.write('\n'.join(to_be_written)) + fw.close() + +class Pic(object): + def __init__(self, fileName): + img = pygame.image.load(fileName) + self.img = pygame.transform.scale(img, (width, height)) + self.x, self.y = 0, 0 + self.w, self.h = self.img.get_size() + def draw(self, screen, speed = 5): + screen.blit(self.img, (self.x, self.y), (0, 0, self.w, self.h)) + pygame.draw.rect(screen, (0, 255, 0), (0, 0, speed * 8, 8), 0) + def getPixelGrid(self, x0, y0, sideLen, pixelGrid): + n = sideLen // 2 + for y in range(-n, n + 1): + for x in range(-n, n + 1): + pixelGrid[y + n][x + n] = self.img.get_at((x + x0, y + y0)) + +class Kernel: + def __init__(self): + pygame.init() + self.screen = pygame.display.set_mode((width, height)) + pygame.display.set_caption("Winnux 58") + self.clock = pygame.time.Clock() + self.raster = pygame.font.Font("res/Perfect_DOS_VGA_437.ttf", 15) + self.speed = 5 + self.mousePos = (0, 0) + self.apps = [] + self.appID = 0 + self.dialogs = [] + self.dialogID = 0 + def launch(self): + for app in self.apps: + app.draw(self.screen) + for dialog in self.dialogs: + try: + dialog.draw(self.screen) + except: + pass + pygame.display.update() + self.clock.tick(50) + def addApp(self, app): + app.appID = len(self.apps) + self.apps.append(app) + def addDialog(self, dialog): + dialog.dialogID = len(self.dialogs) + dialog.closeBtn = Secret((dialog.x + 357, dialog.y + 6, 17, 16), dialog.dialogID) + self.dialogs.append(dialog) + def keyUp(self, key): + self.apps[self.appID].keyUp(key) + def keyDown(self, key): + self.apps[self.appID].keyDown(key) + def mouseDown(self, pos, button): + self.apps[self.appID].mouseDown(pos, button) + try: + self.dialogs[self.dialogID].mouseDown(pos, button) + except: + pass + print(event.pos) + def mouseUp(self, pos, button): + self.apps[self.appID].mouseUp(pos, button) + def mouseMotion(self, pos): + self.apps[self.appID].mouseMotion(pos) + +class App: + def __init__(self, picName): + self.pic = Pic(picName) + self.appID = 0 + self.btnList = [] + self.tooltipList = [] + self.txtField = TxtField(0, 0, 0, 0) + self.txtFieldEnabled = False + self.canvas = pygame.Surface((width, height - 60)) + self.canvasEnabled = False + self.secretList = [] + def draw(self, screen): + if framework.appID != self.appID: + return + screen.blit(self.pic.img, (0, 0)) + for button in self.btnList: + button.draw(screen) + for tooltip in self.tooltipList: + tooltip.draw(screen) + if self.txtFieldEnabled: + self.txtField.content = self.txtField.wrap(self.txtField.txtBuffer) + self.txtField.content = self.txtField.content[-self.txtField.h:] + self.txtField.draw(screen, self.txtField.content, (255, 255, 255), self.txtField.y) + if self.canvasEnabled: + if self.appID == snake.appID: + snakeGame.draw(self.canvas) + snakeGame.move() + framework.screen.blit(self.canvas, (0, 60)) + def addButton(self, b): + self.btnList.append(b) + def addTooltip(self, txt, font, x, y, c, rect): + tt = Tooltip(txt, font, x, y, c, rect) + self.txtList.append(tt) + def enableTxtField(self, x, y, w, h): + if self.canvasEnabled: + print("Only one of either the text field or the canvas can be enabled in an App.") + return + self.txtFieldEnabled = True + self.txtField.x, self.txtField.y = x, y + self.txtField.w, self.txtField.h = w, h + def enableCanvas(self): + if self.txtFieldEnabled: + print("Only one of either the text field or the canvas can be enabled in an App.") + return + self.canvasEnabled = True + def mouseDown(self, pos, button): + for btn in self.btnList: + btn.mouseDown(pos, button) + for secret in self.secretList: + secret.mouseDown(pos, button) + def mouseUp(self, pos, button): + for button in self.btnList: + button.mouseUp(pos, button) + def mouseMotion(self, pos): + framework.mousePos = pos + for btn in self.btnList: + btn.mouseMove(pos) + def keyUp(self, key): + if self.txtFieldEnabled: + self.txtField.keyUp(key) + def keyDown(self, key): + if self.txtFieldEnabled: + self.txtField.keyDown(key) + if self.canvasEnabled: + if self.appID == snake.appID: + snakeGame.keyDown(key) + +class Button: + def __init__(self, picFile, x, y, appID, **txt): + self.img = pygame.image.load(picFile).convert() + self.w, self.h = self.img.get_width() // 3, self.img.get_height() + self.x, self.y = x, y + self.rect = pygame.Rect(self.x, self.y, self.w, self.h) + self.status = 0 + self.appID = appID + self.txt = txt + def draw(self, screen): + screen.blit(self.img, (self.x, self.y), + (self.status * self.rect.w, 0, + self.rect.w, self.rect.h)) + if self.txt: + screen.blit(self.txt["font"].render(self.txt["content"], True, (0,0,0)), \ + (self.x + self.w // 2 - 4 * len(self.txt["content"]), self.y + self.h // 2 - 8)) + def mouseDown(self, pos, button): + if self.rect.collidepoint(pos): + self.status = 2 + def mouseUp(self, pos, button): + self.status = 0 + if not self.rect.collidepoint(pos): + return + framework.apps[self.appID].pic.draw(framework.screen, framework.speed) + framework.appID = self.appID + def mouseMove(self, pos): + if self.rect.collidepoint(pos): + self.status = 1 + else: + self.status = 0 + +class Tooltip: + def __init__(self, txt, font, x, y, c, rect): + self.txt = txt + self.img = font.render(txt, True, c) + self.x, self.y = x, y + self.c = c + self.rect = pygame.Rect(rect) + def draw(self, screen): + if self.rect.collidepoint(framework.mousePos): + screen.blit(self.img, (self.x, self.y)) + +class TxtField: + def __init__(self, x, y, w, h): + self.x, self.y = x, y + self.w, self.h = w, h + self.pwd = '/' + self.placeholder = ['%s# ' % self.pwd] + self.txtBuffer = [] + self.content = [] + # MAGIC # + self.caps = { '`': '~', '1': '!', '2': '@', '3': '#', '4': '$', '5': '%', '6': '^', '7': '&', '8': '*', '9': '(', '0': ')', '-': '_', '=': '+', '[': '{', ']': '}', '\\': '|', ';': ':', '\'': '"', ',': '<', '.': '>', '/': '?', 'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'u': 'U', 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y', 'z': 'Z' } + self.shift, self.capsLock = False, False + self.currentLine, self.loc, self.maxIndex = 0, 0, 0 + self.cLineStr = "" + self.raster = pygame.font.Font("res/Perfect_DOS_VGA_437.ttf", 16) + def wrap(self, txtBuffer): + lines = [] + ptr = 0 + prev_i = 0 + ph = self.placeholder[ptr] + if self.loc > self.maxIndex: + self.loc = self.maxIndex + if self.loc < 0: self.loc = 0 + if self.maxIndex < 0: self.loc = 0 + for i in range(len(txtBuffer)): + if txtBuffer[i] == '\n': + self.currentLine = i + 1 + lines.append(ph) + ptr += 1 + ph = self.placeholder[ptr] + prev_i = i + elif txtBuffer[i] == '\r' or (i != 0 and (i - prev_i) % self.w == 0): + ph += txtBuffer[i] if txtBuffer[i] not in ('\n', '\r', ' ', '') else '' + self.currentLine = i + 1 + lines.append(ph) + ph = '' + prev_i = i + elif txtBuffer[i] == '\t': + ph += ' ' + else: + ph += txtBuffer[i] + lines.append(ph) + return lines + def draw(self, screen, lines, c = (255, 255, 255), y = 0): + for line in lines: + img = self.raster.render(line, True, c) + screen.blit(img, (self.x, y)) + y += 16 + def cd(self, dir_name): + def process_dir(dr: str): + for d in dr.split('/'): + if d: + if d[0] == '.': + if d == '..': + self.pwd = "" + try: nTemp.pop() + except: pass + for i in nTemp: + if i: + self.pwd += '/' + i + self.pwd += '/' + elif (d + '/') in ls(self.pwd, [], False): + self.pwd += d + '/' + else: + print("cd: %s: no such directory." % d, end='\r') + return + temp = self.pwd.split('/') + nTemp = [] + self.pwd = "" + for i in temp: + if i: + self.pwd += '/' + i + nTemp.append(i) + self.pwd += '/' + if dir_name[0] == '/': + self.pwd = '/' + dir_name = dir_name[1:] + process_dir(dir_name) + def vis(self, args): + if not args: + print("vis: missing argument.\rUsage: vis ", end='\r') + if type(args) == type([]): + print("vis: too many arguments.\rUsage: vis ", end='\r') + if '/' in args: + print("No.", end='\r') + return + while True: + rm(self.pwd, [args]) + files = open("files.img", 'a+', encoding="ISO-8859-1") + files.write("\n\n!LOC=" + self.pwd + "\n") + files.write("!FNAME=" + args + "\n") + self.txtBuffer.append('\r') + self.txtBuffer.append('\r') + while True: + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + self.txtBuffer = [] + self.content = [] + self.keyDown(event.key, True) + files.write(self.cLineStr + '\n') + framework.launch() + elif event.type == pygame.KEYUP: + if event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT: + self.shift = False + elif event.key == pygame.K_CAPSLOCK: + self.capsLock = 1 - self.capsLock + elif event.type == pygame.QUIT: + pygame.quit() + sys.exit() + framework.launch() + files.close() + def exec_cmd(self, on_scr): + cmd = on_scr.split() + try: main = cmd.pop(0) + except: main = None + output = io.StringIO() + with redirect_stdout(output): + if main: + if main == 'cd': self.cd(cmd[0]) + elif main == 'vis': self.vis(cmd[0]) + else: + try: exec("%s('%s', %s)" % (main, self.pwd, str(cmd))) + except: print("%s: command not found" % on_scr, end='') + else: pass + self.txtBuffer.append('\r') + self.txtBuffer += list(output.getvalue().rstrip('\n')) + self.placeholder.append('%s# ' % self.pwd) + def keyUp(self, key): + if key == pygame.K_LSHIFT or key == pygame.K_RSHIFT: + self.shift = False + elif key == pygame.K_CAPSLOCK: + self.capsLock = 1 - self.capsLock + def keyDown(self, key, isVis = False): + if key == pygame.K_BACKSPACE: + if (self.txtBuffer and self.txtBuffer[-1] != '\n') or not self.txtBuffer: + try: + if self.loc - 1 != -1: + self.txtBuffer.pop(self.loc - 1) + self.maxIndex -= 1 + self.loc -= 1 + except: pass + elif key == pygame.K_DELETE: + try: + if self.loc > self.maxIndex: + self.loc = self.maxIndex + self.txtBuffer.pop(self.currentLine + self.loc) + self.maxIndex -= 1 + except: pass + elif key == pygame.K_RETURN: + cmd = '' + self.loc = 0 + self.maxIndex = 0 + i = -1 + if isVis: + while len(self.txtBuffer) > - i - 1 and self.txtBuffer[i] != '\r': + cmd = self.txtBuffer[i] + cmd + i -= 1 + else: + while len(self.txtBuffer) > - i - 1 and self.txtBuffer[i] != '\n': + cmd = self.txtBuffer[i] + cmd + i -= 1 + if isVis: + self.cLineStr = cmd + self.txtBuffer.append('\r') + return + else: + self.exec_cmd(cmd) + self.txtBuffer.append('\n') + framework.launch() + elif key == pygame.K_TAB: + for i in range(4): + self.txtBuffer.append(' ') + self.loc += 1 + elif key == pygame.K_LSHIFT or key == pygame.K_RSHIFT: + self.shift = True + elif key == pygame.K_CAPSLOCK: + self.capsLock = 1 - self.capsLock + elif key == pygame.K_LEFT: + if self.loc != 0: + self.loc -= 1 + elif key == pygame.K_RIGHT: + if self.loc + 1 <= self.maxIndex: + self.loc += 1 + else: + if 32 <= key <= 126: + if (key == 39 or 44 <= key <= 57 or key == 59 or key == 61 or key == 96 or 91 <= key <= 93) and self.shift: + self.txtBuffer.insert(self.loc, self.caps[chr(key)]) + elif 97 <= event.key <= 122 and (self.shift or self.capsLock): + self.txtBuffer.insert(self.loc, self.caps[chr(key)]) + else: + self.txtBuffer.insert(self.loc, chr(key)) + self.loc += 1 + self.maxIndex += 1 + framework.launch() + print(self.txtBuffer, self.loc, self.maxIndex) + +class Secret: + def __init__(self, rect, dialogID): + self.rect = pygame.Rect(rect) + self.dialogID = dialogID + def mouseDown(self, pos, button): + if self.rect.collidepoint(pos): + framework.dialogs.pop() + +class DlgStatus(Enum): + INFO = 0 + WARNING = 1 + ERROR = 2 + +class Dialog: + def __init__(self, title, content, status=DlgStatus.INFO): + self.img = pygame.image.load("res/dialog/dialog.png") + self.icon = pygame.transform.scale(pygame.image.load("res/dialog/" + str(status) + ".bmp"), (32, 32)) + self.icon.set_colorkey((255, 0, 255)) + self.raster = pygame.font.Font("res/Perfect_DOS_VGA_437.ttf", 16) + self.title = self.raster.render(title, True, (255, 255, 255)) + self.content = self.wrap(content, 35) + self.dialogID = 0 + self.w, self.h = self.img.get_size() + self.x, self.y = 322, 284 + self.closeBtn = None + def wrap(self, txt, w): + processed = [] + temp = "" + for i in range(txt.__len__()): + if i != 0 and i % w == 0: + processed.append(self.raster.render(temp, True, (0, 0, 0))) + temp = "" + temp += txt[i] + processed.append(self.raster.render(temp, True, (0, 0, 0))) + return processed + def draw(self, screen): + screen.blit(self.img, (self.x, self.y)) + screen.blit(self.title, (self.x + 10, self.y + 6)) + screen.blit(self.icon, (self.x + 30, self.y + 90)) + for i in range(len(self.content)): + screen.blit(self.content[i], (self.x + 80, self.y + 35 + i * 16)) + def mouseDown(self, pos, button): + self.closeBtn.mouseDown(pos, button) + +class SnakeGame: + def __init__(self): + self.tileWidth = 32 + self.x0, self.y0 = 192, 144 + self.snakeLen = 1 + self.snakePos = [] + self.direction = 0 + self.speedX = (1, 0, -1, 0) + self.speedY = (0, 1, 0, -1) + self.sx, self.sy = 0, 0 + self.ix, self.iy = random.randint(0, 19), random.randint(0, 14) + self.lost = 0 + self.bigFont = pygame.font.Font("res/Perfect_DOS_VGA_437.ttf", 100) + self.smallFont = pygame.font.Font("res/Perfect_DOS_VGA_437.ttf", 60) + self.bg = pygame.Surface((640, 480)) + self.bg.fill((255, 255, 255)) + self.gameOver = self.bigFont.render("Game Over!", True, (255, 255, 255)) + self.restart = self.smallFont.render("PRESS R TO RESTART", True, (255, 255, 255)) + def draw(self, canvas): + canvas.fill((40, 40, 40)) + if self.lost == 1: + canvas.blit(self.gameOver, (250, 200)) + canvas.blit(self.restart, (230, 300)) + return + canvas.blit(self.bg, (self.x0, self.y0)) + for pos in self.snakePos[-self.snakeLen:]: + pygame.draw.rect(canvas, (60, 110, 5), (self.x0 + pos[0] * self.tileWidth, self.y0 + pos[1] * self.tileWidth, self.tileWidth, self.tileWidth)) + pygame.draw.rect(canvas, (190, 30, 50), (self.x0 + self.ix * self.tileWidth, self.y0 + self.iy * self.tileWidth, self.tileWidth, self.tileWidth)) + def move(self): + if self.lost == 1: + return + self.sx = (self.sx + self.speedX[self.direction]) % 20 + self.sy = (self.sy + self.speedY[self.direction]) % 15 + if (self.sx, self.sy) in self.snakePos[-self.snakeLen:]: + self.lost = 1 + self.snakePos.append((self.sx, self.sy)) + if self.sx == self.ix and self.sy == self.iy: + self.ix, self.iy = random.randint(0, 19), random.randint(0, 14) + self.snakeLen += 1 + def keyDown(self, key): + if key == pygame.K_UP: + self.direction = 3 + if key == pygame.K_DOWN: + self.direction = 1 + if key == pygame.K_LEFT: + self.direction = 2 + if key == pygame.K_RIGHT: + self.direction = 0 + if key == pygame.K_r and self.lost == 1: + self.snakeLen = 1 + self.snakePos = [] + self.sx, self.sy = 0, 0 + self.ix, self.iy = random.randint(0, 19), random.randint(0, 14) + self.lost = 0 + +framework = Kernel() +bg = App("res/clouds.jpg") +term = App("res/blank.jpg") +snake = App("res/blank.jpg") +snake.enableCanvas() +snakeGame = SnakeGame() +framework.appID = bg.appID +framework.addApp(bg) +framework.addApp(term) +framework.addApp(snake) +framework.addDialog(Dialog("Hey there!", "Welcome to Winnux 58!")) +bg.addButton(Button("res/button/txt_btn.bmp", 20, 20, term.appID, font=framework.raster, content="TERMINAL")) +bg.addButton(Button("res/button/txt_btn.bmp", 20, 60, snake.appID, font=framework.raster, content="SNAKE")) +term.addButton(Button("res/button/txt_btn.bmp", width // 2 - 35, 20, bg.appID, font=framework.raster, content="CLOSE")) +snake.addButton(Button("res/button/txt_btn.bmp", width // 2 - 35, 20, bg.appID, font=framework.raster, content="CLOSE")) +term.enableTxtField(50, 60, 100, 40) + +while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + if event.type == pygame.KEYDOWN: + framework.keyDown(event.key) + elif event.type == pygame.KEYUP: + framework.keyUp(event.key) + if event.type == pygame.MOUSEBUTTONDOWN: + framework.mouseDown(event.pos, event.button) + elif event.type == pygame.MOUSEBUTTONUP: + framework.mouseUp(event.pos, event.button) + elif event.type == pygame.MOUSEMOTION: + framework.mouseMotion(event.pos) + framework.launch() \ No newline at end of file diff --git a/wiki/FAQ.md b/wiki/FAQ.md new file mode 100644 index 0000000..77a0892 --- /dev/null +++ b/wiki/FAQ.md @@ -0,0 +1,14 @@ +## What do I need? +You will need: +- A working Python interpreter, version 3 and up +- The [pygame module](pygame.org) installed + +## How do I run the program? +Run `stub.py` either by double-clicking on it (in Windows) or using the command line. + +## FAQ for `vis` +### How do I use `vis`? +When you open it up through the virtual terminal, you will be put in *input mode*, which means whatever you type will be written into the file. `vis` is a *line-based* editor, meaning you can only edit the line you're on. + +### Where do the files go? +Everything you created in Winnux 58 will go into the `files.img` *virtual disk*. You can view them using the `cat` command in the virtual terminal. \ No newline at end of file diff --git a/wiki/FAQ.pdf b/wiki/FAQ.pdf new file mode 100644 index 0000000..d22410c Binary files /dev/null and b/wiki/FAQ.pdf differ