Implement the ability to update text screen with GR commands and vice versa
authorNick Downing <nick@ndcode.org>
Tue, 24 May 2022 04:27:47 +0000 (14:27 +1000)
committerNick Downing <nick@ndcode.org>
Tue, 24 May 2022 07:32:10 +0000 (17:32 +1000)
apple_io.py

index 9b6b7d7..22d2730 100644 (file)
@@ -232,6 +232,7 @@ mem = {
   HW_PB2: 0,
 }
 pdl_value = [255 for i in range(4)]
+gr_on = False
 
 def init():
   global termios_attr, pcm
@@ -315,9 +316,20 @@ def _print(data):
       addr = bascalc(low_mem[ZP_CV])
       low_mem[ZP_BASL] = addr & 0xff
       low_mem[ZP_BASH] = addr >> 8
-      low_mem[addr + low_mem[ZP_CH]] = (ord(ch) & 0x7f) | 0x80
+      i = ord(ch)
+      data = (
+        (i & 0x3f) ^ 0x60
+      if invflg == INVFLG_FLASH else
+        (i & 0x3f) ^ 0x20
+      if invflg == INVFLG_INVERSE else
+        i | 0x80
+      )
+      low_mem[addr + low_mem[ZP_CH]] = data
 
-      write(ch)
+      if gr_on and low_mem[ZP_CV] < 20:
+        write(gr_encode([data])[0] + invflg())
+      else:
+        write(ch)
       low_mem[ZP_CH] += 1
       if low_mem[ZP_CH] >= low_mem[ZP_WNDLFT] + low_mem[ZP_WNDWTH]:
         crlf()
@@ -385,12 +397,19 @@ def clreop():
   write('\x1b[J')
 
 def home():
-  write(f'\x1b[2J\x1b[{low_mem[ZP_WNDLFT] + 1:d};{low_mem[ZP_WNDTOP] + 1:d}H')
+  # move cursor home, clear to end of screen
+  # just approximates proper behaviour of clearing the window rectangle
+  write(
+    f'\x1b[{low_mem[ZP_WNDTOP] + 1:d};{low_mem[ZP_WNDLFT] + 1:d}H\x1b[J'
+  )
   low_mem[ZP_CH] = low_mem[ZP_WNDLFT]
   low_mem[ZP_CV] = low_mem[ZP_WNDTOP]
+  for i in range(low_mem[ZP_WNDTOP], 24):
+    addr = bascalc(i)
+    low_mem[addr:addr + 40] = [0xa0] * 40
 
 def invflg():
-  write(
+  return (
     '\x1b[0;7;5m'
   if low_mem[ZP_INVFLG] == INVFLG_FLASH else
     '\x1b[0;7m'
@@ -401,17 +420,17 @@ def invflg():
 def normal():
   if low_mem[ZP_INVFLG] != INVFLG_NORMAL:
     low_mem[ZP_INVFLG] = INVFLG_NORMAL
-    invflg()
+    write(invflg())
 
 def inverse():
   if low_mem[ZP_INVFLG] != INVFLG_INVERSE:
     low_mem[ZP_INVFLG] = INVFLG_INVERSE
-    invflg()
+    write(invflg())
 
 def flash():
   if low_mem[ZP_INVFLG] != INVFLG_FLASH:
     low_mem[ZP_INVFLG] = INVFLG_FLASH
-    invflg()
+    write(invflg())
 
 def tone(freq, dur): # Hz, s
   if beep_style == BEEP_STYLE_VT100:
@@ -471,7 +490,12 @@ def poke(addr, data):
     data &= 0xff
     if addr < 0x800:
       low_mem[addr] = data
-      if addr == ZP_WNDTOP or addr == ZP_WNDBTM:
+      if addr >= 0x400:
+        y = ((addr >> 7) & 7) | (((addr & 0x7f) // 40) << 3)
+        if y < 24:
+          x = (addr & 0x7f) % 40
+          gr_update(x, y, x + 1, y + 1)
+      elif addr == ZP_WNDTOP or addr == ZP_WNDBTM:
         # save cursor, set scrolling region, restore cursor
         write(
           f'\x1b[s\x1b[{low_mem[ZP_WNDTOP] + 1:d};{low_mem[ZP_WNDBTM]:d}r\x1b[u'
@@ -484,21 +508,22 @@ def call(addr):
 
   addr &= 0xffff
   if addr == NICK_REST or addr == NICK_TONE:
-    # for lemonade_patched, see test/lemonade_tone_nick.py
-    if addr == NICK_REST:
-      low_mem[NICK_FREQL] = 0
-      low_mem[NICK_FREQH] = 0
-    frequency_incr = low_mem[NICK_FREQL] + (low_mem[NICK_FREQH] << 8)
-    duration_count = -(low_mem[NICK_DURL] + (low_mem[NICK_DURH] << 8))
-    duration_count = ((duration_count - 1) & 0xffff) + 1
-    duration = 3.58309497e-10 + duration_count * (
-      2.34821712e-05 + frequency_incr * 4.47591203e-11
-    )
-    if frequency_incr:
-      period = 0x1fffe * (2.34821712e-05 / frequency_incr + 4.47591203e-11)
-      tone(1. / period, duration)
-    else:
-      time.sleep(duration)
+    pass
+    ## for lemonade_patched, see test/lemonade_tone_nick.py
+    #if addr == NICK_REST:
+    #  low_mem[NICK_FREQL] = 0
+    #  low_mem[NICK_FREQH] = 0
+    #frequency_incr = low_mem[NICK_FREQL] + (low_mem[NICK_FREQH] << 8)
+    #duration_count = -(low_mem[NICK_DURL] + (low_mem[NICK_DURH] << 8))
+    #duration_count = ((duration_count - 1) & 0xffff) + 1
+    #duration = 3.58309497e-10 + duration_count * (
+    #  2.34821712e-05 + frequency_incr * 4.47591203e-11
+    #)
+    #if frequency_incr:
+    #  period = 0x1fffe * (2.34821712e-05 / frequency_incr + 4.47591203e-11)
+    #  tone(1. / period, duration)
+    #else:
+    #  time.sleep(duration)
   elif addr == TONE_TONE or addr == TONE_TONE1:
     # for lemonade, see test/lemonade_tone_patched.py
     period_count = ((low_mem[TONE_PERIOD] - 1) & 0xff) + 1
@@ -550,15 +575,21 @@ def bascalc(y):
   return 0x400 | ((y & 7) << 7) | ((y >> 3) * 40)
 
 def text():
+  global gr_on
+
   # save cursor, set scrolling region, restore cursor
   write('\x1b[s\x1b1;24r\x1b[u')
   low_mem[ZP_WNDLFT] = 0
   low_mem[ZP_WNDWTH] = 40
   low_mem[ZP_WNDTOP] = 0
   low_mem[ZP_WNDBTM] = 24
+  gr_on = False
 
 def gr():
+  global gr_on
+
   # clear screen, set scrolling region (homes cursor)
+  #write(f'\x1b[2J\x1b[21;24r\x1b[1;21H')
   write(f'\x1b[2J\x1b[21;24r')
   low_mem[ZP_WNDLFT] = 0
   low_mem[ZP_WNDWTH] = 40
@@ -569,11 +600,12 @@ def gr():
   for i in range(20):
     addr = bascalc(i)
     low_mem[addr:addr + 40] = [0] * 40
+  gr_on = True
 
 # takes memory encoded with upper pixel in bits 0-3, lower in bits 4-7
 # returns a stream of unicode characters with attribute-setting escapes
 # attribute-setting can be optimized by passing in a previous attribute
-def gr_encode(_in, attr = (-1, -1, -1)):
+def gr_encode(_in, attr = (-1, -1, -1, -1, -1)):
   out = []
   for i in _in:
     background = colors[i & 0xf]
@@ -593,28 +625,58 @@ def gr_encode(_in, attr = (-1, -1, -1)):
     else:
       background = 0
       ch = '█'
-    new_attr = (background & 7, foreground & 7, foreground >> 3)
+    new_attr = (background & 7, foreground & 7, foreground >> 3, -1, -1)
     if new_attr != attr:
       out.append(
         '\x1b[0;{0:d};{1:d}{2:s}m'.format(
-          new_attr[0] + 40,
-          new_attr[1] + 30,
-          ';1' if new_attr[2] else ''
+          new_attr[0] + 40, # foreground
+          new_attr[1] + 30, # background
+          ';1' if new_attr[2] else '' # intense
         )
       )
       attr = new_attr
     out.append(ch)
   return ''.join(out), attr
 
+# similar to gr_encode(), but for the text portion of screen (lines 21-24)
+# decodes hi bits of characters into attributes interleaved with characters
+text_hibits = [
+  (0x40, (-1, -1, -1, 1, 0)), # inverse @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+  (0x20, (-1, -1, -1, 1, 0)), # inverse  !"#$%&'()*+,-./0123456789:;<=>?
+  (0x40, (-1, -1, -1, 1, 1)), # blink   @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+  (0x20, (-1, -1, -1, 1, 1)), # blink    !"#$%&'()*+,-./0123456789:;<=>?
+  (0x40, (-1, -1, -1, 0, 0)), # normal  @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+  (0x20, (-1, -1, -1, 0, 0)), # normal   !"#$%&'()*+,-./0123456789:;<=>?
+  (0x40, (-1, -1, -1, 0, 0)), # normal  @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+  (0x60, (-1, -1, -1, 0, 0)), # normal  `abcdefghijklmnopqrstuvwxyz{|}~del
+]
+def text_encode(_in, attr = (-1, -1, -1, -1, -1)):
+  out = []
+  for i in _in:
+    base, new_attr = text_hibits[i >> 5]
+    if new_attr != attr:
+      out.append(
+        '\x1b[0{0:s}{1:s}m'.format(
+          ';7' if new_attr[3] else '', # reverse video
+          ';5' if new_attr[4] else '' # blink
+        )
+      )
+      attr = new_attr
+    out.append(chr(base | (i & 0x1f)))
+  return ''.join(out), attr
+
 def gr_update(x0, y0, x1, y1):
   write('\x1b[s') # save cursor
-  attr = (-1, -1, -1)
+  attr = (-1, -1, -1, -1, -1)
   for i in range(y0, y1):
     addr = bascalc(i)
-    data, attr = gr_encode(low_mem[addr + x0:addr + x1], attr)
+    data, attr = (
+      gr_encode(low_mem[addr + x0:addr + x1], attr)
+    if gr_on and i < 20 else
+      text_encode(low_mem[addr + x0:addr + x1], attr)
+    )
     write(f'\x1b[{i + 1:d};{x0 + 1:d}H{data:s}')
-  write('\x1b[u') # restore cursor
-  invflg() # restore normal/inverse/flash
+  write('\x1b[u' + invflg()) # restore cursor and normal/inverse/flash
 
 def color(n):
   low_mem[ZP_COLOR] = n