marmots/scripting.py

977 lines
34 KiB
Python
Raw Normal View History

2020-06-28 15:27:56 +00:00
# This file is part of MarMOTS.
#
# MarMOTS 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.
#
# MarMOTS 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 MarMOTS. If not,
# see <https://www.gnu.org/licenses/>.
#
# Copyright 2009, 2010, 2011, 2020 Jeremy Penner
2011-03-19 00:10:02 +00:00
from engine import *
from basetoken import *
from tpers import *
from util import *
2011-09-07 02:14:17 +00:00
from contextlib import contextmanager
from vm import *
2011-09-07 02:14:17 +00:00
2011-09-15 22:19:20 +00:00
# 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
2011-09-07 02:14:17 +00:00
def RegStmt(clsStmt):
SyntLine.rgclsStmt.append(clsStmt)
return clsStmt
def RegRtype(clsRtype):
Rtype.rgclsRtype.append(clsRtype)
return clsRtype
class Rtype(TPrs):
"Runtime type"
2011-09-07 02:14:17 +00:00
rgclsRtype = []
@classmethod
def RtypeMax(cls):
"Return an instance of this rtype which will match anything that any instance of this rtype matches."
2011-09-07 02:14:17 +00:00
return cls()
def RglitFromSt(self, st):
"Return literal objects that match a (partially entered) string."
2011-09-07 02:14:17 +00:00
return []
def RgsyntFromSt(self, syntBlock, st):
"Return syntax objects whose rtypes match this that match a (partially entered) string."
2011-09-07 02:14:17 +00:00
for lit in self.RglitFromSt(st):
yield SyntLit(lit, self)
2011-09-07 02:14:17 +00:00
for synt in self.RgsyntVarRefFromSt(syntBlock, st):
yield synt
def RgsyntVarRefFromSt(self, syntBlock, st):
"Return variable references that match a (partially entered) string."
2011-09-07 02:14:17 +00:00
while syntBlock != None:
if isinstance(syntBlock, SyntBlock):
for synt in self.RgsyntFromSyntBlock(syntBlock, st):
yield synt
syntBlock = syntBlock.syntParent
# helper for RgsyntVarRefFromSt
2011-09-07 02:14:17 +00:00
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)
2011-09-07 02:14:17 +00:00
def StForSobj(self, sobj):
"Return a user-visible description of a sobj."
2011-09-07 02:14:17 +00:00
return str(sobj)
def FMember(self, sobj):
"Returns true if sobj is a member of this type."
2011-09-07 02:14:17 +00:00
return isinstance(sobj, self.pythontype)
def FOverlap(self, rtype):
"Returns true if there is some overlap between this type and rtype."
2011-09-07 02:14:17 +00:00
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 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
2011-09-07 02:14:17 +00:00
self.rgsynt = []
self.Populate()
@classmethod
def SyntDefault(cls):
return cls()
2011-09-07 02:14:17 +00:00
2011-09-07 12:55:58 +00:00
@classmethod
def RgsyntReplace(cls, syntChild, st, rtype):
return []
2011-09-07 02:14:17 +00:00
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
2011-09-07 02:14:17 +00:00
self.rgsynt[self.Isynt(syntOld)] = syntNew
def Append(self, syntNew):
assert syntNew.syntParent == None
2011-09-07 02:14:17 +00:00
syntNew.syntParent = self
self.rgsynt.append(syntNew)
2011-09-07 02:14:17 +00:00
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."
2011-09-07 02:14:17 +00:00
pass
def Project(self, pcur):
"Project the current synt into its corresponding pws."
2011-09-07 02:14:17 +00:00
pass
def Compile(self):
2011-09-15 22:19:20 +00:00
"Return a list of instrs to be interpreted. See vm.py for details on what an instr can be."
return [self.Eval]
2011-09-07 02:14:17 +00:00
# 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):
2011-09-12 01:43:53 +00:00
for desce in cls.Desc():
2011-09-07 02:14:17 +00:00
if isinstance(desce, list) and len(desce) == 1:
return desce[0]
@staticmethod
def DefProp(isynt):
"Create an alias for rgsynt[isynt]."
2011-09-12 01:43:53 +00:00
def getI(self):
2011-09-07 02:14:17 +00:00
return self.rgsynt[isynt]
2011-09-12 01:43:53 +00:00
def setI(self, syntNew):
2011-09-07 02:14:17 +00:00
self.Replace(isynt, syntNew)
2011-09-12 01:43:53 +00:00
return property(getI, setI)
2011-09-07 02:14:17 +00:00
2011-09-12 01:43:53 +00:00
@classmethod
def Desc(cls):
return cls.desc
2011-09-07 02:14:17 +00:00
def Populate(self):
2011-09-12 01:43:53 +00:00
for desce in self.Desc():
2011-09-07 02:14:17 +00:00
if isinstance(desce, tuple):
synt = desce[0].SyntDefault()
self.Append(synt)
2011-09-07 02:14:17 +00:00
# for synt in self.rgsynt:
# synt.Populate()
2011-09-07 12:55:58 +00:00
def DesceChild(self, isyntChild):
isyntDesce = 0;
2011-09-12 01:43:53 +00:00
for desce in self.Desc():
2011-09-07 02:14:17 +00:00
if isinstance(desce, tuple):
if isyntDesce == isyntChild:
2011-09-07 12:55:58 +00:00
return desce
2011-09-07 02:14:17 +00:00
isyntDesce += 1
2011-09-07 12:55:58 +00:00
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]
2011-09-07 02:14:17 +00:00
return None
2011-09-07 12:55:58 +00:00
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 []
2011-09-07 02:14:17 +00:00
def Project(self, pcur):
"adds more horizontal pws to a horizontal pw"
isynt = 0
2011-09-12 01:43:53 +00:00
for desce in self.Desc():
2011-09-07 02:14:17 +00:00
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)
2011-09-20 12:38:41 +00:00
self.st = ""
def InitTransient(self):
Synt.InitTransient(self)
self.SetStTypein(self.st)
2011-09-07 02:14:17 +00:00
def Project(self, pcur):
2011-09-07 12:55:58 +00:00
PwTypein(pcur.PwHoriz(self), self.St(), self)
def St(self):
return self.GetStTypein()
2011-09-20 12:38:41 +00:00
def HandleTypeinKey(self, key):
val = Synt.HandleTypeinKey(self, key)
self.st = self.GetStTypein()
return val
2011-09-07 02:14:17 +00:00
class SyntExpr(Synt):
2011-09-12 01:43:53 +00:00
rgclsSyntOp = []
2011-09-07 12:55:58 +00:00
@classmethod
def RgsyntReplace(cls, syntChild, st, rtype):
2011-09-12 01:43:53 +00:00
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()
2011-09-12 01:43:53 +00:00
2011-09-07 02:14:17 +00:00
def Project(self, pcur):
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, " ")
2011-09-15 22:19:20 +00:00
def Eval(self, vm):
Fail.Push(vm, "Empty expression", self)
class SyntLine(SyntDesc):
2011-09-07 02:14:17 +00:00
rgclsStmt = []
desc = [[" "]]
@classmethod
def RgsyntReplace(cls, syntChild, st, rtype):
for clsStmt in cls.rgclsStmt:
2011-09-07 02:14:17 +00:00
if clsStmt.StForTypein().lower().startswith(st.lower()):
yield clsStmt()
2011-09-15 22:19:20 +00:00
def Compile(self):
return []
2011-09-07 02:14:17 +00:00
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)
2011-09-07 02:14:17 +00:00
def InsertLineAfter(self, syntAfter, syntStmt):
if syntStmt == None:
syntStmt = SyntLine()
2011-09-07 02:14:17 +00:00
if syntAfter == None:
self.rgsynt.insert(0, syntStmt)
2011-09-07 02:14:17 +00:00
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
2011-09-07 02:14:17 +00:00
class SyntLit(Synt):
def InitPersistent(self, value, rtype):
Synt.InitPersistent(self)
2011-09-07 02:14:17 +00:00
self.value = value
self.rtype = rtype
2011-09-07 12:55:58 +00:00
def StForTypein(self):
return self.rtype.StForSobj(self.value)
2011-09-07 02:14:17 +00:00
def Rtype(self):
2011-09-07 12:55:58 +00:00
return self.rtype
def Project(self, pcur):
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein())
def Eval(self, vm):
vm.Push(self.value)
2011-09-07 02:14:17 +00:00
@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):
2011-09-07 12:55:58 +00:00
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())
2011-09-07 02:14:17 +00:00
class SyntVarRef(Synt):
def InitPersistent(self, syntVar = None):
Synt.InitPersistent(self)
2011-09-07 02:14:17 +00:00
self.syntVar = syntVar
@classmethod
def RgsyntReplace(cls, syntChild, st, rtype):
rtype = rtype or RtypeAny()
return rtype.RgsyntVarRefFromSt(syntChild, st)
2011-09-07 02:14:17 +00:00
def Rtype(self):
return self.syntVar.Rtype()
2011-09-07 12:55:58 +00:00
def StForTypein(self):
if self.syntVar:
return self.syntVar.name.St()
return "<var>"
2011-09-07 12:55:58 +00:00
def Project(self, pcur):
self.syntParent.ProjectTypeinForChild(pcur.PwHoriz(self), self, self.StForTypein())
def Eval(self, vm):
2011-09-15 22:19:20 +00:00
vm.Push(vm.vars.Get(self.syntVar, self))
2011-09-12 01:43:53 +00:00
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__
2011-09-15 22:19:20 +00:00
def Eval(self, vm):
right = vm.Pop()
left = vm.Pop()
try:
vm.Push(dgEval(left, right))
except Exception as e:
2011-09-15 22:33:50 +00:00
Fail.Push(vm, "Failed to " + op + ": " + str(e), self)
def Compile(self):
2011-09-15 22:19:20 +00:00
return [self.left, self.right, self.Eval]
clsNew = type(stName, (cls,), {"Compile": Compile, "Eval": Eval, "op": op})
2011-09-12 01:43:53 +00:00
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?
2011-09-12 01:43:53 +00:00
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")
2011-09-12 01:43:53 +00:00
2011-09-07 02:14:17 +00:00
@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()
2011-03-19 00:10:02 +00:00
@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):
2011-09-15 22:19:20 +00:00
val = vm.Pop()
if not isinstance(val, Fail):
vm.Log(vm.vars.Set(self.lvalue.syntVar, vm.Pop(), self))
2011-09-20 13:00:19 +00:00
class SyntEvent(SyntDesc):
rgclsSyntEvent = []
desc = [[" "]]
@classmethod
def RgsyntReplace(cls, syntChild, st, rtype):
for clsSyntEvent in cls.rgclsSyntEvent:
if clsSyntEvent.StForTypein().lower().startswith(st.lower()):
yield clsSyntEvent.SyntDefault()
for synt in SyntExpr.RgsyntReplace(syntChild, st, rtype):
yield synt
2011-09-22 12:30:49 +00:00
class SyntWhen(SyntDesc):
desc = [["When"], " ", (SyntEvent, 1), ":", (SyntBlock, 2)]
2011-09-15 22:33:50 +00:00
#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()
2011-03-19 00:10:02 +00:00
class Fail(TPrs):
2011-09-15 22:19:20 +00:00
"""
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
2011-09-15 22:33:50 +00:00
def Push(vm, stFailure, synt):
2011-09-15 22:19:20 +00:00
fail = Fail(stFailure, synt)
vm.Push(fail)
vm.Log(fail)
@staticmethod
2011-09-15 22:33:50 +00:00
def Log(vm, stFailure, synt):
2011-09-15 22:19:20 +00:00
vm.Log(Fail(stFailure, synt))
2011-03-19 00:10:02 +00:00
2011-09-07 02:14:17 +00:00
# projection cursor
class Pcur(object):
@staticmethod
def PwProjected(synt):
pcur = Pcur()
synt.Project(pcur)
return pcur.pwVert
2011-03-19 00:10:02 +00:00
def __init__(self):
2011-09-07 02:14:17 +00:00
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()
2011-03-19 00:10:02 +00:00
2011-09-07 02:14:17 +00:00
def PwHoriz(self, synt):
if self.pwHoriz == None:
self.ProjectHorizI(self.pwVert, synt)
return self.pwHoriz
2011-03-19 00:10:02 +00:00
2011-09-07 02:14:17 +00:00
def EndLine(self):
self.pwHoriz = None
2011-03-19 00:10:02 +00:00
2011-09-07 02:14:17 +00:00
def ProjectHorizI(self, pwParent, synt):
self.pwHoriz = PwList(pwParent, synt, self.dxindent)
2011-03-19 00:10:02 +00:00
# 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
2011-09-07 02:14:17 +00:00
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__
2011-03-19 00:10:02 +00:00
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
2011-09-07 02:14:17 +00:00
class PwContainOne(Pw):
def AddChild(self, pw):
self.pwChild = pw
def RgpwChild(self):
return [self.pwChild]
def FContainer(self):
return True
2011-03-19 00:10:02 +00:00
2011-09-07 02:14:17 +00:00
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)
2011-03-19 00:10:02 +00:00
class PwStatic(Pw):
def __init__(self, pwParent, stText):
Pw.__init__(self, pwParent)
self.stText = stText
2011-09-07 02:14:17 +00:00
def StDebug(self):
return Pw.StDebug(self) + " '" + self.StTextDisplay(Ksel.NAV) + "'"
2011-03-19 00:10:02 +00:00
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 or key == ansi.K_NEWLINE:
2011-09-07 02:14:17 +00:00
self.dgEnter(self.Value(), psel)
2011-03-19 00:10:02 +00:00
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):
2011-03-19 00:10:02 +00:00
def __init__(self, pwParent, dgHandleKey):
PwContainOne.__init__(self, pwParent)
2011-03-19 00:10:02 +00:00
self.dgHandleKey = dgHandleKey
def DxDyNew(self, w, dxStart, mpksel):
return self.pwChild.DxDyNew(w, dxStart, mpksel)
2011-03-19 00:10:02 +00:00
def Draw(self, ascr, x, y, w, dxStart, mpksel, fOverlay):
self.pwChild.Draw(ascr, x, y, w, dxStart, mpksel, fOverlay)
2011-03-19 00:10:02 +00:00
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):
2011-09-12 01:43:53 +00:00
def __init__(self, pwParent, synt, dxIndent):
2011-03-19 00:10:02 +00:00
PwContainer.__init__(self, pwParent)
2011-09-12 01:43:53 +00:00
self.synt = synt
2011-03-19 00:10:02 +00:00
self.dxIndent = dxIndent
def Value(self):
2011-09-12 01:43:53 +00:00
return self.synt
2011-03-19 00:10:02 +00:00
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):
2011-09-07 02:14:17 +00:00
return Pcur.PwProjected(self.block)
2011-03-19 00:10:02 +00:00
def run(self):
while True:
key = self.client.evKey.receive(self)
2011-09-15 22:33:50 +00:00
if key == ansi.K_PGDN:
Vm().RunSynt(self.block)
continue
2011-03-19 00:10:02 +00:00
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)
2011-09-22 12:30:49 +00:00
class PselState(LeaveJoinToken):
2011-03-19 00:10:02 +00:00
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
2011-09-07 02:14:17 +00:00
self.mpclient_psel[client] = Psel(Pcur.PwProjected(self.block).PwFirstSelectable(), Ksel.NAV)
2011-03-19 00:10:02 +00:00
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]
2011-09-22 12:30:49 +00:00
2011-03-19 00:10:02 +00:00
class GameScriptTest(Game):
def InitPersistent(self):
Game.InitPersistent(self)
self.block = SyntBlock()
2011-03-19 00:10:02 +00:00
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")