diff --git a/TinyEdit/TinyEdit.py b/TinyEdit/TinyEdit.py index 0e945ccd..e80ad2ae 100755 --- a/TinyEdit/TinyEdit.py +++ b/TinyEdit/TinyEdit.py @@ -1,940 +1,85 @@ -#Note: code V squished to save mem from thumbyGraphics import display as d from thumbySprite import Sprite -import thumbyHardware as hardware -from thumbyAudio import audio from sys import print_exception -from os import ilistdir -from random import randint -from re import search from sys import path if not '/Games/TinyEdit' in path: - path.append( '/Games/TinyEdit' ) -import btn + path.append( '/Games/TinyEdit' ) +import te_btn +from te_utility import buzz, sideScroll, defaultFont +from te_keyboard import Keyboard +from editor import Editor -baseFPS=12 -d.setFPS(baseFPS) - -def defaultFont(): - d.setFont("/lib/font5x7.bin",5,10,1) - -def noise(length): - for _ in range(length): - audio.playBlocking(randint(400,500),10) - -def buzz(): - d.display.invert(1) - d.update() - noise(3) - d.display.invert(0) - d.update() - noise(12) - -def sideScroll(st,x,y,w,b,lm): - if len(st)*6>w: - global baseFPS - d.setFPS(45) - stLen=len(st)*6+w - o=b if b>0 else 0 - while True: - d.drawFilledRectangle(x,y,w,8,0) - d.drawText(st,x+w-o,y,1) - if 'lFill' in lm: - lm['lFill']() - d.update() - b=btn.which(False) - if b and b.upper() in lm: - d.setFPS(baseFPS) - return lm[b.upper()]() - o=(o+1)%(stLen) - elif 'lFill' in lm: - lm['lFill']() - d.drawText(st,x,y,1) - d.update() - b=btn.which(False) - if b and b.upper() in lm: - return lm[b.upper()]() +inEmulator = None +try: + import emulator + inEmulator = True +except ImportError: + inEmulator = False def splash(): - d.fill(0) - d.drawSprite(Sprite(37,20, - bytearray([128,192,224,112,48,48,112,224,128,0,0,0,0,0,0,0,192,160,80,168,212,234,245,250,253,254,127,191,95,175,87,43,21,10,5,2,1,159,255,240,224,240,56,28,15,3,0,0,0,0,128,252,245,234,213,171,215,47,95,55,43,21,10,5,2,1,0,0,0,0,0,0,0,0,3,1,0,1,3,3,6,6,6,6,6,3,3,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]), - 0,0,0)) - d.setFont("/lib/font8x8.bin",8,8,1) - d.drawText("Tiny",38,0,1) - d.drawText("Edit",30,10,1) - defaultFont() - d.drawText("Text editor",3,22,1) - d.drawText('A/B: Start',6,33,1) - while not btn.actionJustPressed(): - d.update() - -def getDirAbove(path): - return path[0:path.rfind('/',0,len(path)-1)] - -class Sprites: - shiftKey=Sprite(7,8, - bytearray([255,251,129,252,129,251,255,0,4,126,3,126,4,0])) - tabKey=Sprite(9,8, - bytearray([247,247,247,128,193,227,247,128,255,8,8,8,127,62,28,8,127,0])) - arrowFrameData=bytearray([247,227,193,128,247,247,255,8,28,62,127,8,8,0]) - leftArrowKey=Sprite(7,8, - arrowFrameData) - rightArrowKey=Sprite(7,8, - arrowFrameData,0,0,0,1) - delEndFrameData=bytearray([247,235,213,170,213,170,221,190,170,182,170,190,128,8,20,42,85,42,85,34,65,85,73,85,65,127]) - leftDelEndKey=Sprite(13,8, - delEndFrameData) - rightDelEndKey=Sprite(13,8, - delEndFrameData,0,0,0,1) - delWordFrameData=bytearray([247,235,213,170,221,190,170,182,170,190,128,8,20,42,85,34,65,85,73,85,65,127]) - leftDelWordKey=Sprite(11,8, - delWordFrameData) - rightDelWordKey=Sprite(11,8, - delWordFrameData,0,0,0,1) - delFrameData=bytearray([247,235,221,190,170,182,170,190,128,8,20,34,65,85,73,85,65,127]) - leftDelKey=Sprite(9,8, - delFrameData) - rightDelKey=Sprite(9,8, - delFrameData,0,0,0,1) - moveIcon=Sprite(5,11, - bytearray([68,198,255,198,68,0,0,1,0,0]), - 0,16,0) - -class Menu: - def __init__(s,items): - s.items=items - s.count=len(items) - s.item=0 - s.result=None - - def a(s): - s.result=s.items[s.item][1]() - return True - - def u(s): - if s.item>0: - s.item=s.item-1 - return False - - def d(s): - if s.item=s.count else s.items[s.item+1][0]), - (None if s.item+2>=s.count else s.items[s.item+2][0]) - ] - - def display(s): - def lF(): - d.drawFilledRectangle(0,16,6,8,0) - d.drawText('>',0,16,1) - s.result=None - while True: - d.fill(0) - itemY=0 - for item in s.displayItems(): - if item: - if itemY==16: - curr=item - else: - d.drawText(item,6,itemY,1) - itemY=itemY+8 - if sideScroll(curr,6,16,d.width,d.width,{'A':lambda:s.a(),'B':lambda:True,'U':lambda:s.u(),'D':lambda:s.d(),'lFill':lambda:lF()}): - break - return s.result - -shownKbInstructions=False - -class Key: - def __init__(s,character): - s.character=character - s.selected=False - - def display(s,x,y): - if s.character==' ': - if s.selected: - d.drawFilledRectangle(x,y,16,8,1) - d.setFont("/lib/font3x5.bin",3,5,1) - d.drawText('[sp]',x+1,y+2,0 if s.selected else 1) - defaultFont() - return 16 - if s.selected: - d.drawFilledRectangle(x,y,7,8,1) - d.drawText(s.character,x+1,y,0 if s.selected else 1) - return 7 - - def select(s,selected): - s.selected=selected - - def press(s): - return s.character - -class ActionKey: - def __init__(s,action,sprite): - s.selected=False - s.sprite=sprite - s.action=action - - def display(s,x,y): - s.sprite.x=x - s.sprite.y=y - s.sprite.setFrame(0 if s.selected else 1) - d.drawSprite(s.sprite) - return s.sprite.width - - def select(s,selected): - s.selected=selected - - def press(s): - ch=s.action() - return ch if ch else '' - -class KeyRow: - def __init__(s,rowNum,characters,selected=0): - s.rowNum=rowNum - s.keys=[] - for character in characters: - s.keys.append(Key(character)) - s.selected=selected - if selected: - s.select(selected) - - def append(s,key): - s.keys.append(key) - - def insert(s,index,key): - s.keys.insert(index,key) - - def select(s,index): - s.keys[s.selected].select(False) - if index<0: - s.selected=len(s.keys)-1 - elif index>=len(s.keys): - s.selected=0 - else: - s.selected=index - s.keys[s.selected].select(True) - - def deselect(s): - s.keys[s.selected].select(False) - - def display(s): - offset=s.rowNum - for i,key in enumerate(s.keys): - offset=offset+key.display(offset,s.rowNum*8) - - def right(s): - s.select(s.selected+1) - - def left(s): - s.select(s.selected-1) - - def getCharacter(s): - return s.keys[s.selected].press() - -class KeyLayer: - def __init__(s,characterRows): - s.rows=[] - for i,characterRow in enumerate(characterRows): - s.rows.append(KeyRow(i,characterRow)) - s.row=3 - - def add(s,key,rowNum): - s.rows[rowNum].append(key) - - def insert(s,key,rowNum,index): - s.rows[rowNum].insert(index,key) - - def display(s): - for row in s.rows: - row.display() - - def up(s): - if s.row>0: - index=s.rows[s.row].selected - s.rows[s.row].deselect() - s.row=s.row-1 - s.rows[s.row].select(index) - - def down(s): - if s.row?/|\\{}','',' ']) - ] - shiftKey=ActionKey(lambda:s.shiftKey(),Sprites.shiftKey) - shiftKey.selected=True - tabKey=ActionKey(lambda:'\t',Sprites.tabKey) - for i in range(4): - s.layers[i].insert(tabKey,3,0) - s.layers[i].insert(shiftKey,3,0) - - leftArrowKey=ActionKey(lambda:s.leftArrow(),Sprites.leftArrowKey) - for i in range(4): - s.layers[i].add(leftArrowKey,3) - rightArrowKey=ActionKey(lambda:s.rightArrow(),Sprites.rightArrowKey) - for i in range(4): - s.layers[i].add(rightArrowKey,3) - - s.layers[3].add(ActionKey(lambda:s.leftDelEnd(),Sprites.leftDelEndKey),2) - - leftDelWordKey=ActionKey(lambda:s.leftDelWord(),Sprites.leftDelWordKey) - for i in range(3): - s.layers[i].add(leftDelWordKey,3) - s.layers[3].add(leftDelWordKey,2) - - delKey=ActionKey(lambda:s.delKey(),Sprites.leftDelKey) - for i in range(3): - s.layers[i].add(delKey,3) - s.layers[3].add(delKey,2) - - s.layers[3].add(ActionKey(lambda:s.rightDel(),Sprites.rightDelKey),2) - s.layers[3].add(ActionKey(lambda:s.rightDelWord(),Sprites.rightDelWordKey),2) - s.layers[3].add(ActionKey(lambda:s.rightDelEnd(),Sprites.rightDelEndKey),2) - - s.layer=layer - s.text=text - s.cursor=min(position+6,len(text)) - - def shiftKey(s): - s.layer=s.layer+1 - if s.layer>=len(s.layers): - s.layer=0 - - def delKey(s): - if s.cursor>0: - s.text=s.text[0:s.cursor-1]+s.text[s.cursor:] - s.cursor=s.cursor-1 - else: - buzz() - - def rightDel(s): - if s.cursor < len(s.text): - s.text=s.text[0:s.cursor]+s.text[s.cursor+1:] - else: - buzz() - - def leftDelWord(s): - pre=s.text[0:s.cursor] - match=search(r'(\w+|\W+)$',pre) - if match: - start=match.start() - s.text=pre[0:start]+s.text[s.cursor:] - s.cursor=start - else: - buzz() - - def rightDelWord(s): - post=s.text[s.cursor:] - match=search(r'^(\w+|\W+)',post) - if match: - end=match.end() - s.text=s.text[0:s.cursor]+post[end:] - else: - buzz() - - def leftDelEnd(s): - s.text=s.text[s.cursor:] - s.cursor=0 - - def rightDelEnd(s): - s.text=s.text[0:s.cursor] - - def leftArrow(s): - if s.cursor>0: - s.cursor=s.cursor-1 - - def rightArrow(s): - if s.cursor0: - d.drawText('<',0,33,1) - adjCursor=s.cursor-windowStart - for i,ch in enumerate(windowText): - x=6*i+6 - if i==adjCursor: - d.drawFilledRectangle(x,33,6,8,1) - colour=0 - else: - colour=1 - d.drawText(ch,x,33,colour) - if s.cursor==textLen: - d.drawFilledRectangle(adjCursor*6+6,33,6,8,1) - if textLen>windowEnd: - d.drawText('>',66,33,1) - d.update() - - def handleInput(s): - b=btn.which() - if not b: - return None - if b in 'Uu': - s.layers[s.layer].up() - elif b in 'Dd': - s.layers[s.layer].down() - elif b in 'Rr': - s.layers[s.layer].right() - elif b in 'Ll': - s.layers[s.layer].left() - elif b in 'Aa': - s.handlePress() - elif b=='B': - result=s.text - s.text='' - return result - return None - - def handlePress(s): - keyOut=s.layers[s.layer].getCharacter() - if len(keyOut)>0: - s.text=s.text[0:s.cursor]+keyOut+s.text[s.cursor:] - s.cursor=s.cursor+1 - - def instructions(s): - global shownKbInstructions - shownKbInstructions=True - d.fill(0) - d.drawText('Keyboard:',12,0,1) - d.drawText('U',6,8,1) - d.drawText('L R cursor',0,16,1) - d.drawText('D',6,24,1) - d.drawText('--more--',12,32,1) - while(not btn.actionJustPressed()): - d.update() - d.fill(0) - d.drawText('Keyboard:',12,0,1) - d.drawText('A: press key',0,8,1) - d.drawText('B: close',0,16,1) - d.drawText('--start--',6,32,1) - while(not btn.actionJustPressed()): - d.update() - - def getOutput(s): - global shownKbInstructions - if not shownKbInstructions: - s.instructions() - while True: - s.display() - output=s.handleInput() - if output or output=='': - return output - -class Line: - def __init__(s,t): - s.text=t[0:-1] if t.endswith('\n') else t - s.size=len(s.text) - - def getText(s,position): - if position>s.size: - return '<' - if position>0: - result='<' - else: - result=' ' - maxRight=position+10 - result+=s.text[position:min(maxRight,s.size)] - if maxRight' - return result - -class Mode: - MENU=1 - SCROLL=2 - MOVE=3 - SPLIT=4 - -class File: - def __init__(s,fileName): - s.fileName=fileName - s.lines=[] - if fileName: - with open(fileName,encoding="utf-8") as f: - for line in f: - s.lines.append(Line(line)) - if len(s.lines)==0: - s.lines.append(Line("")) - s.line=0 - s.position=0 - s.splitPosition=-1 - - def getDisplayLine(s,index): - if index<0 or index>=len(s.lines): - return None - return s.lines[index].getText(s.position) - - def getDisplayLines(s): - return[ - s.getDisplayLine(s.line-2), - s.getDisplayLine(s.line-1), - s.getDisplayLine(s.line), - s.getDisplayLine(s.line+1), - s.getDisplayLine(s.line+2) - ] - - def getFullLine(s,index=None): - if index==None: - return s.lines[s.line].text - if index<0 or index>=len(s.lines): - return None - return s.lines[index].text - - def display(s,mode): - d.fill(0) - if mode==Mode.SPLIT: - Sprites.leftArrowKey.setFrame(1) - Sprites.rightArrowKey.setFrame(1) - lineY=0 - for line in s.getDisplayLines(): - if line: - if lineY==16: - if mode==Mode.MOVE: - d.drawText(line,1,lineY,1) - d.drawRectangle(6,lineY,d.width-12,8,1) - d.drawFilledRectangle(0,lineY,6,7,0) - Sprites.moveIcon.x=0 - d.drawSprite(Sprites.moveIcon) - d.drawFilledRectangle(d.width-6,lineY,6,7,0) - Sprites.moveIcon.x=d.width-5 - d.drawSprite(Sprites.moveIcon) - elif mode==Mode.SPLIT: - d.drawText(line,1,lineY,1) - top=lineY-1 - rhs=d.width-7 - d.drawLine(6,top,rhs,top,1) - d.drawFilledRectangle(0,lineY,6,7,0) - Sprites.leftArrowKey.x=0 - Sprites.leftArrowKey.y=lineY - d.drawSprite(Sprites.leftArrowKey) - d.drawLine(6,lineY,6,lineY+7,1) - d.drawFilledRectangle(rhs,lineY,6,7,0) - Sprites.rightArrowKey.x=rhs - Sprites.rightArrowKey.y=lineY - d.drawSprite(Sprites.rightArrowKey) - d.drawLine(rhs,lineY,rhs,lineY+7,1) - else: - d.drawFilledRectangle(0,16,d.width,8,1) - d.drawText(line,1,lineY,0) - else: - d.drawText(line,1,lineY,1) - if lineY==24 and mode==Mode.SPLIT: - d.drawFilledRectangle(0,lineY,6,7,0) - Sprites.leftArrowKey.y=lineY - d.drawSprite(Sprites.leftArrowKey) - d.drawLine(6,lineY,6,lineY+7,1) - rhs=d.width-7 - bottom=lineY+7 - d.drawLine(6,bottom,rhs,bottom,1) - d.drawFilledRectangle(rhs,lineY,6,7,0) - Sprites.rightArrowKey.y=lineY - d.drawSprite(Sprites.rightArrowKey) - d.drawLine(rhs,bottom,rhs,lineY,1) - lineY=lineY+8 - d.update() - - def goTo(s,line): - if line>=0 and line0: - s.line=s.line-1 - - def down(s): - if s.line+10: - s.lines.insert(s.line-1,s.lines.pop(s.line)) - s.line=s.line-1 - else: - buzz() - - def moveDown(s): - if s.line0: - s.position=s.position-1 - - def right(s): - s.position=s.position+1 - - def setPosition(s,position): - if position>=0: - s.position=position - else: - buzz() - - def selectLine(s,lineNumber): - if lineNumber>=0 and lineNumber1: - s.lines.pop(s.line) - if s.line>=len(s.lines): - s.line=len(s.lines)-1 - else: - buzz() - - def joinNext(s): - if s.linelenOriginalLine: - s.position=max(lenOriginalLine-6,0) - s.splitPosition=min(s.position+6,lenOriginalLine) - s.duplicateLine() - s.replaceLine(s.getFullLine()[s.splitPosition:]) - s.up() - s.replaceLine(originalLine[0:s.splitPosition]) - - def shiftSplitLeft(s): - if s.splitPosition>0: - s.down() - s.replaceLine(s.lines[s.line-1].text[-1]+s.getFullLine()) - s.up() - s.replaceLine(s.getFullLine()[0:-1]) - if s.position>0: - s.position=s.position-1 - s.splitPosition=s.splitPosition-1 - else: - buzz() - - def shiftSplitRight(s): - if len(s.lines[s.line+1].text)>0: - s.replaceLine(s.getFullLine()+s.lines[s.line+1].text[0]) - s.down() - s.replaceLine(s.getFullLine()[1:]) - s.up() - if s.splitPosition>6: - s.position=s.position+1 - s.splitPosition=s.splitPosition+1 - else: - buzz() - - def duplicateLine(s): - line=s.lines[s.line] - newLine=Line(line.text) - s.lines.insert(s.line,newLine) - s.line=s.line+1 - - def save(s,dir): - if not s.fileName: - s.fileName=dir+'/'+Keyboard("",0).getOutput() - with open(s.fileName,'w',encoding="utf-8") as f: - for thisLine in s.lines: - f.write(thisLine.text) - f.write('\n') - -class Editor: - def __init__(s): - s.mode=Mode.MENU - s.file=None - s.dir='' - s.findStr='' - s.findLine=0 - s.noFileMenu=Menu([ - ('Open...',lambda:s.open()), - ('Help',lambda:s.hlp()), - ('Exit',lambda:hardware.reset()) - ]) - s.withFileMenu=Menu([ - ('File...',lambda:s.fileMenu.display()), - ('Line...',lambda:s.lineMenu.display()), - ('Help',lambda:s.hlp()), - ('Exit',lambda:hardware.reset()) - ]) - s.fileMenu=Menu([ - ('Open...',lambda:s.open()), - ('Save...',lambda:s.save()), - ('New...',lambda:s.new()) - ]) - s.lineMenu=Menu([ - ('Duplicate',lambda:s.file.duplicateLine()), - ('Move',lambda:s.move()), - ('Find...',lambda:s.find()), - ('Find next',lambda:s.findNext()), - ('Go to...',lambda:s.goTo()), - ('Join next',lambda:s.file.joinNext()), - ('Split',lambda:s.split()), - ('Delete',lambda:s.file.deleteLine()) - ]) - - def save(s): - if s.file.fileName: - m=Menu([ - (s.file.fileName[s.file.fileName.rfind('/')+1:],lambda:s.file.save(s.dir)), - ('Save as...',lambda:s.saveAs()) - ]) - m.display() - else: - s.file.save(s.dir) - - def saveAs(s): - s.file.fileName=None - s.file.save(s.dir) - - def hlp(s): - d.fill(0) - d.drawText("Keys:",18,0,1) - d.drawText("U",12,8,1) - d.drawText("L R scroll",6,16,1) - d.drawText("D",12,24,1) - d.drawText("--more--",12,32,1) - while not btn.actionJustPressed(): - d.update() - d.fill(0) - d.drawText("A menu,",0,0,1) - d.drawText("choose",24,8,1) - d.drawText("B edit line,",0,16,1) - d.drawText("close menu",12,24,1) - d.drawText("--key help--",0,32,1) - while not btn.actionJustPressed(): - d.update() - def drwSp(sp,x,y): - sp.x=x - sp.y=y - sp.setFrame(1) - d.drawSprite(sp) - d.fill(0) - drwSp(Sprites.leftDelKey,0,0) - drwSp(Sprites.rightDelKey,10,0) - d.drawText("Del",21,0,1) - drwSp(Sprites.leftDelWordKey,0,8) - drwSp(Sprites.rightDelWordKey,12,8) - d.drawText("Del chnk",25,8,1) - drwSp(Sprites.leftDelEndKey,0,16) - drwSp(Sprites.rightDelEndKey,14,16) - d.drawText("Del EOL",29,16,1) - drwSp(Sprites.shiftKey,2,24) - d.drawText("Shift",12,24,1) - sideScroll('More help: http://codeberg.org/JBanana/TinyEdit',0,33,d.width,0,{'A':lambda:None,'B':lambda:None}) - - def chooseFile(s,dirPath=None): - if not dirPath: - dirPath='/' - while True: - if not dirPath[-1:]=='/': - dirPath=dirPath+'/' - dirEntry=s.chooseDirEntry(dirPath) - if not dirEntry: - return None - newPath=dirEntry[0] - entryType=dirEntry[1] - if entryType==0x4000: #dir - dirPath=newPath - elif entryType==0x8000: #file - return newPath - - def chooseDirEntry(s,dirPath): - def makeLambda(dirEntry): - return lambda:dirEntry - dirMenuItems=[] - if dirPath!='/': - dirAbove=getDirAbove(dirPath) - if dirAbove=='': - dirAbove='/' - dirMenuItems.append(('../',makeLambda((dirAbove,0x4000)))) - for dirEntry in ilistdir(dirPath): - entryType=dirEntry[1] - if entryType==0x4000: - dirMenuItems.append((dirEntry[0]+'/',makeLambda((dirPath+dirEntry[0],0x4000)))) - elif entryType==0x8000: - dirMenuItems.append((dirEntry[0],makeLambda((dirPath+dirEntry[0],0x8000)))) - return Menu(dirMenuItems).display() - - def open(s,fileName=None): - if fileName: - s.file=File(fileName) - return - if s.file: - chosenPath=s.chooseFile(getDirAbove(s.file.fileName)) - else: - chosenPath=s.chooseFile() - if chosenPath: - s.file=File(chosenPath) - s.dir=getDirAbove(chosenPath) - - def new(s): - lastDir=s.dir - s.file=File(None) - s.dir=lastDir - - def editLine(s): - s.file.replaceLine(Keyboard(s.file.getFullLine(),s.file.position).getOutput()) - - def move(s): - s.mode=Mode.MOVE - - def find(s): - s.findStr=Keyboard(s.findStr,0).getOutput() - s.findLine=0 - s.findNext() - - def goTo(s): - try: - s.file.goTo(int(Keyboard(str(s.file.line+1),0,2).getOutput())-1) - except ValueError: - buzz() - - def split(s): - s.file.startSplit() - s.mode=Mode.SPLIT - - def findNext(s): - while True: - line=s.file.getFullLine(s.findLine) - if line or line=='': - s.findLine=s.findLine+1 - position=line.find(s.findStr) - if position>=0: - s.file.selectLine(s.findLine-1) - s.file.setPosition(max(0,position-3)) - break - else: - buzz() - break - - def pressedU(s): - if s.mode==Mode.SPLIT: - return - if s.mode==Mode.MOVE: - s.file.moveUp() - else: - s.file.up() - - def pressedD(s): - if s.mode==Mode.SPLIT: - return - if s.mode==Mode.MOVE: - s.file.moveDown() - else: - s.file.down() - - def pressedL(s): - if s.mode==Mode.SPLIT: - s.file.shiftSplitLeft() - else: - s.file.left() - - def pressedR(s): - if s.mode==Mode.SPLIT: - s.file.shiftSplitRight() - else: - s.file.right() - - def handleInput(s): - b=btn.which() - if not b: - return - if b in 'Uu': - s.pressedU() - elif b in 'Dd': - s.pressedD() - elif b in 'Ll': - s.pressedL() - elif b in 'Rr': - s.pressedR() - elif b=='B': - if s.mode==Mode.MOVE or s.mode==Mode.SPLIT: - s.mode=Mode.SCROLL - else: - s.editLine() - elif b=='A': - if s.mode==Mode.MOVE or s.mode==Mode.SPLIT: - s.mode=Mode.SCROLL - else: - s.mode=Mode.MENU - - def display(s): - if s.mode==Mode.MENU: - if s.file: - s.withFileMenu.display() - else: - s.noFileMenu.display() - if s.file and s.mode==Mode.MENU: - s.mode=Mode.SCROLL - elif s.file: - s.file.display(s.mode) - else: - buzz() - s.mode=Mode.MENU + d.fill(0) + d.drawSprite(Sprite(37,20, + bytearray([128,192,224,112,48,48,112,224,128,0,0,0,0,0,0,0,192,160,80,168,212,234,245,250,253,254,127,191,95,175,87,43,21,10,5,2,1,159,255,240,224,240,56,28,15,3,0,0,0,0,128,252,245,234,213,171,215,47,95,55,43,21,10,5,2,1,0,0,0,0,0,0,0,0,3,1,0,1,3,3,6,6,6,6,6,3,3,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]), + 0,0,0)) + d.setFont("/lib/font8x8.bin",8,8,1) + d.drawText("Tiny",38,0,1) + d.drawText("Edit",30,10,1) + d.setFont("/lib/font3x5.bin",3,5,1) + d.drawText('2',69,12,1) + defaultFont() + d.drawText("Text editor",3,22,1) + d.drawText('A/B: Start',6,33,1) + while not te_btn.actionJustPressed(): + d.update() + te_btn.clear() + +def makeSprites(): + sprites = { + 'shiftKey': Sprite( 7, 8, bytearray([255,251,129,252,129,251,255,0,4,126,3,126,4,0]) ), + 'tabKey': Sprite( 9, 8, bytearray([247,247,247,128,193,227,247,128,255,8,8,8,127,62,28,8,127,0]) ), + } + + arrowFrameData = bytearray([247,227,193,128,247,247,255,8,28,62,127,8,8,0]) + sprites[ 'leftArrowKey' ] = Sprite( 7, 8, arrowFrameData ) + sprites[ 'rightArrowKey' ] = Sprite( 7, 8, arrowFrameData, 0, 0, 0, 1 ) + + delEndFrameData = bytearray([247,235,213,170,213,170,221,190,170,182,170,190,128,8,20,42,85,42,85,34,65,85,73,85,65,127]) + sprites[ 'leftDelEndKey' ] = Sprite( 13, 8, delEndFrameData ) + sprites[ 'rightDelEndKey' ] = Sprite( 13, 8, delEndFrameData, 0, 0, 0, 1 ) + + delWordFrameData = bytearray([247,235,213,170,221,190,170,182,170,190,128,8,20,42,85,34,65,85,73,85,65,127]) + sprites[ 'leftDelWordKey' ] = Sprite( 11, 8, delWordFrameData ) + sprites[ 'rightDelWordKey' ] = Sprite( 11, 8, delWordFrameData, 0, 0, 0, 1 ) + + delFrameData = bytearray([247,235,221,190,170,182,170,190,128,8,20,34,65,85,73,85,65,127]) + sprites[ 'leftDelKey' ] = Sprite( 9, 8, delFrameData ) + sprites[ 'rightDelKey' ] = Sprite( 9, 8, delFrameData, 0, 0, 0, 1 ) + + return sprites + +sprites = makeSprites() try: - splash() - editor=Editor() - while True: - editor.handleInput() - editor.display() + splash() + editor = Editor( + lambda s, t, p, l = 0: Keyboard( s, t, p, l ), + lambda n: sprites[ n ] + ) + while True: + editor.handleInput() + if editor.display(): + break except Exception as x: - buzz() - - try: - import emulator - print_exception(x) - except ImportError: - with open('/Games/TinyEdit/crashdump.log','w',encoding="utf-8") as f: - print_exception(x,f) - d.fill(0) - d.drawText("Editor died",3,8,1) - d.drawText("Problem was:",0,22,1) - sideScroll(str(x),0,30,d.width,-1,{'A':lambda:hardware.reset(),'B':lambda:hardware.reset()}) + buzz() + if inEmulator: + print_exception( x ) + else: + with open( '/Games/TinyEdit/crashdump.log', 'w', encoding = "utf-8" ) as f: + print_exception( x, f ) + d.fill( 0 ) + d.drawText( "Editor died", 3, 8, 1 ) + d.drawText( "Problem was:", 0, 22, 1 ) + sideScroll( str( x ), 0, 30, d.width, -1, { + 'A': lambda: True, + 'B': lambda: True + } ) diff --git a/TinyEdit/arcade_description.txt b/TinyEdit/arcade_description.txt index cf901bd1..bfa6313b 100755 --- a/TinyEdit/arcade_description.txt +++ b/TinyEdit/arcade_description.txt @@ -8,5 +8,6 @@ Warning! Don't edit anything unless you have a backup. More info at http://codeberg.org/JBanana/TinyEdit -v1.1 -Can load slightly larger files +v2.0 +Can now edit arbitrarily large files +Can now be called from another thumby app diff --git a/TinyEdit/btn.py b/TinyEdit/btn.py deleted file mode 100755 index d8065004..00000000 --- a/TinyEdit/btn.py +++ /dev/null @@ -1,49 +0,0 @@ -import thumbyButton as b - -r = 0 - -def actionJustPressed(): - return b.actionJustPressed() - -def which(actOk=True): - global r - if r >= 4: - if b.buttonU.pressed(): - return 'u' - elif b.buttonD.pressed(): - return 'd' - elif b.buttonL.pressed(): - return 'l' - elif b.buttonR.pressed(): - return 'r' - elif actOk and b.buttonA.pressed(): - return 'a' - elif actOk and b.buttonB.pressed(): - return 'b' - else: - r = 0 - elif r > 0: - if b.inputPressed(): - r = r + 1 - else: - r = 0 - else: - if b.buttonU.justPressed(): - r = 1 - return 'U' - if b.buttonD.justPressed(): - r = 1 - return 'D' - if b.buttonL.justPressed(): - r = 1 - return 'L' - if b.buttonR.justPressed(): - r = 1 - return 'R' - if b.buttonA.justPressed(): - r = 1 - return 'A' - if b.buttonB.justPressed(): - r = 1 - return 'B' - return None diff --git a/TinyEdit/editor.py b/TinyEdit/editor.py new file mode 100755 index 00000000..6a92c0a1 --- /dev/null +++ b/TinyEdit/editor.py @@ -0,0 +1,480 @@ +from thumbyGraphics import display as d +from thumbySprite import Sprite +from os import ilistdir +from sys import path +if not '/Games/TinyEdit' in path: + path.append( '/Games/TinyEdit' ) +import te_btn +from te_utility import buzz, sideScroll, getDirAbove +from te_menu import Menu +from filechunks import FileChunks + +class Mode: + MENU = 1 + SCROLL = 2 + MOVE = 3 + SPLIT = 4 + +class File: + def __init__( self, fileName, keyboardGetter, spriteGetter ): + self.fileName = fileName if fileName else "" + self.fileChunks = FileChunks( self.fileName ) + self.position = 0 + self.splitPosition = -1 + self.getKeyboard = keyboardGetter + self.getSprite = spriteGetter + + def getCurrentLineIndex( self ): + return self.fileChunks.getCurrentLineIndex() + + def getDisplayLine( self, index ): + if index < 0 or index >= self.fileChunks.lineCount(): + return None + return self.fileChunks.getLine( index ).getText( self.position ) + + def getDisplayLines( self ): + line = self.fileChunks.getCurrentLineIndex() + return[ + self.getDisplayLine( line - 2 ), + self.getDisplayLine( line - 1 ), + self.getDisplayLine( line ), + self.getDisplayLine( line + 1 ), + self.getDisplayLine( line + 2 ) + ] + + def getFullLine( self, index = None ): + if index == None: + return self.fileChunks.getCurrentLine().text + if index < 0 or index >= self.fileChunks.lineCount(): + return None + return self.fileChunks.getLine( index ).text + + def display( self, mode ): + d.fill( 0 ) + leftArrowKey = self.getSprite( 'leftArrowKey' ) + rightArrowKey = self.getSprite( 'rightArrowKey' ) + if mode == Mode.SPLIT: + leftArrowKey.setFrame( 1 ) + rightArrowKey.setFrame( 1 ) + lineY = 0 + moveIcon = Sprite( 5, 11, + bytearray([68,198,255,198,68,0,0,1,0,0]), + 0, 16, 0 ) + for line in self.getDisplayLines(): + if line: + if lineY == 16: + if mode == Mode.MOVE: + d.drawText( line, 1, lineY, 1 ) + d.drawRectangle( 6, lineY, d.width - 12, 8, 1 ) + d.drawFilledRectangle( 0, lineY, 6, 7, 0 ) + moveIcon.x = 0 + d.drawSprite( moveIcon ) + d.drawFilledRectangle( d.width - 6, lineY, 6, 7, 0 ) + moveIcon.x = d.width - 5 + d.drawSprite( moveIcon ) + elif mode == Mode.SPLIT: + d.drawText( line, 1, lineY, 1 ) + top = lineY - 1 + rhs = d.width - 7 + d.drawLine( 6, top, rhs, top, 1 ) + d.drawFilledRectangle( 0, lineY, 6, 7, 0 ) + leftArrowKey.x = 0 + leftArrowKey.y = lineY + d.drawSprite( leftArrowKey ) + d.drawLine( 6, lineY, 6, lineY + 7, 1 ) + d.drawFilledRectangle( rhs, lineY, 6, 7, 0 ) + rightArrowKey.x = rhs + rightArrowKey.y = lineY + d.drawSprite( rightArrowKey ) + d.drawLine( rhs, lineY, rhs, lineY + 7, 1 ) + else: + d.drawFilledRectangle( 0, 16, d.width, 8, 1 ) + d.drawText( line, 1, lineY, 0 ) + else: + d.drawText( line, 1, lineY, 1 ) + if lineY == 24 and mode == Mode.SPLIT: + d.drawFilledRectangle( 0, lineY, 6, 7, 0 ) + leftArrowKey.y = lineY + d.drawSprite( leftArrowKey ) + d.drawLine( 6, lineY, 6, lineY + 7, 1 ) + rhs = d.width - 7 + bottom = lineY + 7 + d.drawLine( 6, bottom, rhs, bottom, 1 ) + d.drawFilledRectangle( rhs, lineY, 6, 7, 0 ) + rightArrowKey.y = lineY + d.drawSprite( rightArrowKey ) + d.drawLine( rhs, bottom, rhs, lineY, 1 ) + lineY = lineY + 8 + d.update() + + def goTo( self, line ): + if line >= 0 and line < self.fileChunks.lineCount(): + self.fileChunks.setCurrentLineIndex( line ) + else: + buzz() + + def up( self ): + line = self.fileChunks.getCurrentLineIndex() + if line > 0: + self.fileChunks.setCurrentLineIndex( line - 1 ) + + def down( self ): + line = self.fileChunks.getCurrentLineIndex() + if line + 1 < self.fileChunks.lineCount(): + self.fileChunks.setCurrentLineIndex( line + 1 ) + + def moveUp( self ): + line = self.fileChunks.getCurrentLineIndex() + if line > 0: + self.fileChunks.moveUp() + else: + buzz() + + def moveDown( self ): + line = self.fileChunks.getCurrentLineIndex() + if line < self.fileChunks.lineCount() - 1: + self.fileChunks.moveDown() + else: + buzz() + + def left( self ): + if self.position > 0: + self.position = self.position - 1 + + def right( self ): + self.position = self.position + 1 + + def setPosition( self, position ): + if position >= 0: + self.position = position + else: + buzz() + + def replaceLine( self, newText ): + self.fileChunks.replaceCurrentLine( newText ) + + def deleteLine( self ): + self.fileChunks.deleteCurrentLine() + + def joinNext( self ): + if self.fileChunks.getCurrentLineIndex() < self.fileChunks.lineCount() - 1: + nextLine = self.getCurrentLineIndex() + 1 + self.replaceLine( self.getFullLine() + ' ' + self.getFullLine( nextLine ) ) + self.goTo( nextLine ) + self.deleteLine() + self.goTo( nextLine - 1 ) + else: + buzz() + + def startSplit( self ): + originalLine = self.getFullLine() + lenOriginalLine = len( originalLine ) + if self.position + 6 > lenOriginalLine: + self.position = max( lenOriginalLine - 6, 0 ) + self.splitPosition = min( self.position + 6, lenOriginalLine ) + self.duplicateLine() + self.replaceLine( self.getFullLine()[ self.splitPosition : ] ) + self.up() + self.replaceLine( originalLine[ 0 : self.splitPosition ] ) + + def shiftSplitLeft( self ): + currentText = self.getFullLine() + nextText = self.getFullLine( self.getCurrentLineIndex() + 1 ) + if self.splitPosition > 0: + self.down() + self.replaceLine( currentText[ -1 ] + nextText ) + self.up() + self.replaceLine( currentText[ 0 : -1 ] ) + if self.position > 0: + self.position = self.position - 1 + self.splitPosition = self.splitPosition - 1 + else: + buzz() + + def shiftSplitRight( self ): + currentText = self.getFullLine() + nextText = self.getFullLine( self.getCurrentLineIndex() + 1 ) + if len( nextText ) > 0: + self.replaceLine( currentText + nextText[ 0 ] ) + self.down() + self.replaceLine( nextText[ 1 : ] ) + self.up() + if self.splitPosition > 6: + self.position = self.position + 1 + self.splitPosition = self.splitPosition + 1 + else: + buzz() + + def duplicateLine( self ): + self.fileChunks.duplicateCurrentLine() + self.down() + + def insertLine( self ): + self.fileChunks.insertLine( "" ) + + def save( self, dir ): + if not self.fileName: + self.fileName = dir + '/' + self.getKeyboard( self.getSprite, "", 0 ).getOutput() + with open( self.fileName, 'w', encoding = "utf-8" ) as f: + self.fileChunks.writeTo( f ) + + def close ( self ): + self.fileChunks.close() + +class Editor: + def __init__( self, keyboardGetter, spriteGetter, log = None, filePath = None ): + if log: log( 'Editor __init__ start' ) + self.getKeyboard = keyboardGetter + self.getSprite = spriteGetter + self.mode = Mode.MENU + self.file = None + self.dir = '' + self.findStr = '' + self.findLine = 0 + self.noFileMenu = Menu( [ + ( 'Open...', lambda: self.open() ), + ( 'Help', lambda: self.hlp() ), + ( 'Exit', lambda: True ) + ] ) + self.withFileMenu = Menu( [ + ( 'File...', lambda: self.fileMenu.display() ), + ( 'Line...', lambda: self.lineMenu.display() ), + ( 'Help', lambda: self.hlp() ), + ( 'Exit', lambda: self.exit() ) + ] ) + self.fileMenu = Menu( [ + ( 'Open...', lambda: self.open() ), + ( 'Save...', lambda: self.save() ), + ( 'New...', lambda: self.new() ) + ] ) + self.lineMenu = Menu( [ + ( 'Insert', lambda: self.file.insertLine() ), + ( 'Duplicate',lambda: self.file.duplicateLine() ), + ( 'Move', lambda: self.move() ), + ( 'Find...', lambda: self.find() ), + ( 'Find next',lambda: self.findNext() ), + ( 'Go to...', lambda: self.goTo() ), + ( 'Join next',lambda: self.file.joinNext() ), + ( 'Split', lambda: self.split() ), + ( 'Delete', lambda: self.file.deleteLine() ) + ]) + if filePath: + self.open( filePath ) + self.mode = Mode.SCROLL + if log: log( 'Editor __init__ end' ) + + def save( self ): + if self.file.fileName: + m = Menu( [ + ( self.file.fileName[ self.file.fileName.rfind( '/' ) + 1 : ], lambda: self.file.save( self.dir ) ), + ( 'Save as...', lambda: self.saveAs() ) + ]) + m.display() + else: + self.file.save( self.dir ) + + def saveAs( self ): + self.file.fileName = None + self.file.save( self.dir ) + + def hlp( self ): + d.fill( 0 ) + d.drawText( "Keys:", 18, 0, 1 ) + d.drawText( "U", 12, 8, 1 ) + d.drawText( "L R scroll", 6, 16, 1 ) + d.drawText( "D", 12, 24, 1 ) + d.drawText( "--more--", 12, 32, 1 ) + while not te_btn.actionJustPressed(): + d.update() + d.fill( 0 ) + d.drawText( "A menu,", 0, 0, 1 ) + d.drawText( "choose", 24, 8, 1 ) + d.drawText( "B edit line,", 0, 16, 1 ) + d.drawText( "close menu", 12, 24, 1 ) + d.drawText( "--key help--", 0, 32, 1 ) + while not te_btn.actionJustPressed(): + d.update() + def drwSp( sp, x, y ): + sp.x = x + sp.y = y + sp.setFrame( 1 ) + d.drawSprite( sp ) + d.fill( 0 ) + drwSp( self.getSprite( 'leftDelKey' ), 0, 0 ) + drwSp( self.getSprite( 'rightDelKey' ), 10, 0 ) + d.drawText( "Del", 21, 0, 1 ) + drwSp( self.getSprite( 'leftDelWordKey' ), 0, 8 ) + drwSp( self.getSprite( 'rightDelWordKey' ), 12, 8 ) + d.drawText( "Del chnk", 25, 8, 1 ) + drwSp( self.getSprite( 'leftDelEndKey' ), 0, 16 ) + drwSp( self.getSprite( 'rightDelEndKey' ), 14, 16 ) + d.drawText( "Del EOL", 29, 16, 1 ) + drwSp( self.getSprite( 'shiftKey' ), 2, 24 ) + d.drawText( "Shift", 12, 24, 1 ) + sideScroll( 'More help: http://codeberg.org/JBanana/TinyEdit', 0, 33, d.width, 0, { + 'A': lambda: None, + 'B': lambda: None + } ) + + def exit( self ): + self.file.close() + return True + + def chooseFile( self, dirPath = None ): + if not dirPath: + dirPath = '/' + while True: + if not dirPath[ -1 : ] == '/': + dirPath = dirPath + '/' + dirEntry = self.chooseDirEntry( dirPath ) + if not dirEntry: + return None + newPath = dirEntry[ 0 ] + entryType = dirEntry[ 1 ] + if entryType == 0x4000: #dir + dirPath = newPath + elif entryType == 0x8000: #file + return newPath + + def chooseDirEntry( self, dirPath ): + def makeLambda( dirEntry ): + return lambda: dirEntry + dirMenuItems = [] + if dirPath != '/': + dirAbove = getDirAbove( dirPath ) + if dirAbove == '': + dirAbove = '/' + dirMenuItems.append( ( '../', makeLambda( ( dirAbove, 0x4000 ) ) ) ) + for dirEntry in ilistdir( dirPath ): + entryType = dirEntry[ 1 ] + if entryType == 0x4000: + dirMenuItems.append( ( dirEntry[ 0 ] + '/', makeLambda( ( dirPath + dirEntry[ 0 ], 0x4000 ) ) ) ) + elif entryType == 0x8000: + dirMenuItems.append( ( dirEntry[ 0 ], makeLambda( ( dirPath + dirEntry[ 0 ], 0x8000 ) ) ) ) + return Menu( dirMenuItems ).display() + + def open( self, fileName = None ): + if fileName: + if self.file: + self.file.close() + self.file = File( fileName, self.getKeyboard, self.getSprite ) + return + if self.file: + chosenPath = self.chooseFile( getDirAbove( self.file.fileName ) ) + else: + chosenPath = self.chooseFile() + if chosenPath: + if self.file: + self.file.close() + self.file = File( chosenPath, self.getKeyboard, self.getSprite ) + self.dir = getDirAbove( chosenPath ) + + def new( self ): + lastDir = self.dir + if self.file: + self.file.close() + self.file = File( None, self.getKeyboard, self.getSprite ) + self.file.insertLine() + self.dir = lastDir + + def editLine( self ): + self.file.replaceLine( self.getKeyboard( self.getSprite, self.file.getFullLine(), self.file.position ).getOutput() ) + + def move( self ): + self.mode = Mode.MOVE + + def find( self ): + self.findStr = self.getKeyboard( self.getSprite, self.findStr, 0 ).getOutput() + self.findLine = 0 + self.findNext() + + def goTo( self ): + try: + self.file.goTo( int( self.getKeyboard( self.getSprite, str( self.file.getCurrentLineIndex() + 1 ), 0, 2 ).getOutput() ) - 1 ) + except ValueError: + buzz() + + def split( self ): + self.file.startSplit() + self.mode=Mode.SPLIT + + def findNext( self ): + while True: + self.file.goTo( self.findLine ) + text = self.file.getFullLine( self.findLine ) + if text == None: + buzz() + break + position = text.find( self.findStr ) + self.findLine += 1 + if position >= 0: + self.file.setPosition( max( 0, position - 3 ) ) + break + + def pressedU( self ): + if self.mode == Mode.SPLIT: + return + if self.mode == Mode.MOVE: + self.file.moveUp() + else: + self.file.up() + + def pressedD( self ): + if self.mode == Mode.SPLIT: + return + if self.mode == Mode.MOVE: + self.file.moveDown() + else: + self.file.down() + + def pressedL( self ): + if self.mode == Mode.SPLIT: + self.file.shiftSplitLeft() + else: + self.file.left() + + def pressedR( self ): + if self.mode == Mode.SPLIT: + self.file.shiftSplitRight() + else: + self.file.right() + + def handleInput( self ): + b = te_btn.which() + if not b: + return + if b in 'Uu': + self.pressedU() + elif b in 'Dd': + self.pressedD() + elif b in 'Ll': + self.pressedL() + elif b in 'Rr': + self.pressedR() + elif b=='B': + if self.mode == Mode.MOVE or self.mode == Mode.SPLIT: + self.mode = Mode.SCROLL + else: + self.editLine() + elif b == 'A': + if self.mode == Mode.MOVE or self.mode == Mode.SPLIT: + self.mode = Mode.SCROLL + else: + self.mode = Mode.MENU + + def display( self ): + if self.mode == Mode.MENU: + result = None + if self.file: + result = self.withFileMenu.display() + else: + result = self.noFileMenu.display() + if self.file and self.mode == Mode.MENU: + self.mode = Mode.SCROLL + return result + if self.file: + self.file.display( self.mode ) + return None + buzz() + self.mode = Mode.MENU + return None diff --git a/TinyEdit/filechunks.py b/TinyEdit/filechunks.py new file mode 100755 index 00000000..5a8973a4 --- /dev/null +++ b/TinyEdit/filechunks.py @@ -0,0 +1,352 @@ +from os import stat, mkdir, listdir, remove + +class ChunkProblem( Exception ): + pass + +class Line: + def __init__( self, text ): + self.setText( text ) + + def getText( self, position ): + if position > self.size: + return '<' + if position > 0: + result = '<' + else: + result=' ' + maxRight = position + 10 + result += self.text[ position : min( maxRight, self.size ) ] + if maxRight < self.size: + result = result + '>' + return result + + def setText( self, newText ): + self.text = newText[ 0 : -1 ] if newText.endswith( '\n' ) else newText + self.size = len( self.text ) + +class FileChunk: + CHUNK_PATH = "/Games/TinyEdit/tmp" + + def __init__( self, lines, startIndex ): + self.lines = [ Line( line ) for line in lines ] + self.name = None + self.startIndex = startIndex + self.lnCount = len( lines ) + + def setName( self, name ): + self.name = str( name ) + + def getPath( self ): + if not self.name: + raise ChunkProblem( 'Chunk path not set' ) + return FileChunk.CHUNK_PATH + '/' + self.name + '.chunk' + + def write( self ): + # Possible optimisation: don't write unless content changed since last write + with open( self.getPath(), 'w', encoding = 'utf-8' ) as f: + for line in self.lines: + f.write( line.text ) + f.write( '\n' ) + + def read( self ): + self.lines = [] + with open( self.getPath(), 'r', encoding = 'utf-8' ) as f: + for line in f: + self.lines.append( Line( line ) ) + self.lnCount = len( self.lines ) + return self.lines + + def dropContent( self ): + self.lines = [] + + def writeDrop( self ): + self.write() + self.dropContent() + + def lineCount( self ): + return self.lnCount + + def lastIndex( self ): + return self.startIndex + self.lnCount - 1 + + def includes( self, index ): + return index >= self.startIndex and index < self.startIndex + self.lineCount() + + def nearTop( self, index ): + return self.startIndex > 0 and index - 2 < self.startIndex + + def nearBottom( self, index ): + return index + 2 > self.lastIndex() + + def includesX( self, index ): + if not self.includes( index ): + raise ChunkProblem( "Requested index " + str( index ) + " not in range " + str( self.startIndex ) + " to " + str( self.lastIndex() ) ) + + def getLine( self, index ): + self.includesX( index ) + return self.lines[ index - self.startIndex ] + + def replaceLine( self, index, newText ): + self.includesX( index ) + self.lines[ index - self.startIndex ].setText( newText ) + self.write() + + def writeTo( self, f ): + empty = 0 == len( self.lines ) + if empty: + self.read() + for line in self.lines: + f.write( line.text ) + f.write( '\n' ) + if empty: + self.dropContent() + + def duplicateLine( self, index ): + self.includesX( index ) + offset = index - self.startIndex + self.lines.insert( offset, Line( self.lines[ offset ].text ) ) + self.lnCount = len( self.lines ) + self.write() + + def deleteLine( self, index ): + self.includesX( index ) + offset = index - self.startIndex + deletedLine = self.lines.pop( offset ) + self.lnCount = len( self.lines ) + self.write() + return deletedLine.text + + def appendLine( self, text ): + self.lines.append( Line( text ) ) + self.lnCount = len( self.lines ) + self.write() + + def insertLine( self, index, text ): + self.lines.insert( index - self.startIndex, Line( text ) ) + self.lnCount = len( self.lines ) + self.write() + + def adjustIndexes( self, adjustment ): + self.startIndex += adjustment + + def moveUp( self, index ): + self.includesX( index ) + if index == self.startIndex: + raise ChunkProblem( "Can't move first line of chunk up" ) + offset = index - self.startIndex + self.lines.insert( offset - 1, self.lines.pop( offset ) ) + self.write() + + def moveDown( self, index ): + self.includesX( index ) + if index == self.lastIndex(): + raise ChunkProblem( "Can't move last line of chunk down" ) + offset = index - self.startIndex + self.lines.insert( offset + 1, self.lines.pop( offset ) ) + self.write() + + def close( self ): + self.lines = [] + remove( self.getPath() ) + +class FileChunks: + def __init__( self, fileName ): + try: + x = stat( FileChunk.CHUNK_PATH ) + if x[ 0 ] & 0x4000 != 0x4000: # if not a dir + raise ChunkProblem( "Temp dir " + FileChunk.CHUNK_PATH + " exists but is not a directory: " + str( x ) ) + except Exception as x: + mkdir( FileChunk.CHUNK_PATH ) + def newChunk( chunks, lines, lineIndex, chunkIndex ): + chunk = FileChunk( lines, lineIndex ) + chunks.chunkList.append( chunk ) + chunk.setName( chunkIndex ) + chunk.write() + if chunks.topChunkIndex < 0: + chunks.topChunkIndex = chunkIndex + elif chunks.bottomChunkIndex < 0: + chunks.bottomChunkIndex = chunkIndex + else: + chunk.dropContent() + self.chunkList = [] + chunkIndex = 0 + self.topChunkIndex = -1 + self.bottomChunkIndex = -1 + thisChunkLines = [] + thisChunkSize = 0 + if fileName: + with open( fileName, encoding = "utf-8" ) as f: + fileLineIndex = 0 + chunkStartLineIndex = 0 + for line in f: + thisChunkLines.append( line ) + thisChunkSize += len( line ) + if thisChunkSize >= 2048 and len( thisChunkLines ) >= 10: + newChunk( self, thisChunkLines, chunkStartLineIndex, chunkIndex ) + thisChunkLines = [] + thisChunkSize = 0 + chunkStartLineIndex = fileLineIndex + 1 + chunkIndex += 1 + fileLineIndex += 1 + if thisChunkSize: + newChunk( self, thisChunkLines, chunkStartLineIndex, chunkIndex ) + if len( self.chunkList ) == 0: + newChunk( self, [], 0, 0 ) + self.currentLineIndex = 0 + + def getChunkX( self, index ): + return self.chunkList[ self.getChunkIndexX( index ) ] + + def getChunkIndexX( self, index ): + if self.topChunkIndex >= 0 and self.chunkList[ self.topChunkIndex ].includes( index ): + return self.topChunkIndex + if self.bottomChunkIndex >= 0 and self.chunkList[ self.bottomChunkIndex ].includes( index ): + return self.bottomChunkIndex + if index == 0 and len( self.chunkList ) == 1 and self.topChunkIndex == 0 and self.chunkList[ 0 ].lineCount() == 0: + return 0 + topChunk = self.chunkList[ self.topChunkIndex ] + bottomChunk = self.chunkList[ self.bottomChunkIndex ] + raise ChunkProblem( 'Line index ' + str( index ) + ' not in top (' + str( topChunk.startIndex ) + ',' + str( topChunk.lastIndex() ) + ') or bottom (' + str( bottomChunk.startIndex ) + ',' + str( bottomChunk.lastIndex() ) + ') chunk.' ) + + def getLine( self, index ): + return self.getChunkX( index ).getLine( index ) + + def lineCount( self ): + return sum( [ chunk.lineCount() for chunk in self.chunkList ] ) + + def getCurrentLineIndex( self ): + return self.currentLineIndex + + def getCurrentLine( self ): + return self.getLine( self.currentLineIndex ) + + def replaceCurrentLine( self, newText ): + self.getChunkX( self.currentLineIndex ).replaceLine( self.currentLineIndex, newText ) + + def duplicateCurrentLine( self ): + self.getChunkX( self.currentLineIndex ).duplicateLine( self.currentLineIndex ) + for chunk in self.chunkList[ self.getChunkIndexX( self.currentLineIndex ) + 1 : ]: + chunk.adjustIndexes( 1 ) + + def insertLine( self, text ): + self.getChunkX( self.currentLineIndex ).insertLine( self.currentLineIndex, text ) + for chunk in self.chunkList[ self.getChunkIndexX( self.currentLineIndex ) + 1 : ]: + chunk.adjustIndexes( 1 ) + + def deleteCurrentLine( self ): + nextChunkIndex = self.getChunkIndexX( self.currentLineIndex ) + 1 + self.getChunkX( self.currentLineIndex ).deleteLine( self.currentLineIndex ) + for chunk in self.chunkList[ nextChunkIndex : ]: + chunk.adjustIndexes( -1 ) + self.maybeMergeX() + + def maybeMergeX( self ): + if len( self.chunkList ) == 1: + return + thisChunk = self.getChunkX( self.currentLineIndex ) + if len( thisChunk.lines ) >= 10: + return + # I *think* we could just drop the content here without writing... + self.chunkList[ self.topChunkIndex ].writeDrop() + self.chunkList[ self.bottomChunkIndex ].writeDrop() + thisChunkIndex = self.getChunkIndexX( self.currentLineIndex ) + if thisChunkIndex == 0: + chunkToGrowIndex = 0 + newTopIndex = 0 + newBottomIndex = 1 + elif thisChunkIndex == len( self.chunkList ) - 1: + chunkToGrowIndex = thisChunkIndex - 1 + newTopIndex = chunkToGrowIndex - 1 + else: + lenAbove = self.chunkList[ thisChunkIndex - 1 ].lnCount + lenBelow = self.chunkList[ thisChunkIndex + 1 ].lnCount + if lenAbove < lenBelow: + chunkToGrowIndex = thisChunkIndex - 1 + else: + chunkToGrowIndex = thisChunkIndex + newTopIndex = chunkToGrowIndex + chunkToGrow = self.chunkList[ chunkToGrowIndex ] + self.chunkList[ chunkToGrowIndex ].read() + self.chunkList[ chunkToGrowIndex + 1 ].read() + for line in self.chunkList[ chunkToGrowIndex + 1 ].lines: + chunkToGrow.appendLine( line.text ) + del( self.chunkList[ chunkToGrowIndex + 1 ] ) + self.chunkList[ chunkToGrowIndex ].writeDrop() + self.topChunkIndex = newTopIndex + self.bottomChunkIndex = newTopIndex + 1 + self.chunkList[ self.topChunkIndex ].read() + self.chunkList[ self.bottomChunkIndex ].read() + + def moveUp( self ): + index = self.currentLineIndex + chunk = self.getChunkX( index ) + if self.bottomChunkIndex == self.getChunkIndexX( index ) and index == chunk.startIndex: + #move line between chunks + textToMoveUp = chunk.deleteLine( index ) + topChunk = self.chunkList[ self.topChunkIndex ] + textToMoveDown = topChunk.deleteLine( index - 1 ) + topChunk.appendLine( textToMoveUp ) + chunk.insertLine( index, textToMoveDown ) + else: + #move line within chunk + chunk.moveUp( index ) + self.setCurrentLineIndex( index - 1 ) + + def moveDown( self ): + index = self.currentLineIndex + chunk = self.getChunkX( index ) + if self.topChunkIndex == self.getChunkIndexX( index ) and index == chunk.lastIndex(): + #move line between chunks + textToMoveDown = chunk.deleteLine( index ) + bottomChunk = self.chunkList[ self.bottomChunkIndex ] + textToMoveUp = bottomChunk.deleteLine( index + 1 ) + chunk.appendLine( textToMoveUp ) + bottomChunk.insertLine( index + 1, textToMoveDown ) + else: + #move line within chunk + chunk.moveDown( index ) + self.setCurrentLineIndex( index + 1 ) + + def setCurrentLineIndex( self, index ): + self.currentLineIndex = index + if self.topChunkIndex >= 0 and self.chunkList[ self.topChunkIndex ].nearTop( index ): + # Probably the prev chunk, but not if it's a big jump + # So get rid of both chunks and read new ones + # Possible optimisation: decide if we can keep one chunk + self.chunkList[ self.topChunkIndex ].writeDrop() + self.chunkList[ self.bottomChunkIndex ].writeDrop() + while True: + self.topChunkIndex -= 1 + isFirstChunk = self.topChunkIndex == 0 + chunkEndsBeforeIndex = self.chunkList[ self.topChunkIndex ].lastIndex() < index + if isFirstChunk or chunkEndsBeforeIndex: + break; + self.bottomChunkIndex = self.topChunkIndex + 1 + self.chunkList[ self.topChunkIndex ].read() + self.chunkList[ self.bottomChunkIndex ].read() + elif self.bottomChunkIndex >= 0 and self.bottomChunkIndex + 1 < len( self.chunkList ) and self.chunkList[ self.bottomChunkIndex ].nearBottom( index ): + # Probably the next chunk, but not if it's a big jump + # So get rid of both chunks and read new ones + # Possible optimisation: decide if we can keep one chunk + self.chunkList[ self.topChunkIndex ].writeDrop() + self.chunkList[ self.bottomChunkIndex ].writeDrop() + while True: + self.bottomChunkIndex += 1 + isLastChunk = self.bottomChunkIndex == len( self.chunkList ) - 1 + chunkStartsAfterIndex = self.chunkList[ self.bottomChunkIndex ].startIndex > index + if isLastChunk or chunkStartsAfterIndex: + break; + self.topChunkIndex = self.bottomChunkIndex - 1 + self.chunkList[ self.topChunkIndex ].read() + self.chunkList[ self.bottomChunkIndex ].read() + + def writeTo( self, f ): + for chunk in self.chunkList: + chunk.writeTo( f ) + + def close( self ): + for chunk in self.chunkList: + chunk.close() + self.chunkList = [] + for f in listdir( FileChunk.CHUNK_PATH ): + remove( FileChunk.CHUNK_PATH + '/' + f ) diff --git a/TinyEdit/te_btn.py b/TinyEdit/te_btn.py new file mode 100755 index 00000000..9fe63161 --- /dev/null +++ b/TinyEdit/te_btn.py @@ -0,0 +1,79 @@ +import thumbyButton as b +from time import ticks_ms, ticks_diff + +REPEAT_TIMEOUT_MS = 750 +REPEAT_THRESHOLD_MS = 500 +REPEAT_MIN_GAP_MS = 160 +NORMAL = 'normal' +PRESSED = 'pressed' +REPEATING = 'repeating' + +keyMode = NORMAL +baseTicks = ticks_ms() + +def actionJustPressed(): + return b.actionJustPressed() + +def resetTimeCounter(): + global baseTicks + baseTicks = ticks_ms() + +def normalPress(): + result = None + if b.buttonU.justPressed(): result = 'U' + elif b.buttonD.justPressed(): result = 'D' + elif b.buttonL.justPressed(): result = 'L' + elif b.buttonR.justPressed(): result = 'R' + elif b.buttonA.justPressed(): result = 'A' + elif b.buttonB.justPressed(): result = 'B' + clear() + return result + +def clear(): + b.buttonU.justPressed() + b.buttonD.justPressed() + b.buttonL.justPressed() + b.buttonR.justPressed() + b.buttonA.justPressed() + b.buttonB.justPressed() + +def repeatPress( abOk ): + result = None + if b.buttonU.pressed(): result = 'u' + elif b.buttonD.pressed(): result = 'd' + elif b.buttonL.pressed(): result = 'l' + elif b.buttonR.pressed(): result = 'r' + elif abOk and b.buttonA.pressed(): result = 'a' + elif abOk and b.buttonB.pressed(): result = 'b' + return result + +def which( abOk = True ): + global keyMode + timePassed = ticks_diff( ticks_ms(), baseTicks ) + result = None + if keyMode == REPEATING: + if timePassed > REPEAT_TIMEOUT_MS: + keyMode = NORMAL + resetTimeCounter() + result = normalPress() + elif timePassed > REPEAT_MIN_GAP_MS: + resetTimeCounter() + result = repeatPress( abOk ) + if not result: + keyMode = NORMAL + resetTimeCounter() + else: + result = None + elif keyMode == PRESSED: + if timePassed > REPEAT_THRESHOLD_MS: + keyMode = REPEATING + resetTimeCounter() + result = repeatPress( abOk ) + if not result: + keyMode = NORMAL + else: + resetTimeCounter() + result = normalPress() + if result: + keyMode = PRESSED + return result diff --git a/TinyEdit/te_keyboard.py b/TinyEdit/te_keyboard.py new file mode 100755 index 00000000..2f0f78f1 --- /dev/null +++ b/TinyEdit/te_keyboard.py @@ -0,0 +1,317 @@ +from sys import path +from re import search +from thumbySprite import Sprite +from thumbyGraphics import display as d +if not '/Games/TinyEdit' in path: + path.append( '/Games/TinyEdit' ) +import te_utility +import te_btn + +shownKbInstructions=False + +class Key: + def __init__( self,character ): + self.character = character + self.selected = False + + def display( self, x, y ): + if self.character == ' ': + if self.selected: + d.drawFilledRectangle( x, y, 16, 8, 1 ) + d.setFont( "/lib/font3x5.bin", 3, 5, 1 ) + d.drawText( '[sp]', x + 1, y + 2, 0 if self.selected else 1 ) + te_utility.defaultFont() + return 16 + if self.selected: + d.drawFilledRectangle( x, y, 7, 8, 1 ) + d.drawText( self.character, x + 1, y, 0 if self.selected else 1 ) + return 7 + + def select( self, selected ): + self.selected = selected + + def press( self ): + return self.character + +class ActionKey: + def __init__( self, action, sprite ): + self.selected = False + self.sprite = sprite + self.action = action + + def display( self, x, y ): + self.sprite.x = x + self.sprite.y = y + self.sprite.setFrame( 0 if self.selected else 1 ) + d.drawSprite( self.sprite ) + return self.sprite.width + + def select( self, selected ): + self.selected = selected + + def press( self ): + ch = self.action() + return ch if ch else '' + +class KeyRow: + def __init__( self, rowNum, characters, selected = 0 ): + self.rowNum = rowNum + self.keys = [] + for character in characters: + self.keys.append( Key( character ) ) + self.selected = selected + if selected: + self.select( selected ) + + def append( self, key ): + self.keys.append( key ) + + def insert( self, index, key ): + self.keys.insert( index, key ) + + def select( self, index ): + self.keys[ self.selected ].select( False ) + if index < 0: + self.selected = len( self.keys ) - 1 + elif index >= len( self.keys ): + self.selected = 0 + else: + self.selected = index + self.keys[ self.selected ].select( True ) + + def deselect( self ): + self.keys[ self.selected ].select( False ) + + def display( self ): + offset = self.rowNum + for i, key in enumerate( self.keys ): + offset = offset + key.display( offset, self.rowNum * 8 ) + + def right( self ): + self.select( self.selected + 1 ) + + def left( self ): + self.select( self.selected - 1 ) + + def getCharacter( self ): + return self.keys[ self.selected ].press() + +class KeyLayer: + def __init__( self, characterRows ): + self.rows = [] + for i, characterRow in enumerate( characterRows ): + self.rows.append( KeyRow( i, characterRow ) ) + self.row = 3 + + def add( self, key, rowNum ): + self.rows[ rowNum ].append( key ) + + def insert( self, key, rowNum, index ): + self.rows[ rowNum ].insert( index, key ) + + def display( self ): + for row in self.rows: + row.display() + + def up( self ): + if self.row > 0: + index = self.rows[ self.row ].selected + self.rows[ self.row ].deselect() + self.row = self.row - 1 + self.rows[ self.row ].select( index ) + + def down( self ): + if self.row < len( self.rows ) - 1: + index = self.rows[ self.row ].selected + self.rows[ self.row ].deselect() + self.row = self.row + 1 + self.rows[ self.row ].select( index ) + + def left( self ): + self.rows[ self.row ].left() + + def right( self ): + self.rows[ self.row ].right() + + def getCharacter( self ): + return self.rows[ self.row ].getCharacter(); + +class Keyboard: + def __init__( self, getSprite, text, position, layer = 0 ): + self.layers = [ + KeyLayer( [ 'qwertyuiop', 'asdfghjkl', 'zxcvbnm,.', ' ' ] ), + KeyLayer( [ 'QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM()', ' ' ] ), + KeyLayer( [ '1234567890', '!"#$%^&*[]', "`_+-=;:@'~", ' ' ] ), + KeyLayer( [ '1234567890', '#<>?/|\\{}', '', ' ' ] ) + ] + shiftKey = ActionKey( lambda: self.shiftKey(), getSprite( 'shiftKey' ) ) + shiftKey.selected = True + tabKey = ActionKey( lambda: '\t', getSprite( 'tabKey' ) ) + for i in range( 4 ): + self.layers[ i ].insert( tabKey, 3, 0 ) + self.layers[ i ].insert( shiftKey, 3, 0 ) + + leftArrowKey = ActionKey( lambda: self.leftArrow(), getSprite( 'leftArrowKey' ) ) + for i in range( 4 ): + self.layers[ i ].add( leftArrowKey, 3 ) + rightArrowKey = ActionKey( lambda: self.rightArrow(), getSprite( 'rightArrowKey' ) ) + for i in range( 4 ): + self.layers[ i ].add( rightArrowKey, 3 ) + + self.layers[ 3 ].add( ActionKey( lambda: self.leftDelEnd(), getSprite( 'leftDelEndKey' ) ), 2 ) + + leftDelWordKey = ActionKey( lambda: self.leftDelWord(), getSprite( 'leftDelWordKey' ) ) + for i in range( 3 ): + self.layers[ i ].add( leftDelWordKey, 3 ) + self.layers[ 3 ].add( leftDelWordKey, 2 ) + + delKey = ActionKey( lambda: self.delKey(), getSprite( 'leftDelKey' ) ) + for i in range( 3 ): + self.layers[ i ].add( delKey, 3 ) + self.layers[ 3 ].add( delKey, 2 ) + + self.layers[ 3 ].add( ActionKey( lambda: self.rightDel(), getSprite( 'rightDelKey' ) ), 2 ) + self.layers[ 3 ].add( ActionKey( lambda: self.rightDelWord(), getSprite( 'rightDelWordKey' ) ), 2 ) + self.layers[ 3 ].add( ActionKey( lambda: self.rightDelEnd(), getSprite( 'rightDelEndKey' ) ), 2 ) + + self.layer = layer + self.text = text + self.cursor = min( position + 6, len( text ) ) + + def shiftKey( self ): + self.layer = self.layer + 1 + if self.layer >= len( self.layers ): + self.layer = 0 + + def delKey( self ): + if self.cursor > 0: + self.text = self.text[ 0 : self.cursor - 1 ] + self.text[ self.cursor : ] + self.cursor = self.cursor - 1 + else: + te_utility.buzz() + + def rightDel( self ): + if self.cursor < len( self.text ): + self.text = self.text[ 0 : self.cursor ] + self.text[ self.cursor + 1 : ] + else: + te_utility.buzz() + + def leftDelWord( self ): + pre = self.text[ 0 : self.cursor ] + match = search( r'(\w+|\W+)$', pre ) + if match: + start = match.start() + self.text = pre[ 0 : start ] + self.text[ self.cursor : ] + self.cursor = start + else: + te_utility.buzz() + + def rightDelWord( self ): + post = self.text[ self.cursor : ] + match = search( r'^(\w+|\W+)', post ) + if match: + end = match.end() + self.text = self.text[ 0 : self.cursor] + post[ end : ] + else: + te_utility.buzz() + + def leftDelEnd( self ): + self.text = self.text[ self.cursor : ] + self.cursor = 0 + + def rightDelEnd( self ): + self.text = self.text[ 0 : self.cursor ] + + def leftArrow( self ): + if self.cursor > 0: + self.cursor = self.cursor - 1 + + def rightArrow( self ): + if self.cursor < len( self.text ): + self.cursor = self.cursor + 1 + + def display( self ): + d.fill( 0 ) + self.layers[ self.layer ].display() + textLen = len( self.text ) + if textLen <= 10: + windowStart = 0 + windowEnd = len( self.text ) + elif self.cursor <= 6: + windowStart = 0 + windowEnd = min( 10, textLen ) + else: + windowStart = self.cursor - 6 + windowEnd = min( textLen, self.cursor + 4 ) + windowText = self.text[ windowStart : windowEnd ] + if windowStart > 0: + d.drawText( '<', 0, 33, 1 ) + adjCursor = self.cursor - windowStart + for i, ch in enumerate( windowText ): + x = 6 * i + 6 + if i == adjCursor: + d.drawFilledRectangle( x, 33, 6, 8, 1 ) + colour = 0 + else: + colour = 1 + d.drawText( ch, x, 33, colour ) + if self.cursor == textLen: + d.drawFilledRectangle( adjCursor * 6 + 6, 33, 6, 8, 1 ) + if textLen > windowEnd: + d.drawText( '>', 66, 33, 1 ) + d.update() + + def handleInput( self ): + b = te_btn.which() + if not b: + return None + if b in 'Uu': + self.layers[ self.layer ].up() + elif b in 'Dd': + self.layers[ self.layer ].down() + elif b in 'Rr': + self.layers[ self.layer ].right() + elif b in 'Ll': + self.layers[ self.layer ].left() + elif b in 'Aa': + self.handlePress() + elif b == 'B': + result = self.text + self.text = '' + return result + return None + + def handlePress( self ): + keyOut = self.layers[ self.layer ].getCharacter() + if len( keyOut ) > 0: + self.text = self.text[ 0 : self.cursor ] + keyOut + self.text[ self.cursor : ] + self.cursor = self.cursor + 1 + + def instructions( self ): + global shownKbInstructions + shownKbInstructions = True + d.fill( 0 ) + d.drawText( 'Keyboard:', 12, 0, 1 ) + d.drawText( 'U', 6, 8, 1 ) + d.drawText( 'L R cursor', 0, 16, 1 ) + d.drawText( 'D', 6, 24, 1 ) + d.drawText( '--more--', 12, 32, 1 ) + while( not te_btn.actionJustPressed () ): + d.update() + d.fill( 0 ) + d.drawText( 'Keyboard:', 12, 0, 1 ) + d.drawText ( 'A: press key', 0, 8, 1 ) + d.drawText ( 'B: close', 0, 16, 1 ) + d.drawText ( '--start--', 6, 32, 1 ) + while( not te_btn.actionJustPressed() ): + d.update() + + def getOutput( self ): + global shownKbInstructions + if not shownKbInstructions: + self.instructions() + while True: + self.display() + output = self.handleInput() + if output or output == '': + return output diff --git a/TinyEdit/te_menu.py b/TinyEdit/te_menu.py new file mode 100755 index 00000000..918686f9 --- /dev/null +++ b/TinyEdit/te_menu.py @@ -0,0 +1,73 @@ +from thumbyGraphics import display as d +from sys import path +if not '/Games/TinyEdit' in path: + path.append( '/Games/TinyEdit' ) +from te_utility import sideScroll + +class Menu: + def __init__( self, items ): + self.items = items + self.count = len( items ) + self.item = 0 + self.action = None + + def a( self ): + self.action = self.items[ self.item ][ 1 ] + return True + + def u( self ): + if self.item > 0: + self.item = self.item - 1 + return False + + def d( self ): + if self.item < len( self.items ) - 1: + self.item = self.item + 1 + return False + + def l( self ): + return True + + def r( self ): + item = self.items[ self.item ] + self.action = item[ 2 if len( item ) > 2 else 1 ] + return True + + def displayItems( self ): + return[ + ( None if self.item < 2 else self.items[ self.item - 2 ][ 0 ] ), + ( None if self.item < 1 else self.items[ self.item - 1 ][ 0 ] ), + ( self.items[ self.item ][ 0 ] ), + ( None if self.item + 1 >= self.count else self.items[ self.item + 1 ][ 0 ] ), + ( None if self.item + 2 >= self.count else self.items[ self.item + 2 ][ 0 ] ) + ] + + def display( self ): + def leftFill(): + d.drawFilledRectangle( 0, 16, 6, 8, 0 ) + d.drawText( '>', 0, 16, 1 ) + self.action = None + while True: + d.fill( 0 ) + itemY = 0 + for item in self.displayItems(): + if item: + if itemY == 16: + curr = item + else: + d.drawText( item, 6, itemY, 1 ) + itemY = itemY + 8 + if sideScroll( + curr, 6, 16, d.width, d.width, { + 'A': lambda: self.a(), + 'B': lambda: True, + 'U': lambda: self.u(), + 'D': lambda: self.d(), + 'u': lambda: self.u(), + 'd': lambda: self.d(), + 'L': lambda: self.l(), + 'R': lambda: self.r(), + 'lFill': lambda: leftFill() + }): + break + return self.action() if self.action else None diff --git a/TinyEdit/te_utility.py b/TinyEdit/te_utility.py new file mode 100755 index 00000000..b733a956 --- /dev/null +++ b/TinyEdit/te_utility.py @@ -0,0 +1,60 @@ +from random import randint +from thumbyGraphics import display as d +from thumbyAudio import audio +import te_btn + +baseFPS=12 +d.setFPS(baseFPS) + +def defaultFont(): + d.setFont( "/lib/font5x7.bin", 5, 10, 1 ) + +def noise( length ): + for _ in range( length ): + audio.playBlocking( randint( 400, 500 ), 10 ) + +def buzz(): + d.display.invert( 1 ) + d.update() + noise( 3 ) + d.display.invert( 0 ) + d.update() + noise( 12 ) + +baseFPS = 6 +sideScrollFPS = 45 +def setFPS( fps ): + d.setFPS( fps ) +setFPS( baseFPS ) + +def sideScroll( displayString, x, y, w, b, lambdas ): + if ( 1 + len( displayString ) ) * 6 > w: + setFPS( sideScrollFPS ) + displayLen = len( displayString ) * 6 + w + o = b if b > 0 else 0 + pauseStart = 10 if o > 0 else 0 + while True: + d.drawFilledRectangle( x, y, w, 8, 0 ) + d.drawText( displayString, x + w - o, y, 1 ) + if 'lFill' in lambdas: + lambdas[ 'lFill' ]() + d.update() + b = te_btn.which( False ) + if b and b.upper() in lambdas: + result = lambdas[ b.upper() ]() + setFPS( baseFPS ) + return result + if pauseStart > 0: + pauseStart -= 1 + else: + o = ( o + 1 ) % displayLen + elif 'lFill' in lambdas: + lambdas[ 'lFill' ]() + d.drawText( displayString, x, y, 1 ) + d.update() + b = te_btn.which( False ) + if b and b in lambdas: + return lambdas[ b ]() + +def getDirAbove( path ): + return path[ 0 : path.rfind( '/', 0, len( path ) - 1 ) ]