#      ----------------------------------------------------------------------------
#      "THE TSCHUNK-WARE LICENSE" (Revision 23):
#      Niklas Roy wrote this file. As long as you retain this notice you
#      can do whatever you want with this stuff. If we meet some day, and you think
#      this stuff is worth it, you can buy me a tschunk in return
#      ----------------------------------------------------------------------------


import gpio
import buttons
import display
import utime
import bhi160
import vibra
import leds
import light_sensor

AUTOSAVE = 5000
WIDTH  = 160
HEIGHT = 80


mini_alphabet=(
    ((0,0,0,0,0,0,0), # 32:' '
     (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,0,0,0),
     (0,0,0,0,0,0,0),
     (0,0,0,0,0,0,0)),
    
    ((0,0,1,0,0,0,0), #!
     (0,0,1,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,0,0,0,0,0),
     (0,0,1,0,0,0,0)),
    
    ((0,0,1,0,1,0,0), #"
     (0,0,1,0,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,0,0,0,0,0),
     (0,0,0,0,0,0,0)),
    
    ((0,0,1,0,1,0,0), ##
     (0,0,1,0,1,0,0),
     (1,1,1,1,1,1,1),
     (0,0,1,0,1,0,0),
     (1,1,1,1,1,1,1),
     (0,0,1,0,1,0,0),
     (0,0,1,0,1,0,0)),
    
    ((0,0,0,1,0,0,0), #$
     (1,1,1,1,1,1,1),
     (1,0,0,1,0,0,0),
     (1,1,1,1,1,1,1),
     (0,0,0,1,0,0,1),
     (1,1,1,1,1,1,1),
     (0,0,0,1,0,0,0)),
    
    ((0,0,0,0,0,0,1), #%
     (0,1,0,0,0,1,0),
     (0,0,0,0,1,0,0),
     (0,0,0,1,0,0,0),
     (0,0,1,0,0,0,0),
     (0,1,0,0,0,1,0),
     (1,0,0,0,0,0,0)),
    
    ((0,0,1,1,0,0,0), #&
     (0,1,0,0,1,0,0),
     (0,0,1,0,1,0,0),
     (0,1,1,1,1,0,1),
     (1,0,0,0,1,1,0),
     (1,0,0,0,1,1,0),
     (0,1,1,1,0,0,1)),
    
    ((0,0,0,1,0,0,0), #'
     (0,0,0,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,0,0,0,0,0,0),
     (0,0,0,0,0,0,0)),
    
    ((0,0,0,1,0,0,0), #(
     (0,0,1,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,0,1,0,0,0)),
    
    ((0,0,0,1,0,0,0), #)
     (0,0,0,0,1,0,0),
     (0,0,0,0,1,0,0),
     (0,0,0,0,1,0,0),
     (0,0,0,0,1,0,0),
     (0,0,0,0,1,0,0),
     (0,0,0,1,0,0,0)),
    
    ((0,0,0,1,0,0,0), #*
     (0,1,0,1,0,1,0),
     (0,0,1,1,1,0,0),
     (1,1,1,1,1,1,1),
     (0,0,1,1,1,0,0),
     (0,1,0,1,0,1,0),
     (0,0,0,1,0,0,0)),
    
    ((0,0,0,1,0,0,0), #+
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (1,1,1,1,1,1,1),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,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,0,0,0,0,0,0),
     (0,0,0,0,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,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),
     (1,1,1,1,1,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,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,0,0,0,0,0,0),
     (0,0,0,0,0,0,0),
     (0,0,0,1,0,0,0)),
    
    ((0,0,0,0,0,0,1), #/
     (0,0,0,0,0,1,0),
     (0,0,0,0,1,0,0),
     (0,0,0,1,0,0,0),
     (0,0,1,0,0,0,0),
     (0,1,0,0,0,0,0),
     (1,0,0,0,0,0,0)),
    
    ((1,1,1,1,1,1,1), #0
     (1,0,0,0,0,1,1),
     (1,0,0,0,1,0,1),
     (1,0,0,1,0,0,1),
     (1,0,1,0,0,0,1),
     (1,1,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((0,1,1,1,0,0,0), #1
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #2
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #3
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (0,0,1,1,1,1,1),
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,0,0,0,0,0,0), #4
     (1,0,0,1,0,0,0),
     (1,0,0,1,0,0,0),
     (1,1,1,1,1,1,1),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0)),
    
    ((1,1,1,1,1,1,1), #5
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,1,1),
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #6
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #7
     (0,0,0,0,0,1,0),
     (0,0,0,0,1,0,0),
     (0,1,1,1,1,1,0),
     (0,0,1,0,0,0,0),
     (0,1,0,0,0,0,0),
     (1,0,0,0,0,0,0)),
    
    ((1,1,1,1,1,1,1), #8
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #9
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1),
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((0,0,0,0,0,0,0), #:
     (0,0,0,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,0,0,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,0,0,0,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,0,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0)),
    
    ((0,0,0,0,1,0,0), #<
     (0,0,0,1,0,0,0),
     (0,0,1,0,0,0,0),
     (0,1,0,0,0,0,0),
     (0,0,1,0,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,0,1,0,0)),
    
    ((0,0,0,0,0,0,0), #=
     (0,0,0,0,0,0,0),
     (1,1,1,1,1,1,1),
     (0,0,0,0,0,0,0),
     (1,1,1,1,1,1,1),
     (0,0,0,0,0,0,0),
     (0,0,0,0,0,0,0)),
    
    ((0,0,1,0,0,0,0), #>
     (0,0,0,1,0,0,0),
     (0,0,0,0,1,0,0),
     (0,0,0,0,0,1,0),
     (0,0,0,0,1,0,0),
     (0,0,0,1,0,0,0),
     (0,0,1,0,0,0,0)),
    
    ((1,1,1,1,1,1,1), #?
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (0,0,0,1,1,1,1),
     (0,0,0,1,0,0,0),
     (0,0,0,0,0,0,0),
     (0,0,0,1,0,0,0)),
    
    ((1,1,1,1,1,1,1), #@
     (1,0,0,0,0,0,1),
     (1,0,1,1,1,0,1),
     (1,0,1,0,1,0,1),
     (1,0,1,1,1,1,1),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,1,1)),
    
    ((0,0,0,1,0,0,0), #A
     (0,0,1,0,1,0,0),
     (0,1,0,0,0,1,0),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1)),
    
    ((1,1,1,1,1,0,0), #B
     (1,0,0,0,1,0,0),
     (1,0,0,0,1,0,0),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((0,0,1,1,1,1,1), #C
     (0,1,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (0,1,0,0,0,0,0),
     (0,0,1,1,1,1,1)),
    
    ((1,1,1,1,1,0,0), #D
     (1,0,0,0,0,1,0),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,1,0),
     (1,1,1,1,1,0,0)),
    
    ((1,1,1,1,1,1,1), #E
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #F
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0)),
    
    ((1,1,1,1,1,1,1), #G
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,1,1,1,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,0,0,0,0,0,1), #H
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1)),
    
    ((1,1,1,1,1,1,1), #I
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #J
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (0,1,0,0,0,1,0),
     (0,0,1,1,1,0,0)),
    
    ((1,0,0,0,0,0,1), #K
     (1,0,0,0,0,1,0),
     (1,0,0,0,1,0,0),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1)),
    
    ((1,0,0,0,0,0,0), #L
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,1,1)),
    
    ((1,0,0,0,0,0,1), #M
     (1,1,0,0,0,1,1),
     (1,0,1,0,1,0,1),
     (1,0,0,1,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1)),
    
    ((1,0,0,0,0,0,1), #N
     (1,1,0,0,0,0,1),
     (1,0,1,0,0,0,1),
     (1,0,0,1,0,0,1),
     (1,0,0,0,1,0,1),
     (1,0,0,0,0,1,1),
     (1,0,0,0,0,0,1)),
    
    ((1,1,1,1,1,1,1), #O
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #P
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0)),
    
    ((1,1,1,1,1,1,1), #Q
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,1,0,1),
     (1,0,0,0,0,1,0),
     (1,1,1,1,1,0,1)),
    
    ((1,1,1,1,1,1,1), #R
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1),
     (1,0,0,0,1,0,0),
     (1,0,0,0,0,1,0),
     (1,0,0,0,0,0,1)),
    
    ((1,1,1,1,1,1,1), #S
     (1,0,0,0,0,0,0),
     (1,0,0,0,0,0,0),
     (1,1,1,1,1,1,1),
     (0,0,0,0,0,0,1),
     (0,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,1,1,1,1,1,1), #T
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0)),
    
    ((1,0,0,0,0,0,1), #U
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,1,1,1,1,1,1)),
    
    ((1,0,0,0,0,0,1), #V
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (0,1,0,0,0,1,0),
     (0,0,1,0,1,0,0),
     (0,0,0,1,0,0,0)),
    
    ((1,0,0,0,0,0,1), #W
     (1,0,0,0,0,0,1),
     (1,0,0,0,0,0,1),
     (1,0,0,1,0,0,1),
     (1,0,1,0,1,0,1),
     (1,1,0,0,0,1,1),
     (1,0,0,0,0,0,1)),
    
    ((1,0,0,0,0,0,1), #X
     (0,1,0,0,0,1,0),
     (0,0,1,0,1,0,0),
     (0,0,0,1,0,0,0),
     (0,0,1,0,1,0,0),
     (0,1,0,0,0,1,0),
     (1,0,0,0,0,0,1)),
    
    ((1,0,0,0,0,0,1), #Y
     (0,1,0,0,0,1,0),
     (0,0,1,0,1,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0),
     (0,0,0,1,0,0,0)),
    
    ((1,1,1,1,1,1,1), #Z
     (0,0,0,0,0,1,0),
     (0,0,0,0,1,0,0),
     (0,0,0,1,0,0,0),
     (0,0,1,0,0,0,0),
     (0,1,0,0,0,0,0),
     (1,1,1,1,1,1,1)),
    
    ((1,0,1,0,1,0,1), #CURSOR
     (0,1,0,1,0,1,0),
     (1,0,1,0,1,0,1),
     (0,1,0,1,0,1,0),
     (1,0,1,0,1,0,1),
     (0,1,0,1,0,1,0),
     (1,0,1,0,1,0,1)))
    



#-------------------------------------------------------------- mini text
def mini_text(text,x_text,y_text,text_col):

    for i in range(len(text)):
        c = ord(text[i])
        if c>96 and c<123: # a->A ... z->Z
            c-=32
        c-=32
        if c<0 or c>59:
            c=0
        if c==59 and saved == False:
            text_col=(255,64,64) # indicate unsaved file by red cursor

        if x_text < WIDTH:
            for x in range(7): # show character
                for y in range(7):
                    if (mini_alphabet[c][y][x]==1):
                        x_px =  x  + x_text 
                        y_px =  y  + y_text
                        if x_px>=0 and x_px<WIDTH and y_px>=0 and y_px<HEIGHT:
                            disp.pixel(x_px, y_px,col=text_col)


            x_text += 10 # increment start position of next character


#-------------------------------------------------------------- constrain
def constrain(value, minimum, maximum):
    if value<minimum:
        value=minimum
    if value>maximum:
        value=maximum
    return value


#-------------------------------------------------------------- read bhi / orientation / imu
gyro            = bhi160.BHI160Orientation(sample_rate=20)
gx              = 0.0 # gyro values
gy              = 0.0
gz              = 0.0
gs              = 0

def read_bhi():
    global gx
    global gy
    global gz
    #.......................................................... read virtual gyro

    samples = gyro.read()
    
    if len(samples) > 0:                     
        sample = samples[len(samples)-1]
          
        gx =   ( gx + sample.x*2 )/3
        gy =   ( gy + sample.y*2 )/3
        gz =   ( gz + sample.z*2 )/3
        
    return



############################################################### typer specific functions

#============================================================== draw keyboard

keyboard=((('Q','W','E','R','T','Z','U','I','O','P'),
          ('A','S','D','F','G','H','J','K','L','ENTER'),
          ('Y','X','C','V','B','N','M','.','ENTER'),
          ('123!',' ','BACK')),
          (('1','2','3','4','5','6','7','8','9','0'),
          ('!','?','@','$','%','&','/','(',')','ENTER'),
          ('+','-','*','<','>',',',';',':','ENTER'),
          ('ABC.',' ','BACK')))

key_set   = 0
p_key_set = -1

pre_selected_key       = ''
p_pre_selected_key     = ''
pre_selection_duration = 0
selected_key           = ''
def show_keyboard():
    global selected_key  
    global p_key_set
    global pre_selected_key        
    global p_pre_selected_key      
    global pre_selection_duration
    global repeat_millis 
    
    update = False
    
    if p_key_set != key_set:
        update = True
        disp.rect(0,30,159,79,col=(0,0,0),filled=True)#........ clear keyboard background
    
    for y_key in range(4):
        keys      = len(keyboard[key_set][y_key])
        y_pos     = 31 + y_key * 12

        if y_key !=3: #........................................ draw all keys except 'space bar' row and except 'enter'
            if y_key == 1 or y_key == 2:
                keys -= 1 # remove 'enter' from list
            x_shift   = (WIDTH-(keys*14))//2
            for x_key in range(keys):
                if update:
                    mini_text(keyboard[key_set][y_key][x_key],x_key*14+4+x_shift,y_pos+3,(255,255,255))
                disp.rect(x_key*14+x_shift,y_pos,x_key*14+14+x_shift,y_pos+12,col=(200,200,200),filled=False)
        else:   #.............................................. draw 'space bar' row
            x_shift   = (WIDTH-(keys*52))//2
            for x_key in range(keys):
                if update:
                    if x_key == 0:
                        mini_text(keyboard[key_set][y_key][x_key],x_key*52+9+x_shift,y_pos+3,(255,255,255))
                    else:
                        mini_text(keyboard[key_set][y_key][x_key],x_key*52+8+x_shift,y_pos+3,(255,255,255))
                disp.rect(x_key*52+x_shift,y_pos,x_key*52+52+x_shift,y_pos+12,col=(200,200,200),filled=False)
                
        disp.line(142,43,158,43,col=(200,200,200)) # - draw 'enter' frame
        disp.line(158,43,158,66,col=(200,200,200)) # |
        if update:
            disp.line(150,49,150,61,col=(255,255,255)) # | draw 'enter' arrow
            disp.line(150,61,140,61,col=(255,255,255)) # -  
            disp.line(140,61,143,58,col=(255,255,255)) # /  
            disp.line(140,61,143,64,col=(255,255,255)) # \  

    #------------------------------------------------------------------------- pre-selected key
     
    row              = int(constrain(1-(gy+25)/40,0,1)*3+.5)  #                   pre-select key based on gyro
    column           = int(constrain(1-(gz+30)/60,0,1)*(len(keyboard[key_set][row])-1)+.5)
    pre_selected_key = keyboard[key_set][row][column] # ...................... look up character of pre-selected key
    selection_color  = (255,64,64)
    
    if p_pre_selected_key != pre_selected_key:
        repeat_millis = millis + 10000000 # avoid multiple button hits when button is pressed long
        pre_selection_duration = 0
        vibra.vibrate(5)
    else:
        pre_selection_duration += 1
        if pre_selection_duration >=2:      # if the same key is pre-selected for 2 cycles:
            selected_key = pre_selected_key # select this key

    p_pre_selected_key = pre_selected_key

    
    y_pos            = 31 + row * 12
    
    #------------------------------------------------------------------------- highlight pre-selected / selected key
     
    if row!=3: #............................................... highlight all keys except 'space bar' row and except 'enter'

        keys      = len(keyboard[key_set][row])
        if row == 1 or row == 2:
            keys -= 1 
        x_shift   = (WIDTH-keys*14)//2

        if pre_selected_key!='ENTER': 
            disp.rect(column*14+x_shift-1, y_pos-1, column*14+15+x_shift, y_pos+13, col=(0,0,0),    filled=False)
            disp.rect(column*14+x_shift,   y_pos,   column*14+14+x_shift, y_pos+12, col=selection_color,filled=False)
        
    else:  #................................................... highlight 'space bar' row
        x_shift   = (WIDTH-len(keyboard[key_set][row])*52)//2
        disp.rect(column*52+x_shift-1,y_pos-1,column*52+53+x_shift,y_pos+13,col=(0,0,0),filled=False)
        disp.rect(column*52+x_shift,y_pos,column*52+52+x_shift,y_pos+12,col=selection_color,filled=False)

    if pre_selected_key == 'ENTER':  #............................. highlight 'enter'
        disp.line(143,43,158,43, col=selection_color) # - draw highlighted 'enter' frame
        disp.line(158,43,158,67, col=selection_color) # |        
        disp.line(158,67,136,67, col=selection_color) # -         
        disp.line(136,67,136,55, col=selection_color) # |
        disp.line(136,55,143,55, col=selection_color) # -        
        disp.line(143,55,143,43, col=selection_color) # | 

        disp.line(142,42,158,42, col=(0,0,0)) # - draw black 'enter' outline      
        disp.line(158,68,135,68, col=(0,0,0)) # -         
        disp.line(135,68,135,54, col=(0,0,0)) # |       
        disp.line(142,55,142,42, col=(0,0,0)) # | 
    
    p_key_set = key_set # ABC or 123
    
    return selected_key

#============================================================== read buttons

p_button_state = 0
button_state = 0
button_left_pressed   = False
button_right_pressed  = False
button_select_pressed = False
last_interaction_ms   = 0
repeat_millis         = 10000000

def read_buttons():
    global p_button_state 
    global button_state 
    global button_left_pressed   
    global button_right_pressed 
    global button_select_pressed
    global last_interaction_ms
    global repeat_millis
    
    vibra_tock = 0
    
    p_button_state = button_state
    button_state = buttons.read(0xFF)

    button_left_pressed   = (button_state & buttons.BOTTOM_LEFT != 0  and p_button_state == 0)
    button_right_pressed  = (button_state & buttons.BOTTOM_RIGHT != 0 and p_button_state == 0)
    button_select_pressed = (button_state & buttons.TOP_RIGHT != 0    and p_button_state == 0)
        
    if p_button_state != button_state:
        last_interaction_ms = millis
        if button_state !=0:
            repeat_millis = millis + 1200
            vibra.vibrate(8)
            vibra_tock = 10
        else:
            repeat_millis = millis + 10000000
                
    if millis>repeat_millis and button_state != 0: # long button push -> repeated triggers
        last_interaction_ms = millis
        repeat_millis = millis + 250
        vibra_tock = 7
        button_left_pressed   = (button_state & buttons.BOTTOM_LEFT  != 0)
        button_right_pressed  = (button_state & buttons.BOTTOM_RIGHT != 0)
        button_select_pressed = (button_state & buttons.TOP_RIGHT    != 0)
        
    return vibra_tock

#============================================================== edit text
text_line = 0
text=[' ']

def edit_text(selected_character):
    
    global text
    global text_line
    global key_set
    global button_select_pressed

    if p_state == EDIT:
        text[text_line] = text[text_line][:-1] # delete last character in line (=cursor)
        
    if button_select_pressed == True:      # button is pressed
        if selected_character == 'BACK':   # backspace:
            if len(text[text_line])>=1:     # delete character if there is one in this line
                text[text_line] = text[text_line][:-1]
        elif selected_character == 'ENTER':# enter:
            text.append('')                # create a new text line
            text_line +=1
            save_text()                    # save text
            button_select_pressed = False  # hack to avoid changing save state by autosave
            
        elif selected_character == '123!': # 123/ABC change key set
            key_set = 1
        elif selected_character == 'ABC.':
            key_set = 0                
        else:                              # otherwise: add character
            text[text_line] = text[text_line] + selected_character
            if len(text[text_line]) == 15: # end of line:
                text.append('')            # create a new text line
                text_line +=1                      

    if (millis//500)%2==0: #               add cursor as last character of this line
        text[text_line] = text[text_line] + '['
    else:
        text[text_line] = text[text_line] + ' '
    return


#============================================================== load text
def load_text ():
    global text
    global text_line
    
    try: # check if there's a text file
        with open('/apps/tiny_typer/text.txt','r+') as file:
            text_line = -1
            for line in file:
                text_line +=1
                if text_line>=1:
                    text.append('')            # create a new text line
                text[text_line] = line.strip('\r\n')
                
          
    except:
        save_text()
        
    return

#============================================================== backup text

def backup_text ():
    global saved

    time_tuple  = utime.localtime()
    time_string = '%i-%i-%i-%i-%i'%(time_tuple[2],time_tuple[1],time_tuple[3],time_tuple[4],time_tuple[5])
    with open('/apps/tiny_typer/'+time_string+'.txt','w+') as file:
        for line in text:
            file.write(line+'\r\n')
            
    saved = True
    vibra.vibrate(9)
    return

#============================================================== save text
saved = True
def save_text ():
    global saved
    with open('/apps/tiny_typer/text.txt','w+') as file:
        for line in text:
            file.write(line+'\r\n')
            
    saved = True
    vibra.vibrate(9)
    return

#============================================================== autosave

def auto_save():
    global saved
    if last_interaction_ms+AUTOSAVE<millis:
        if saved == False:
            save_text()

    if button_select_pressed:
        saved = False
    return
#============================================================== show text
screen_text   = ['','','','','','','','']
p_screen_text = ['','','','','','','','']
scroll_text   = 0.0

def show_text():
    global screen_text
    global p_screen_text

        
    start_line  = constrain(text_line-2,0,text_line) # display text on top of screen
    line_number = 0
    for i in range(start_line, text_line + 1, 1):
        p_screen_text[line_number] = screen_text[line_number]
        screen_text[line_number] = text[i]
        line_number += 1
        
    for i in range(3):
        if screen_text[i]!=p_screen_text[i] or p_state!=state:
            disp.rect(0,i*10,159,i*10+10,col=(0,0,0),filled=True)
            mini_text(screen_text[i],10,i*10,(255,255,255))
    
    mini_text('>',0,constrain(len(text)-1,0,2)*10,(255,255,255))
    return

#============================================================== scroll text

scroll_pos = 0

def scroll_text():
    global scroll_pos

    if p_state!=state:
        scroll_pos = 0
        mini_text('>READ TEXT.TXT:',0,0,(200,200,200))

    max_line   = len(text)-1
    
    if max_line > 6:    
        scroll = 0
        if gy<-10:
            scroll=1
        if gy>5:
            scroll=-1
        scroll_pos += scroll
        scroll_pos = constrain(scroll_pos,0,len(text)-7)    
                                        
        disp.rect(0,10,159,79, col=(0,0,0), filled = True)
        
        start_line = scroll_pos
        for i in range(8):
            if start_line==max_line:
                mini_text(text[start_line],10,i*10+10,(255,255,255))
                
            if start_line<max_line:
                mini_text(text[start_line],10,i*10+10,(255,255,255))
                start_line += 1
            
        lt = len(text) - 7
        if lt>0:
            scroll_pos_indicator = int((scroll_pos/lt)*60)
            mini_text('[',0,scroll_pos_indicator+10,(255,64,64))
    else:
        
        for i in range(8):
            if i==max_line:
                mini_text(text[i],10,i*10+10,(255,255,255))
                
            if i<max_line:
                mini_text(text[i],10,i*10+10,(255,255,255))
    return

#============================================================== automatic screen brightness adjustment
                
display_brightness = 20
target_brightness  = 20

def auto_brightness():
    
    global target_brightness
    global display_brightness

    target_brightness =(target_brightness *50 + (light_sensor.read()*17 - 170))/51

    if target_brightness < display_brightness-2:
        display_brightness -= 1
    if target_brightness > display_brightness+2:
        display_brightness = int(target_brightness+1)
    display_brightness = constrain(display_brightness,10,100)
    disp.backlight(display_brightness)
    return

#============================================================== pov out
pov_x = 0
pov_string = ''

def pov_out():
    global pov_x
    global pov_string
    
    if p_state != state:
        pov_x=0

        if len(text[len(text)-1])>0:
            pov_string = text[len(text)-1]
        else:
            pov_string = '            '

    if pov_x>=(len(pov_string))*10:
        pov_x=0
    
    c = ord(pov_string[pov_x//10])-32
    
    if c<0 or c>59:
        c=0

    for y in range(7):
        leds.prep(y+2,(0,0,0))
        if (pov_x%10)<7:
            if (mini_alphabet[c][y][pov_x%10]==1):
                leds.prep(y+2,(255,255,255))
    leds.prep(0,(0,0,0))
    leds.prep(10,(0,0,0))
    if pov_x%2==0:
        leds.prep(0,(255,64,64))
        leds.prep(10,(255,64,64))

    disp.rect(0,38,159,42,col=(0,0,0))
    for i in range(5):
     disp.line(pov_x+10-i,38+i,pov_x+10+i,38+i,col=(255,64,64))
        
    leds.update()
    pov_x += 1
    return
#============================================================== reset leds
def reset_leds():
    for i in range(12):
        leds.prep(i,(0,0,0))
    leds.update()
    return

#============================================================== morse tx
morse_alphabet=(
    '',       # 32:' '
    '',       #!
    '.----.', #"
    '',       ##
    '',       #$
    '',       #%
    '',       #&
    '.----.', #'
    '-.--.',  #(
    '-.--.-', #)
    '',       #*
    '.-.-.',  #+
    '--..--', #,
    '-....-', #-
    '.-.-.-', #.
    '-..-.',  #/
    '-----',  #0
    '.----',  #1
    '..---',  #2
    '...--',  #3
    '....-',  #4
    '.....',  #5
    '-....',  #6
    '--...',  #7
    '---..',  #8
    '----.',  #9
    '---...', #:
    '-.-.-.', #;
    '',       #<
    '-...-',  #=
    '',       #>
    '..--..', #?
    '.--.-.', #@
    '.-',     #A
    '-...',   #B
    '-.-.',   #C
    '-..',    #D
    '.',      #E
    '..-.',   #F
    '--.',    #G
    '....',   #H
    '..',     #I
    '.---',   #J
    '-.-',    #K
    '.-..',   #L
    '--',     #M
    '-.',     #N
    '---',    #O
    '.--.',   #P
    '--.-',   #Q
    '.-.',    #R
    '...',    #S
    '-',      #T
    '..-',    #U
    '...-',   #V
    '.--',    #W
    '-..-',   #X
    '-.--',   #Y
    '--..',   #Z
    '')       #CURSOR

morse_line              = 0
morse_char_index_index  = 0
morse_symbol_index      = 0
morse_code              = ''
morse_character         = ''
morse_restart           = False
morse_last_symbol_ms    = 0
morse_pause             = 0

DIT             = 150
DAH             = DIT * 3
SYMBOL_PAUSE    = DIT
CHARACTER_PAUSE = DAH
SPACE_PAUSE     = DIT * 7
    
def morse_tx():
    global morse_line
    global morse_char_index
    global morse_symbol_index
    global morse_code
    global morse_character
    global morse_restart
    global morse_last_symbol_ms
    global morse_pause

    if p_state!=state or morse_restart == True: #.............. init
        morse_line         = 0
        morse_char_index   = -1
        morse_symbol_index = 0  
        morse_code         = ''
        morse_character    = 0

    if morse_symbol_index < len(morse_code): # ................ is there a symbol to morse?
        
        code = morse_code[morse_symbol_index]

        while utime.monotonic_ms() < (morse_last_symbol_ms + morse_pause): # wait until next symbol can be sent
            print('/')
        
        print ('tx: send '+code)
        
        if code=='.': # ....................................... morse dit
            leds_on()
            vibra.vibrate(10)
            utime.sleep_ms(DIT)
            vibra.vibrate(8)
            leds_off()
                
        if code=='-': # ....................................... morse dah
            leds_on()
            vibra.vibrate(15)
            utime.sleep_ms(DAH)
            vibra.vibrate(13)
            leds_off()
            
        morse_last_symbol_ms = utime.monotonic_ms() # ......... remember when symbol was sent for precise pause
        morse_pause          = SYMBOL_PAUSE
        
    else: # ................................................... character is sent; no further symbol to morse:     
        print ('tx: end of character!')
        if morse_character==0:
            if state == p_state:
                morse_pause = SPACE_PAUSE # ................... space: wait dit*7 until next character is sent 
        else:
            morse_pause = CHARACTER_PAUSE # ................... wait duration of a dah until next character is sent
        morse_char_index  += 1 # .............................. move to next character
        morse_symbol_index = -1 
        
    morse_symbol_index += 1 # ................................. increment morse symbol index (next loop - next symbol)

    if morse_char_index == len(text[morse_line]): # ........... end of line?
        print ('tx: end of line!')
        morse_line        += 1
        morse_char_index   = 0
        morse_symbol_index = 0
        morse_pause = SPACE_PAUSE # ........................... space: wait dit*7 until next character is sent 
        disp.rect(0,18,159,30,col=(0,0,0), filled = True) #.... clear line
        
    if morse_line == len(text): # ............................. end of text?
        print ('tx: end of text!')
        morse_line         = 0
        morse_char_index   = 0
        morse_symbol_index = 0

    morse_restart = True
    if morse_line<len(text):
        if morse_char_index<len(text[morse_line]):
            print('line:%i char:%i '%(morse_line,morse_char_index))
            morse_restart = False
            morse_character = ord(text[morse_line][morse_char_index])-32 # find character  which has to be sent out next
            morse_code = morse_alphabet[morse_character] # .... find morse code which has to be sent out next
            # ................................................. write character on screen
            mini_text(text[morse_line][morse_char_index],morse_char_index*10+10,18,(255,255,255))
            
            disp.rect(0,38,159,42,col=(0,0,0)) # .............. clear morse code background

            x_morse = 10
            
            for i in range(len(morse_code)): # ................ display morse code on screen
                morse_color = (255,255,255)
                if i== morse_symbol_index:
                    morse_color=(255,64,64)
                code = morse_code[i]
                if code=='.':
                    disp.rect(x_morse,38,x_morse+5, 42,col=morse_color,filled=True)
                    x_morse+=10
                if code=='-':
                    disp.rect(x_morse,38,x_morse+15,42,col=morse_color,filled=True)
                    x_morse+=20
    return     
#============================================================== leds on/off    
def leds_on():
    for i in range(11):
        leds.prep(i,(255,255,255))
    leds.update()
    #gpio.set_mode(3,  gpio.mode.OUTPUT) # <-- tried to address the IR LED on GPIO3 - didn't work :(
    #gpio.write(3,True)

def leds_off():
    for i in range(11):
        leds.prep(i,(0,0,0))
    leds.update()
    #gpio.set_mode(3,  gpio.mode.OUTPUT) # <-- tried to address the IR LED on GPIO3 - didn't work :(
    #gpio.write(3,False)

#============================================================== read morse code
morse_in_index    = 0
p_light_in        = 0
light_in          = 0
light_in_lpf      = 0
light_received    = False
p_light_received  = False

morse_signal_in   = False
p_morse_signal_in = False
lohi_time_ms      = 0
hilo_time_ms      = 0
morse_symbol_in   = ''
space_added       = True

morse_rx_line     = ''
morse_in_character_count   = 0
p_morse_in_character_count = 0

def morse_rx():
    
    global morse_in_index
    global p_light_in
    global light_in
    global light_in_lpf
    global morse_signal_in
    global p_morse_signal_in
    global lohi_time_ms    
    global hilo_time_ms
    global morse_symbol_in
    global space_added
    global text
    global text_line
    global morse_rx_line
    global millis
    global morse_in_character_count
    global p_morse_in_character_count
    global saved

    red_cursor_y = 20 #                                         layout
    stripe_y     = 20
    text_y       = 15
    
    # ......................................................... init
    if p_state!=state:
        morse_symbol_in   = ''
        morse_rx_line     = ''
        morse_in_index    = 0
        morse_in_character_count   = 0
        p_morse_in_character_count = 0
        
    morse_in_index += 1
    
    if morse_in_index==140:
        morse_in_index=0
    
    p_morse_in_character_count = morse_in_character_count
    
    # ......................................................... morse signal detection based on light receiver
    p_light_in = light_in
    light_in   = 0
    for i in range(5):
        light_in += light_sensor.get_reading()

    light_in     /=5
    light_in_lpf  = (light_in_lpf*50 + light_in)/51

    p_morse_signal_in = morse_signal_in # ..................... remember if there was a signal in prev. loop for flange detection
    
    if light_in > (light_in_lpf+1.5) and p_light_in > (light_in_lpf+1.5) : # ....................... light above threshold: morse signal detected
        morse_signal_in = True

    if light_in < (light_in_lpf-.5) and p_light_in < (light_in_lpf-.5): # ........................ light below threshold: no morse signal detected
        morse_signal_in = False
    # ......................................................... flange detection based on signal and previous signal
    end_of_symbol = False
    space_added   = False
    millis = utime.monotonic_ms()
    
    if p_morse_signal_in == True and morse_signal_in == False:# hi-lo flange detected
        hilo_time_ms = millis
        hi_time      = millis - lohi_time_ms
        hi_pulse     = (hi_time+DIT//2) // DIT

        print ('HI_PULSE: ////> %i'%(hi_pulse))
        
        if hi_pulse == 1 and hi_time>DIT/3:
            print ('                         DIT: %i'%(millis-lohi_time_ms-DIT))
            morse_symbol_in += '.'
            
        elif hi_pulse == 3:
            print ('                                   DAH: %i'%(millis-lohi_time_ms-DAH))
            morse_symbol_in += '-'
            
    if p_morse_signal_in == False and morse_signal_in == True: # lo-hi flange detected
        lohi_time_ms = millis
        lo_time      = millis - hilo_time_ms
        lo_pulse     = (lo_time+DIT//2)  // DIT

        print ('lo_pulse: ----> %i'%(lo_pulse))
        
        if lo_pulse == 3:
            end_of_symbol = True
            
        elif lo_pulse > 3 and p_state == MORSE_IN:
            end_of_symbol = True
            space_added   = True

    if lohi_time_ms < hilo_time_ms and (hilo_time_ms + DIT * 2) < millis:
        end_of_symbol = True

    end_of_line = False
    
    if end_of_symbol:
        
        morse_in_index=-1
        
        if morse_symbol_in != '':    
            
            for i in range(59):                                 # compare symbols and find character
                if morse_alphabet[i] == morse_symbol_in:
                    morse_rx_line += chr(i+32)                  # add character to morse rx string
                    saved = False
                    morse_in_character_count += 1
                    print ('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  '+morse_symbol_in+' :'+chr(i+32))                             # complete morse symbol received; print it

                    if len(morse_rx_line) == 15:                # end of line?
                        end_of_line = True

                     
        if space_added:
            morse_rx_line += ' '                                # add character to morse rx string
            morse_in_character_count += 1
            end_of_line = True
                

        if end_of_line:
            text.append(morse_rx_line)                          # create a new text line with morse rx line content
            saved = False
            text_line +=1
            morse_rx_line = ''
            morse_in_character_count = 0
            
            disp.rect(0,4+text_y,159,13+text_y,col=(0,0,0),filled=True)
            if len(text)>1:
                mini_text(text[text_line-1],10,4+text_y,(255,255,255)) # display old line 1
            
            disp.rect(0,14+text_y,159,23+text_y,col=(0,0,0),filled=True)
            mini_text(text[text_line],10,14+text_y,(255,255,255)) # display old line 2

        morse_symbol_in = ''                                    # reset morse symbol input string

    if morse_in_index == 1:
        disp.rect(11,38+stripe_y,159,42+stripe_y,col=(0,0,0))   # clear morse input stripe
        
    if morse_in_character_count == 0 and p_morse_in_character_count != morse_in_character_count:
        disp.rect(0,24+text_y,159,32+text_y,col=(0,0,0),filled=True) # clr line
    else:
        disp.rect(morse_in_character_count*10,24+text_y,159,32+text_y,col=(0,0,0),filled=True) # clr character
    blinky_cursor=' '
    if (millis//500)%2==0:                                      # add cursor as last character of this line
        blinky_cursor='['
    x_char_shift = 0
    if morse_in_character_count == 0:
        x_char_shift = 10
        
    mini_text(morse_rx_line[-1:] + blinky_cursor,morse_in_character_count*10+x_char_shift,24+text_y,(255,255,255))  # show latest character
        
 

    receive_color=(0,0,0)                                       # draw received signal as stripe
    leds.prep(10,(0,0,0))
    
    if morse_signal_in:
        receive_color=(255,255,255)
        
        leds.prep(10,(128,128,128))                             # show received signal on led
        

    disp.line(morse_in_index+10,38+stripe_y,morse_in_index+10,42+stripe_y,col=receive_color) # received signal

    
    
    disp.rect(0,45+red_cursor_y,159,52+red_cursor_y,col=(0,0,0)) # clear red cursor bgnd
    for i in range(5):
            disp.line(morse_in_index+11-i,47+i+red_cursor_y,morse_in_index+11+i,47+i+red_cursor_y,col=(255,64,64)) # red cursor
     
    disp.line(9,38+stripe_y,9,42+stripe_y,col=(0,0,0))
    
    leds.update()
    
############################################################### main

millis=0

EDIT      = 0
READ      = 1
POV_OUT   = 2
MORSE_OUT = 3
MORSE_IN  = 4
NEW       = 5

state     = EDIT
p_state   = -1




with display.open() as disp:
    print('--- tiny typer! ---')
    load_text()
    leds.dim_top(8)
    
    while True:

        millis = utime.monotonic_ms()
        tock   = read_buttons()
        read_bhi()

        if button_left_pressed:
            state = (state-1)%6
            
        if button_right_pressed:
            state = (state+1)%6
            
        if p_state!=state:
            disp.clear()
            key_set   = 0
            p_key_set = -1
            if p_state == MORSE_IN:
                if morse_rx_line!='':
                    text.append(morse_rx_line)                # create a new text line with morse rx line content
                    text_line +=1
                    morse_rx_line = ''
                    
            if p_state == EDIT or p_state == MORSE_IN:
                if saved == False:
                    save_text()
                    
            if p_state == EDIT or p_state == -1: # if coming from edit - remove cursor in last line
                last_line        = text[text_line]
                last_line_length = len(last_line)
                
                if last_line_length>0 and (text[text_line][-1:]==' ' or text[text_line][-1:]=='[' ):
                    text[text_line] = last_line[:(last_line_length-1)]
                    
            if p_state == POV_OUT:
                reset_leds()

            if state != MORSE_IN:
                leds.prep(10,(0,0,0))
                leds.update()
                


        
        #------------------------------------------------------ state: EDIT
        if state == EDIT:
            edit_text(show_keyboard())
            show_text() 
            auto_save()
        #------------------------------------------------------ state: READ
        if state == READ:
            scroll_text() 
        #------------------------------------------------------ state: POV_OUT
        if state == POV_OUT:
            if p_state != state:
                mini_text('>LIGHT WRITER:',0,0,(200,200,200))
                mini_text(text[len(text)-1],10,18,(255,255,255))
            pov_out()
            
        #------------------------------------------------------ state: MORSE_OUT
        if state == MORSE_OUT:
            if p_state != state:
                mini_text('>MORSE TRANSMIT:',0,0,(200,200,200))
            if len(text[0])>1 or len(text)>0:
                morse_tx()
        #------------------------------------------------------ state: MORSE_IN
        if state == MORSE_IN:  
            if p_state != state:
                mini_text('>MORSE RECEIVE:',0,0,(200,200,200))
            morse_rx()
        #------------------------------------------------------ state: DELETE
        if state == NEW:
            if p_state != state:
                mini_text(' START NEW FILE:',0,0,(200,200,200))
                mini_text('>',0,10,(255,255,255))
                mini_text('OK?',10,10,(255,64,64))
            if button_select_pressed:
                if text==[''] or text==[' '] or text==['[']:
                    disp.rect(0,10,159,79,col=(0,0,0), filled = True)
                    mini_text('OK?',10,10,(255,255,255))
                    mini_text('>',0,20,(255,255,255))
                    mini_text('TEXT IS EMPTY!',10,20,(255,64,64))            
                else:
                    disp.rect(0,10,159,79,col=(0,0,0), filled = True)
                    mini_text('OK?',10,10,(255,255,255))
                    mini_text('BACKING UP...',10,20,(255,255,255))
                    disp.update()
                    backup_text()
                    text           = ['']
                    text_line      = 0
                    screen_text    = ['','','','','','','','']
                    p_screen_text  = ['','','','','','','','']
                    mini_text('CREATE NEW FILE',10,30,(255,255,255))
                    disp.update()
                    save_text()
                    mini_text('>',0,40,(255,255,255))
                    mini_text('READY',10,40,(255,64,64))
                
        #...................................................... confirmation vibration
        if tock>0:
            vibra.vibrate(tock)
            
        #...................................................... update screen
        if state!= MORSE_OUT and state!= POV_OUT:
            auto_brightness()
        disp.update()
        
        p_state = state
        


	
