├── K2.po ├── README.md ├── aboutk2 ├── fbtext.pla ├── images ├── intro.png ├── introbw.png └── styling.png ├── inc └── fbtext.plh ├── k2.pla └── util.pla /K2.po: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsheehan/K2/a6a50aa80c75d9644000114961d7ec62cd387a39/K2.po -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | K2 an Apple IIe/c Presentation Program 2 | ====================================== 3 | Quickstart 4 | ---------- 5 | Boot the K2.po disk and type `+k2 aboutk2` at the prompt. 6 | 7 | About 8 | ----- 9 | K2 can be used to give presentations or talks using slides containing optional titles and points. The Apple II 40 column text screen with mousetext characters are used for the display. 10 | 11 | The slides are contained in a single text file. The format of the text file is described below. 12 | 13 | What makes K2 unique is that it uses the coloured text screen made available on many Video7 RGB cards and their compatibles. This includes the A2DVI cards which have been around since 2024 and also the AppleWin emulator when in RGB card/monitor mode. See the [github page](https://github.com/ThorstenBr/A2DVI-Firmware) for the A2DVI firmware which provides this feature. 14 | 15 | ![image info](images/intro.png) 16 | Images are from the AppleWin emulator. 17 | 18 | Installing K2 19 | ------------- 20 | K2 is written in [David Schmenk's PLASMA](https://github.com/dschmenk/PLASMA) (which is wonderful). K2 has not been optimised, and parts could be rewritten in assembly to make the slide presentation faster. It works adequately at 1MHz but if you have an accelerator or emulator running faster than that I would recommend it. 21 | 22 | You can install PLASMA and compile the source in that environment if you desire. 23 | 24 | Alternatively there is a disk image file K2.po contained in this repository with the program files and the example [aboutk2](aboutk2) slideshow. This includes a recent version of a minimal PLASMA distribution to run K2. 25 | 26 | How to use K2 27 | ------------- 28 | 1. Prepare the slide file. Use any Apple II program which can save a text file. 29 | 2. At the command prompt type `+k2 slideFilename` 30 | 3. Move through the slides with the **spacebar** or right arrow key. (The **spacebar** is also used to show point by point - see below.) 31 | 4. Move backwards through the slides with the left arrow key. 32 | 5. Type "Q" to quit. 33 | 34 | The slides show arrows at bottom right and left hand corners indicating if there are next or previous slides. These arrows only become visible when all points in a slide are showing. 35 | 36 | Text slide files 37 | ---------------- 38 | A K2 slide file is vaguely WYSIWYG (in a similar way to Markdown). 39 | 40 | e.g. The source of the slide in the image above is: 41 | 42 | #----------------------------------- 43 | 44 | What is K2? 45 | 46 | #----------------------------------- 47 | 48 | + An Apple IIe/c presentation 49 | program 50 | 51 | + Can display *{MEDIUM_BLUE}multi**{GREEN}colou**{MAGENTA}red* text if 52 | an A2DVI or Video7 compatible 53 | board provides the output 54 | 55 | + Uses mousetext characters for the 56 | boxes, bullets and _underlining_ 57 | 58 | The best way to understand the source format is by looking at [aboutk2](aboutk2). 59 | 60 | List of Commands and Values 61 | --------------------------- 62 | Command lines are prefixed with a `#` as _the first character in the line_. 63 | The currently available commands are: 64 | 65 | #SLIDE:, #TITLE:, #-, #CONTENT:, #BULLET:, #BUILD: 66 | 67 | `#SLIDE:` is the whole slide command and is followed by a foreground and a background colour separated by a `/`; e.g. `white/purple`. This sets the foreground and background colours for all slides following this command. Slide colours usually only apply to the edges of the slide outside of any title and content areas. In particular this sets the colour of any mousetext boxes surrounding titles and contents. 68 | 69 | `#TITLE:` is the title command and can be followed by values in this order - justification, outline, colours. Justification is one of `left`, `centre` or `right` and indicates the placement of the title across the screen. Outline is either `line` or `none` and indicates whether the title should be surrounded by a mousetext outlined box or not. Colours are the foreground/background colours as in the `#SLIDE:` command, but these colours apply to the title area. 70 | 71 | The `#TITLE:` values stay in effect for all following titles until changed later in the source file. 72 | 73 | `#-` indicates the title area of a slide, everything after this on the same line is ignored. The next line starting with `#-` shows the end of the title area. 74 | 75 | Slide titles consist of **one line of text** but the box surrounding the title can contain any number of lines. The number of lines between matching `#-` commands is the number of lines K2 will include inside the title area. If there are additional lines of text between the `#-` commands those lines are ignored but do count towards the number of lines inside the text area. 76 | 77 | K2 counts the number of spaces on the line before the title text and duplicates this following the title text. e.g. If the title line is " Hi there" with two spaces before the "H" then the displayed title inside the title area will be surrounded by two spaces on the left and two spaces on the right. This is because the PLASMA +ed editor is the easiest way to prepare K2 slides and it does not save spaces at the right end of lines. 78 | 79 | `#CONTENT:` is the content command and accepts outline and colour values as in `#TITLE:`. This determines whether the content area will be inside a mousetext box or not and what foreground and background colours to use inside the content area. Like other commands it stays active until changed later in the source. 80 | 81 | `#BULLET:` is the bullet command and is followed by the name of the character to use as a bullet and the bullet's colour. Bullets are included in the content area whereever a `+` character appears. 82 | 83 | * the `+` and any other character can be escaped to see it in the usual way `\+` . 84 | 85 | Bullet character names are: `diamond`, `cross`, `openapple`, `closedapple`, `checkmark`, `ellipsis`, `hyphen` and `arrow`. 86 | 87 | `#BUILD:` is the build command and has two possible values: `all` and `point`. If `all` is chosen all following slides until the next `#BUILD` command will each be presented by showing all of their points without further user interaction. If `point` is chosen then K2 will pause before each point in the slide. The user must press the **spacebar** to show the next point. If there are no more points in the slide the **spacebar** will progress to the next slide. So **spacebar** works differently from the right arrow key, which always moves directly to the next slide. 88 | 89 | Formatting in the Content area 90 | ------------------------------ 91 | The content area can have additional formatting which applies to runs of text. 92 | 93 | * Underlining 94 | * an area can be underlined using the `_` character at the start and end of the underlined text. This only works if the following line in the content area is empty because it uses a mousetext character. 95 | 96 | * Different colours 97 | * The content text has a foreground colour determined by the `#CONTENT:` command, but this can be overridden over a string sequence by preceding the string with a colour value between `*{` and `}` as in `the word *{ORANGE}blue* will display in orange`. The colour applies until a terminating `*` or the end of the line. 98 | 99 | * Automatic substitutions 100 | * Three dots `...` in the content area gets turned into the ellipsis character. 101 | * `+` as the first non-blank character on a line gets turned into a bullet with the current mousetext character and colour. 102 | * `->` gets turned into the mousetext right arrow. 103 | * `<-` gets turned into the mousetext left arrow. 104 | 105 | ![image info](images/styling.png) 106 | 107 | Next slide 108 | ---------- 109 | Slides start with title areas. Title areas can be empty in which case the slide only shows a content area. Any use of a `#` command ends the current slide. 110 | 111 | 112 | Themes 113 | ------ 114 | As command values stay active until they are explicitly altered you can create themes by setting values at the start of the slideshow. 115 | 116 | 117 | Works in monochrome 118 | ------------------- 119 | If you don't have the means to see the coloured text, as shown above, the program still works and shows titles in inverse characters. It is not as pretty but it works and displays the mousetext characters and underlining. 120 | 121 | ![image info](images/introbw.png) 122 | 123 | 124 | Why K2? 125 | ------- 126 | I have always used Keynote for presentations on Macs and I thought it would be appropriate to have a simple presentation program for my Apple II machines. Hence the name. -------------------------------------------------------------------------------- /aboutk2: -------------------------------------------------------------------------------- 1 | #SLIDE: magenta/dark_blue 2 | #TITLE: centre, line, light_blue/black 3 | #CONTENT: line, white/black 4 | #BUILD: point 5 | #BULLET: diamond, orange 6 | #------------------------------------ 7 | 8 | What is K2? 9 | 10 | #------------------------------------ 11 | 12 | An Apple IIe/c presentation program 13 | 14 | *{GREY2}(Press space to show points) 15 | 16 | + Displays *{MEDIUM_BLUE}multi**{GREEN}colou**{MAGENTA}red* text if an 17 | A2DVI or Video7 compatible board 18 | provides the output 19 | 20 | + Uses mousetext characters for 21 | boxes, bullets and _underlining_ 22 | 23 | #BUILD: all 24 | #------------------------------------ 25 | 26 | User control 27 | 28 | #------------------------------------ 29 | 30 | + Right arrow "->" for next slide 31 | 32 | + Left arrow "<-" for previous 33 | slide 34 | 35 | + Spacebar will show successive 36 | points - if build by points 37 | enabled 38 | 39 | + If the slide is complete, spacebar 40 | moves to the next slide 41 | 42 | + Onscreen arrows indicate next and 43 | previous slides 44 | 45 | + "Q" to quit 46 | #TITLE: left, none 47 | #CONTENT: yellow/black 48 | #------------------------------------ 49 | 50 | Slide styling 51 | 52 | #------------------------------------ 53 | 54 | + Styling is determined by ... 55 | 56 | + commands, starting with *{WHITE}# 57 | 58 | + Commands include 59 | 60 | + Slide foreground and background 61 | colours e.g. *{white}#SLIDE: white/black 62 | 63 | + Title justification, border and 64 | colours *{white}#TITLE: centre, line, 65 | *{white}light\_blue/black 66 | 67 | + Content border and colours 68 | *{white}#CONTENT: none, grey1/grey2 69 | 70 | #BULLET: cross, magenta 71 | #------------------------------------ 72 | 73 | Commands 74 | 75 | #------------------------------------ 76 | 77 | + foreground/background colours 78 | any *{WHITE}combination* of *{WHITE}colours* (later) 79 | 80 | + applies to slide, title, content 81 | 82 | + title justification 83 | 84 | + *{WHITE}left, centre, right 85 | 86 | + title and contents outline 87 | 88 | + *{WHITE}line, none 89 | 90 | + bullet shapes and colours 91 | 92 | + e.g. *{WHITE}cross, magenta 93 | #BULLET: diamond, orange 94 | #BUILD: point 95 | #------------------------------------ 96 | 97 | Content Styling and Themes 98 | 99 | #------------------------------------ 100 | 101 | + The content can be styled with 102 | 103 | + *{purple}coloured* *{pink}text 104 | 105 | + _underlining_ 106 | 107 | + Bullets can be a variety of 108 | mousetext characters 109 | 110 | And in different colours 111 | 112 | + Content can be presented point by 113 | point or all at once using the 114 | *{white}#BUILD:* command 115 | 116 | + It is easy to have themes 117 | #BULLET: diamond, orange 118 | #BUILD: all 119 | #------------------------------------ 120 | 121 | Slide layout 122 | 123 | #------------------------------------ 124 | 125 | + Layout is determined by ... 126 | 127 | + spacing you include in the text 128 | 129 | + this way the text source 130 | document is largely WYSIWYG 131 | 132 | the next slide shows the _source_ of 133 | this one 134 | 135 | #SLIDE: white/black 136 | #CONTENT: none, white/black 137 | #------------------------------------ 138 | #------------------------------------ 139 | \#------------------------------------ 140 | 141 | Slide layout 142 | 143 | \#------------------------------------ 144 | 145 | \+ Layout is determined by \... 146 | 147 | \+ spacing you include in the text 148 | 149 | \+ this way the text source 150 | document is largely WYSIWYG 151 | 152 | the next slide shows the \_source\_ of 153 | this one 154 | 155 | #SLIDE: magenta/dark_blue 156 | #------------------------------------ 157 | 158 | Available Colours 159 | 160 | #------------------------------------ 161 | 162 | *{magenta}Magenta 163 | *{dark_blue}Dark blue 164 | *{purple}Purple 165 | *{dark_green}Dark green 166 | *{grey1}Grey1 167 | *{medium_blue}Medium blue 168 | *{light_blue}Light blue 169 | *{brown}Brown 170 | *{orange}Orange 171 | *{grey2}Grey2 172 | *{pink}Pink 173 | *{green}Green 174 | *{yellow}Yellow 175 | *{aquamarine}Aquamarine 176 | *{white}White (and of course black) 177 | 178 | #CONTENT: line 179 | #------------------------------------ 180 | #------------------------------------ 181 | 182 | + You can have slides without titles 183 | if you need more room for your 184 | points. 185 | 186 | #CONTENT: none, yellow/dark_blue 187 | #------------------------------------ 188 | #------------------------------------ 189 | This is a slide with no box around the 190 | content. 191 | 192 | 1234567890123456789012345678901234567890 193 | 194 | As you can see this gives you an area 195 | of 40x23 for your text. 196 | 197 | 9 198 | 10 199 | 11 200 | 12 201 | 13 202 | 14 203 | 15 204 | 16 205 | 17 206 | 18 207 | 19 208 | 20 209 | 21 210 | 22 211 | 23 212 | #CONTENT: line, white/black 213 | #BUILD: point 214 | #------------------------------------ 215 | 216 | K2 Limitations 217 | 218 | #------------------------------------ 219 | 220 | + The box *{GREEN}colours* are determined by 221 | the initial *{MAGENTA}foreground* and 222 | *{DARK_BLUE}background* colours ... 223 | 224 | + so the title and content box 225 | outlines are the same *{GREY2}(as here) 226 | 227 | + Underlines are only allowed if the 228 | following line is _*{AQUAMARINE}empty*_ at that 229 | location. 230 | 231 | + Underlines are in the colour of 232 | the content foreground even if the 233 | underlined text is a _*{YELLOW}different_ 234 | colour. 235 | #------------------------------------ 236 | #------------------------------------ 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | *{ORANGE}THE END 247 | -------------------------------------------------------------------------------- /fbtext.pla: -------------------------------------------------------------------------------- 1 | include "inc/cmdsys.plh" 2 | include "inc/conio.plh" 3 | include "inc/fbtext.plh" // so we have access to the constants 4 | 5 | import util 6 | predef byteCmp(data, upper, size)#1 7 | end 8 | 9 | const AN3OFF = $C05E // R/W 10 | const AN3ON = $C05F // R/W 11 | const TEXTOFF = $C050 // R/W 12 | const TEXTON = $C051 // R/W 13 | const COL80ON = $C00D // W 14 | const COL80OFF = $C00C // W 15 | const ST80ON = $C001 // W 16 | const ST80OFF = $C000 // W 17 | const COLOURSTORE = $C055 // R/W PAGE2 On - actually 1x if 80Store on 18 | const TEXTSTORE = $C054 // R/W PAGE1 19 | 20 | const WNDLFT = $20 21 | const WNDWIDTH = $21 22 | const WNDTOP = $22 23 | const CURSH = $24 // N.B. horizontal cursor relative to viewport 24 | const CURSV = $25 // vertical cursor relative to screen 25 | 26 | word txt1scrn[] = $0400,$0480,$0500,$0580,$0600,$0680,$0700,$0780 27 | word = $0428,$04A8,$0528,$05A8,$0628,$06A8,$0728,$07A8 28 | word = $0450,$04D0,$0550,$05D0,$0650,$06D0,$0750,$07D0 29 | 30 | export byte[] colours 31 | byte = "BLACK" 32 | byte = "MAGENTA" 33 | byte = "DARK_BLUE" 34 | byte = "PURPLE" 35 | byte = "DARK_GREEN" 36 | byte = "GREY1" 37 | byte = "MEDIUM_BLUE" 38 | byte = "LIGHT_BLUE" 39 | byte = "BROWN" 40 | byte = "ORANGE" 41 | byte = "GREY2" 42 | byte = "PINK" 43 | byte = "GREEN" 44 | byte = "YELLOW" 45 | byte = "AQUAMARINE" 46 | byte = "WHITE" 47 | 48 | export byte bulletColour = ORANGE 49 | export byte bulletChar = '[' // the diamond 50 | 51 | def extendedVideoOn#0 52 | call($C300, $A0, 0, 0, 0) 53 | putc($11) 54 | end 55 | 56 | def extendedVideoOff#0 57 | putc($15) 58 | end 59 | 60 | // Only works with extended video on. 61 | export def mouseTextOn#0 62 | putc($0F) 63 | putc($1B) 64 | end 65 | 66 | export def mouseTextOff#0 67 | putc($18) 68 | putc($0E) 69 | end 70 | 71 | def fullscreen#0 72 | conio:viewport(0, 0, 40, 24) 73 | end 74 | 75 | // Create a viewport with specified colours. 76 | // Doesn't clear the viewport. Goes to top,left. 77 | export def fbViewport(left, top, width, height, fg, bg)#0 78 | word x, y 79 | word first, last, right 80 | byte colourByte 81 | word colourWord 82 | 83 | colourByte = fg << 4 | bg 84 | conio:viewport(left, top, width, height) 85 | colourWord = colourByte << 8 | colourByte 86 | if left % 2 87 | first = left 88 | else 89 | first = 0 90 | fin 91 | if (left + width) % 2 92 | last = left + width - 1 93 | else 94 | last = 0 95 | fin 96 | right = left + width - 1 97 | left = (left + 1) / 2 98 | right = (right - 1) / 2 99 | ^COLOURSTORE 100 | for y = top to top + height - 1 101 | if first 102 | txt1scrn.[y, first] = colourByte 103 | fin 104 | for x = left to right 105 | txt1scrn:[y, x] = colourWord 106 | next 107 | if last 108 | txt1scrn.[y, last] = colourByte 109 | fin 110 | next 111 | ^TEXTSTORE 112 | end 113 | 114 | export def pokeChar(ch, pos, bg)#0 115 | byte colour 116 | 117 | colour = GREY1 118 | if bg == GREY1 119 | colour = GREY2 120 | fin 121 | ^COLOURSTORE 122 | ^pos = colour << 4 | bg 123 | ^TEXTSTORE 124 | ^pos = ch 125 | end 126 | 127 | // Initialise foreground/background text. 128 | // Clears the whole screen. 129 | export def fbInit(fg, bg)#0 130 | extendedVideoOn 131 | ^AN3OFF 132 | ^TEXTON 133 | ^COL80OFF = $00 134 | ^ST80ON = $00 135 | // fill in display with starting colours 136 | fbViewport(0, 0, 40, 24, fg, bg) 137 | end 138 | 139 | // Turn off foreground/background text. 140 | // Clears screen. 141 | // Currently doesn't turn off extended video. 142 | export def fbOff#0 143 | ^ST80OFF = $00 144 | ^AN3ON 145 | fullscreen 146 | conio:clear(cls) 147 | //extendedVideoOff - if not executed stays extended 148 | end 149 | 150 | // Print a caracter in the given foreground/background colour. 151 | export def fbPutc(c, fg, bg)#0 152 | word x, y 153 | byte colourByte // FFFF BBBB foreground background colours 154 | 155 | colourByte = fg << 4 | bg 156 | x = ^WNDLFT + ^CURSH 157 | y = ^CURSV 158 | ^COLOURSTORE 159 | txt1scrn.[y, x] = colourByte 160 | ^TEXTSTORE 161 | putc(c) 162 | end 163 | 164 | // Print a string in the given foreground/background colour. 165 | // Colours don't scroll if it gets to the end of the screen. 166 | export def fbPuts(str, fg, bg)#0 167 | word x, y 168 | word length 169 | byte colourByte // FFFF BBBB foreground background colours 170 | 171 | // first store the colour information 172 | colourByte = fg << 4 | bg 173 | length = ^str 174 | x = ^WNDLFT + ^CURSH 175 | y = ^CURSV 176 | ^COLOURSTORE 177 | while length > 0 178 | txt1scrn.[y, x] = colourByte 179 | x++ 180 | if x > 39 181 | x = 0 182 | y++ 183 | fin 184 | length-- 185 | loop 186 | // then print the string 187 | ^TEXTSTORE 188 | puts(str) 189 | end 190 | 191 | // Draw a box outline. 192 | // Dimensions are screen dimensions of the outline. 193 | // Take into account top and height <= 22 194 | export def fbBox(left, top, width, height)#0 195 | byte x, y 196 | 197 | fullscreen 198 | conio:gotoxy(left + 1, top) 199 | for x = 1 to width 200 | putc('_') 201 | next 202 | mouseTextOn 203 | for y = 1 to height 204 | conio:gotoxy(left, top + y) 205 | putc('Z') 206 | conio:gotoxy(left + width + 1, top + y) 207 | putc('_') 208 | next 209 | conio:gotoxy(left + 1, top + height + 1) 210 | for x = 1 to width 211 | putc('L') 212 | next 213 | mouseTextOff 214 | end 215 | 216 | // Draws a horizontal line of upper and lower edges 217 | // to enable boxes to be separated by only one line. 218 | export def fbTrack(left, top, width)#0 219 | byte x 220 | 221 | fullscreen 222 | conio:gotoxy(left, top) 223 | mouseTextOn 224 | for x = 1 to width 225 | putc('\\') 226 | next 227 | mouseTextOff 228 | end 229 | 230 | // Checks next bytes to see if they are a colour. 231 | // Returns colour value (or -1) and pos pointer. 232 | // pos pointer unchanged if no colour match. 233 | def colourValue(pos)#2 234 | byte colour 235 | word colourPos 236 | word wordPos 237 | byte c 238 | byte length 239 | word i 240 | 241 | if ^pos <> '{' 242 | return 16, 0 // no colour, no jump 243 | fin 244 | wordPos = pos + 1 // back to the start 245 | colourPos = @colours 246 | for colour = 0 to 15 247 | length = ^colourPos 248 | colourPos++ 249 | if byteCmp(wordPos, colourPos, length) 250 | break 251 | fin 252 | colourPos = colourPos + length 253 | next 254 | if ^(pos + 1 + length) <> '}' 255 | return 16, 0 // no colour, no jump 256 | fin 257 | return colour, length + 2 258 | end 259 | 260 | def ellipsis(pos)#1 261 | return ^pos == '.' and ^(pos + 1) == '.' 262 | end 263 | 264 | // Prints the character in the specified foreground colour. 265 | // The background stays as it was. 266 | def putcFg(c, fg)#0 267 | byte x, y 268 | byte extract 269 | 270 | x = ^WNDLFT + ^CURSH 271 | y = ^CURSV 272 | ^COLOURSTORE 273 | extract = txt1scrn.[y, x] 274 | txt1scrn.[y, x] = fg << 4 | (extract & $0F) 275 | ^TEXTSTORE 276 | putc(c) 277 | end 278 | 279 | def putFormattedCharacter(c, x, y, colour, under)#0 280 | if colour < 16 281 | putcFg(c, colour) 282 | else 283 | putc(c) 284 | fin 285 | if under 286 | conio:gotoxy(x, y + 1) 287 | mouseTextOn 288 | putc('L') 289 | mouseTextOff 290 | conio:gotoxy(x + 1, y) 291 | fin 292 | end 293 | 294 | export def fbBullet(c, colour)#0 295 | bulletChar = c 296 | bulletColour = colour 297 | end 298 | 299 | // Prints a string taking formatting into account. 300 | export def fbPutf(string)#0 301 | byte x, y 302 | byte i, c 303 | byte jump 304 | byte under 305 | byte colour 306 | 307 | // puts(" ") // always indented 308 | 309 | x = ^CURSH 310 | y = ^CURSV - ^WNDTOP 311 | 312 | under = FALSE 313 | colour = 16 // if < 16 this is an override colour 314 | 315 | i = 1 316 | while i <= ^string // and ^CURSH < ^WNDWIDTH - 1 // chops off at edge 317 | c = ^(string + i) 318 | when c 319 | is '\\' 320 | i++ 321 | if i <= ^string 322 | c = ^(string + i) 323 | putFormattedCharacter(c, x, y, colour, under) 324 | x++ 325 | fin 326 | break 327 | is '.' 328 | if ellipsis(string + i + 1) 329 | mouseTextOn 330 | putFormattedCharacter('I', x, y, colour, under) 331 | mouseTextOff 332 | i = i + 2 333 | else 334 | putFormattedCharacter('.', x, y, colour, under) 335 | fin 336 | x++ 337 | break 338 | is '+' 339 | mouseTextOn 340 | putFormattedCharacter(bulletChar, x, y, bulletColour, under) 341 | mouseTextOff 342 | x++ 343 | break 344 | is '_' 345 | under = not under 346 | break 347 | is '*' 348 | if colour < 16 349 | colour = 16 // foreground colour off 350 | else 351 | colour, jump = colourValue(string + i + 1) 352 | if colour < 16 353 | i = i + jump 354 | else 355 | x++ 356 | fin 357 | fin 358 | break 359 | is '-' 360 | if ^(string + i + 1) == '>' 361 | mouseTextOn 362 | putFormattedCharacter('U', x, y, colour, under) 363 | mouseTextOff 364 | i++ 365 | else 366 | putFormattedCharacter('-', x, y, colour, under) 367 | fin 368 | break 369 | is '<' 370 | if ^(string + i + 1) == '-' 371 | mouseTextOn 372 | putFormattedCharacter('H', x, y, colour, under) 373 | mouseTextOff 374 | i++ 375 | else 376 | putFormattedCharacter('<', x, y, colour, under) 377 | fin 378 | break 379 | is $0D 380 | if x == ^WNDWIDTH 381 | break 382 | fin 383 | otherwise 384 | putFormattedCharacter(c, x, y, colour, under) 385 | x++ 386 | wend 387 | i++ 388 | loop 389 | end 390 | 391 | // Draws a coloured area around a string/title. 392 | // To enable title areas to stand out on monocrome screens 393 | // they are inverted. This means that fg and bg are backwards. 394 | // Height is expected to be an odd number. 395 | // Title text cannot use formatting 396 | export def fbTitleArea(title, left, top, height, fg, bg)#0 397 | byte x, y 398 | 399 | fbViewport(left, top, ^title, height, bg, fg) 400 | putc($0F) // inverse 401 | conio:clear(cls) 402 | conio:gotoxy(0, height / 2) 403 | if height < 3 // == 1 404 | ^title-- // don't print last char 405 | puts(title) 406 | ^title++ // restore title length 407 | // Poke the last character in, to prevent scrolling 408 | x = ^WNDLFT + ^title - 1 409 | y = ^CURSV 410 | txt1scrn.[y, x] = ^(title + ^title) // inverted char 411 | else 412 | puts(title) 413 | fin 414 | putc($0E) 415 | end 416 | 417 | done -------------------------------------------------------------------------------- /images/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsheehan/K2/a6a50aa80c75d9644000114961d7ec62cd387a39/images/intro.png -------------------------------------------------------------------------------- /images/introbw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsheehan/K2/a6a50aa80c75d9644000114961d7ec62cd387a39/images/introbw.png -------------------------------------------------------------------------------- /images/styling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsheehan/K2/a6a50aa80c75d9644000114961d7ec62cd387a39/images/styling.png -------------------------------------------------------------------------------- /inc/fbtext.plh: -------------------------------------------------------------------------------- 1 | // fbtext.plh 2 | 3 | import fbtext 4 | // 5 | // Colours 6 | // 7 | const BLACK = 0 8 | const MAGENTA = 1 9 | const DARK_BLUE = 2 10 | const PURPLE = 3 11 | const DARK_GREEN = 4 12 | const GREY1 = 5 13 | const MEDIUM_BLUE = 6 14 | const LIGHT_BLUE = 7 15 | const BROWN = 8 16 | const ORANGE = 9 17 | const GREY2 = 10 18 | const PINK = 11 19 | const GREEN = 12 20 | const YELLOW = 13 21 | const AQUAMARINE = 14 22 | const WHITE = 15 23 | // 24 | // Box types 25 | // 26 | const NONEB = 0 27 | const LINEB = 1 28 | 29 | byte[] colours 30 | 31 | byte bulletChar 32 | byte bulletColour 33 | 34 | // 35 | // API 36 | // 37 | predef fbInit(fg, bg)#0 38 | predef fbOff#0 39 | predef fbPutc(c, fg, bg)#0 40 | predef fbPuts(text, fg, bg)#0 41 | predef fbViewport(left, top, width, height, fg, bg)#0 42 | predef fbBox(left, top, width, height)#0 43 | predef fbTitleArea(title, left, top, height, fg, bg)#0 44 | predef fbTrack(left, top, width)#0 45 | predef mouseTextOn#0, mouseTextOff#0 46 | predef fbPutf(string)#0 47 | predef fbBullet(c, colour)#0 48 | predef pokeChar(ch, pos, bg)#0 49 | end 50 | -------------------------------------------------------------------------------- /k2.pla: -------------------------------------------------------------------------------- 1 | include "inc/cmdsys.plh" 2 | include "inc/fileio.plh" 3 | include "inc/fbtext.plh" 4 | include "inc/conio.plh" 5 | include "inc/args.plh" 6 | 7 | sysflags restxt1|resxtxt1 8 | 9 | import util 10 | predef upperCase(c)#1 11 | predef byteCmp(data, upper, size)#1 12 | end 13 | 14 | predef slide(linePos)#0 15 | predef titleBox(linePos)#0 16 | predef contentBox(linePos)#0 17 | predef build(linePos)#0 18 | predef bullet(linePos)#0 19 | 20 | const BOT_LEFT = $07D0 // screen address of bottom,left char 21 | const BOT_RIGHT = $07F7 // screen address of bottom,right char 22 | 23 | const NUM_COMS = 5 24 | const NUM_COLOURS = 16 25 | const NUM_BOXTYPE = 2 26 | const NUM_BUILD = 2 27 | const NUM_JUST = 3 28 | const NUM_BULLET = 8 29 | 30 | struc t_slide 31 | byte sForeground 32 | byte sBackground 33 | end 34 | 35 | byte[t_slide] slideInfo = WHITE, BLACK 36 | byte slideBuild = 0 // all at once 37 | 38 | struc t_content 39 | byte cBoxOutline 40 | byte cForeground 41 | byte cBackground 42 | end 43 | 44 | byte[t_content] contentInfo = 0, WHITE, BLACK // line 45 | 46 | struc t_title 47 | byte tBoxOutline 48 | byte tForeground 49 | byte tBackground 50 | byte tLeft // gets filled in when justified 51 | byte tJust // justification: left, centre, right 52 | byte tHeight // title height (inside, not outline) 53 | byte[41] tText // title string 54 | end 55 | 56 | byte[t_title] titleInfo = 0, WHITE, BLACK, 0, 1, 1, 0 57 | // box?, fg, bg, left, just, height, no-string 58 | 59 | byte[] commandText 60 | byte = "SLIDE" // 0 61 | byte = "TITLE" // 1 62 | byte = "CONTENT" // 2 63 | byte = "BUILD" // 3 64 | byte = "BULLET" // 4 65 | 66 | byte[] boxType 67 | byte = "NONE" // 0 68 | byte = "LINE" // 1 69 | 70 | byte[] buildup 71 | byte = "ALL" // 0 72 | byte = "POINT" // 1 73 | 74 | byte[] justification 75 | byte = "LEFT" // 0 76 | byte = "CENTRE" // 1 77 | byte = "RIGHT" // 2 78 | 79 | byte[] bulletNames 80 | byte = "DIAMOND" // 0 81 | byte = "CROSS" // 1 82 | byte = "OPENAPPLE" // 2 83 | byte = "CLOSEDAPPLE" // 3 84 | byte = "CHECKMARK" // 4 85 | byte = "ELLIPSIS" // 5 86 | byte = "HYPHEN" // 6 87 | byte = "ARROW" // 7 88 | 89 | byte[] bulletChars = '[', ']', 'A', '@', 'D', 'I', 'S', 'U' 90 | 91 | word k2commands[] = @slide, @titleBox, @contentBox, @build, @bullet 92 | 93 | struc slideP 94 | byte slideFG 95 | byte slideBG 96 | byte slideTJust 97 | byte slideTBox 98 | byte slideTFG 99 | byte slideTBG 100 | byte slideCBox 101 | byte slideCFG 102 | byte slideCBG 103 | byte slideBld 104 | byte slideBChar 105 | byte slideBCol 106 | end 107 | 108 | word file 109 | word[100] slidePositions 110 | byte[slideP] slideParams[100] 111 | 112 | const ST_START = 0 113 | const ST_TITLE = 1 114 | const ST_CONTENT = 2 115 | const ST_NEWSLIDE = 3 116 | 117 | // 118 | // Justification 119 | // 120 | const LEFTJ = 0 121 | const CENTREJ = 1 122 | const RIGHTJ = 2 123 | 124 | // Returns the left position where a justified text 125 | // area of width will start. 126 | // Takes into account whether there is a surrounding 127 | // box. 128 | // Remember that a box around the text will 129 | // take up one extra space on each side. 130 | // just: the type of justification 131 | // width: the width of the text 132 | // box: whether the text will be enclosed by a box 133 | def justifiedStart(just, width, box)#1 134 | byte left 135 | 136 | when just 137 | is LEFTJ 138 | left = box ?? 1 :: 0 139 | break 140 | is CENTREJ 141 | left = (40 - width) / 2 142 | break 143 | is RIGHTJ 144 | left = box ?? 39 - width :: 40 - width 145 | wend 146 | return left 147 | end 148 | 149 | // move over characters which should be ignored 150 | def ignore(linePos, ignored)#1 // ignored is a string of chars 151 | byte i 152 | 153 | repeat 154 | for i = 1 to ^ignored 155 | if ^linePos == ^(ignored + i) // found an ignored char 156 | linePos++ 157 | break 158 | fin 159 | next 160 | until i > ^ignored 161 | return linePos 162 | end 163 | 164 | // return option number and new position 165 | def optionHere(linePos, optionPos, numOptions)#2 166 | byte length 167 | byte option 168 | 169 | for option = 0 to numOptions - 1 170 | length = ^optionPos 171 | optionPos++ 172 | if byteCmp(linePos, optionPos, length) 173 | break 174 | fin 175 | optionPos = optionPos + length 176 | length = 0 // return linePos as was if not found 177 | next 178 | return option, linePos + length 179 | end 180 | 181 | // Get any foreground/background colours. 182 | def getFgBg(linePos)#3 183 | byte fgColour 184 | byte bgColour 185 | 186 | fgColour, linePos = optionHere(linePos, @colours, NUM_COLOURS) 187 | if ^linePos == '/' 188 | linePos++ 189 | fin 190 | bgColour, linePos = optionHere(linePos, @colours, NUM_COLOURS) 191 | return fgColour, bgColour, linePos 192 | end 193 | 194 | // Get any box outline type. 195 | def getLineFG(linePos)#4 196 | byte outline 197 | byte fgColour 198 | byte bgColour 199 | 200 | outline, linePos = optionHere(linePos, @boxType, NUM_BOXTYPE) 201 | linePos = ignore(linePos, ", ") 202 | fgColour, bgColour = getFgBg(linePos) 203 | return outline, fgColour, bgColour, linePos 204 | end 205 | 206 | // Deal with slide info 207 | def slide(linePos)#0 208 | byte fgColour 209 | byte bgColour 210 | 211 | linePos = ignore(linePos, " ") 212 | fgColour, bgColour, linePos = getFgBg(linePos) 213 | 214 | if fgColour < NUM_COLOURS 215 | slideInfo[sForeground] = fgColour 216 | fin 217 | if bgColour < NUM_COLOURS 218 | slideInfo[sBackground] = bgColour 219 | fin 220 | end 221 | 222 | def titleBox(linePos)#0 223 | byte just 224 | byte outline 225 | byte fgColour 226 | byte bgColour 227 | 228 | // check for justification, line, fg/bg 229 | linePos = ignore(linePos, " ") 230 | just, linePos = optionHere(linePos, @justification, NUM_JUST) 231 | if just < NUM_JUST 232 | titleInfo[tJust] = just 233 | fin 234 | linePos = ignore(linePos, ", ") 235 | outline, fgColour, bgColour, linePos = getLineFG(linePos) 236 | 237 | if outline < NUM_BOXTYPE 238 | titleInfo[tBoxOutline] = outline 239 | fin 240 | if fgColour < NUM_COLOURS 241 | titleInfo[tForeground] = fgColour 242 | fin 243 | if bgColour < NUM_COLOURS 244 | titleInfo[tBackground] = bgColour 245 | fin 246 | end 247 | 248 | def contentBox(linePos)#0 249 | byte outline 250 | byte fgColour 251 | byte bgColour 252 | 253 | // check for line, fg/bg 254 | linePos = ignore(linePos, " ") 255 | outline, fgColour, bgColour, linePos = getLineFG(linePos) 256 | 257 | if outline < NUM_BOXTYPE 258 | contentInfo[cBoxOutline] = outline 259 | fin 260 | if fgColour < NUM_COLOURS 261 | contentInfo[cForeground] = fgColour 262 | fin 263 | if bgColour < NUM_COLOURS 264 | contentInfo[cBackground] = bgColour 265 | fin 266 | end 267 | 268 | def build(linePos)#0 269 | byte buildType 270 | 271 | linePos = ignore(linePos, " ") 272 | buildType, linePos = optionHere(linePos, @buildup, NUM_BUILD) 273 | if buildType < NUM_BUILD 274 | slideBuild = buildType 275 | fin 276 | end 277 | 278 | def bullet(linePos)#0 279 | byte bulletType 280 | byte bulletColour 281 | 282 | linePos = ignore(linePos, " ") 283 | bulletType, linePos = optionHere(linePos, @bulletNames, NUM_BULLET) 284 | linePos = ignore(linePos, ", ") 285 | // also a single colour 286 | bulletColour, linePos = optionHere(linePos, @colours, NUM_COLOURS) 287 | if bulletType < NUM_BULLET and bulletColour < NUM_COLOURS 288 | fbBullet(bulletChars[bulletType], bulletColour) 289 | fin 290 | end 291 | 292 | def displayTitle()#1 293 | byte screenLine 294 | 295 | screenLine = 0 296 | if titleInfo[tText] 297 | titleInfo[tleft] = justifiedStart(titleInfo[tJust], titleInfo[tText], titleInfo[tBoxOutline]) 298 | if titleInfo[tBoxOutline] 299 | fbBox(titleInfo[tleft] - titleInfo[tBoxOutline], screenLine, titleInfo[tText], titleInfo[tHeight]) 300 | fin 301 | screenLine++ 302 | fbTitleArea(@titleInfo[tText], titleInfo[tleft], screenLine, titleInfo[tHeight], titleInfo[tForeground], titleInfo[tBackground]) 303 | screenLine = screenLine + titleInfo[tHeight] 304 | fin 305 | return screenLine 306 | end 307 | 308 | def prepareContent(screenLine)#0 309 | byte contentHeight 310 | 311 | if titleInfo[tText] and not contentInfo[cBoxOutline] 312 | screenLine++ 313 | contentHeight = 24 - screenLine - 1 314 | else 315 | contentHeight = 24 - screenLine 316 | fin 317 | if contentInfo[cBoxOutline] 318 | contentHeight = contentHeight - 2 319 | fbBox(0, screenLine, 38, contentHeight) 320 | if titleInfo[tBoxOutline] 321 | fbTrack(titleInfo[tleft], screenLine, titleInfo[tText]) 322 | fin 323 | screenLine++ 324 | fin 325 | fbViewport(contentInfo[cBoxOutline], screenLine, 40 - 2 * contentInfo[cBoxOutline], contentHeight, contentInfo[cForeground], contentInfo[cBackground]) 326 | end 327 | 328 | def parseCommand(linePos)#1 329 | word comPos 330 | byte comLen 331 | byte com 332 | 333 | com, linePos = optionHere(linePos, @commandText, NUM_COMS) 334 | linePos = ignore(linePos, ": ,") 335 | 336 | if com < NUM_COMS 337 | k2commands[com](linePos)#0 338 | fin 339 | return com < NUM_COMS 340 | end 341 | 342 | // fills in title text if non-space characters on the line 343 | // Now puts the same number of spaces on the right as on the left 344 | // to help when using +ed editor. 345 | def scanForTitle(lineStr)#0 346 | byte c 347 | byte spaces 348 | 349 | spaces = 0 350 | ^lineStr-- // last char '\n' 351 | for c = 1 to ^lineStr 352 | if ^(lineStr + c) <> ' ' 353 | memset(@titleInfo + tText + 1, ' ', 40) 354 | strcpy(@titleInfo + tText, lineStr) 355 | break 356 | else 357 | spaces++ 358 | fin 359 | next 360 | titleInfo[tText] = titleInfo[tText] + spaces 361 | end 362 | 363 | def parseLine(lineStr, state, slide)#1 364 | byte nextChar 365 | 366 | when ^(lineStr + 1) 367 | is '#' 368 | if state == ST_CONTENT 369 | state = ST_NEWSLIDE 370 | return state 371 | fin 372 | nextChar = ^(lineStr + 2) // over length and '#' 373 | if parseCommand(lineStr + 2) 374 | state = ST_START 375 | else 376 | if nextChar == '-' // either side of title area 377 | when state 378 | is ST_START // start of title area 379 | state = ST_TITLE 380 | titleInfo[tHeight] = 0 381 | titleInfo[tText] = 0 // length byte 382 | // start of a new slide 383 | fbInit(slideInfo[sForeground], slideInfo[sBackground]) 384 | break 385 | is ST_TITLE // finished title area 386 | state = ST_CONTENT 387 | prepareContent(displayTitle()) // start showing slide 388 | break 389 | wend 390 | fin 391 | fin 392 | break 393 | is '\n' 394 | when state 395 | is ST_TITLE 396 | titleInfo[tHeight]++ 397 | break 398 | is ST_CONTENT 399 | putln 400 | wend 401 | break 402 | otherwise 403 | if state == ST_TITLE 404 | titleInfo[tHeight]++ 405 | if titleInfo[tText] == 0 // no title text so far 406 | scanForTitle(lineStr) 407 | fin 408 | else 409 | fbPutf(lineStr) // formatted lines added to slide 410 | fin 411 | wend 412 | return state 413 | end 414 | 415 | def userCommand()#1 416 | return upperCase(conio:getKey()) 417 | end 418 | 419 | def storeSlideParameters(slide)#0 420 | word this 421 | 422 | this = @slideParams + slide * slideP // @slideParams[slide] 423 | this->slideFG = slideInfo[sForeground] 424 | this->slideBG = slideInfo[sBackground] 425 | this->slideTJust = titleInfo[tJust] 426 | this->slideTBox = titleInfo[tBoxOutline] 427 | this->slideTFG = titleInfo[tForeground] 428 | this->slideTBG = titleInfo[tBackground] 429 | this->slideCBox = contentInfo[cBoxOutline] 430 | this->slideCFG = contentInfo[cForeground] 431 | this->slideCBG = contentInfo[cBackground] 432 | this->slideBld = slideBuild 433 | this->slideBChar = bulletChar 434 | this->slideBCol = bulletColour 435 | end 436 | 437 | def restoreSlideParameters(slide)#0 438 | word this 439 | 440 | this = @slideParams + slide * slideP // @slideParams[slide] 441 | slideInfo[sForeground] = this->slideFG 442 | slideInfo[sBackground] = this->slideBG 443 | titleInfo[tJust] = this->slideTJust 444 | titleInfo[tBoxOutline] = this->slideTBox 445 | titleInfo[tForeground] = this->slideTFG 446 | titleInfo[tBackground] = this->slideTBG 447 | contentInfo[cBoxOutline] = this->slideCBox 448 | contentInfo[cForeground] = this->slideCFG 449 | contentInfo[cBackground] = this->slideCBG 450 | slideBuild = this->slideBld 451 | bulletChar = this->slideBChar 452 | bulletColour = this->slideBCol 453 | end 454 | 455 | const CONTSLIDE = 0 456 | const QUITSLIDE = 'Q' 457 | const PREVSLIDE = $08 // left arrow 458 | const NEXTSLIDE = $15 // right arrow 459 | 460 | def commandInsideSlide(line)#1 // will handle moving when building slide by points 461 | word linePos 462 | linePos = ignore(line + 1, " ") 463 | if ^linePos == '+' // found a new point 464 | // wait here until the user does something 465 | return userCommand() 466 | fin 467 | return CONTSLIDE 468 | end 469 | 470 | def readUntilNextSlide(refnum)#1 // return TRUE if no next slide 471 | byte[81] line 472 | word lineLo 473 | byte lineHi 474 | 475 | repeat 476 | lineLo, lineHi = fileio:getMark(refnum)#2 477 | line = fileio:read(refnum, @line + 1, 80) 478 | if line <> 0 and ^(@line + 1) == '#' 479 | fileio:setMark(refnum, lineLo, lineHi) 480 | return FALSE 481 | fin 482 | until line == 0 483 | return TRUE 484 | end 485 | 486 | def showSlide(refnum, slide)#2 487 | byte[81] line // should be big enough 488 | byte state 489 | byte slideLeave // leaving this slide early 490 | word lineLo 491 | byte lineHi 492 | 493 | state = ST_START 494 | if slidePositions[slide] <> -1 495 | restoreSlideParameters(slide) 496 | fileio:setMark(refnum, slidePositions[slide], 0) 497 | else 498 | lineLo, lineHi = fileio:getMark(refnum)#2 499 | slidePositions[slide] = lineLo 500 | storeSlideParameters(slide) 501 | fin 502 | repeat 503 | lineLo, lineHi = fileio:getMark(refnum)#2 504 | line = fileio:read(refnum, @line + 1, 80) 505 | if line 506 | 507 | if state == ST_CONTENT and slideBuild 508 | slideLeave = commandInsideSlide(@line) 509 | when slideLeave 510 | is QUITSLIDE 511 | return QUITSLIDE, TRUE 512 | is NEXTSLIDE 513 | state = readUntilNextSlide(refnum) 514 | return NEXTSLIDE, state 515 | is PREVSLIDE 516 | if slide == 0 517 | fileio:setMark(refnum, lineLo, lineHi) 518 | continue 519 | fin 520 | return PREVSLIDE, FALSE 521 | is ' ' 522 | is CONTSLIDE 523 | break 524 | otherwise 525 | fileio:setMark(refnum, lineLo, lineHi) 526 | continue 527 | wend 528 | fin 529 | 530 | state = parseLine(@line, state, slide) 531 | if state == ST_NEWSLIDE 532 | fileio:setMark(refnum, lineLo, lineHi) 533 | return CONTSLIDE, FALSE 534 | fin 535 | fin 536 | until line == 0 537 | return CONTSLIDE, TRUE 538 | end 539 | 540 | def slideArrows(leftArrow, rightArrow)#0 541 | byte bg 542 | 543 | bg = slideInfo[sBackground] 544 | if leftArrow 545 | pokeChar('H', BOT_LEFT, bg) // left arrow 546 | fin 547 | if rightArrow 548 | pokeChar('U', BOT_RIGHT, bg) // right arrow 549 | fin 550 | end 551 | 552 | def slideShow(refnum)#0 553 | byte slide 554 | byte finished 555 | byte slideLeave 556 | byte current 557 | byte command 558 | 559 | current = -1 560 | slide = 0 561 | while TRUE 562 | slideLeave = CONTSLIDE 563 | if current <> slide 564 | slideLeave, finished = showSlide(refnum, slide) 565 | slideArrows(slide > 0, not finished) 566 | fin 567 | current = slide 568 | command = slideLeave ?? slideLeave :: userCommand() 569 | when command 570 | is QUITSLIDE 571 | return 572 | is ' ' 573 | is NEXTSLIDE 574 | if not finished 575 | slide++ 576 | fin 577 | break 578 | is PREVSLIDE 579 | if slide > 0 580 | slide-- 581 | fin 582 | wend 583 | loop 584 | end 585 | 586 | def openK2file(name)#0 587 | word refnum 588 | 589 | refnum = fileio:open(name) 590 | if refnum 591 | fileio:newline(refnum, $7F, $0D) 592 | slideShow(refnum) 593 | fileio:close(refnum) 594 | else 595 | puts("Error opening \"") 596 | puts(name) 597 | puts("\":") 598 | puti(perr) 599 | putln 600 | fin 601 | end 602 | 603 | memset(@slidePositions, -1, 100) 604 | file = argNext(argFirst) 605 | if ^file 606 | openK2file(file) 607 | fbOff() 608 | else 609 | puts("Usage: +k2 filename\n") 610 | fin 611 | 612 | done -------------------------------------------------------------------------------- /util.pla: -------------------------------------------------------------------------------- 1 | include "inc/cmdsys.plh" 2 | 3 | export def upperCase(c)#1 4 | return c >= 'a' and c <= 'z' ?? c - $20 :: c 5 | end 6 | 7 | // Compares the bytes at two addresses. 8 | // 'data' can have upper or lowercase letters 9 | // 'upper' must be uppercase 10 | export def byteCmp(data, upper, size)#1 11 | word i 12 | 13 | for i = 1 to size 14 | if upperCase(^data) <> ^upper 15 | return FALSE 16 | fin 17 | data++ 18 | upper++ 19 | next 20 | return TRUE 21 | end 22 | 23 | done --------------------------------------------------------------------------------