editor: add example
This adds a simple text editor with basic visual line-editing capabilities.
This commit is contained in:
parent
3b82199d08
commit
c86fcbd6af
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
*.out
|
||||
res/mai.data
|
||||
tools/ttf-convert
|
||||
tools/ttf-bitmap
|
||||
common/keyboard.cpp
|
||||
common/keyboard.hpp
|
||||
wordle/word_list.hpp
|
7
Makefile
7
Makefile
@ -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:
|
||||
|
@ -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
106
common/intback.hpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
@ -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
13
common/minmax.hpp
Normal 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;
|
||||
}
|
@ -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
383
editor/editor.hpp
Normal 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
257
editor/main_saturn.cpp
Normal 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
310
editor/test_editor.cpp
Normal 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;
|
||||
}
|
@ -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,
|
||||
|
@ -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
130
tools/ttf-bitmap.cpp
Normal 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);
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user