953 lines
32 KiB
Python
953 lines
32 KiB
Python
from engine import *
|
|
from basetoken import *
|
|
from tpers import *
|
|
from util import *
|
|
from contextlib import contextmanager
|
|
from vm import *
|
|
|
|
# sobj -- generic hungarian for any object that is usable in scripts
|
|
# stmt -- statement; a synt that can be used as a line in a SyntBlock
|
|
def RegStmt(clsStmt):
|
|
SyntLine.rgclsStmt.append(clsStmt)
|
|
return clsStmt
|
|
|
|
def RegRtype(clsRtype):
|
|
Rtype.rgclsRtype.append(clsRtype)
|
|
return clsRtype
|
|
|
|
class Rtype(TPrs):
|
|
"Runtime type"
|
|
rgclsRtype = []
|
|
@classmethod
|
|
def RtypeMax(cls):
|
|
"Return an instance of this rtype which will match anything that any instance of this rtype matches."
|
|
return cls()
|
|
def RglitFromSt(self, st):
|
|
"Return literal objects that match a (partially entered) string."
|
|
return []
|
|
def RgsyntFromSt(self, syntBlock, st):
|
|
"Return syntax objects whose rtypes match this that match a (partially entered) string."
|
|
for lit in self.RglitFromSt(st):
|
|
yield SyntLit(lit, self)
|
|
|
|
for synt in self.RgsyntVarRefFromSt(syntBlock, st):
|
|
yield synt
|
|
|
|
def RgsyntVarRefFromSt(self, syntBlock, st):
|
|
"Return variable references that match a (partially entered) string."
|
|
while syntBlock != None:
|
|
if isinstance(syntBlock, SyntBlock):
|
|
for synt in self.RgsyntFromSyntBlock(syntBlock, st):
|
|
yield synt
|
|
syntBlock = syntBlock.syntParent
|
|
|
|
# helper for RgsyntVarRefFromSt
|
|
def RgsyntFromSyntBlock(self, syntBlock, st):
|
|
# yield all vars in scope
|
|
for syntVar in syntBlock.rgsynt:
|
|
if SyntVar.FVarOfType(syntVar, self) and syntVar.name.St().lower().startswith(st.lower()):
|
|
yield SyntVarRef(syntVar)
|
|
|
|
def StForSobj(self, sobj):
|
|
"Return a user-visible description of a sobj."
|
|
return str(sobj)
|
|
def FMember(self, sobj):
|
|
"Returns true if sobj is a member of this type."
|
|
return isinstance(sobj, self.pythontype)
|
|
def FOverlap(self, rtype):
|
|
"Returns true if there is some overlap between this type and rtype."
|
|
return isinstance(rtype, self.__class__)
|
|
|
|
@RegRtype
|
|
class RtypeBool(Rtype):
|
|
pythontype = bool
|
|
def RglitFromSt(self, st):
|
|
if "true".startswith(st.lower()):
|
|
yield True
|
|
if "false".startswith(st.lower()):
|
|
yield False
|
|
@RegRtype
|
|
class RtypeNum(Rtype):
|
|
pythontype = float
|
|
def RglitFromSt(self, st):
|
|
try:
|
|
yield float(st)
|
|
except:
|
|
pass
|
|
@RegRtype
|
|
class RtypeString(Rtype):
|
|
pythontype = str
|
|
def RglitFromSt(self, st):
|
|
if st.startswith('"') or st.startswith("'"):
|
|
yield st[1:]
|
|
|
|
class RtypeAny(Rtype):
|
|
def RgsyntFromSt(self, syntBlock, st):
|
|
for clsRtype in self.rgclsRtype:
|
|
rtype = clsRtype.RtypeMax()
|
|
for synt in rtype.RgsyntFromSt(syntBlock, st):
|
|
yield synt
|
|
def FMember(self, sobj):
|
|
return True
|
|
def FOverlap(self, rtype):
|
|
return True
|
|
|
|
class Typeable(TPrs):
|
|
def InitTransient(self):
|
|
self.steditTypein = Stedit()
|
|
def PersistStedit(self):
|
|
assert self.FPersist()
|
|
try:
|
|
del self.steditTypein
|
|
except:
|
|
pass
|
|
self.steditTypein = Stedit()
|
|
def GetStTypein(self):
|
|
st = self.steditTypein.GetValue()
|
|
return None if st == "" else st
|
|
def SetStTypein(self, stTypein):
|
|
self.steditTypein.SetValue(stTypein or "")
|
|
def HandleTypeinKey(self, key):
|
|
return self.steditTypein.HandleKey(key)
|
|
|
|
class Synt(Typeable):
|
|
"A piece of syntax"
|
|
def InitPersistent(self):
|
|
self.syntParent = None
|
|
self.rgsynt = []
|
|
self.Populate()
|
|
|
|
@classmethod
|
|
def SyntDefault(cls):
|
|
return cls()
|
|
|
|
@classmethod
|
|
def RgsyntReplace(cls, syntChild, st, rtype):
|
|
return []
|
|
|
|
def ProjectTypeinForChild(self, pwParent, syntChild, st):
|
|
def OnSelect(synt, psel):
|
|
self.Replace(syntChild, synt)
|
|
syntChild.SetStTypein(None)
|
|
if syntChild.GetStTypein() != None:
|
|
pwParent = PwDropdown(pwParent)
|
|
PwTypein(pwParent, st, syntChild)
|
|
if syntChild.GetStTypein() != None:
|
|
for synt in self.RgsyntForChild(syntChild, syntChild.GetStTypein()):
|
|
PwButton(pwParent, synt.StForTypein(), synt, OnSelect)
|
|
|
|
def RgsyntForChild(self, syntChild, st):
|
|
return []
|
|
|
|
def Replace(self, syntOld, syntNew):
|
|
assert syntNew.syntParent == None
|
|
syntNew.syntParent = self
|
|
self.rgsynt[self.Isynt(syntOld)] = syntNew
|
|
|
|
def Append(self, syntNew):
|
|
assert syntNew.syntParent == None
|
|
syntNew.syntParent = self
|
|
self.rgsynt.append(syntNew)
|
|
|
|
def Isynt(self, syntChild):
|
|
if isinstance(syntChild, int):
|
|
return syntChild
|
|
return self.rgsynt.index(syntChild)
|
|
|
|
def Rtype(self):
|
|
return None
|
|
|
|
def RtypeTopForChild(self, syntChild):
|
|
return None
|
|
|
|
def Populate(self):
|
|
"Populate rgsynt with useful default values."
|
|
pass
|
|
|
|
def Project(self, pcur):
|
|
"Project the current synt into its corresponding pws."
|
|
pass
|
|
def Compile(self):
|
|
"Return a list of instrs to be interpreted. See vm.py for details on what an instr can be."
|
|
return [self.Eval]
|
|
|
|
|
|
# desc - list of desces
|
|
# desce:
|
|
# string: "foo" -- literal uneditable text
|
|
# list: ["foo"] -- text to edit the current synt
|
|
# tuple: (clsSynt, type) -- child description
|
|
# clsSynt: class of child synt
|
|
# type: number - same as another child expression with that number
|
|
# None - no rtype specified / possible
|
|
# tuple (number, rtypeTop) - number == same as another child expression with that number, rtypeTop = required type
|
|
class SyntDesc(Synt):
|
|
@classmethod
|
|
def StForTypein(cls):
|
|
for desce in cls.Desc():
|
|
if isinstance(desce, list) and len(desce) == 1:
|
|
return desce[0]
|
|
|
|
@staticmethod
|
|
def DefProp(isynt):
|
|
"Create an alias for rgsynt[isynt]."
|
|
def getI(self):
|
|
return self.rgsynt[isynt]
|
|
def setI(self, syntNew):
|
|
self.Replace(isynt, syntNew)
|
|
return property(getI, setI)
|
|
|
|
@classmethod
|
|
def Desc(cls):
|
|
return cls.desc
|
|
def Populate(self):
|
|
for desce in self.Desc():
|
|
if isinstance(desce, tuple):
|
|
synt = desce[0].SyntDefault()
|
|
self.Append(synt)
|
|
# for synt in self.rgsynt:
|
|
# synt.Populate()
|
|
|
|
def DesceChild(self, isyntChild):
|
|
isyntDesce = 0;
|
|
for desce in self.Desc():
|
|
if isinstance(desce, tuple):
|
|
if isyntDesce == isyntChild:
|
|
return desce
|
|
isyntDesce += 1
|
|
return None
|
|
|
|
def RtypeTopForChild(self, syntChild):
|
|
isyntChild = self.Isynt(syntChild)
|
|
desce = self.DesceChild(isyntChild)
|
|
if desce != None and isinstance(desce[1], tuple):
|
|
return desce[1][0]
|
|
return None
|
|
|
|
def RgsyntForChild(self, syntChild, st):
|
|
isyntChild = self.Isynt(syntChild)
|
|
desce = self.DesceChild(isyntChild)
|
|
if desce != None:
|
|
rtype = None
|
|
if isinstance(desce[1], tuple):
|
|
rtype = desce[1][1]
|
|
return desce[0].RgsyntReplace(syntChild, st, rtype)
|
|
return []
|
|
def Project(self, pcur):
|
|
"adds more horizontal pws to a horizontal pw"
|
|
isynt = 0
|
|
for desce in self.Desc():
|
|
if isinstance(desce, str):
|
|
PwStatic(pcur.PwHoriz(self), desce)
|
|
elif isinstance(desce, list):
|
|
# selection for the synt itself
|
|
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, desce[0])
|
|
elif isinstance(desce, tuple):
|
|
# embedded syntax
|
|
self.rgsynt[isynt].Project(pcur)
|
|
isynt += 1
|
|
|
|
class SyntName(Synt):
|
|
def InitPersistent(self):
|
|
Synt.InitPersistent(self)
|
|
self.PersistStedit()
|
|
def Project(self, pcur):
|
|
PwTypein(pcur.PwHoriz(self), self.St(), self)
|
|
def St(self):
|
|
return self.GetStTypein()
|
|
|
|
class SyntExpr(Synt):
|
|
rgclsSyntOp = []
|
|
@classmethod
|
|
def RgsyntReplace(cls, syntChild, st, rtype):
|
|
for synt in RtypeAny().RgsyntFromSt(syntChild.syntParent, st):
|
|
yield synt
|
|
for clsSyntOp in cls.rgclsSyntOp:
|
|
if clsSyntOp.StForTypein().lower().startswith(st.lower()):
|
|
yield clsSyntOp.SyntDefault()
|
|
|
|
def Project(self, pcur):
|
|
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ")
|
|
def Eval(self, vm):
|
|
Fail.Push(vm, "Empty expression", self)
|
|
|
|
class SyntLine(SyntDesc):
|
|
rgclsStmt = []
|
|
desc = [[" "]]
|
|
@classmethod
|
|
def RgsyntReplace(cls, syntChild, st, rtype):
|
|
for clsStmt in cls.rgclsStmt:
|
|
if clsStmt.StForTypein().lower().startswith(st.lower()):
|
|
yield clsStmt()
|
|
def Compile(self):
|
|
return []
|
|
|
|
class SyntBlock(Synt):
|
|
def Project(self, pcur):
|
|
with pcur.Indent(2 if pcur.pwHoriz != None else 0):
|
|
pcur.EndLine()
|
|
if pcur.pwVert == None:
|
|
pcur.pwVert = PwBlock(None, self, pcur.dxindent)
|
|
def OnInsertNewLine(syntAfter, psel):
|
|
self.InsertLineAfter(syntAfter, None)
|
|
psel.Inc(pcur.pwVert)
|
|
PwButtonHidden(pcur.pwVert, "[insert new line]", None, OnInsertNewLine, pcur.dxindent)
|
|
for syntLine in self.rgsynt:
|
|
pwKey = PwKeyHandler(pcur.pwVert, self.HandleKey)
|
|
with pcur.ProjectHoriz(pwKey, syntLine):
|
|
syntLine.Project(pcur)
|
|
|
|
def RgsyntForChild(self, syntChild, st):
|
|
return SyntLine.RgsyntReplace(syntChild, st, None)
|
|
|
|
def InsertLineAfter(self, syntAfter, syntStmt):
|
|
if syntStmt == None:
|
|
syntStmt = SyntLine()
|
|
if syntAfter == None:
|
|
self.rgsynt.insert(0, syntStmt)
|
|
else:
|
|
self.rgsynt.insert(self.rgsynt.index(syntAfter) + 1, syntStmt)
|
|
syntStmt.syntParent = self
|
|
return syntStmt
|
|
|
|
def HandleKey(self, pwKey, pov, psel, key):
|
|
if ansi.FEnter(key):
|
|
self.InsertLineAfter(pwKey.pwChild.Value(), None)
|
|
psel.Inc(pwKey.PwParent())
|
|
return True
|
|
return False
|
|
def Compile(self):
|
|
return self.rgsynt
|
|
|
|
class SyntLit(Synt):
|
|
def InitPersistent(self, value, rtype):
|
|
Synt.InitPersistent(self)
|
|
self.value = value
|
|
self.rtype = rtype
|
|
def StForTypein(self):
|
|
return self.rtype.StForSobj(self.value)
|
|
def Rtype(self):
|
|
return self.rtype
|
|
def Project(self, pcur):
|
|
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein())
|
|
def Eval(self, vm):
|
|
vm.Push(self.value)
|
|
|
|
@RegStmt
|
|
class SyntVar(SyntDesc):
|
|
desc = [["Define"], " ", (SyntName, None), " to be ", (SyntExpr, 1)]
|
|
name = SyntDesc.DefProp(0)
|
|
expr = SyntDesc.DefProp(1)
|
|
@classmethod
|
|
def FVarOfType(cls, synt, rtype):
|
|
return isinstance(synt, cls) and synt.expr != None and rtype.FOverlap(synt.expr.Rtype())
|
|
def Compile(self):
|
|
return [self.expr, self.Eval]
|
|
def Eval(self, vm):
|
|
vm.vars.Define(self, vm.Pop())
|
|
|
|
class SyntVarRef(Synt):
|
|
def InitPersistent(self, syntVar = None):
|
|
Synt.InitPersistent(self)
|
|
self.syntVar = syntVar
|
|
@classmethod
|
|
def RgsyntReplace(cls, syntChild, st, rtype):
|
|
rtype = rtype or RtypeAny()
|
|
return rtype.RgsyntVarRefFromSt(syntChild, st)
|
|
def Rtype(self):
|
|
return self.syntVar.Rtype()
|
|
def StForTypein(self):
|
|
if self.syntVar:
|
|
return self.syntVar.name.St()
|
|
return "<var>"
|
|
def Project(self, pcur):
|
|
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein())
|
|
def Eval(self, vm):
|
|
vm.Push(vm.vars.Get(self.syntVar, self))
|
|
|
|
class SyntBinOp(SyntDesc):
|
|
@classmethod
|
|
def Desc(cls):
|
|
return [(SyntExpr, None), " ", [cls.op], " ", (SyntExpr, None)]
|
|
left = SyntDesc.DefProp(0)
|
|
right = SyntDesc.DefProp(1)
|
|
@classmethod
|
|
def Create(cls, op, rtype, dgEval = None, stNameI = None):
|
|
def Decorate(dgEval):
|
|
stName = stNameI or dgEval.__name__
|
|
def Eval(self, vm):
|
|
right = vm.Pop()
|
|
left = vm.Pop()
|
|
try:
|
|
vm.Push(dgEval(left, right))
|
|
except Exception as e:
|
|
Fail.Push(vm, "Failed to " + op + ": " + str(e), self)
|
|
def Compile(self):
|
|
return [self.left, self.right, self.Eval]
|
|
clsNew = type(stName, (cls,), {"Compile": Compile, "Eval": Eval, "op": op})
|
|
SyntExpr.rgclsSyntOp.append(clsNew)
|
|
return clsNew
|
|
|
|
if dgEval == None: # used as a decorator
|
|
return Decorate
|
|
clsNew = Decorate(dgEval)
|
|
if stNameI != None:
|
|
globals()[stNameI] = clsNew
|
|
return clsNew
|
|
|
|
SyntBinOp.Create("+", RtypeNum(), lambda l,r: l + r, "SyntPlus")
|
|
SyntBinOp.Create("-", RtypeNum(), lambda l,r: l - r, "SyntMinus")
|
|
SyntBinOp.Create("*", RtypeNum(), lambda l,r: l * r, "SyntMult")
|
|
SyntBinOp.Create("/", RtypeNum(), lambda l,r: l / r, "SyntDiv") # use ansi?
|
|
SyntBinOp.Create("=", RtypeBool(), lambda l,r: l == r, "SyntEq")
|
|
SyntBinOp.Create(">", RtypeBool(), lambda l,r: l > r, "SyntGt")
|
|
SyntBinOp.Create(">=", RtypeBool(), lambda l,r: l >= r, "SyntGte") # use ansi?
|
|
SyntBinOp.Create("<", RtypeBool(), lambda l,r: l < r, "SyntLt")
|
|
SyntBinOp.Create("<=", RtypeBool(), lambda l,r: l <= r, "SyntLte") # use ansi?
|
|
SyntBinOp.Create("and", RtypeBool(), lambda l,r: l and r, "SyntAnd")
|
|
SyntBinOp.Create("or", RtypeBool(), lambda l,r: l or r, "SyntOr")
|
|
|
|
@RegStmt
|
|
class SyntIf(SyntDesc):
|
|
desc = [["If"], " ", (SyntExpr, (1, RtypeBool)), ", then:", (SyntBlock, None), "or else:", (SyntBlock, None)]
|
|
expr = SyntDesc.DefProp(0)
|
|
blockIfTrue = SyntDesc.DefProp(1)
|
|
blockIfFalse = SyntDesc.DefProp(2)
|
|
def Compile(self):
|
|
return [self.expr, self.Compare, self.PopCtx]
|
|
def Compare(self, vm):
|
|
val = vm.Pop()
|
|
vm.vars.Push() # lexical scoping, bitches
|
|
if val:
|
|
return self.blockIfTrue
|
|
else:
|
|
return self.blockIfFalse
|
|
def PopCtx(self, vm):
|
|
vm.vars.Pop()
|
|
|
|
@RegStmt
|
|
class SyntSet(SyntDesc):
|
|
desc = [["Set"], " ", (SyntVarRef, 1), " to ", (SyntExpr, 1)]
|
|
lvalue = SyntDesc.DefProp(0)
|
|
expr = SyntDesc.DefProp(1)
|
|
def Compile(self):
|
|
return [self.expr, self.Eval]
|
|
def Eval(self, vm):
|
|
val = vm.Pop()
|
|
if not isinstance(val, Fail):
|
|
vm.Log(vm.vars.Set(self.lvalue.syntVar, vm.Pop(), self))
|
|
|
|
#debug
|
|
@RegStmt
|
|
class SyntPrint(SyntDesc):
|
|
desc = [["Print"], " ", (SyntExpr, 1)]
|
|
expr = SyntDesc.DefProp(0)
|
|
def Compile(self):
|
|
return [self.expr, self.Eval]
|
|
def Eval(self, vm):
|
|
print vm.Pop()
|
|
|
|
class Fail(TPrs):
|
|
"""
|
|
Failure object. Our scripting language has no exceptions; instead, if a failure object is involved in a calculation,
|
|
that calculation fails as well. Inspired by Icon. I would like this model to have the property that the designer can
|
|
edit a script while the game is running and see the effect of their changes immediately; this suggests that failures
|
|
should be logged and the script should continue to run.
|
|
"""
|
|
def InitPersistent(self, stFailure, synt):
|
|
self.stFailure = stFailure
|
|
self.synt = synt
|
|
@staticmethod
|
|
def Push(vm, stFailure, synt):
|
|
fail = Fail(stFailure, synt)
|
|
vm.Push(fail)
|
|
vm.Log(fail)
|
|
@staticmethod
|
|
def Log(vm, stFailure, synt):
|
|
vm.Log(Fail(stFailure, synt))
|
|
|
|
# projection cursor
|
|
class Pcur(object):
|
|
@staticmethod
|
|
def PwProjected(synt):
|
|
pcur = Pcur()
|
|
synt.Project(pcur)
|
|
return pcur.pwVert
|
|
|
|
def __init__(self):
|
|
self.pwVert = None
|
|
self.pwHoriz = None
|
|
self.dxindent = 0
|
|
|
|
@contextmanager
|
|
def Indent(self, dxindent = 2):
|
|
self.dxindent += dxindent
|
|
yield
|
|
self.dxindent -= dxindent
|
|
|
|
@contextmanager
|
|
def ProjectHoriz(self, pwParent, synt):
|
|
self.ProjectHorizI(pwParent, synt)
|
|
yield
|
|
self.EndLine()
|
|
|
|
def PwHoriz(self, synt):
|
|
if self.pwHoriz == None:
|
|
self.ProjectHorizI(self.pwVert, synt)
|
|
return self.pwHoriz
|
|
|
|
def EndLine(self):
|
|
self.pwHoriz = None
|
|
|
|
def ProjectHorizI(self, pwParent, synt):
|
|
self.pwHoriz = PwList(pwParent, synt, self.dxindent)
|
|
|
|
# pw - projected widget
|
|
# Nothing ever depends on the object identity or permanence of a pw!
|
|
# They are generated on demand, whenever we need to draw or process a
|
|
# keypress.
|
|
|
|
class Pw(object):
|
|
def __init__(self, pwParent):
|
|
if pwParent != None:
|
|
pwParent.AddChild(self)
|
|
self.pwParent = pwParent
|
|
def PrintTree(self, dxindent = 0):
|
|
print (" " * dxindent) + self.StDebug()
|
|
for pwChild in self.RgpwChild():
|
|
pwChild.PrintTree(dxindent + 2)
|
|
def StDebug(self):
|
|
return self.__class__.__name__
|
|
def PwParent(self):
|
|
return self.pwParent
|
|
def Value(self):
|
|
return None
|
|
def FSelectable(self):
|
|
return False
|
|
def FContainer(self):
|
|
return False
|
|
def RgpwChild(self):
|
|
return []
|
|
def HandleKey(self, pov, psel, key):
|
|
return False
|
|
def PwFirstSelectable(self):
|
|
if self.FSelectable():
|
|
return self
|
|
for pwChild in self.RgpwChild():
|
|
pw = pwChild.PwFirstSelectable()
|
|
if pw != None:
|
|
return pw
|
|
return None
|
|
|
|
class PwContainer(Pw):
|
|
def __init__(self, pwParent):
|
|
Pw.__init__(self, pwParent)
|
|
self.rgpwContents = []
|
|
def AddChild(self, pw):
|
|
self.rgpwContents.append(pw)
|
|
def RgpwChild(self):
|
|
return self.rgpwContents
|
|
def FContainer(self):
|
|
return True
|
|
|
|
class PwContainOne(Pw):
|
|
def AddChild(self, pw):
|
|
self.pwChild = pw
|
|
def RgpwChild(self):
|
|
return [self.pwChild]
|
|
def FContainer(self):
|
|
return True
|
|
|
|
class PwExpand(PwContainOne):
|
|
def DxDyNew(self, w, dxStart, mpksel):
|
|
dxdy = self.pwChild.DxDyNew(w, dxStart, mpksel)
|
|
if dxdy[0] == 0 and dxdy[1] > 0:
|
|
return dxdy
|
|
return (0, dxdy[0] + 1)
|
|
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
|
|
if not fOverlay:
|
|
ascr.PutSt(" " * w, x, y, self.pwChild.ColFg(), self.pwChild.ColBg(mpksel.Get(self.pwChild)))
|
|
self.pwChild.Draw(ascr, x, y, w, dxStart, mpksel, fOverlay)
|
|
|
|
class PwStatic(Pw):
|
|
def __init__(self, pwParent, stText):
|
|
Pw.__init__(self, pwParent)
|
|
self.stText = stText
|
|
def StDebug(self):
|
|
return Pw.StDebug(self) + " '" + self.StTextDisplay(Ksel.NAV) + "'"
|
|
def DxDyNew(self, w, dxStart, mpksel):
|
|
rgsnippet = self.Rgsnippet(w, dxStart, mpksel)
|
|
if len(rgsnippet) == 1:
|
|
return (dxStart + len(rgsnippet[0].st), 0)
|
|
else:
|
|
return (len(rgsnippet[-1].st), len(rgsnippet) - 1)
|
|
def StTextDisplay(self, ksel):
|
|
return self.stText
|
|
def Rgsnippet(self, w, dxStart, mpksel):
|
|
ksel = mpksel.Get(self)
|
|
return list(RgSnippetWrapped(self.StTextDisplay(ksel), w, ksel == Ksel.NAV, dxStart))
|
|
def ColBg(self, ksel):
|
|
return ansi.BLACK
|
|
def ColFg(self):
|
|
return ansi.WHITE | ansi.FBRIGHT
|
|
def RgSnippetXY(self, x, y, w, dxStart, mpksel):
|
|
fFirst = True
|
|
for snippet in self.Rgsnippet(w, dxStart, mpksel):
|
|
xT = x + dxStart if fFirst else x
|
|
yield (snippet, xT, y)
|
|
y = y + 1
|
|
fFirst = False
|
|
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
|
|
if not fOverlay:
|
|
ksel = mpksel.Get(self)
|
|
for (snippet, x, y) in self.RgSnippetXY(x, y, w, dxStart, mpksel):
|
|
ascr.PutSt(snippet.st, x, y, self.ColFg(), self.ColBg(ksel))
|
|
|
|
class PwTypein(PwStatic):
|
|
def __init__(self, pwParent, stSobj, typeable):
|
|
if typeable.GetStTypein() == None:
|
|
stDisplay = stSobj
|
|
else:
|
|
stDisplay = None
|
|
PwStatic.__init__(self, pwParent, stDisplay)
|
|
self.typeable = typeable
|
|
def Value(self):
|
|
return self.typeable
|
|
def StTextDisplay(self, ksel):
|
|
if self.stText == None:
|
|
return self.typeable.steditTypein.StForSize(ksel == Ksel.NAV)
|
|
return self.stText
|
|
def FSelectable(self):
|
|
return True
|
|
def ColBg(self, ksel):
|
|
if ksel == Ksel.NAV:
|
|
return ansi.YELLOW
|
|
elif ksel == Ksel.OTHERNAV:
|
|
return ansi.MAGENTA
|
|
return ansi.BLUE
|
|
def HandleKey(self, pov, psel, key):
|
|
return self.typeable.HandleTypeinKey(key)
|
|
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
|
|
if not fOverlay:
|
|
ksel = mpksel.Get(self)
|
|
fTextSelect = ksel == Ksel.NAV and self.typeable.GetStTypein() != None
|
|
for (snippet, x, y) in self.RgSnippetXY(x, y, w, dxStart, mpksel):
|
|
ascr.PutSt(snippet.st, x, y, self.ColFg(), self.ColBg(Ksel.NONE if fTextSelect else ksel))
|
|
if fTextSelect:
|
|
self.typeable.steditTypein.DrawCursor(ascr, x, y, self.ColFg(), self.ColBg(ksel), snippet)
|
|
else:
|
|
for pwChild in self.RgpwChild():
|
|
pwChild.Draw(ascr, x, y, dxStart, mpksel, fOverlay)
|
|
|
|
class PwButton(PwStatic):
|
|
def __init__(self, pwParent, stText, value, dgEnter):
|
|
PwStatic.__init__(self, pwParent, stText)
|
|
self.value = value
|
|
self.dgEnter = dgEnter
|
|
def Value(self):
|
|
return self.value
|
|
def FSelectable(self):
|
|
return True
|
|
def ColBg(self, ksel):
|
|
if ksel == Ksel.NAV:
|
|
return ansi.YELLOW
|
|
elif ksel == Ksel.OTHERNAV:
|
|
return ansi.MAGENTA
|
|
return ansi.BLUE
|
|
def ColFg(self):
|
|
return ansi.BLUE | ansi.FBRIGHT
|
|
def HandleKey(self, pov, psel, key):
|
|
if key == ansi.K_RETURN:
|
|
self.dgEnter(self.Value(), psel)
|
|
return True
|
|
return False
|
|
|
|
class PwButtonHidden(PwButton):
|
|
def __init__(self, pwParent, stText, value, dgEnter, dxIndent):
|
|
PwButton.__init__(self, pwParent, stText, value, dgEnter)
|
|
self.dxIndent = dxIndent
|
|
def DxDyNew(self, w, dxStart, mpksel):
|
|
if mpksel.Get(self) == Ksel.NAV:
|
|
return PwButton.DxDyNew(self, w - self.dxIndent, dxStart + self.dxIndent, mpksel)
|
|
return (0, 0)
|
|
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
|
|
if mpksel.Get(self) == Ksel.NAV:
|
|
PwButton.Draw(self, ascr, x + self.dxIndent, y, w - self.dxIndent, dxStart, mpksel, fOverlay)
|
|
|
|
class PwKeyHandler(PwContainOne):
|
|
def __init__(self, pwParent, dgHandleKey):
|
|
PwContainOne.__init__(self, pwParent)
|
|
self.dgHandleKey = dgHandleKey
|
|
def DxDyNew(self, w, dxStart, mpksel):
|
|
return self.pwChild.DxDyNew(w, dxStart, mpksel)
|
|
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
|
|
self.pwChild.Draw(ascr, x, y, w, dxStart, mpksel, fOverlay)
|
|
def HandleKey(self, pov, psel, key):
|
|
self.dgHandleKey(self, pov, psel, key)
|
|
|
|
class PwBlock(PwContainer):
|
|
def __init__(self, pwParent, block, dxIndent = 2):
|
|
PwContainer.__init__(self, pwParent)
|
|
self.block = block
|
|
self.dxIndent = dxIndent
|
|
def Value(self):
|
|
return self.block
|
|
def DxIndent(self):
|
|
return self.dxIndent
|
|
def DxDyNew(self, w, dxStart, mpksel):
|
|
return (0, 1 + sum([pw.DxDyNew(w - self.DxIndent(), 0, mpksel)[1] + 1 for pw in self.rgpwContents]))
|
|
def RgpwChildToDraw(self):
|
|
return self.RgpwChild()
|
|
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
|
|
for pw in self.RgpwChildToDraw():
|
|
pw.Draw(ascr, x + self.DxIndent(), y, w - self.DxIndent(), 0, mpksel, fOverlay)
|
|
(dxNew, dyNew) = pw.DxDyNew(w - self.DxIndent(), 0, mpksel)
|
|
if dxNew > 0 or dyNew > 0:
|
|
y = y + dyNew + 1
|
|
def HandleKey(self, pov, psel, key):
|
|
if key == ansi.K_UP:
|
|
return psel.Dec(self)
|
|
elif key == ansi.K_DOWN:
|
|
return psel.Inc(self)
|
|
elif key == ansi.K_DEL:
|
|
pass
|
|
return False
|
|
|
|
class PwDropdown(PwBlock):
|
|
def __init__(self, pwParent):
|
|
PwBlock.__init__(self, pwParent, None, 0)
|
|
def WidthMax(self, mpksel):
|
|
w = -1
|
|
for pw in self.RgpwChild():
|
|
wPw = pw.DxDyNew(1000000, 0, mpksel)[0]
|
|
if wPw > w:
|
|
w = wPw
|
|
return w
|
|
def RgpwChildToDraw(self):
|
|
return PwBlock.RgpwChild(self)[1:]
|
|
def PwChildFirst(self):
|
|
return PwBlock.RgpwChild(self)[0]
|
|
def DxDyNew(self, w, dxStart, mpksel):
|
|
return self.PwChildFirst().DxDyNew(w, dxStart, mpksel)
|
|
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
|
|
if not fOverlay:
|
|
self.PwChildFirst().Draw(ascr, x, y, w, dxStart, mpksel, False)
|
|
else:
|
|
wMax = self.WidthMax(mpksel)
|
|
w = w - dxStart
|
|
x = x + dxStart
|
|
if x + wMax > w and wMax < w:
|
|
x = w - self.WidthMax(mpksel)
|
|
PwBlock.Draw(self, ascr, x, y + 1, w, 0, mpksel, False)
|
|
|
|
class PwList(PwContainer):
|
|
def __init__(self, pwParent, synt, dxIndent):
|
|
PwContainer.__init__(self, pwParent)
|
|
self.synt = synt
|
|
self.dxIndent = dxIndent
|
|
def Value(self):
|
|
return self.synt
|
|
def DxDyNew(self, w, dxStart, mpksel):
|
|
dx = dxStart
|
|
dy = 0
|
|
for pw in self.rgpwContents:
|
|
(dx, dyPw) = pw.DxDyNew(w, dx, mpksel)
|
|
dy = dy + dyPw
|
|
return (dx, dy)
|
|
def DxIndent(self):
|
|
return self.dxIndent
|
|
def Draw(self, ascr, x, y, w, dx, mpksel, fOverlay):
|
|
for pw in self.rgpwContents:
|
|
pw.Draw(ascr, x + self.DxIndent(), y, w - self.DxIndent(), dx, mpksel, fOverlay)
|
|
(dx, dy) = pw.DxDyNew(w, dx, mpksel)
|
|
y = y + dy
|
|
def HandleKey(self, pov, psel, key):
|
|
if key == ansi.K_LEFT:
|
|
return psel.Dec(self)
|
|
elif key == ansi.K_RIGHT:
|
|
return psel.Inc(self)
|
|
return False
|
|
|
|
class Ksel(object):
|
|
FOTHER = 0x2000
|
|
NONE = FOTHER | 0x1000 # OR together all ksel flags and add 0x1000; largest possible ksel
|
|
NAV = 0
|
|
OTHERNAV = NAV | FOTHER
|
|
|
|
@staticmethod
|
|
def KselBest(ksel1, ksel2):
|
|
if ksel1 < ksel2:
|
|
return ksel1
|
|
return ksel2
|
|
|
|
class Psel(TPrs):
|
|
def InitPersistent(self, pw, ksel):
|
|
assert len(pw.RgpwChild()) == 0 #leaf
|
|
self.rgo_ipw = self.Rgo_ipwFromPw(pw)
|
|
self.ksel = ksel
|
|
|
|
def Rgo_ipwFromPw(self, pw):
|
|
rgo_ipw = []
|
|
if pw != None:
|
|
while pw.PwParent() != None:
|
|
rgo_ipw.insert(0, (pw.Value(), pw.PwParent().RgpwChild().index(pw)))
|
|
pw = pw.PwParent()
|
|
return rgo_ipw
|
|
|
|
def Validate(self, pwRoot):
|
|
assert pwRoot.PwParent() == None #root
|
|
io_ipw = 0
|
|
pwSelected = pwRoot
|
|
while len(pwSelected.RgpwChild()) > 0 and io_ipw < len(self.rgo_ipw):
|
|
(o, ipw) = self.rgo_ipw[io_ipw]
|
|
ipwNew = 0
|
|
for pwChild in pwSelected.RgpwChild():
|
|
if o != None and pwChild.Value() == o:
|
|
break
|
|
ipwNew = ipwNew + 1
|
|
if ipwNew == len(pwSelected.RgpwChild()):
|
|
ipwLim = ipw + 1 if ipw < len(pwSelected.RgpwChild()) else len(pwSelected.RgpwChild())
|
|
ipwNew = IoDec(pwSelected.RgpwChild(), ipwLim, self.FPwValid)
|
|
if ipwNew < 0:
|
|
break
|
|
pwSelected = pwSelected.RgpwChild()[ipwNew]
|
|
io_ipw = io_ipw + 1
|
|
|
|
# we've made our best guess as to what the closest selectable thing is -- now make sure we point at SOMEthing selectable.
|
|
if not pwSelected.FSelectable():
|
|
pwT = pwSelected
|
|
while pwT != None:
|
|
pwSelected = pwT.PwFirstSelectable()
|
|
if pwSelected != None:
|
|
break
|
|
pwT = pwT.PwParent()
|
|
|
|
self.rgo_ipw = self.Rgo_ipwFromPw(pwSelected)
|
|
|
|
def FPwValid(self, pw):
|
|
if pw.FSelectable():
|
|
return True
|
|
for pwChild in pw.RgpwChild():
|
|
if self.FPwValid(pwChild):
|
|
return True
|
|
return False
|
|
|
|
def Value(self, pwRoot):
|
|
self.Validate(pwRoot)
|
|
if len(self.rgo_ipw) > 0:
|
|
return self.rgo_ipw[-1][0]
|
|
return None
|
|
|
|
def PwSelected(self, pwRoot, clevel = None):
|
|
self.Validate(pwRoot)
|
|
return self.PwSelectedI(pwRoot, self.rgo_ipw[:clevel])
|
|
|
|
def PwSelectedI(self, pw, rgo_ipw):
|
|
for (_, ipw) in rgo_ipw:
|
|
pw = pw.RgpwChild()[ipw]
|
|
return pw
|
|
# contract: pwContainer is actually selected
|
|
def Inc(self, pwContainer):
|
|
return self.ChangeI(pwContainer, IoInc)
|
|
def Dec(self, pwContainer):
|
|
return self.ChangeI(pwContainer, IoDec)
|
|
|
|
def ChangeI(self, pwContainer, fnChange):
|
|
clevelChild = 1
|
|
pwRoot = pwContainer
|
|
while pwRoot.PwParent() != None:
|
|
clevelChild = clevelChild + 1
|
|
pwRoot = pwRoot.PwParent()
|
|
self.Validate(pwRoot)
|
|
assert self.PwSelected(pwRoot, clevelChild).PwParent() == pwContainer
|
|
|
|
ipw = self.rgo_ipw[clevelChild - 1][1]
|
|
rgpw = pwContainer.RgpwChild()
|
|
ipwNew = fnChange(rgpw, ipw, self.FPwValid)
|
|
self.rgo_ipw[clevelChild - 1] = (rgpw[ipwNew].Value(), ipwNew)
|
|
self.Validate(pwRoot)
|
|
return ipwNew != ipw
|
|
|
|
# projector overlay -- handles navigation, drawing the projection, etc
|
|
class Pov(TokenClient):
|
|
def InitPersistent(self, owner, client, block):
|
|
TokenClient.InitPersistent(self, owner, client, "drawable", "overlay")
|
|
self.block = block
|
|
self.pselstate = self.game.rgtoken("pselstate")[0]
|
|
|
|
def PwProjected(self):
|
|
return Pcur.PwProjected(self.block)
|
|
|
|
def run(self):
|
|
while True:
|
|
key = self.client.evKey.receive(self)
|
|
if key == ansi.K_PGDN:
|
|
Vm().RunSynt(self.block)
|
|
continue
|
|
psel = self.pselstate.PselByClient(self.client)
|
|
pwSel = psel.PwSelected(self.PwProjected())
|
|
while pwSel != None:
|
|
if pwSel.HandleKey(self, psel, key):
|
|
break
|
|
pwSel = pwSel.PwParent()
|
|
|
|
def draw(self, ascr, client):
|
|
if client == self.client:
|
|
pw = self.PwProjected()
|
|
mpksel = self.pselstate.GetMpksel(pw, client)
|
|
pw.Draw(ascr, 1, 1, ascr.W(), 0, mpksel, False)
|
|
pw.Draw(ascr, 1, 1, ascr.W(), 0, mpksel, True)
|
|
|
|
class PselState(Token):
|
|
def InitPersistent(self, owner, block):
|
|
Token.InitPersistent(self, owner, "pselstate")
|
|
self.block = block
|
|
def InitTransient(self):
|
|
Token.InitTransient(self)
|
|
self.mpclient_psel = {}
|
|
def OnLeave(self, client):
|
|
del self.mpclient_psel[client]
|
|
def OnJoin(self, client):
|
|
print "client joined,", client
|
|
self.mpclient_psel[client] = Psel(Pcur.PwProjected(self.block).PwFirstSelectable(), Ksel.NAV)
|
|
class Mpksel(object):
|
|
def __init__(self, pwRoot, client, mpclient_psel):
|
|
self.mppw_ksel = {}
|
|
for clientT, psel in mpclient_psel.items():
|
|
kselNew = Ksel.NAV
|
|
if clientT != client:
|
|
kselNew = kselNew | Ksel.FOTHER
|
|
pw = psel.PwSelected(pwRoot)
|
|
self.mppw_ksel[pw] = Ksel.KselBest(kselNew, self.Get(pw))
|
|
|
|
def Get(self, pw):
|
|
return self.mppw_ksel.get(pw, Ksel.NONE)
|
|
|
|
def GetMpksel(self, pwRoot, client):
|
|
return self.Mpksel(pwRoot, client, self.mpclient_psel)
|
|
def PselByClient(self, client):
|
|
return self.mpclient_psel[client]
|
|
def run(self):
|
|
print "pselstate running"
|
|
with self.game.evLeave.oob(self, self.OnLeave):
|
|
while True:
|
|
self.OnJoin(self.game.evJoin.receive(self))
|
|
|
|
class GameScriptTest(Game):
|
|
def InitPersistent(self):
|
|
Game.InitPersistent(self)
|
|
self.block = SyntBlock()
|
|
|
|
def GetRgclsTokTrans(self):
|
|
return [[PselState, self.block], [AutoJoiner, [Pov, self.block]]]
|
|
|
|
class RunnerScriptTest(Runner):
|
|
def InitPersistent(self):
|
|
self.game = GameScriptTest()
|
|
Runner.InitPersistent(self)
|
|
|
|
def RunGame(self, client):
|
|
client.joinGame(self.game)
|
|
|
|
if __name__ == "__main__":
|
|
Run(RunnerScriptTest, "script_test.marm") |