# Editor widget: takes a string and screen coordinates, modifying them in place. recipe main [ default-space:address:array:location <- new location:type, 30:literal switch-to-display width:number <- display-width height:number <- display-height divider:number, _ <- divide-with-remainder width:number, 2:literal draw-vertical 0:literal/screen, divider:number, 0:literal/top, height:number # shorten bottom border and darken to make it seem thinner border-left:number <- multiply divider:number, 0.2 border-right:number <- multiply divider:number, 0.8 draw-horizontal 0:literal/screen, 5:literal/row, border-left:number, border-right:number, 241:literal/grey in:address:array:character <- new [abcdef] edit in:address:array:character, 0:literal/screen, 0:literal/top, 0:literal/left, 5:literal/bottom, divider:number/right, 0:literal/keyboard wait-for-key-from-keyboard return-to-console ] scenario edit-prints-string-to-screen [ assume-screen 10:literal/width, 5:literal/height assume-keyboard [] run [ s:address:array:character <- new [abc] s:address:array:character, screen:address, keyboard:address <- edit s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/bottom, 5:literal/right, keyboard:address ] screen-should-contain [ .abc . . . ] ] recipe edit [ default-space:address:array:location <- new location:type, 30:literal s:address:array:character <- next-ingredient screen:address <- next-ingredient # no clipping of bounds top:number <- next-ingredient left:number <- next-ingredient bottom:number <- next-ingredient bottom:number <- subtract bottom:number, 1:literal right:number <- next-ingredient right:number <- subtract right:number, 1:literal keyboard:address <- next-ingredient screen:address <- render s:address:array:character, screen:address, top:number, left:number, bottom:number, right:number reply s:address:array:character/same-as-ingredient:0, screen:address/same-as-ingredient:1, keyboard:address/same-as-ingredient:6 ] recipe render [ default-space:address:array:location <- new location:type, 30:literal s:address:array:character <- next-ingredient screen:address <- next-ingredient top:number <- next-ingredient left:number <- next-ingredient bottom:number <- next-ingredient right:number <- next-ingredient # traversing inside s len:number <- length s:address:array:character/deref i:number <- copy 0:literal # traversing inside screen row:number <- copy top:number column:number <- copy left:number move-cursor screen:address, row:number, column:number { +next-character done?:boolean <- greater-or-equal i:number, len:number break-if done?:boolean off-screen?:boolean <- greater-than row:number, bottom:number break-if off-screen?:boolean c:character <- index s:address:array:character/deref, i:number { # newline? move to left rather than 0 newline?:boolean <- equal c:character, 10:literal/newline break-unless newline?:boolean row:number <- add row:number, 1:literal column:number <- copy left:number move-cursor screen:address, row:number, column:number i:number <- add i:number, 1:literal loop +next-character:label } { # at right? more than one letter left in the line? wrap at-right?:boolean <- equal column:number, right:number break-unless at-right?:boolean next-index:number <- add i:number, 1:literal next-at-end?:boolean <- greater-or-equal next-index:number, len:number break-if next-at-end?:boolean next:character <- index s:address:array:character/deref, next-index:number next-character-is-newline?:boolean <- equal next:character, 10:literal/newline break-if next-character-is-newline?:boolean # wrap print-character screen:address, 8617:literal/loop-back-to-left, 245:literal/grey column:number <- copy left:number row:number <- add row:number, 1:literal move-cursor screen:address, row:number, column:number # don't increment i loop +next-character:label } print-character screen:address, c:character i:number <- add i:number, 1:literal column:number <- add column:number, 1:literal loop } reply screen:address/same-as-ingredient:1 ] scenario edit-prints-multiple-lines [ assume-screen 5:literal/width, 3:literal/height assume-keyboard [] run [ s:address:array:character <- new [abc def] s:address:array:character, screen:address, keyboard:address <- edit s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/bottom, 5:literal/right, keyboard:address ] screen-should-contain [ .abc . .def . . . ] ] scenario edit-handles-offsets [ assume-screen 5:literal/width, 3:literal/height assume-keyboard [] run [ s:address:array:character <- new [abc] s:address:array:character, screen:address, keyboard:address <- edit s:address:array:character, screen:address, 0:literal/top, 1:literal/left, 10:literal/bottom, 5:literal/right, keyboard:address ] screen-should-contain [ . abc . . . . . ] ] scenario edit-prints-multiple-lines-at-offset [ assume-screen 5:literal/width, 3:literal/height assume-keyboard [] run [ s:address:array:character <- new [abc def] s:address:array:character, screen:address, keyboard:address <- edit s:address:array:character, screen:address, 0:literal/top, 1:literal/left, 10:literal/bottom, 5:literal/right, keyboard:address ] screen-should-contain [ . abc . . def . . . ] ] scenario edit-wraps-long-lines [ assume-screen 5:literal/width, 3:literal/height assume-keyboard [] run [ s:address:array:character <- new [abc def] s:address:array:character, screen:address, keyboard:address <- edit s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/bottom, 5:literal/right, keyboard:address ] screen-should-contain [ .abc ↩. .def . . . ] screen-should-contain-in-color, 245:literal/grey [ . ↩. . . . . ] ] recipe draw-box [ default-space:address:array:location <- new location:type, 30:literal screen:address <- next-ingredient top:number <- next-ingredient left:number <- next-ingredient bottom:number <- next-ingredient right:number <- next-ingredient color:number, color-found?:boolean <- next-ingredient { # default color to white break-if color-found?:boolean color:number <- copy 245:literal/grey } # top border draw-horizontal screen:address, top:number, left:number, right:number, color:number draw-horizontal screen:address, bottom:number, left:number, right:number, color:number draw-vertical screen:address, left:number, top:number, bottom:number, color:number draw-vertical screen:address, right:number, top:number, bottom:number, color:number draw-top-left screen:address, top:number, left:number, color:number draw-top-right screen:address, top:number, right:number, color:number draw-bottom-left screen:address, bottom:number, left:number, color:number draw-bottom-right screen:address, bottom:number, right:number, color:number # position cursor inside box move-cursor screen:address, top:number, left:number cursor-down screen:address cursor-right screen:address ] recipe draw-horizontal [ default-space:address:array:location <- new location:type, 30:literal screen:address <- next-ingredient row:number <- next-ingredient x:number <- next-ingredient right:number <- next-ingredient color:number, color-found?:boolean <- next-ingredient { # default color to white break-if color-found?:boolean color:number <- copy 245:literal/grey } move-cursor screen:address, row:number, x:number { continue?:boolean <- lesser-than x:number, right:number break-unless continue?:boolean print-character screen:address, 9472:literal/horizontal, color:number x:number <- add x:number, 1:literal loop } ] recipe draw-vertical [ default-space:address:array:location <- new location:type, 30:literal screen:address <- next-ingredient col:number <- next-ingredient x:number <- next-ingredient bottom:number <- next-ingredient color:number, color-found?:boolean <- next-ingredient { # default color to white break-if color-found?:boolean color:number <- copy 245:literal/grey } { continue?:boolean <- lesser-than x:number, bottom:number break-unless continue?:boolean move-cursor screen:address, x:number, col:number print-character screen:address, 9474:literal/vertical, color:number x:number <- add x:number, 1:literal loop } ] recipe draw-top-left [ default-space:address:array:location <- new location:type, 30:literal screen:address <- next-ingredient top:number <- next-ingredient left:number <- next-ingredient color:number, color-found?:boolean <- next-ingredient { # default color to white break-if color-found?:boolean color:number <- copy 245:literal/grey } move-cursor screen:address, top:number, left:number print-character screen:address, 9484:literal/down-right, color:number ] recipe draw-top-right [ default-space:address:array:location <- new location:type, 30:literal screen:address <- next-ingredient top:number <- next-ingredient right:number <- next-ingredient color:number, color-found?:boolean <- next-ingredient { # default color to white break-if color-found?:boolean color:number <- copy 245:literal/grey } move-cursor screen:address, top:number, right:number print-character screen:address, 9488:literal/down-left, color:number ] recipe draw-bottom-left [ default-space:address:array:location <- new location:type, 30:literal screen:address <- next-ingredient bottom:number <- next-ingredient left:number <- next-ingredient color:number, color-found?:boolean <- next-ingredient { # default color to white break-if color-found?:boolean color:number <- copy 245:literal/grey } move-cursor screen:address, bottom:number, left:number print-character screen:address, 9492:literal/up-right, color:number ] recipe draw-bottom-right [ default-space:address:array:location <- new location:type, 30:literal screen:address <- next-ingredient bottom:number <- next-ingredient right:number <- next-ingredient color:number, color-found?:boolean <- next-ingredient { # default color to white break-if color-found?:boolean color:number <- copy 245:literal/grey } move-cursor screen:address, bottom:number, right:number print-character screen:address, 9496:literal/up-left, color:number ]