https://github.com/akkartik/mu/blob/main/500text-screen.mu
  1 # Testable primitives for writing text to screen.
  2 # (Mu doesn't yet have testable primitives for graphics.)
  3 #
  4 # Unlike the top-level, this text mode has no scrolling.
  5 
  6 # coordinates here don't match top-level
  7 # Here we're consistent with graphics mode. Top-level is consistent with
  8 # terminal emulators.
  9 type screen {
 10   width: int
 11   height: int
 12   data: (handle array screen-cell)
 13   cursor-x: int
 14   cursor-y: int
 15 }
 16 
 17 type screen-cell {
 18   data: grapheme
 19   color: int
 20   background-color: int
 21 }
 22 
 23 fn initialize-screen screen: (addr screen), width: int, height: int {
 24   var screen-addr/esi: (addr screen) <- copy screen
 25   var tmp/eax: int <- copy 0
 26   var dest/edi: (addr int) <- copy 0
 27   # screen->width = width
 28   dest <- get screen-addr, width
 29   tmp <- copy width
 30   copy-to *dest, tmp
 31   # screen->height = height
 32   dest <- get screen-addr, height
 33   tmp <- copy height
 34   copy-to *dest, tmp
 35   # screen->data = new screen-cell[width*height]
 36   {
 37     var data-addr/edi: (addr handle array screen-cell) <- get screen-addr, data
 38     tmp <- multiply width
 39     populate data-addr, tmp
 40   }
 41   # screen->cursor-x = 0
 42   dest <- get screen-addr, cursor-x
 43   copy-to *dest, 0
 44   # screen->cursor-y = 0
 45   dest <- get screen-addr, cursor-y
 46   copy-to *dest, 0
 47 }
 48 
 49 # in graphemes
 50 fn screen-size screen: (addr screen) -> _/eax: int, _/ecx: int {
 51   var width/eax: int <- copy 0
 52   var height/ecx: int <- copy 0
 53   compare screen, 0
 54   {
 55     break-if-!=
 56     return 0x80/128, 0x30/48
 57   }
 58   # fake screen
 59   var screen-addr/esi: (addr screen) <- copy screen
 60   var tmp/edx: (addr int) <- get screen-addr, width
 61   width <- copy *tmp
 62   tmp <- get screen-addr, height
 63   height <- copy *tmp
 64   return width, height
 65 }
 66 
 67 # testable screen primitive
 68 fn draw-grapheme screen: (addr screen), g: grapheme, x: int, y: int, color: int, background-color: int {
 69   {
 70     compare screen, 0
 71     break-if-!=
 72     draw-grapheme-on-real-screen g, x, y, color, background-color
 73     return
 74   }
 75   # fake screen
 76   var screen-addr/esi: (addr screen) <- copy screen
 77   var idx/ecx: int <- screen-cell-index screen-addr, x, y
 78   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 79   var data/eax: (addr array screen-cell) <- lookup *data-ah
 80   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 81   var dest-cell/ecx: (addr screen-cell) <- index data, offset
 82   var dest-grapheme/eax: (addr grapheme) <- get dest-cell, data
 83   var g2/edx: grapheme <- copy g
 84   copy-to *dest-grapheme, g2
 85   var dest-color/eax: (addr int) <- get dest-cell, color
 86   var src-color/edx: int <- copy color
 87   copy-to *dest-color, src-color
 88   dest-color <- get dest-cell, background-color
 89   src-color <- copy background-color
 90   copy-to *dest-color, src-color
 91 }
 92 
 93 # we can't really render non-ASCII yet, but when we do we'll be ready
 94 fn draw-code-point screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
 95   var g/eax: grapheme <- copy c
 96   draw-grapheme screen, g, x, y, color, background-color
 97 }
 98 
 99 # not really needed for a real screen, though it shouldn't do any harm
100 fn screen-cell-index screen-on-stack: (addr screen), x: int, y: int -> _/ecx: int {
101   var screen/esi: (addr screen) <- copy screen-on-stack
102   # only one bounds check isn't automatically handled
103   {
104     var xmax/eax: (addr int) <- get screen, width
105     var xcurr/ecx: int <- copy x
106     compare xcurr, *xmax
107     break-if-<
108     abort "tried to print out of screen bounds"
109   }
110   var width-addr/eax: (addr int) <- get screen, width
111   var result/ecx: int <- copy y
112   result <- multiply *width-addr
113   result <- add x
114   return result
115 }
116 
117 fn cursor-position screen: (addr screen) -> _/eax: int, _/ecx: int {
118   {
119     compare screen, 0
120     break-if-!=
121     var x/eax: int <- copy 0
122     var y/ecx: int <- copy 0
123     x, y <- cursor-position-on-real-screen
124     return x, y
125   }
126   # fake screen
127   var screen-addr/esi: (addr screen) <- copy screen
128   var cursor-x-addr/eax: (addr int) <- get screen-addr, cursor-x
129   var cursor-y-addr/ecx: (addr int) <- get screen-addr, cursor-y
130   return *cursor-x-addr, *cursor-y-addr
131 }
132 
133 fn set-cursor-position screen: (addr screen), x: int, y: int {
134   {
135     compare screen, 0
136     break-if-!=
137     set-cursor-position-on-real-screen x, y
138     return
139   }
140   # fake screen
141   var screen-addr/esi: (addr screen) <- copy screen
142   # ignore x < 0
143   {
144     compare x, 0
145     break-if->=
146     return
147   }
148   # ignore x >= width
149   {
150     var width-addr/eax: (addr int) <- get screen-addr, width
151     var width/eax: int <- copy *width-addr
152     compare x, width
153     break-if-<=
154     return
155   }
156   # ignore y < 0
157   {
158     compare y, 0
159     break-if->=
160     return
161   }
162   # ignore y >= height
163   {
164     var height-addr/eax: (addr int) <- get screen-addr, height
165     var height/eax: int <- copy *height-addr
166     compare y, height
167     break-if-<
168     return
169   }
170   # screen->cursor-x = x
171   var dest/edi: (addr int) <- get screen-addr, cursor-x
172   var src/eax: int <- copy x
173   copy-to *dest, src
174   # screen->cursor-y = y
175   dest <- get screen-addr, cursor-y
176   src <- copy y
177   copy-to *dest, src
178 }
179 
180 fn draw-cursor screen: (addr screen), g: grapheme {
181   {
182     compare screen, 0
183     break-if-!=
184     draw-cursor-on-real-screen g
185     return
186   }
187   # fake screen
188   var cursor-x/eax: int <- copy 0
189   var cursor-y/ecx: int <- copy 0
190   cursor-x, cursor-y <- cursor-position screen
191   draw-grapheme screen, g, cursor-x, cursor-y, 0/fg, 7/bg
192 }
193 
194 fn clear-screen screen: (addr screen) {
195   {
196     compare screen, 0
197     break-if-!=
198     clear-real-screen
199     return
200   }
201   # fake screen
202   set-cursor-position screen, 0, 0
203   var screen-addr/esi: (addr screen) <- copy screen
204   var y/eax: int <- copy 0
205   var height/ecx: (addr int) <- get screen-addr, height
206   {
207     compare y, *height
208     break-if->=
209     var x/edx: int <- copy 0
210     var width/ebx: (addr int) <- get screen-addr, width
211     {
212       compare x, *width
213       break-if->=
214       draw-code-point screen, 0x20/space, x, y, 0/fg=black, 0/bg=black
215       x <- increment
216       loop
217     }
218     y <- increment
219     loop
220   }
221   set-cursor-position screen, 0, 0
222 }
223 
224 # there's no grapheme that guarantees to cover every pixel, so we'll bump down
225 # to pixels for a real screen
226 fn clear-real-screen {
227   var y/eax: int <- copy 0
228   {
229     compare y, 0x300/screen-height=768
230     break-if->=
231     var x/edx: int <- copy 0
232     {
233       compare x, 0x400/screen-width=1024
234       break-if->=
235       pixel-on-real-screen x, y, 0/color=black
236       x <- increment
237       loop
238     }
239     y <- increment
240     loop
241   }
242 }
243 
244 fn screen-grapheme-at screen-on-stack: (addr screen), x: int, y: int -> _/eax: grapheme {
245   var screen-addr/esi: (addr screen) <- copy screen-on-stack
246   var idx/ecx: int <- screen-cell-index screen-addr, x, y
247   var result/eax: grapheme <- screen-grapheme-at-idx screen-addr, idx
248   return result
249 }
250 
251 fn screen-grapheme-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> _/eax: grapheme {
252   var screen-addr/esi: (addr screen) <- copy screen-on-stack
253   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
254   var data/eax: (addr array screen-cell) <- lookup *data-ah
255   var idx/ecx: int <- copy idx-on-stack
256   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
257   var cell/eax: (addr screen-cell) <- index data, offset
258   var src/eax: (addr grapheme) <- get cell, data
259   return *src
260 }
261 
262 fn screen-color-at screen-on-stack: (addr screen), x: int, y: int -> _/eax: int {
263   var screen-addr/esi: (addr screen) <- copy screen-on-stack
264   var idx/ecx: int <- screen-cell-index screen-addr, x, y
265   var result/eax: int <- screen-color-at-idx screen-addr, idx
266   return result
267 }
268 
269 fn screen-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> _/eax: int {
270   var screen-addr/esi: (addr screen) <- copy screen-on-stack
271   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
272   var data/eax: (addr array screen-cell) <- lookup *data-ah
273   var idx/ecx: int <- copy idx-on-stack
274   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
275   var cell/eax: (addr screen-cell) <- index data, offset
276   var src/eax: (addr int) <- get cell, color
277   var result/eax: int <- copy *src
278   return result
279 }
280 
281 fn screen-background-color-at screen-on-stack: (addr screen), x: int, y: int -> _/eax: int {
282   var screen-addr/esi: (addr screen) <- copy screen-on-stack
283   var idx/ecx: int <- screen-cell-index screen-addr, x, y
284   var result/eax: int <- screen-background-color-at-idx screen-addr, idx
285   return result
286 }
287 
288 fn screen-background-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> _/eax: int {
289   var screen-addr/esi: (addr screen) <- copy screen-on-stack
290   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
291   var data/eax: (addr array screen-cell) <- lookup *data-ah
292   var idx/ecx: int <- copy idx-on-stack
293   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
294   var cell/eax: (addr screen-cell) <- index data, offset
295   var src/eax: (addr int) <- get cell, background-color
296   var result/eax: int <- copy *src
297   return result
298 }