switched curses to ratuil

This commit is contained in:
troido 2019-09-18 00:32:35 +02:00
parent c55635b577
commit 30cdaa55c9
11 changed files with 419 additions and 80 deletions

View File

@ -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"

View File

@ -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):

View File

@ -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

View File

@ -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)))

View File

@ -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

View File

@ -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

View File

@ -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:

View File

View File

@ -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()

View File

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<hbox>
<vbox width="20" align="right">
<bar id="health" height="2" full-char="+" empty-char="-" full-style="fg:1; bg:2" empty-style="fg:2; bg: 1;"></bar>
<switchbox id="switch">
<listing id="inventory">
milk
eggs
bread
</listing>
<listing id="equipment">
cotton underwear
cotton shirt
jeans
friendship bracelet
</listing>
<listing id="ground">
concrete
</listing>
</switchbox>
<textbox id="info"></textbox>
</vbox>
<vbox>
<textinput id="textinput" align="bottom" height="1">hello</textinput>
<log id="msg" align="bottom" height="20%%">
Welcome to asciifarm
</log>
<field id="field" char-size="1"></field>
</vbox>
</hbox>

View File

@ -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