#Simple Driver for ILI9488 on Pico
#defaults to:
#LCDCS (12)
#LCDRST (13)
#LCDDC (14)
#TOUCHCS (15)
#MISOPIN (16)
#TOUCHIRQ (17)
#SCKPIN (18)
#MOSIPIN (19)
#BLPIN (20)

from machine import Pin,SPI
import utime
import LSNBFONT

class LCD_BUTTON:
    def __init__(self,
        x=0,y=0,w=50,h=50,s="Button"
    ):
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.s = s
        self.pressed = 0
        self.visible = 1
        self.lastchange = 0
        self.textc=0xFFFFFF
        self.bc=0x000000
        self.bordc=0x808080
        self.font=LSNBFONT

    def set(self,v):
        self.visible=v

class ILI9488LIB:

    def __init__(self,
        misopin=16,mosipin=19,sckpin=18,
        lcdcs=12,lcdrst=13,lcddc=14,
        touchcs=15,touchirq=17,
        lcdbl=20
    ):
        #init LCD
        self.misopin = misopin
        self.mosipin = mosipin
        self.sckpin = sckpin
        self.lcdcs = lcdcs
        self.lcdrst = lcdrst
        self.lcddc = lcddc
        self.touchcs = touchcs
        self.touchirq = touchirq
        self.lcdbl = lcdbl
        #none of these will be sensible till after init/rotate
        self.width = 0
        self.height = 0
        self.rotation = 0
        #pseudo constants
        self.SPIspeedlcd = 50000000
        self.SPIspeedtouch = 2000000 #note 2MHz
        self.touchz = 20
        self.touchx0 = 110
        self.touchx1 = 2001
        self.touchy0 = 1993
        self.touchy1 = 76
        self.touchsamples = 16

    def init(self):
        #backlight on
        self.lcdblio = Pin(self.lcdbl, Pin.OUT)
        self.lcdblio.value(1)
        #control IO
        self.lcdcsio = Pin(self.lcdcs, Pin.OUT)
        self.lcdcsio.value(1)
        self.touchcsio = Pin(self.touchcs, Pin.OUT)
        self.touchcsio.value(1)
        self.lcdrstio = Pin(self.lcdrst, Pin.OUT)
        self.lcdrstio.value(0)
        self.lcddcio = Pin(self.lcddc, Pin.OUT)
        self.lcddcio.value(1)
        #these are our two configs so we can use different speeds etc
        self.spi = SPI(0,
                  baudrate=self.SPIspeedlcd,
                  polarity=1,
                  phase=1,
                  bits=8,
                  firstbit=SPI.MSB,
                  sck=Pin(self.sckpin),
                  mosi=Pin(self.mosipin),
                  miso=Pin(self.misopin))
        utime.sleep_ms(15)
        self.lcdrstio.value(1)               #come out of reset
        utime.sleep_ms(15)
        self.lcdcsio.value(0)
        self.cmd(0x1)                        #sw reset
        utime.sleep_ms(120)
        self.cmd(0x11)                       #sleep out
        utime.sleep_ms(120)
        self.cmd(0x13)   #normal
        self.cmd(0x20)   #no inversion
        self.cmd(0x28)   #display off
        self.cmd(0x38)   #idle mode off
        self.cmd(0xC0)   #power control 1
        self.data(0x17)
        self.data(0x15)
        self.cmd(0xC1)   #power control 2
        self.data(0x41)
        self.cmd(0xC5)   #VCOM control
        self.data(0x0e)
        self.data(0x0e)
        self.cmd(0x36)   #Memory Access control
        self.data(88)    #suits rotation 0
        self.cmd(0x3A)   #Pixel interface format
        self.data(0x66)
        self.cmd(0xB4)   #inversion control
        self.data(0x2)
        self.cmd(0xB6)   #Function control
        self.data(0x2)
        self.data(0x2)
        self.data(0x3B)
        self.cmd(0x29)   #display on
        self.cmd(0x2A)   #set column
        self.data(0x0)
        self.data(0x0)
        self.data(0x1)
        self.data(0x3F)
        self.cmd(0x2B)   #set row
        self.data(0x0)
        self.data(0x0)
        self.data(0x1)
        self.data(0xDF)
        self.cmd(0x2C)   #draw
        self.lcdcsio.value(1)
        #End of init

    #send data byte
    def data(self,d):
        msg = bytearray()
        msg.append(d)
        self.spi.write(msg)
    
    def data16(self,d):
        msg = bytearray()
        msg.append(d>>8)
        msg.append(d)
        self.spi.write(msg)
        
    def data24(self,d):            #primarily for colours
        msg = bytearray()
        msg.append(d>>16)
        msg.append(d>>8)
        msg.append(d)
        self.spi.write(msg)

#send command byte
    def cmd(self,d):
        self.lcddcio.value(0)
        msg = bytearray()
        msg.append(d)
        self.spi.write(msg)
        self.lcddcio.value(1)

    def setRotation(self,r):
        self.lcdcsio.value(0)
        if r == 1:
            self.rotation=1
            self.width=320
            self.height=480
            self.cmd(0x36)   #set MADCTL
            self.data(88)
        if r == 2:
            self.rotation=2
            self.width=480
            self.height=320
            self.cmd(0x36)   #set MADCTL
            self.data(56)
        if r == 3:
            self.rotation=3
            self.width=320
            self.height=480
            self.cmd(0x36)   #set MADCTL
            self.data(152)
        if r == 4:
            self.rotation=4
            self.width=480
            self.height=320
            self.cmd(0x36)   #set MADCTL
            self.data(248)
        self.lcdcsio.value(1)
        self.setarea(0,0,self.width-1,self.height-1)
    
    def setarea(self,x0,y0,x1,y1):
        if x1<x0:
             t=x0
             x0=x1
             x1=t
        if y1<y0:
             t=y0
             y0=y1
             y1=t
        self.lcdcsio.value(0)
        self.cmd(0x2A)                 #column
        self.data16(x0)                #start
        self.data16(x1)                #end
        self.cmd(0x2B)                 #row
        self.data16(y0)                #start
        self.data16(y1)                #end
        self.cmd(0x2C)                 #draw mode
        self.lcdcsio.value(1)

    def box(self,x0,y0,x1,y1,c):
        if x1<x0:
             t=x0
             x0=x1
             x1=t
        if y1<y0:
             t=y0
             y0=y1
             y1=t
        r=(c>>16)
        g=(c>>8)
        b=(c)
        msg = bytearray()
        self.setarea(x0,y0,x1,y1)
        for x in range(x1-x0+1):
            msg.append(r)
            msg.append(g)
            msg.append(b)
        self.lcdcsio.value(0)
        for y in range(y1-y0+1):
            self.spi.write(msg)            
        self.lcdcsio.value(1)

    def clear(self,c):
        self.box(0,0,self.width-1,self.height-1,c)

    def hbox(self,x0,y0,x1,y1,c):
        self.hline(x0,y0,x1,c)
        self.hline(x0,y1,x1,c)
        self.vline(x0,y0,y1,c)
        self.vline(x1,y0,y1,c)

    def drawchar(self,x0,y0,c,f,fc,bc):
        g=f.get_ch(c)                  #get bytes, memoryview at g[0]
        w=g[2]
        h=f.height()
        hh=(h+7)//8
        fcb = bytearray()
        fcb.append(fc>>16)
        fcb.append(fc>>8)
        fcb.append(fc)
        bcb = bytearray()
        bcb.append(bc>>16)
        bcb.append(bc>>8)
        bcb.append(bc)
        for x in range(g[2]):
            self.setarea(x+x0,y0,x+x0,y0+h-1)                
            self.lcdcsio.value(0)        
            i=x*hh
            b=1
            d=g[0][i]
            for y in range(h):                
                if (d & b) != 0:
                    self.spi.write(fcb)
                else:
                    self.spi.write(bcb)
                b=b*2
                if (b>128):
                    b=1
                    i=i+1
                    if(i < len(g[0])):
                        d=g[0][i]                        
            self.lcdcsio.value(1)        
        return w

    def drawstr(self,x0,y0,s,f,fc,bc):
        for i in s:
            x0=x0+self.drawchar(x0,y0,i,f,fc,bc)
    
    def charheight(self,c,f):
        g=f.get_ch(c)
        w=g[1]
        return w

    def charwidth(self,c,f):
        g=f.get_ch(c)                  #get bytes, memoryview at g[0]
        w=g[2]
        return w
    
    def strwidth(self,s,f):
        w=0
        for i in s:
            w=w+self.charwidth(i,f)
        return w
    
    def point(self,x,y,c):
        self.setarea(x,y,x,y)
        self.lcdcsio.value(0)        
        self.data24(c)
        self.lcdcsio.value(1)                

    def touchraw(self,d):         #get raw response from touch controller with command d
        n=0
        self.spi = SPI(0,
            baudrate=self.SPIspeedtouch,
            polarity=1,
            phase=1,
            bits=8,
            firstbit=SPI.MSB,
            sck=Pin(self.sckpin),
            mosi=Pin(self.mosipin),
            miso=Pin(self.misopin))
        self.touchcsio.value(0)
        self.data(d)
        for i in range(self.touchsamples):
            n=n+self.spi16(d)
        self.touchcsio.value(1)        
        self.spi = SPI(0,
            baudrate=self.SPIspeedlcd,
            polarity=1,
            phase=1,
            bits=8,
            firstbit=SPI.MSB,
            sck=Pin(self.sckpin),
            mosi=Pin(self.mosipin),
            miso=Pin(self.misopin))
        return n//(16*self.touchsamples)

    def touchx(self):
        z=self.touchraw(0xB1)
        r=-1
        if (z>self.touchz):
            if(self.rotation == 1):
                n=self.touchraw(0xD1)
                r=self.map(n,self.touchy1,self.touchy0,0,self.width-1)
            if(self.rotation == 2):
                n=self.touchraw(0x91)
                r=self.map(n,self.touchx1,self.touchx0,0,self.width-1)
            if(self.rotation == 3):
                n=self.touchraw(0xD1)
                r=self.map(n,self.touchy0,self.touchy1,0,self.width-1)
            if(self.rotation == 4):
                n=self.touchraw(0x91)
                r=self.map(n,self.touchx0,self.touchx1,0,self.width-1)
            if (r<0):
                r=-1
            if (r>self.width-1):
                r=-1
        return int(r)

    def touchy(self):
        z=self.touchraw(0xB1)
        r=-1
        if (z>self.touchz):
            if(self.rotation == 1):
                n=self.touchraw(0x91)
                r=self.map(n,self.touchx1,self.touchx0,0,self.height-1)
            if(self.rotation == 2):
                n=self.touchraw(0xD1)
                r=self.map(n,self.touchy0,self.touchy1,0,self.height-1)
            if(self.rotation == 3):
                n=self.touchraw(0x91)
                r=self.map(n,self.touchx0,self.touchx1,0,self.height-1)
            if(self.rotation == 4):
                n=self.touchraw(0xD1)
                r=self.map(n,self.touchy1,self.touchy0,0,self.height-1)
            if (r<0):
                r=-1
            if (r>self.height-1):
                r=-1
        return int(r)

    def spi16(self,d):           #write and return read
        tx = bytearray()
        tx.append(d>>8)
        tx.append(d)
        rx = bytearray(2)
        self.spi.write_readinto(tx,rx);
        return rx[0]*256+rx[1]

    def map(self,x,in1,in2,out1,out2):
        return ((x-in1)*(out2-out1))/(in2-in1)+out1
    
    def line(self,x1,y1,x2,y2,c):  #free line with Bresenham algorithm
        stepsx=int(abs(x1-x2))
        stepsy=int(abs(y1-y2))
        steps=max(stepsx,stepsy)+1
        xinc=0
        yinc=0
        if ((x2-x1)>0):
            xinc=1
        if ((x2-x1)<0):
            xinc=-1
        if ((y2-y1)>0):
            yinc=1
        if ((y2-y1)<0):
            yinc=-1
        x=x1
        y=y1
        if (stepsx>stepsy):
            d=int(stepsx//2)
            for i in range(steps):
                self.point(x,y,c)
                x=x+xinc
                d=d+stepsy
                if(d>stepsx):
                    d=d-stepsx
                    y=y+yinc
        else:
            d=int(stepsy//2)
            for i in range(steps):
                self.point(x,y,c)
                y=y+yinc
                d=d+stepsx
                if(d>stepsy):
                    d=d-stepsy
                    x=x+xinc
                        
    def hline(self,x1,y1,x2,c):
        if(x2<x1):
             t=x1
             x1=x2
             x2=t
        self.setarea(x1,y1,x2,y1)
        self.lcdcsio.value(0)        
        for i in range(x2+1-x1):
            self.data24(c)
        self.lcdcsio.value(1)                
    
    def vline(self,x1,y1,y2,c):
        if(y2<y1):
             t=y1
             y1=y2
             y2=t
        self.setarea(x1,y1,x1,y2)
        self.lcdcsio.value(0)        
        for i in range(y2+1-y1):
            self.data24(c)
        self.lcdcsio.value(1)                

    def fcircle(self,x0,y0,r,c):
        e=0
        x=r
        y=0
        while (x >= y):
            self.vline(x0-y,y0+x,y0-x,c)
            self.vline(x0+y,y0+x,y0-x,c)
            e=e+1+2*y
            y=y+1
            if((2*(e-x)+1) > 0):
                y=y-1
                self.vline(x0-x,y0-y,y0+y,c)
                self.vline(x0+x,y0-y,y0+y,c)
                y=y+1
                x=x-1
                e=e+1-(2*x)
                
    def circle(self,x0,y0,r,c):
        e=0
        x=r
        y=0
        while (x >= y):
            self.point(x0+x,y0+y,c)
            self.point(x0+x,y0-y,c)
            self.point(x0-x,y0+y,c)
            self.point(x0-x,y0-y,c)
            self.point(x0+y,y0+x,c)
            self.point(x0+y,y0-x,c)
            self.point(x0-y,y0+x,c)
            self.point(x0-y,y0-x,c)
            e=e+1+2*y
            y=y+1
            if((2*(e-x)+1) > 0):
                x=x-1
                e=e+1-(2*x)

    def textbox(self,x0,y0,w,h,s,f,tc,bc,bordc):#x,y,w,h,string,font,text colour, back colour, border colour
        tw=self.strwidth(s,f)
        th=self.charheight(s[0],f)
        x1=x0+w-1
        y1=y0+h-1
        tx0=x0+int((w-tw)/2)
        tx1=tx0+tw-1
        ty0=y0+int((h-th)/2)
        ty1=ty0+th-1
        #border
        self.vline(x0,y0,y1,bordc)
        self.vline(x1,y0,y1,bordc)
        x0=x0+1
        x1=x1-1
        self.hline(x0,y0,x1,bordc)
        self.hline(x0,y1,x1,bordc)
        y0=y0+1
        y1=y1-1
        #remainder done from top to bottom
        self.box(x0,y0,x1,ty0-1,bc);   #above text (full width)
        self.box(x0,ty0,tx0-1,ty1,bc); #left of text (text height only)
        self.drawstr(tx0,ty0,s,f,tc,bc);    #text
        self.box(tx1+1,ty0,x1,ty1,bc); #right of text (text height only)
        self.box(x0,ty1+1,x1,y1,bc);   #below text (full width)
        
#these interface to button objects
    def button(self,b):
        if( b.visible != 0 ):
            if( b.pressed != 0 ):
                self.textbox(b.x,b.y,b.w,b.h,b.s,b.font,b.textc,b.bc,b.bordc)
            else:
                self.textbox(b.x,b.y,b.w,b.h,b.s,b.font,b.bc,b.textc,b.bordc)                
        else:
            self.box(b.x,b.y,b.x+b.w-1,b.y+b.h-1,b.bc)
    
    def check(self,b):
        x=self.touchx()
        y=self.touchy()
        r=0
        p=1
        if( x < b.x ):
            p=0
        if( y < b.y ):
            p=0
        if( x > b.x+b.w ):
            p=0
        if( y > b.y+b.h ):
            p=0
        if( (b.pressed==0) and (p!=0) ):  #button down
            r=1
        if( (b.pressed!=0) and (p==0) ):  #button up
            r=-1
        b.pressed=p
        b.lastchange=r
        return r
            