editor: add example

This adds a simple text editor with basic visual line-editing
capabilities.
This commit is contained in:
Zack Buhman 2023-06-08 22:50:07 +00:00
parent 3b82199d08
commit c86fcbd6af
16 changed files with 1358 additions and 229 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@
*.out
res/mai.data
tools/ttf-convert
tools/ttf-bitmap
common/keyboard.cpp
common/keyboard.hpp
wordle/word_list.hpp

View File

@ -91,6 +91,13 @@ scsp/sound_cpu__slot.elf: scsp/sound_cpu__slot.o m68k/slot.bin.o
scsp/sound_cpu__interrupt.elf: scsp/sound_cpu__interrupt.o m68k/interrupt.bin.o sh/lib1funcs.o res/sperrypc.font.bin.o common/draw_font.o common/palette.o
res/sperrypc.bitmap.bin: tools/ttf-bitmap
./tools/ttf-bitmap 20 7f res/Bm437_SperryPC_CGA.otb $@
editor/main_saturn.o: common/keyboard.hpp editor/editor.hpp
editor/main_saturn.elf: editor/main_saturn.o res/sperrypc.bitmap.bin.o sh/lib1funcs.o common/keyboard.o saturn/start.o
# clean
clean: clean-sh
clean-sh:

View File

@ -1,19 +1,44 @@
#pragma once
#include <stdint.h>
template <typename T>
inline void copy(T * dst, const T * src, int32_t n) noexcept
{
n = n / (sizeof (T));
while (n > 0) {
*dst++ = *src++;
n -= (sizeof (T));
n--;
}
}
template <typename T>
inline void fill(T * dst, const T src, int32_t n) noexcept
{
n = n / (sizeof (T));
while (n > 0) {
*dst++ = src;
n -= (sizeof (T));
n--;
}
}
template <typename T>
inline void move(T * dst, const T * src, int32_t n) noexcept
{
n = n / (sizeof (T));
if (dst < src) {
// d s
// 0123456789
while (n > 0) {
*dst++ = *src++;
n--;
}
} else {
// s d
// 0123456789
while (n) {
n--;
dst[n] = src[n];
}
}
}

106
common/intback.hpp Normal file
View File

@ -0,0 +1,106 @@
namespace intback {
enum intback_fsm {
PORT_STATUS = 0,
PERIPHERAL_ID,
DATA1,
DATA2,
DATA3,
DATA4,
FSM_NEXT
};
struct intback_state {
uint8_t fsm;
uint8_t controller_ix;
uint8_t port_ix;
uint8_t oreg_ix;
uint8_t port_connected;
uint8_t data_size;
uint8_t peripheral_type;
uint8_t kbd_bits;
};
typedef void (*keyboard_func_ptr)(const enum keysym k, uint8_t kbd_bits);
static intback_state state;
inline void keyboard_fsm(keyboard_func_ptr callback)
{
if ((smpc.reg.SR & SR__PDL) != 0) {
// PDL == 1; 1st peripheral data
state.oreg_ix = 0;
state.controller_ix = 0;
state.port_ix = 0;
state.fsm = PORT_STATUS;
state.port_connected = 0;
state.data_size = 0;
state.peripheral_type = 0;
state.kbd_bits = 0;
}
/*
This intback handling is oversimplified:
- up to 2 controllers may be (directly) connected
- multitaps are not parsed correctly
*/
while (state.oreg_ix < 32) {
reg8 const& oreg = smpc.reg.oreg[state.oreg_ix++];
switch (state.fsm) {
case PORT_STATUS:
state.port_connected = (PORT_STATUS__CONNECTORS(oreg) == 1);
if (state.port_connected) {
if (PORT_STATUS__MULTITAP_ID(oreg) != 0xf) {
// this port is not directly connected; abort parse:
goto abort;
}
} else {
state.fsm = FSM_NEXT;
}
break;
case PERIPHERAL_ID:
state.peripheral_type = PERIPHERAL_ID__TYPE(oreg);
state.data_size = PERIPHERAL_ID__DATA_SIZE(oreg);
break;
case DATA1:
break;
case DATA2:
break;
case DATA3:
state.kbd_bits = oreg & 0b1111;
break;
case DATA4:
{
uint32_t keysym = oreg;
enum keysym k = scancode_to_keysym(keysym);
callback(k, state.kbd_bits);
}
break;
default:
break;
}
if ((state.fsm >= PERIPHERAL_ID && state.data_size <= 0) || state.fsm == FSM_NEXT) {
if (state.port_ix == 1)
break;
else {
state.port_ix++;
state.controller_ix++;
state.fsm = PORT_STATUS;
}
} else {
state.fsm++;
state.data_size--;
}
}
if ((smpc.reg.SR & SR__NPE) != 0) {
smpc.reg.ireg[0] = INTBACK__IREG0__CONTINUE;
} else {
abort:
smpc.reg.ireg[0] = INTBACK__IREG0__BREAK;
}
}
}

View File

@ -20,7 +20,8 @@ def parse_printable():
for line in f:
if not line.strip():
continue
yield line.strip().split()
k, v1, v2 = line.strip().split()
yield k, (v1, v2)
scancodes = set()
@ -46,7 +47,15 @@ keymap = build_keymap()
printable = dict(parse_printable())
printable["SPACE"] = ' '
printable["SPACE"] = (' ', ' ')
def e(s):
if s == '\\':
return '\\\\'
elif s == '\'':
return '\\\''
else:
return s
import sys
if sys.argv[1] == "header":
@ -62,8 +71,9 @@ if sys.argv[1] == "header":
print()
print("enum keysym scancode_to_keysym(uint32_t scancode);")
print("char16_t keysym_to_char16(enum keysym k);")
print("int32_t keysym_to_char(enum keysym k, bool shift);")
if sys.argv[1] == "definition":
print("#include <stdint.h>")
print("#include \"keyboard.hpp\"")
@ -81,15 +91,38 @@ if sys.argv[1] == "definition":
print()
print("char16_t keysym_to_char16(enum keysym k)")
print("static constexpr inline int32_t unshift_char(enum keysym k)")
print("{")
print(" switch(k) {")
for keysym, _ in keymap:
if keysym in printable:
value = printable[keysym]
if value == '\\':
value = '\\\\';
print(f" case keysym::{keysym}: return '{value}';")
print(f" case keysym::{keysym}: return '{e(value[0])}';")
print(" default: return -1;")
print(" }")
print("}")
print()
print("static constexpr inline int32_t shift_char(enum keysym k)")
print("{")
print(" switch(k) {")
for keysym, _ in keymap:
if keysym in printable:
value = printable[keysym]
print(f" case keysym::{keysym}: return '{e(value[1])}';")
print(" default: return -1;")
print(" }")
print("}")
print()
print("""int32_t keysym_to_char(enum keysym k, bool shift)
{
if (shift) {
return shift_char(k);
} else {
return unshift_char(k);
}
}
""")

View File

@ -93,3 +93,8 @@ PAGE_UP 8b
DELETE 85
END 88
PAGE_DOWN 8c
ARROW_LEFT 86
ARROW_UP 89
ARROW_DOWN 8A
ARROW_RIGHT 8d

13
common/minmax.hpp Normal file
View File

@ -0,0 +1,13 @@
#pragma once
template <typename T>
static inline constexpr T min(T a, T b)
{
return a > b ? b : a;
}
template <typename T>
static inline constexpr T max(T a, T b)
{
return a > b ? a : b;
}

View File

@ -1,52 +1,52 @@
K1 1
K2 2
K3 3
K4 4
K5 5
K6 6
K7 7
K8 8
K9 9
K0 0
MINUS -
CAROT ^
JPY $
K1 1 !
K2 2 "
K3 3 #
K4 4 $
K5 5 %
K6 6 &
K7 7 '
K8 8 (
K9 9 )
K0 0 ~
MINUS - =
CAROT ^ ~
JPY $ |
Q q
W w
E e
R r
T t
Y y
U u
I i
O o
P p
AT @
LEFT_SQUARE [
Q q Q
W w W
E e E
R r R
T t T
Y y Y
U u U
I i I
O o O
P p P
AT @ `
LEFT_SQUARE [ {
A a
S s
D d
F f
G g
H h
J j
K k
L l
SEMICOLON ;
COLON :
RIGHT_SQUARE ]
A a A
S s S
D d D
F f F
G g G
H h H
J j J
K k K
L l L
SEMICOLON ; +
COLON : *
RIGHT_SQUARE ] }
Z z
X x
C c
V v
B b
N n
M m
COMMA ,
PERIOD .
FORWARD_SLASH /
BACK_SLASH \
Z z Z
X x X
C c C
V v V
B b B
N n N
M m M
COMMA , <
PERIOD . >
FORWARD_SLASH / ?
BACK_SLASH \ _

383
editor/editor.hpp Normal file
View File

@ -0,0 +1,383 @@
#pragma once
#include <stdint.h>
#include "../common/copy.hpp"
#include "../common/minmax.hpp"
namespace editor {
template <int C>
struct line {
uint8_t buf[C];
int32_t length;
};
struct cursor {
int32_t row;
int32_t col;
};
template <int C, int R>
struct buffer {
line<C> row[R];
line<C> * lines[R];
int32_t length;
int32_t alloc_ix;
struct {
int32_t cell_width;
int32_t cell_height;
int32_t top;
int32_t left;
} window;
struct cursor cursor;
typedef line<C> line_type;
inline static constexpr int32_t rows_max_length(){return R;}
inline static constexpr int32_t cols_max_length(){return C;}
inline constexpr buffer(int32_t width, int32_t height);
inline constexpr line<C> * allocate();
inline constexpr void deallocate(line<C> *& l);
inline constexpr bool put(uint8_t c);
inline constexpr bool backspace();
inline constexpr bool cursor_left();
inline constexpr bool cursor_right();
inline constexpr bool cursor_up();
inline constexpr bool cursor_down();
inline constexpr bool cursor_home();
inline constexpr bool cursor_end();
inline constexpr bool enter();
private:
inline constexpr void scroll_left();
inline constexpr void scroll_right();
inline constexpr void scroll_up();
inline constexpr void scroll_down();
};
template <int C, int R>
inline constexpr buffer<C, R>::buffer(int32_t width, int32_t height)
{
this->length = 1;
this->alloc_ix = 0;
this->window.top = 0;
this->window.left = 0;
this->window.cell_height = height;
this->window.cell_width = width;
this->cursor.row = 0;
this->cursor.col = 0;
for (int32_t i = 0; i < R; i++) {
this->row[i].length = -1;
this->lines[i] = nullptr;
for (int32_t j = 0; j < C; j++) {
this->row[i].buf[j] = 0x7f;
}
}
}
template <int C, int R>
inline constexpr line<C> * buffer<C, R>::allocate()
{
line<C> * l;
while ((l = &this->row[this->alloc_ix])->length != -1) {
// R must be a power of 2
static_assert((R & (R - 1)) == 0);
this->alloc_ix = (this->alloc_ix + 1) & (R - 1);
}
l->length = 0;
return l;
}
template <int C, int R>
inline constexpr void buffer<C, R>::deallocate(line<C> *& l)
{
// does not touch alloc_ix
fill<uint8_t>(l->buf, 0x7f, l->length);
l->length = -1;
l = nullptr;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::put(const uint8_t c)
{
struct cursor& cur = this->cursor;
line<C> *& l = this->lines[cur.row];
if (l == nullptr) {
l = this->allocate();
}
// v
// 0123
if (l->length >= C || cur.col >= C || cur.col < 0)
return false;
if (l->length > cur.col) {
// c (5)
// 01234
// 012a34
// 4, 3, (5 - 3)
move(&l->buf[cur.col+1], &l->buf[cur.col], l->length - cur.col);
}
l->buf[cur.col] = c;
l->length++;
cur.col++;
scroll_right();
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::backspace()
{
struct cursor& cur = this->cursor;
if (cur.col < 0 || cur.col > C)
return false;
line<C> *& l = this->lines[cur.row];
if (l == nullptr) {
// is it possible to get in this state?
return false;
}
if (l->length < 0) {
// is it possible to get in this state?
return false;
}
if (cur.col == 0) {
if (cur.row == 0) return false;
// combine this line with the previous line
line<C> *& lp = this->lines[cur.row-1];
if (lp == nullptr) lp = allocate();
// blindly truncate overflowing lines
auto length = min(l->length - cur.col, C - lp->length);
if (length > 0) move(&lp->buf[lp->length], &l->buf[cur.col], length);
cur.col = lp->length;
scroll_right();
lp->length += length;
deallocate(l);
// 0 a
// 1
// 2 _ (cur.row, deleted)
// 3 b
int32_t n_lines = this->length - (cur.row + 1);
move(&this->lines[cur.row], &this->lines[cur.row+1], (sizeof (line<C>*)) * n_lines);
this->length--;
this->lines[this->length] = nullptr;
cur.row--;
scroll_up();
} else {
// c
// 01234
auto length = l->length - cur.col;
if (length > 0) move(&l->buf[cur.col-1], &l->buf[cur.col], length);
cur.col--;
scroll_left();
l->length--;
l->buf[l->length] = 0x7f;
}
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::cursor_left()
{
struct cursor& cur = this->cursor;
if (cur.col <= 0) {
if (cur.row <= 0)
return false;
cur.row--;
scroll_up();
line<C> const * const l = this->lines[cur.row];
int32_t length = (l == nullptr) ? 0 : l->length;
cur.col = length;
scroll_right();
} else {
cur.col--;
scroll_left();
}
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::cursor_right()
{
struct cursor& cur = this->cursor;
line<C> const * const l = this->lines[cur.row];
int32_t length = (l == nullptr) ? 0 : l->length;
if (cur.col >= length) {
if (cur.row >= this->length)
return false;
cur.row++;
scroll_down();
cur.col = 0;
scroll_left();
} else {
cur.col++;
scroll_right();
}
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::cursor_up()
{
struct cursor& cur = this->cursor;
if (cur.row <= 0)
return false;
cur.row--;
scroll_up();
line<C> const * const l = this->lines[cur.row];
int32_t length = (l == nullptr) ? 0 : l->length;
cur.col = min(cur.col, length);
scroll_left();
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::cursor_down()
{
struct cursor& cur = this->cursor;
if (cur.row >= this->length)
return false;
cur.row++;
scroll_down();
line<C> const * const l = this->lines[cur.row];
int32_t length = (l == nullptr) ? 0 : l->length;
cur.col = min(cur.col, length);
scroll_left();
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::cursor_home()
{
struct cursor& cur = this->cursor;
line<C> const * const l = this->lines[cur.row];
if (l == nullptr)
return false;
cur.col = 0;
scroll_left();
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::cursor_end()
{
struct cursor& cur = this->cursor;
line<C> const * const l = this->lines[cur.row];
if (l == nullptr)
return false;
cur.col = l->length;
scroll_right();
return true;
}
template <int C, int R>
inline constexpr bool buffer<C, R>::enter()
{
struct cursor& cur = this->cursor;
if (cur.row >= R || cur.row < 0)
return false;
if (this->length > cur.row) {
// first, move all lines down one
int32_t n_lines = this->length - (cur.row + 1);
// don't care about nullptr here, this never dereferences
move(&this->lines[cur.row+2], &this->lines[cur.row+1], (sizeof (line<C>*)) * n_lines);
}
// column-wise copy of the cursor position to the newly-created line
line<C> * old_l = this->lines[cur.row];
line<C> * new_l = allocate();
// v
// 01234 (5)
if (old_l != nullptr) {
new_l->length = old_l->length - cur.col;
if (new_l->length > 0) {
old_l->length -= new_l->length;
copy(&new_l->buf[0], &old_l->buf[cur.col], new_l->length);
fill<uint8_t>(&old_l->buf[cur.col], 0x7f, new_l->length);
}
} else {
// nothing to do, new_l->length is already 0
}
cur.row++;
scroll_down();
this->lines[cur.row] = new_l;
cur.col = 0;
scroll_left();
// because copy() is used instead of put(), length needs to be
// incremented here.
this->length++;
return true;
}
template <int C, int R>
inline constexpr void buffer<C, R>::scroll_up()
{
if (this->cursor.row < this->window.top)
this->window.top = this->cursor.row;
}
template <int C, int R>
inline constexpr void buffer<C, R>::scroll_down()
{
// 0: a -
// 1: bv -
// 0: a
// 1: b -
// 2: cv -
if (this->cursor.row >= this->window.top + this->window.cell_height)
this->window.top = (this->cursor.row - (this->window.cell_height - 1));
}
template <int C, int R>
inline constexpr void buffer<C, R>::scroll_left()
{
if (this->cursor.col < this->window.left)
this->window.left = this->cursor.col;
}
template <int C, int R>
inline constexpr void buffer<C, R>::scroll_right()
{
// 0: a -
// 1: bv -
// 0: a
// 1: b -
// 2: cv -
if (this->cursor.col >= this->window.left + this->window.cell_width)
this->window.left = (this->cursor.col - (this->window.cell_width - 1));
}
}

257
editor/main_saturn.cpp Normal file
View File

@ -0,0 +1,257 @@
#include <stdint.h>
#include "vdp2.h"
#include "smpc.h"
#include "scu.h"
#include "sh2.h"
#include "../common/copy.hpp"
#include "../common/vdp2_func.hpp"
#include "../common/keyboard.hpp"
#include "../common/intback.hpp"
#include "editor.hpp"
extern void * _sperrypc_bitmap_start __asm("_binary_res_sperrypc_bitmap_bin_start");
using buffer_type = editor::buffer<64, 64>;
constexpr int32_t viewport_max_col = 320 / 8;
constexpr int32_t viewport_max_row = 240 / 8;
static buffer_type buffer {viewport_max_col, viewport_max_row};
void palette_data()
{
vdp2.cram.u16[1 + 0 ] = 0;
vdp2.cram.u16[2 + 0 ] = 0x7fff;
vdp2.cram.u16[1 + 16] = 0x7fff;
vdp2.cram.u16[2 + 16] = 0;
}
namespace pix_fmt_4bpp
{
constexpr inline uint32_t
bit(uint8_t n, int32_t i)
{
i &= 7;
auto b = (n >> (7 - i)) & 1;
return ((b + 1) << ((7 - i) * 4));
}
constexpr inline uint32_t
bits(uint8_t n)
{
return
bit(n, 0) | bit(n, 1) | bit(n, 2) | bit(n, 3)
| bit(n, 4) | bit(n, 5) | bit(n, 6) | bit(n, 7);
}
static_assert(bits(0b1100'1110) == 0x2211'2221);
static_assert(bits(0b1010'0101) == 0x2121'1212);
static_assert(bits(0b1000'0000) == 0x2111'1111);
}
void cell_data()
{
const uint8_t * buf = reinterpret_cast<uint8_t*>(&_sperrypc_bitmap_start);
for (int ix = 0; ix <= (0x7f - 0x20); ix++) {
for (int y = 0; y < 8; y++) {
uint8_t row = buf[ix * 8 + y];
vdp2.vram.u32[(ix * 8) + y] = pix_fmt_4bpp::bits(row);
}
}
}
struct modifiers {
uint8_t left_shift;
uint8_t right_shift;
};
static modifiers modifier_state = { 0 };
void keyboard_callback(const enum keysym k, uint8_t kbd_bits)
{
int32_t shift = modifier_state.left_shift || modifier_state.right_shift;
int32_t c = keysym_to_char(k, shift);
if (KEYBOARD__MAKE(kbd_bits)) {
if (k == keysym::UNKNOWN)
return;
if (c != -1) {
buffer.put(c);
} else {
switch (k) {
case keysym::BACKSPACE : buffer.backspace(); break;
case keysym::ARROW_LEFT : buffer.cursor_left(); break;
case keysym::ARROW_RIGHT: buffer.cursor_right(); break;
case keysym::ARROW_UP : buffer.cursor_up(); break;
case keysym::ARROW_DOWN : buffer.cursor_down(); break;
case keysym::HOME : buffer.cursor_home(); break;
case keysym::END : buffer.cursor_end(); break;
case keysym::ENTER : buffer.enter(); break;
case keysym::LEFT_SHIFT : modifier_state.left_shift = 1; break;
case keysym::RIGHT_SHIFT: modifier_state.right_shift = 1; break;
default: break;
}
}
} else if (KEYBOARD__BREAK(kbd_bits)) {
switch (k) {
case keysym::LEFT_SHIFT : modifier_state.left_shift = 0; break;
case keysym::RIGHT_SHIFT: modifier_state.right_shift = 0; break;
default: break;
}
}
}
extern "C"
void smpc_int(void) __attribute__ ((interrupt_handler));
void smpc_int(void)
{
scu.reg.IST &= ~(IST__SMPC);
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
intback::keyboard_fsm(keyboard_callback);
}
constexpr int32_t plane_a = 2;
constexpr inline int32_t plane_offset(int32_t n) { return n * 0x2000; }
constexpr int32_t page_size = 64 * 64 * 2; // N0PNB__1WORD (16-bit)
constexpr int32_t plane_size = page_size * 1;
constexpr int32_t cell_size = (8 * 8) / 2; // N0CHCN__16_COLOR (4-bit)
constexpr int32_t character_size = cell_size * (1 * 1); // N0CHSZ__1x1_CELL
constexpr int32_t page_width = 64;
static int plane_ix = 0;
inline void
set_char(int32_t x, int32_t y, uint8_t palette, uint8_t c)
{
const auto ix = (plane_offset(plane_a + plane_ix) / 2) + (y * page_width) + x;
vdp2.vram.u16[ix] =
PATTERN_NAME_TABLE_1WORD__PALETTE(palette)
| PATTERN_NAME_TABLE_1WORD__CHARACTER((c - 0x20));
}
void render()
{
for (int row = 0; row < buffer.window.cell_height; row++) {
const buffer_type::line_type * l = buffer.lines[buffer.window.top + row];
for (int col = 0; col < buffer.window.cell_width; col++) {
uint8_t c;
c = ' ';
if (l != nullptr && (buffer.window.left + col) < l->length)
c = l->buf[buffer.window.left + col];
set_char(col, row, 0, c);
}
}
editor::cursor& cur = buffer.cursor;
const buffer_type::line_type * l = buffer.lines[cur.row];
uint8_t c = (l != nullptr && cur.col < l->length) ? l->buf[cur.col] : ' ';
set_char(cur.col - buffer.window.left, cur.row - buffer.window.top, 1, c);
}
extern "C"
void v_blank_in_int(void) __attribute__ ((interrupt_handler));
void v_blank_in_int()
{
scu.reg.IST &= ~(IST__V_BLANK_IN);
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
// flip planes;
vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a + plane_ix);
plane_ix = !plane_ix;
// wait at least 300us, as specified in the SMPC manual.
// It appears reading FRC.H is mandatory and *must* occur before FRC.L on real
// hardware.
while ((sh2.reg.FTCSR & FTCSR__OVF) == 0 && sh2.reg.FRC.H == 0 && sh2.reg.FRC.L < 63);
if ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) != 0) {
// on real hardware, SF contains uninitialized garbage bits other than the
// lsb.
while ((smpc.reg.SF & 1) != 0);
smpc.reg.SF = 0;
smpc.reg.ireg[0] = INTBACK__IREG0__STATUS_DISABLE;
smpc.reg.ireg[1] = ( INTBACK__IREG1__PERIPHERAL_DATA_ENABLE
| INTBACK__IREG1__PORT2_15BYTE
| INTBACK__IREG1__PORT1_15BYTE
);
smpc.reg.ireg[2] = INTBACK__IREG2__MAGIC;
smpc.reg.COMREG = COMREG__INTBACK;
}
render();
}
void main()
{
v_blank_in();
// DISP: Please make sure to change this bit from 0 to 1 during V blank.
vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE
| TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320);
/* set the color mode to 5bits per channel, 1024 colors */
vdp2.reg.RAMCTL = RAMCTL__CRMD__RGB_5BIT_1024;
/* enable display of NBG0 */
vdp2.reg.BGON = BGON__N0ON;
/* set character format for NBG0 to palettized 16 color
set enable "cell format" for NBG0
set character size for NBG0 to 1x1 cell */
vdp2.reg.CHCTLA = CHCTLA__N0CHCN__16_COLOR
| CHCTLA__N0BMEN__CELL_FORMAT
| CHCTLA__N0CHSZ__1x1_CELL;
/* "Note: In color RAM modes 0 and 2, 2048-color becomes 1024-color" */
/* use 1-word (16-bit) pattern names */
vdp2.reg.PNCN0 = PNCN0__N0PNB__1WORD;
/* plane size */
vdp2.reg.PLSZ = PLSZ__N0PLSZ__1x1;
/* map plane offset
1-word: value of bit 6-0 * 0x2000
2-word: value of bit 5-0 * 0x4000
*/
vdp2.reg.MPOFN = MPOFN__N0MP(0); // bits 8~6
vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0
vdp2.reg.MPCDN0 = MPABN0__N0MPD(0) | MPABN0__N0MPC(0); // bits 5~0
// zeroize character/cell data from 0 up to plane_a_offset
fill<uint32_t>(&vdp2.vram.u32[(0 / 4)], 0, plane_offset(plane_a));
// zeroize plane_a; `0` is the ascii 0x20 ("space") which doubles as
// "transparency" character.
fill<uint32_t>(&vdp2.vram.u32[(plane_offset(plane_a) / 4)], 0, plane_size * 2);
palette_data();
cell_data();
// free-running timer
sh2.reg.TCR = TCR__CKS__INTERNAL_DIV128;
sh2.reg.FTCSR = 0;
// initialize smpc
smpc.reg.DDR1 = 0; // INPUT
smpc.reg.DDR2 = 0; // INPUT
smpc.reg.IOSEL = 0; // SMPC control
smpc.reg.EXLE = 0; //
sh2_vec[SCU_VEC__SMPC] = (u32)(&smpc_int);
sh2_vec[SCU_VEC__V_BLANK_IN] = (u32)(&v_blank_in_int);
scu.reg.IST = 0;
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
}

310
editor/test_editor.cpp Normal file
View File

@ -0,0 +1,310 @@
#include <assert.h>
#include <iostream>
#include "editor.hpp"
using namespace editor;
static void test_allocate()
{
buffer<8, 4> b {4, 2};
decltype(b)::line_type * l;
assert(b.row[0].length == -1);
assert(b.row[1].length == -1);
assert(b.row[2].length == -1);
assert(b.row[3].length == -1);
l = b.allocate();
assert(b.row[0].length == 0);
assert(b.row[1].length == -1);
assert(b.row[2].length == -1);
assert(b.row[3].length == -1);
assert(l == &b.row[0]);
l = b.allocate();
assert(b.row[0].length == 0);
assert(b.row[1].length == 0);
assert(b.row[2].length == -1);
assert(b.row[3].length == -1);
assert(l == &b.row[1]);
l = b.allocate();
assert(b.row[0].length == 0);
assert(b.row[1].length == 0);
assert(b.row[2].length == 0);
assert(b.row[3].length == -1);
assert(l == &b.row[2]);
l = b.allocate();
assert(b.row[0].length == 0);
assert(b.row[1].length == 0);
assert(b.row[2].length == 0);
assert(b.row[3].length == 0);
assert(l == &b.row[3]);
l = &b.row[1];
b.deallocate(l);
assert(b.row[0].length == 0);
assert(b.row[1].length == -1);
assert(b.row[2].length == 0);
assert(b.row[3].length == 0);
l = b.allocate();
assert(b.row[0].length == 0);
assert(b.row[1].length == 0);
assert(b.row[2].length == 0);
assert(b.row[3].length == 0);
assert(l == &b.row[1]);
}
static void test_put()
{
// v
// "as" -> "abs"
buffer<8, 4> b {4, 2};
decltype(b)::line_type * l;
assert(b.cursor.col == 0);
assert(b.cursor.row == 0);
assert(b.length == 1);
b.put('a');
l = b.lines[0];
assert(l = &b.row[0]);
assert(l->length == 1);
assert(l->buf[0] == 'a');
assert(l->buf[1] == 0x7f);
assert(b.cursor.col == 1);
assert(b.cursor.row == 0);
assert(b.length == 1);
b.put('b');
l = b.lines[0];
assert(l->length == 2);
assert(l->buf[0] == 'a');
assert(l->buf[1] == 'b');
assert(l->buf[2] == 0x7f);
assert(b.cursor.col == 2);
assert(b.cursor.row == 0);
assert(b.length == 1);
b.cursor.col = 1;
b.put('c');
l = b.lines[0];
assert(l->length == 3);
assert(l->buf[0] == 'a');
assert(l->buf[1] == 'c');
assert(l->buf[2] == 'b');
assert(l->buf[3] == 0x7f);
assert(b.cursor.col == 2);
assert(b.cursor.row == 0);
assert(b.length == 1);
}
void test_backspace()
{
buffer<8, 4> b {4, 2};
decltype(b)::line_type * l;
b.put('a');
l = b.lines[0];
assert(l->length == 1);
assert(l->buf[0] == 'a');
assert(b.backspace() == true);
assert(l->length == 0);
assert(l->buf[0] == 0x7f);
assert(b.backspace() == false);
b.put('b');
b.put('c');
b.put('d');
b.put('e');
b.cursor.col = 2;
assert(l->length == 4);
//"bcde"
assert(b.backspace() == true);
assert(l->buf[0] == 'b');
assert(l->buf[1] == 'd');
assert(l->buf[2] == 'e');
assert(l->buf[3] == 0x7f);
assert(l->length == 3);
}
void test_enter()
{
// [0] asDf
// [1] qwer
// [0] as
// [1] Df
// [2] qwer
buffer<8, 4> b {4, 2};
b.cursor.row = 0;
b.cursor.col = 0;
b.put('a');
b.put('s');
b.put('d');
b.put('f');
b.cursor.row = 1;
b.cursor.col = 0;
b.put('q');
b.put('w');
b.put('e');
b.put('r');
assert(b.length == 2);
assert(b.lines[0]->buf[0] == 'a');
assert(b.lines[0]->buf[3] == 'f');
assert(b.lines[1]->buf[0] == 'q');
assert(b.lines[1]->buf[3] == 'r');
b.cursor.row = 0;
b.cursor.col = 2;
b.enter();
assert(b.length == 3);
assert(b.lines[0]->length == 2);
assert(b.lines[1]->length == 2);
assert(b.lines[2]->length == 4);
assert(b.lines[0]->buf[0] == 'a');
assert(b.lines[0]->buf[1] == 's');
assert(b.lines[1]->buf[0] == 'd');
assert(b.lines[1]->buf[1] == 'f');
assert(b.lines[2]->buf[0] == 'q');
assert(b.lines[2]->buf[1] == 'w');
assert(b.lines[2]->buf[2] == 'e');
assert(b.lines[2]->buf[3] == 'r');
}
void test_enter_backspace1()
{
// abcd
// ab
//
// cd
// abcd
// ab
// cd
buffer<8, 4> b {4, 2};
b.put('a');
b.put('b');
b.put('c');
b.put('d');
b.cursor_left();
b.cursor_left();
b.enter();
b.enter();
assert(b.length == 3);
b.backspace();
b.backspace();
assert(b.length == 1);
b.enter();
assert(b.length == 2);
assert(b.lines[0]->buf[0] == 'a');
assert(b.lines[0]->buf[1] == 'b');
assert(b.lines[1]->buf[0] == 'c');
assert(b.lines[1]->buf[1] == 'd');
}
void test_enter_backspace2()
{
buffer<8, 4> b {4, 2};
b.put('a');
b.enter();
assert(b.cursor.row == 1);
assert(b.cursor.col == 0);
assert(b.length == 2);
b.backspace();
assert(b.cursor.row == 0);
assert(b.cursor.col == 1);
assert(b.length == 1);
}
void test_enter_scroll()
{
buffer<8, 4> b {4, 2};
assert(b.window.top == 0);
b.put('a');
assert(b.window.top == 0);
b.enter();
assert(b.window.top == 0);
b.put('b');
assert(b.window.top == 0);
b.enter();
assert(b.cursor.row == 2);
assert(b.window.top == 1);
b.put('c');
assert(b.window.top == 1);
b.enter();
assert(b.window.top == 2);
b.put('d');
assert(b.window.top == 2);
}
void test_first_enter()
{
buffer<8, 4> b {4, 2};
b.enter();
assert(b.length == 2);
}
void test_enter_backspace3()
{
// a
//
//
// b
buffer<8, 8> b {4, 2};
b.put('a');
b.enter();
b.enter();
b.enter();
b.put('b');
b.cursor_up();
assert(b.lines[0]->buf[0] == 'a');
assert(b.lines[1]->length == 0);
assert(b.lines[2]->length == 0);
assert(b.lines[3]->buf[0] == 'b');
assert(b.lines[4] == nullptr);
assert(b.length == 4);
b.backspace();
assert(b.length == 3);
assert(b.lines[0]->buf[0] == 'a');
assert(b.lines[1]->length == 0);
assert(b.lines[2]->buf[0] == 'b');
assert(b.lines[3] == nullptr);
}
int main()
{
test_allocate();
test_put();
test_backspace();
test_enter_backspace1();
test_enter_backspace2();
test_enter_backspace3();
test_enter_scroll();
test_first_enter();
return 0;
}

View File

@ -243,7 +243,7 @@ void smpc_int(void) {
print_hex(str_num, 2, keysym);
enum keysym k = scancode_to_keysym(keysym);
char16_t c = keysym_to_char16(k);
int32_t c = keysym_to_char(k, false);
if (k != keysym::UNKNOWN && c != -1) {
text[0] = c;
x += draw_font::horizontal_string(font_state,

View File

@ -2,7 +2,7 @@ CFLAGS = -Og -Wall -Wextra -Werror -ggdb -Wno-error=unused-parameter -Wno-error=
CFLAGS += $(shell pkg-config --cflags freetype2)
LDFLAGS = $(shell pkg-config --libs freetype2)
all: ttf-convert
all: ttf-convert ttf-bitmap
%.o: %.cpp
$(CXX) $(CFLAGS) -c $< -o $@
@ -11,7 +11,7 @@ all: ttf-convert
$(CXX) $(LDFLAGS) $< -o $@
clean:
rm -f *.o ttf-convert
rm -f *.o ttf-convert ttf-bitmap
.SUFFIXES:
.INTERMEDIATE:

130
tools/ttf-bitmap.cpp Normal file
View File

@ -0,0 +1,130 @@
#include <sstream>
#include <iostream>
#include <assert.h>
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H
int32_t
load_bitmap_char(FT_Face face,
FT_ULong char_code,
uint8_t * buf)
{
FT_Error error;
FT_UInt glyph_index = FT_Get_Char_Index(face, char_code);
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
if (error) {
std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n';
return -1;
}
assert(face->glyph->format == FT_GLYPH_FORMAT_BITMAP);
//printf("num_grays %d\n", face->glyph->bitmap.num_grays);
//printf("pitch %d\n", face->glyph->bitmap.pitch);
//printf("width %d\n", face->glyph->bitmap.width);
assert(face->glyph->bitmap.width == 8);
//printf("char_code %lx rows %d\n", char_code, face->glyph->bitmap.rows);
assert((face->glyph->bitmap.rows % 8) == 0);
assert(face->glyph->bitmap.width / face->glyph->bitmap.pitch == 8);
for (int y = 0; y < (int)face->glyph->bitmap.rows; y++) {
uint8_t * row = &face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch];
uint8_t row_out = 0;
for (unsigned int x = 0; x < face->glyph->bitmap.width; x++) {
int bit;
if (x < face->glyph->bitmap.width) {
bit = (row[x / 8] >> (7 - (x % 8))) & 1;
} else {
bit = 0;
}
//std::cerr << (bit ? "█" : " ");
row_out |= (bit << (7 - (x % 8)));
}
//std::cerr << '\n';
buf[y] = row_out;
}
// 'pitch' is bytes; 'width' is pixels
return face->glyph->bitmap.rows * face->glyph->bitmap.pitch;
}
template <typename T>
constexpr inline T
parse_num(decltype(std::hex)& format, const char * s)
{
T n;
std::stringstream ss;
ss << format << s;
ss >> n;
return n;
}
int main(int argc, char *argv[])
{
FT_Library library;
FT_Face face;
FT_Error error;
if (argc != 5) {
std::cerr << "usage: " << argv[0] << " [start-hex] [end-hex] [font-file-path] [output-file-path]\n\n";
std::cerr << " ex: " << argv[0] << " 3000 30ff ipagp.ttf font.bin\n";
return -1;
}
auto start = parse_num<uint32_t>(std::hex, argv[1]);
auto end = parse_num<uint32_t>(std::hex, argv[2]);
auto font_file_path = argv[3];
auto output_file_path = argv[4];
error = FT_Init_FreeType(&library);
if (error) {
std::cerr << "FT_Init_FreeType\n";
return -1;
}
error = FT_New_Face(library, font_file_path, 0, &face);
if (error) {
std::cerr << "FT_New_Face\n";
return -1;
}
error = FT_Select_Size(face, 0);
if (error) {
std::cerr << "FT_Select_Size: " << FT_Error_String(error) << ' ' << error << '\n';
return -1;
}
assert(end > start);
uint32_t pixels_per_glyph = (face->size->metrics.height * face->size->metrics.max_advance);
assert(pixels_per_glyph % 8 == 0);
uint32_t bytes_per_glyph = pixels_per_glyph / 8;
uint32_t num_glyphs = (end - start) + 1;
uint8_t buf[bytes_per_glyph * num_glyphs];
uint32_t bitmap_offset = 0;
for (uint32_t char_code = start; char_code <= end; char_code++) {
int32_t bitmap_size = load_bitmap_char(face,
char_code,
&buf[bitmap_offset]);
if (bitmap_size < 0) {
std::cerr << "load_bitmap_char error\n";
return -1;
}
bitmap_offset += bitmap_size;
assert(bitmap_offset < (sizeof (buf)));
}
std::cerr << "bitmap_offset: 0x" << std::dec << bitmap_offset << '\n';
FILE * out = fopen(output_file_path, "w");
if (out == NULL) {
perror("fopen(w)");
return -1;
}
fwrite(reinterpret_cast<void*>(&buf), bitmap_offset, 1, out);
fclose(out);
}

View File

@ -11,47 +11,6 @@
#include "../common/font.hpp"
/*
int load_bitmap_char(FT_Face face, FT_ULong char_code, uint8_t * buf)
{
FT_Error error;
FT_UInt glyph_index = FT_Get_Char_Index(face, char_code);
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
if (error) {
printf("FT_Load_Glyph %s\n", FT_Error_String(error));
return 0;
}
assert(face->glyph->format == FT_GLYPH_FORMAT_BITMAP);
printf("num_grays %d\n", face->glyph->bitmap.num_grays);
printf("pitch %d\n", face->glyph->bitmap.pitch);
printf("width %d\n", face->glyph->bitmap.width);
assert(face->glyph->bitmap.width == 16);
printf("char_code %lx rows %d\n", char_code, face->glyph->bitmap.rows);
assert(face->glyph->bitmap.rows == 8 || (face->glyph->bitmap.rows % 8) == 0);
for (int y = 0; y < (int)face->glyph->bitmap.rows; y++) {
uint8_t * row = &face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch];
uint8_t row_out = 0;
for (int x = 0; x < face->glyph->bitmap.width; x++) {
int bit;
if (x < (int)face->glyph->bitmap.width) {
bit = (row[x / 8] >> (7 - (x % 8))) & 1;
} else {
bit = 0;
}
fprintf(stderr, bit ? "" : " ");
row_out |= (bit << x);
}
fprintf(stderr, "\n");
//buf[y] = row_out;
}
return face->glyph->bitmap.rows * face->glyph->bitmap.pitch;
}
*/
int32_t
load_outline_char(const FT_Face face,
const FT_ULong char_code,
@ -180,21 +139,13 @@ int main(int argc, char *argv[])
return -1;
}
/*
error = FT_Select_Size(face, 0);
if (error) {
fprintf(stderr, "FT_Select_Size: %s %d\n", FT_Error_String(error), error);
return -1;
}
*/
std::stringstream ss3;
int font_size;
ss3 << std::dec << argv[3];
ss3 >> font_size;
std::cerr << "font_size: " << font_size << '\n';
error = FT_Set_Pixel_Sizes (face, 0, font_size);
error = FT_Set_Pixel_Sizes(face, 0, font_size);
if (error) {
std::cerr << "FT_Set_Pixel_Sizes: " << FT_Error_String(error) << error << '\n';
return -1;

View File

@ -10,6 +10,7 @@
#include "../common/draw_font.hpp"
#include "../common/palette.hpp"
#include "../common/vdp2_func.hpp"
#include "../common/intback.hpp"
#include "wordle.hpp"
#include "draw.hpp"
@ -25,28 +26,6 @@ static struct draw_state draw_state;
static struct wordle::screen wordle_state;
#define assert(n) if ((n) == 0) while (1);
enum intback_fsm {
PORT_STATUS = 0,
PERIPHERAL_ID,
DATA1,
DATA2,
DATA3,
DATA4,
FSM_NEXT
};
struct intback_state {
int fsm;
int controller_ix;
int port_ix;
};
static intback_state intback;
static int oreg_ix;
struct xorshift32_state {
uint32_t a;
};
@ -63,107 +42,36 @@ uint32_t xorshift32(struct xorshift32_state *state)
static xorshift32_state random_state = { 0x12345678 };
static uint32_t frame_count = 0;
void keyboard_callback(const enum keysym k, uint8_t kbd_bits)
{
if (!KEYBOARD__MAKE(kbd_bits))
return;
int32_t c = keysym_to_char(k, false);
if (k == keysym::UNKNOWN)
return;
if (c >= 'a' && c <= 'z') {
// uppercase
wordle::type_letter(wordle_state, c);
} else if (k == keysym::ENTER) {
wordle::confirm_word(wordle_state);
} else if (k == keysym::BACKSPACE) {
wordle::backspace(wordle_state);
} else if (k == keysym::ESC) {
random_state.a += frame_count;
uint32_t rand = xorshift32(&random_state);
wordle::init_screen(wordle_state, rand);
}
}
void smpc_int(void) __attribute__ ((interrupt_handler));
void smpc_int(void) {
void smpc_int(void)
{
scu.reg.IST &= ~(IST__SMPC);
scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN);
if ((smpc.reg.SR & SR__PDL) != 0) {
// PDL == 1; 1st peripheral data
oreg_ix = 0;
intback.controller_ix = 0;
intback.port_ix = 0;
intback.fsm = PORT_STATUS;
}
int port_connected = 0;
int data_size = 0;
int peripheral_type = 0;
(void)peripheral_type;
int kbd_bits = 0;
/*
This intback handling is oversimplified:
- up to 2 controllers may be connected
- multitaps are not parsed correctly
*/
while (oreg_ix < 32) {
reg8 const& oreg = smpc.reg.oreg[oreg_ix++];
switch (intback.fsm) {
case PORT_STATUS:
port_connected = (PORT_STATUS__CONNECTORS(oreg) == 1);
if (port_connected) {
assert(PORT_STATUS__MULTITAP_ID(oreg) == 0xf);
} else {
intback.fsm = FSM_NEXT;
}
break;
case PERIPHERAL_ID:
peripheral_type = PERIPHERAL_ID__TYPE(oreg);
data_size = PERIPHERAL_ID__DATA_SIZE(oreg);
break;
case DATA1:
{
//controller_state& c = intback.controller[intback.controller_ix];
}
break;
case DATA2:
break;
case DATA3:
kbd_bits = oreg & 0b1111;
break;
case DATA4:
{
uint32_t keysym = oreg;
if (kbd_bits & 0b1000) { // Make
enum keysym k = scancode_to_keysym(keysym);
char16_t c = keysym_to_char16(k);
if (k != keysym::UNKNOWN) {
if (c >= 'a' && c <= 'z') {
// uppercase
wordle::type_letter(wordle_state, c);
} else if (k == keysym::ENTER) {
wordle::confirm_word(wordle_state);
} else if (k == keysym::BACKSPACE) {
wordle::backspace(wordle_state);
} else if (k == keysym::ESC) {
random_state.a += frame_count;
uint32_t rand = xorshift32(&random_state);
wordle::init_screen(wordle_state, rand);
}
}
} else if (kbd_bits & 0b0001) { // Break
}
}
break;
default:
break;
}
if ((intback.fsm >= PERIPHERAL_ID && data_size <= 0) || intback.fsm == FSM_NEXT) {
if (intback.port_ix == 1)
break;
else {
intback.port_ix++;
intback.controller_ix++;
intback.fsm = PORT_STATUS;
}
} else {
intback.fsm++;
data_size--;
}
}
if ((smpc.reg.SR & SR__NPE) != 0) {
smpc.reg.ireg[0] = INTBACK__IREG0__CONTINUE;
} else {
smpc.reg.ireg[0] = INTBACK__IREG0__BREAK;
}
intback::keyboard_fsm(keyboard_callback);
}
// rendering