From 30cdaa55c954fd6d7a47f197a3fa2a63c5245c5c Mon Sep 17 00:00:00 2001 From: troido Date: Wed, 18 Sep 2019 00:32:35 +0200 Subject: [PATCH] switched curses to ratuil --- asciifarm/client/commandhandler.py | 16 +-- asciifarm/client/display/display.py | 26 ++-- asciifarm/client/display/switcher.py | 26 ++-- asciifarm/client/gameclient.py | 51 ++++++- asciifarm/client/inputhandler.py | 33 +++-- asciifarm/client/listselector.py | 49 +++++++ asciifarm/client/main.py | 61 +++++---- asciifarm/client/newdisplay/__init__.py | 0 asciifarm/client/newdisplay/display.py | 174 ++++++++++++++++++++++++ asciifarm/client/newdisplay/layout.xml | 30 ++++ asciifarm/client/switchselector.py | 33 +++++ 11 files changed, 419 insertions(+), 80 deletions(-) create mode 100644 asciifarm/client/listselector.py create mode 100644 asciifarm/client/newdisplay/__init__.py create mode 100644 asciifarm/client/newdisplay/display.py create mode 100644 asciifarm/client/newdisplay/layout.xml create mode 100644 asciifarm/client/switchselector.py diff --git a/asciifarm/client/commandhandler.py b/asciifarm/client/commandhandler.py index ca72b68..e55fb65 100644 --- a/asciifarm/client/commandhandler.py +++ b/asciifarm/client/commandhandler.py @@ -28,7 +28,7 @@ class CommandHandler: "runinput": self.runInput, "selectwidget": self.selectWidget, "selectitem": self.selectItem, - "inputwithselected": self.actWithSelected, + #"inputwithselected": self.actWithSelected, "use": self.useSelected, "unuse": self.unUseSelected, "take": self.takeSelected, @@ -95,16 +95,16 @@ class CommandHandler: self.client.inputHandler.startTyping(startText) def selectWidget(self, value, relative=False, modular=False): - self.client.display.getWidget("switch").select(value, relative, modular) + self.client.switch.select(value, relative, modular) def selectItem(self, value, relative=False, modular=False): - self.client.display.getWidget("switch").getSelectedItem().getImpl().select(value, relative, modular) + self.client.switch.getSelectedItem()[0].select(value, relative, modular) - def actWithSelected(self, action, widget): - self.input([action, self.client.display.getWidget(widget).getSelected()]) + #def actWithSelected(self, action, widget): + #self.input([action, self.client.display.getWidget(widget).getSelected()]) def useSelected(self): - widget = self.client.display.getWidget("switch").getSelectedItem() + widget = self.client.switch.getSelectedItem() selected = widget.getImpl().getSelected() if widget.name in ("inventory", "equipment"): action = "use" @@ -115,7 +115,7 @@ class CommandHandler: self.input([action, selected]) def unUseSelected(self): - widget = self.client.display.getWidget("switch").getSelectedItem() + menu = self.client.switch.getSelectedItem()[0] selected = widget.getImpl().getSelected() if widget.name == "inventory": action = "drop" @@ -126,7 +126,7 @@ class CommandHandler: self.input([action, selected]) def takeSelected(self): - widget = self.client.display.getWidget("switch").getSelectedItem() + widget = self.client.switch.getSelectedItem()[0] selected = widget.getImpl().getSelected() if widget.name == "ground": action = "take" diff --git a/asciifarm/client/display/display.py b/asciifarm/client/display/display.py index edc3f95..80850b3 100644 --- a/asciifarm/client/display/display.py +++ b/asciifarm/client/display/display.py @@ -65,8 +65,8 @@ class Display: self.addWidget(Inventory("Equipment"), "equipment") - switcher = Switcher([self.widgets["ground"], self.widgets["inventory"], self.widgets["equipment"]], 1) - self.addWidget(switcher, "switch") + #switcher = Switcher([self.widgets["ground"], self.widgets["inventory"], self.widgets["equipment"]], 1) + self.addWidget(Inventory(""), "switch") self.addWidget(Messages(charMap.get("msgcolours", {})), "msg") self.addWidget(TextInput(), "textinput") @@ -110,20 +110,20 @@ class Display: self.getWidget("info").showString(infostring) - def setInventory(self, items): - self.getWidget("inventory").setInventory(items) + #def setInventory(self, items): + #self.getWidget("inventory").setInventory(items) - def setEquipment(self, slots): - self.getWidget("equipment").setInventory( - sorted([ - slot + ": " + (item if item else "") - for slot, item in slots.items() - ]) - ) + #def setEquipment(self, slots): + #self.getWidget("equipment").setInventory( + #sorted([ + #slot + ": " + (item if item else "") + #for slot, item in slots.items() + #]) + #) - def setGround(self, items): - self.getWidget("ground").setInventory(items) + #def setGround(self, items): + #self.getWidget("ground").setInventory(items) def addMessage(self, message, type): diff --git a/asciifarm/client/display/switcher.py b/asciifarm/client/display/switcher.py index 035aea3..6e64fd0 100644 --- a/asciifarm/client/display/switcher.py +++ b/asciifarm/client/display/switcher.py @@ -7,22 +7,22 @@ class Switcher(Inventory): There is a function to switch between the displayed widgets. """ - def __init__(self, widgets, initial=0): - Inventory.__init__(self, "", "", "=") - self.setInventory(widgets) + #def __init__(self, widgets, initial=0): + #Inventory.__init__(self, "", "", "=") + #self.setInventory(widgets) - for wid in widgets: - wid.hidden = True + #for wid in widgets: + #wid.hidden = True - self.select(initial) + #self.select(initial) - def doSelect(self, value): - self.getSelectedItem().hidden = True - self.selector = value - self.change() - newWid = self.getSelectedItem() - newWid.hidden = False - newWid.change() + #def doSelect(self, value): + #self.getSelectedItem().hidden = True + #self.selector = value + #self.change() + #newWid = self.getSelectedItem() + #newWid.hidden = False + #newWid.change() def itemName(self, item): return item.getImpl().title diff --git a/asciifarm/client/gameclient.py b/asciifarm/client/gameclient.py index 2bd5420..46c8553 100644 --- a/asciifarm/client/gameclient.py +++ b/asciifarm/client/gameclient.py @@ -1,4 +1,4 @@ -#! /usr/bin/python3 + import os import sys @@ -10,13 +10,16 @@ import argparse import string from queue import Queue +import ratuil.inputs from .inputhandler import InputHandler +from .listselector import ListSelector +from .switchselector import SwitchSelector class Client: - def __init__(self, stdscr, display, name, connection, keybindings, logFile=None): - self.stdscr = stdscr + def __init__(self, display, name, connection, keybindings, logFile=None): + #self.stdscr = stdscr self.display = display self.name = name self.keepalive = True @@ -24,6 +27,17 @@ class Client: self.logFile = logFile self.closeMessage = None + # temporary, until these have a better place + self.inventory = ListSelector(self.display.getWidget("inventory")) + self.equipment = ListSelector(self.display.getWidget("equipment")) + self.ground = ListSelector(self.display.getWidget("ground")) + self.switch = SwitchSelector(self.display.getWidget("switch")) + #self.switch.setItems([ + #(self.inventory, None, "Inventory"), + #(self.equipment, None, "Equipment"), + #(self.ground, None, "Ground") + #]) + self.inputHandler = InputHandler(self, keybindings["actions"]) self.controlsString = keybindings.get("help", "") @@ -53,9 +67,13 @@ class Client: self.queue.put(("error", error)) def getInput(self): + #try: while True: - key = self.stdscr.getch() + key = ratuil.inputs.get_key() + #key = self.stdscr.getch() self.queue.put(("input", key)) + #except Exception as e: + #self.queue.put(("error", e)) def close(self, msg=None): self.keepalive = False @@ -107,11 +125,23 @@ class Client: if maxHealth is None: self.log("You have died. Restart the client to respawn") if msgType == "inventory": - self.display.setInventory(msg[1]) + self.inventory.setItems(msg[1]) + #invbox = self.display.getWidget("inventory") + #invbox.setInventory(self.inventory.items) + #invbox.select(self.inventory.selector) + #self.display.setInventory(msg[1]) if msgType == "equipment": - self.display.setEquipment(msg[1]) + #self.display.setEquipment(msg[1]) + self.equipment.setItems(msg[1]) + #eqbox = self.display.getWidget("equipment") + #eqbox.setInventory(self.inventory.items) + #eqbox.select(self.equipment.selector) if msgType == "ground": - self.display.setGround(msg[1]) + #self.display.setGround(msg[1]) + self.ground.setItems(msg[1]) + #grbox = self.display.getWidget("ground") + #grbox.setInventory(self.ground.items) + #grbox.select(self.ground.selector) if msgType == "message": self.log(*msg[1:]) if msgType == "options": @@ -138,12 +168,19 @@ class Client: if action[0] == "message": self.update(action[1]) elif action[0] == "input": + if action[1] == "^C": + raise KeyboardInterrupt self.inputHandler.onInput(action[1]) elif action[0] == "error": raise action[1] + elif action[0] == "sigwinch": + self.display.update_size() else: raise Exception("invalid action in queue") + def onSigwinch(self, signum, frame): + self.queue.put(("sigwinch", (signum, frame))) + diff --git a/asciifarm/client/inputhandler.py b/asciifarm/client/inputhandler.py index 2d038d2..de420c0 100644 --- a/asciifarm/client/inputhandler.py +++ b/asciifarm/client/inputhandler.py @@ -1,9 +1,12 @@ -import curses -import curses.ascii +#import curses +#import curses.ascii +import string from .commandhandler import CommandHandler, InvalidCommandException -from .keynames import nameFromKey +#from .keynames import nameFromKey + +import ratuil.inputs as inp class InputHandler: @@ -20,7 +23,7 @@ class InputHandler: def onInput(self, key): if not self.typing: - keyName = nameFromKey(key) + keyName = key#nameFromKey(key) if keyName in self.keybindings: self.commandHandler.execute(self.keybindings[keyName]) else: @@ -58,36 +61,36 @@ class InputHandler: self.client.display.setInputString(self.string, self.cursor if self.typing else -1) def addKey(self, key): - if curses.ascii.isprint(key): - self.string = self.string[:self.cursor] + chr(key) + self.string[self.cursor:] + if key in string.printable:#curses.ascii.isprint(key): + self.string = self.string[:self.cursor] + key + self.string[self.cursor:] self.cursor += 1 - elif key == curses.KEY_BACKSPACE or key == curses.ascii.BS or key == curses.ascii.DEL: + elif key == inp.BACKSPACE: #== curses.KEY_BACKSPACE or key == curses.ascii.BS or key == curses.ascii.DEL: self.string = self.string[:self.cursor-1] + self.string[self.cursor:] self.cursor = max(self.cursor - 1, 0) - elif key == curses.KEY_RIGHT: + elif key == inp.RIGHT:#curses.KEY_RIGHT: self.cursor = min(self.cursor + 1, len(self.string)) - elif key == curses.KEY_LEFT: + elif key == inp.LEFT:#curses.KEY_LEFT: self.cursor = max(self.cursor - 1, 0) - elif key == curses.KEY_DC: + elif key == inp.DELETE:#curses.KEY_DC: self.string = self.string[:self.cursor] + self.string[self.cursor+1:] - elif key == curses.KEY_HOME: + elif key == inp.HOME:#curses.KEY_HOME: self.cursor = 0 - elif key == curses.KEY_END: + elif key == inp.END:#curses.KEY_END: self.cursor = len(self.string) - elif key == curses.ascii.ESC or key == curses.KEY_DL: + elif key == inp.ESCAPE:#curses.ascii.ESC or key == curses.KEY_DL: # throw away entered string and go back to game self.typing = False self.string = "" self.cursor = 0 - elif key == curses.ascii.LF or key == curses.ascii.CR: + elif key == inp.ENTER:#curses.ascii.LF or key == curses.ascii.CR: # process entered string and reset it message = self.string self.string = "" self.cursor = 0 self.typing = False self.processString(message) - elif key == curses.ascii.TAB: + elif key == "^I":#curses.ascii.TAB: # return to game but keep entered string self.typing = False diff --git a/asciifarm/client/listselector.py b/asciifarm/client/listselector.py new file mode 100644 index 0000000..b88b967 --- /dev/null +++ b/asciifarm/client/listselector.py @@ -0,0 +1,49 @@ + +from asciifarm.common import utils + + +class ListSelector: + + def __init__(self, widget): + self.widget = widget + self.items = [] + self.selector = 0 + + def getSelected(self): + return self.selector + + def select(self, value, relative=False, modular=False): + invLen = len(self.items) + if relative: + value += self.selector + if modular and invLen: + value %= invLen + if value < 0: + value = 0 + if value >= invLen: + value = invLen-1 + if value in range(invLen): + self.doSelect(value) + + def doSelect(self, value): + self.selector = value + self.widget.select(value) + + def setItems(self, items): + self.items = items + self.selector = utils.clamp(self.selector, 0, len(items)-1) + self.widget.set_items([self.itemName(item) for item in self.items]) + self.widget.select(self.selector) + + def getItem(self, num): + return self.items[num] + + def getSelectedItem(self): + return self.getItem(self.getSelected()) + + def getNumItems(self): + return len(self.items) + + def itemName(self, item): + return item + diff --git a/asciifarm/client/main.py b/asciifarm/client/main.py index c00b592..b16d252 100644 --- a/asciifarm/client/main.py +++ b/asciifarm/client/main.py @@ -1,13 +1,17 @@ #! /usr/bin/python3 -import curses + import json -import os -import getpass + import sys +import termios +import tty +import signal + from .connection import Connection from .gameclient import Client -from .display.display import Display +from .newdisplay.display import Display from .parseargs import parse_args +from ratuil.screen import Screen def main(argv=None): @@ -24,33 +28,40 @@ def main(argv=None): error = None closeMessage = None - os.environ.setdefault("ESCDELAY", "25") + #os.environ.setdefault("ESCDELAY", "25") + + fd = sys.stdin.fileno() + oldterm = termios.tcgetattr(fd) try: # Initialize curses - stdscr = curses.initscr() + #stdscr = curses.initscr() # Turn off echoing of keys, and enter cbreak mode, # where no buffering is performed on keyboard input - curses.noecho() - curses.cbreak() + #curses.noecho() + #curses.cbreak() - # In keypad mode, escape sequences for special keys - # (like the cursor keys) will be interpreted and - # a special value like curses.KEY_LEFT will be returned - stdscr.keypad(1) + ## In keypad mode, escape sequences for special keys + ## (like the cursor keys) will be interpreted and + ## a special value like curses.KEY_LEFT will be returned + #stdscr.keypad(1) # Start color, too. Harmless if the terminal doesn't have # color; user can test with has_color() later on. The try/catch # works around a minor bit of over-conscientiousness in the curses # module -- the error return from C start_color() is ignorable. - try: - curses.start_color() - except: - pass + #try: + #curses.start_color() + #except: + #pass + + tty.setraw(sys.stdin) + Screen.default.hide_cursor() - display = Display(stdscr, characters, colours) - client = Client(stdscr, display, name, connection, keybindings, logfile) + display = Display(characters) + client = Client(display, name, connection, keybindings, logfile) + signal.signal(signal.SIGWINCH, client.onSigwinch) try: client.start() except KeyboardInterrupt: @@ -61,12 +72,14 @@ def main(argv=None): error = e closeMessage = client.closeMessage finally: - # Set everything back to normal - if 'stdscr' in locals(): - stdscr.keypad(0) - curses.echo() - curses.nocbreak() - curses.endwin() + ## Set everything back to normal + #if 'stdscr' in locals(): + #stdscr.keypad(0) + #curses.echo() + #curses.nocbreak() + #curses.endwin() + termios.tcsetattr(fd, termios.TCSADRAIN, oldterm) + Screen.default.finalize() if error is not None: diff --git a/asciifarm/client/newdisplay/__init__.py b/asciifarm/client/newdisplay/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/asciifarm/client/newdisplay/display.py b/asciifarm/client/newdisplay/display.py new file mode 100644 index 0000000..66f89ca --- /dev/null +++ b/asciifarm/client/newdisplay/display.py @@ -0,0 +1,174 @@ + + + +import os +from ratuil.layout import Layout +from ratuil.bufferedscreen import BufferedScreen as Screen +#from ratuil.screen import Screen +from ratuil.textstyle import TextStyle +from asciifarm.common.utils import get + + +SIDEWIDTH = 20 + +ALPHABET = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + +class Display: + + def __init__(self, charMap): + + self.characters = {} + + def parseSprite(sprite): + if isinstance(sprite, str): + return (sprite, None, None) + char = get(sprite, 0, " ") + fg = get(sprite, 1) + bg = get(sprite, 2) + return (char, fg, bg) + for name, sprite in charMap["mapping"].items(): + vals = parseSprite(sprite) + if vals: + self.characters[name] = vals + + for name, colours in charMap.get("writable", {}).items(): + fg = get(colours, 0) + bg = get(colours, 1) + for i in range(min(len(ALPHABET), len(charMap.get("alphabet", [])))): + self.characters[name + '-' + ALPHABET[i]] = (charMap["alphabet"][i], fg, bg) + + self.defaultChar = parseSprite(charMap.get("default", "?")) + + self.messageColours = charMap.get("msgcolours", {}) + + fname = os.path.join(os.path.dirname(__file__), "layout.xml") + with open(fname) as f: + layouttext = f.read() + + self.layout = Layout(layouttext) + self.layout.get("field").set_char_size(charMap.get("charwidth", 1)) + + self.screen = Screen() + self.screen.clear() + + self.layout.set_target(self.screen) + self.layout.update() + + + #screen = Screen(self, stdscr, self.colours) + #self.screen = screen + + #self.widgets = {} + + #self.addWidget(Field((1, 1), charMap.get("charwidth", 1), self.colours), "field") + #self.addWidget(Info(), "info") + #self.addWidget(Health( + #charMap.get("healthfull", ("@",7, 2)), + #charMap.get("healthempty", ("-",7, 1)) + #), + #"health") + #self.addWidget(Inventory("Inventory"), "inventory") + #self.addWidget(Inventory("Ground"), "ground") + #self.addWidget(Inventory("Equipment"), "equipment") + + + ##switcher = Switcher([self.widgets["ground"], self.widgets["inventory"], self.widgets["equipment"]], 1) + #self.addWidget(Inventory(""), "switch") + #self.addWidget(Messages(charMap.get("msgcolours", {})), "msg") + #self.addWidget(TextInput(), "textinput") + + #self.forced = False + + #def addWidget(self, w, name, winname=None): + #if not winname: + #winname = name + #widget = Widget(w, name) + #self.widgets[name] = widget + #widget.setWin(winname, self.screen) + + def getWidget(self, name): + return self.layout.get(name) + #if name in self.widgets: + #return self.widgets[name].getImpl() + #else: + #return None + + def resizeField(self, size): + self.getWidget("field").set_size(*size) + #self.forced = True + + def drawFieldCells(self, cells): + field = self.getWidget("field") + for cell in cells: + (x, y), spriteNames = cell + if not len(spriteNames): + char, fg, bg = self.getChar(0) + else: + char, fg, bg = self.getChar(spriteNames[0]) + for spriteName in spriteNames[1:]: + if bg is not None: + break + _char, _fg, bg = self.getChar(spriteName) + field.change_cell(x, y, char, TextStyle(fg, bg)) + + + def setFieldCenter(self, pos): + self.getWidget("field").set_center(*pos) + + def setHealth(self, health, maxHealth): + self.getWidget("health").set_total(maxHealth) + self.getWidget("health").set_filled(health) + + + def showInfo(self, infostring): + pass + #self.getWidget("info").showString(infostring) + + + #def setInventory(self, items): + #self.getWidget("inventory").setInventory(items) + + + #def setEquipment(self, slots): + #self.getWidget("equipment").setInventory( + #sorted([ + #slot + ": " + (item if item else "") + #for slot, item in slots.items() + #]) + #) + + #def setGround(self, items): + #self.getWidget("ground").setInventory(items) + + + def addMessage(self, message, type): + self.getWidget("msg").add_message(message, TextStyle(*self.messageColours.get(type, (7,0)))) + + def scrollBack(self, amount, relative=True): + self.getWidget("msg").scroll(amount, relative) + + def setInputString(self, string, cursor): + self.getWidget("textinput").set_text(string, cursor) + + def update(self): + self.layout.update() + self.screen.update() + #changed = False + #for widget in self.widgets.values(): + #if self.forced or widget.isChanged(): + #widget.update() + #changed = True + #if changed: + #self.screen.update() + #self.forced = False + + #def forceUpdate(self): + #self.forced = True + + def getChar(self, sprite): + """This returns the character belonging to some spritename. This does not read a character""" + return self.characters.get(sprite, self.defaultChar) + + def update_size(self): + self.screen.reset() + diff --git a/asciifarm/client/newdisplay/layout.xml b/asciifarm/client/newdisplay/layout.xml new file mode 100644 index 0000000..c7e274c --- /dev/null +++ b/asciifarm/client/newdisplay/layout.xml @@ -0,0 +1,30 @@ + + + + + + + milk + eggs + bread + + + cotton underwear + cotton shirt + jeans + friendship bracelet + + + concrete + + + + + + hello + + Welcome to asciifarm + + + + diff --git a/asciifarm/client/switchselector.py b/asciifarm/client/switchselector.py new file mode 100644 index 0000000..dfc18a7 --- /dev/null +++ b/asciifarm/client/switchselector.py @@ -0,0 +1,33 @@ + + +from .listselector import ListSelector + +class SwitchSelector(ListSelector): + + + + def setItems(self, items): + super().setItems(items) + self.updateVisibility() + + def updateVisibility(self): + pass + #for i, (_menu, widget, _title) in enumerate(self.items): + #if i == self.selector: + #widget.hidden = False + #widget.change() + #else: + #widget.hidden = True + + def doSelect(self, value): + + #self.getSelectedItem().widget.hidden = True + super().doSelect(value) + self.updateVisibility() + #self.getSelectedItem().widimp.change() + #newWid.hidden = False + #newWid.change() + + def itemName(self, item): + _menu, _widget, title = item + return title