Added ANSIBBS widget.
This commit is contained in:
parent
856cc194b2
commit
2e45e4b14d
9 changed files with 1302 additions and 4 deletions
73
README.md
73
README.md
|
|
@ -7,7 +7,7 @@ Motif-style beveled chrome, dirty-rectangle compositing, draggable and
|
|||
resizable windows, dropdown menus, scrollbars, and a declarative widget/layout
|
||||
system with buttons, checkboxes, radios, text inputs, dropdowns, combo boxes,
|
||||
sliders, progress bars, tab controls, tree views, toolbars, status bars,
|
||||
images, image buttons, and drawable canvases.
|
||||
images, image buttons, drawable canvases, and an ANSI BBS terminal emulator.
|
||||
|
||||
## Building
|
||||
|
||||
|
|
@ -628,6 +628,77 @@ solid, and `FillCircle` fills a circle -- all using the current pen
|
|||
color. All operations clip to the canvas bounds. Colors are in display
|
||||
pixel format (use `packColor()` to create them).
|
||||
|
||||
### ANSI Terminal
|
||||
|
||||
```c
|
||||
WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows);
|
||||
```
|
||||
ANSI BBS terminal emulator widget. Displays a character grid (default
|
||||
80x25 if cols/rows are 0) with full ANSI escape sequence support and a
|
||||
16-color CGA palette. The terminal has a 2px sunken bevel border and a
|
||||
vertical scrollbar for scrollback history.
|
||||
|
||||
ANSI escape sequences supported:
|
||||
|
||||
| Sequence | Description |
|
||||
|----------|-------------|
|
||||
| `ESC[H` / `ESC[f` | Cursor position (CUP/HVP) |
|
||||
| `ESC[A/B/C/D` | Cursor up/down/forward/back |
|
||||
| `ESC[J` | Erase display (0=to end, 1=to start, 2=all) |
|
||||
| `ESC[K` | Erase line (0=to end, 1=to start, 2=all) |
|
||||
| `ESC[m` | SGR: colors 30-37/40-47, bright 90-97/100-107, bold(1), blink(5), reverse(7), reset(0) |
|
||||
| `ESC[s` / `ESC[u` | Save / restore cursor position |
|
||||
| `ESC[S` / `ESC[T` | Scroll up / down |
|
||||
| `ESC[L` / `ESC[M` | Insert / delete lines |
|
||||
| `ESC[?7h/l` | Enable / disable auto-wrap |
|
||||
| `ESC[?25h/l` | Show / hide cursor |
|
||||
|
||||
Control characters: CR, LF, BS, TAB, BEL (ignored).
|
||||
|
||||
```c
|
||||
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len);
|
||||
```
|
||||
Feed data through the ANSI parser. Use this to display `.ANS` files or
|
||||
inject content without a communications link.
|
||||
|
||||
```c
|
||||
void wgtAnsiTermClear(WidgetT *w);
|
||||
```
|
||||
Clear the screen, push all visible lines to scrollback, and reset the
|
||||
cursor to the home position.
|
||||
|
||||
```c
|
||||
void wgtAnsiTermSetComm(WidgetT *w, void *ctx,
|
||||
int32_t (*readFn)(void *, uint8_t *, int32_t),
|
||||
int32_t (*writeFn)(void *, const uint8_t *, int32_t));
|
||||
```
|
||||
Set the communications interface. `readFn` should return bytes read
|
||||
(0 if none available). `writeFn` sends bytes. Pass NULL function
|
||||
pointers for a disconnected / display-only terminal.
|
||||
|
||||
When connected, keyboard input is translated to ANSI sequences and sent
|
||||
via `writeFn` (arrows become `ESC[A`..`ESC[D`, etc.).
|
||||
|
||||
```c
|
||||
int32_t wgtAnsiTermPoll(WidgetT *w);
|
||||
```
|
||||
Poll the comm interface for incoming data and process it through the
|
||||
ANSI parser. Returns the number of bytes read. Call this from your
|
||||
event loop or idle handler, then `wgtInvalidate()` if the return
|
||||
value is nonzero.
|
||||
|
||||
```c
|
||||
void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines);
|
||||
```
|
||||
Set the scrollback buffer size (default 500 lines). Clears any existing
|
||||
scrollback. Lines scroll into the buffer when they leave the top of the
|
||||
screen or when the screen is cleared (`ESC[2J` / `wgtAnsiTermClear`).
|
||||
|
||||
A vertical scrollbar appears automatically when there is scrollback
|
||||
content. Click the arrow buttons for single-line scrolling, or the
|
||||
trough for page scrolling. The view auto-follows live output unless the
|
||||
user has scrolled back.
|
||||
|
||||
### Spacing and dividers
|
||||
|
||||
```c
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ LIBDIR = ../lib
|
|||
|
||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c
|
||||
|
||||
WSRCS = widgets/widgetCore.c \
|
||||
WSRCS = widgets/widgetAnsiTerm.c \
|
||||
widgets/widgetCore.c \
|
||||
widgets/widgetLayout.c \
|
||||
widgets/widgetEvent.c \
|
||||
widgets/widgetOps.c \
|
||||
|
|
@ -76,6 +77,7 @@ $(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h dvxTypes.h dvxVideo.h dvxDraw.h dvx
|
|||
|
||||
# Widget file dependencies
|
||||
WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
||||
$(WOBJDIR)/widgetAnsiTerm.o: widgets/widgetAnsiTerm.c $(WIDGET_DEPS)
|
||||
$(WOBJDIR)/widgetCore.o: widgets/widgetCore.c $(WIDGET_DEPS)
|
||||
$(WOBJDIR)/widgetLayout.o: widgets/widgetLayout.c $(WIDGET_DEPS)
|
||||
$(WOBJDIR)/widgetEvent.o: widgets/widgetEvent.c $(WIDGET_DEPS)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ typedef enum {
|
|||
WidgetTreeItemE,
|
||||
WidgetImageE,
|
||||
WidgetImageButtonE,
|
||||
WidgetCanvasE
|
||||
WidgetCanvasE,
|
||||
WidgetAnsiTermE
|
||||
} WidgetTypeE;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -283,6 +284,34 @@ typedef struct WidgetT {
|
|||
int32_t lastX;
|
||||
int32_t lastY;
|
||||
} canvas;
|
||||
|
||||
struct {
|
||||
uint8_t *cells; // character cells: (ch, attr) pairs, cols*rows*2 bytes
|
||||
int32_t cols; // columns (default 80)
|
||||
int32_t rows; // rows (default 25)
|
||||
int32_t cursorRow; // 0-based cursor row
|
||||
int32_t cursorCol; // 0-based cursor column
|
||||
bool cursorVisible;
|
||||
bool wrapMode; // auto-wrap at right margin
|
||||
bool bold; // SGR bold flag (brightens foreground)
|
||||
bool csiPrivate; // '?' prefix in CSI sequence
|
||||
uint8_t curAttr; // current text attribute (fg | bg<<4)
|
||||
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
|
||||
int32_t params[8]; // CSI parameter accumulator
|
||||
int32_t paramCount; // number of CSI params collected
|
||||
int32_t savedRow; // saved cursor position (SCP)
|
||||
int32_t savedCol;
|
||||
// Scrollback
|
||||
uint8_t *scrollback; // circular buffer of scrollback lines
|
||||
int32_t scrollbackMax; // max lines in scrollback buffer
|
||||
int32_t scrollbackCount; // current number of lines stored
|
||||
int32_t scrollbackHead; // write position (circular index)
|
||||
int32_t scrollPos; // view position (scrollbackCount = live)
|
||||
// Communications interface (all NULL = disconnected)
|
||||
void *commCtx;
|
||||
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
|
||||
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
|
||||
} ansiTerm;
|
||||
} as;
|
||||
} WidgetT;
|
||||
|
||||
|
|
@ -444,6 +473,28 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
|
|||
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color);
|
||||
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y);
|
||||
|
||||
// ============================================================
|
||||
// ANSI Terminal
|
||||
// ============================================================
|
||||
|
||||
// Create an ANSI terminal widget (0 for cols/rows = 80x25 default)
|
||||
WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows);
|
||||
|
||||
// Write data through the ANSI parser (for loading .ANS files or feeding data without a connection)
|
||||
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len);
|
||||
|
||||
// Clear the terminal screen and reset cursor to home
|
||||
void wgtAnsiTermClear(WidgetT *w);
|
||||
|
||||
// Set the communications interface (NULL function pointers = disconnected)
|
||||
void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t));
|
||||
|
||||
// Set the scrollback buffer size in lines (default 500). Clears existing scrollback.
|
||||
void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines);
|
||||
|
||||
// Poll the comm interface for incoming data and process it. Returns bytes processed.
|
||||
int32_t wgtAnsiTermPoll(WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// Operations
|
||||
// ============================================================
|
||||
|
|
|
|||
1145
dvx/widgets/widgetAnsiTerm.c
Normal file
1145
dvx/widgets/widgetAnsiTerm.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -97,6 +97,9 @@ void widgetDestroyChildren(WidgetT *w) {
|
|||
free(child->as.canvas.data);
|
||||
} else if (child->type == WidgetImageButtonE) {
|
||||
free(child->as.imageButton.data);
|
||||
} else if (child->type == WidgetAnsiTermE) {
|
||||
free(child->as.ansiTerm.cells);
|
||||
free(child->as.ansiTerm.scrollback);
|
||||
}
|
||||
|
||||
// Clear popup/drag references if they point to destroyed widgets
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
while (top > 0) {
|
||||
WidgetT *w = stack[--top];
|
||||
|
||||
if (w->focused && (w->type == WidgetTextInputE || w->type == WidgetComboBoxE)) {
|
||||
if (w->focused && (w->type == WidgetTextInputE || w->type == WidgetComboBoxE || w->type == WidgetAnsiTermE)) {
|
||||
focus = w;
|
||||
break;
|
||||
}
|
||||
|
|
@ -137,6 +137,13 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle ANSI terminal key input
|
||||
if (focus->type == WidgetAnsiTermE) {
|
||||
widgetAnsiTermOnKey(focus, key);
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle text input for TextInput and ComboBox
|
||||
char *buf = NULL;
|
||||
int32_t bufSize = 0;
|
||||
|
|
@ -544,6 +551,11 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
widgetTreeViewOnMouse(hit, root, vx, vy);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetAnsiTermE && hit->enabled) {
|
||||
AppContextT *actx = (AppContextT *)root->userData;
|
||||
widgetAnsiTermOnMouse(hit, vx, vy, &actx->font);
|
||||
}
|
||||
|
||||
wgtInvalidate(root);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const
|
|||
// Per-widget paint functions
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
|
@ -122,6 +123,7 @@ void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
// Per-widget calcMinSize functions
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
|
|
@ -149,6 +151,8 @@ void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font);
|
|||
// Per-widget mouse functions
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermOnMouse(WidgetT *hit, int32_t vx, int32_t vy, const BitmapFontT *font);
|
||||
void widgetAnsiTermOnKey(WidgetT *w, int32_t key);
|
||||
void widgetButtonOnMouse(WidgetT *hit);
|
||||
void widgetImageButtonOnMouse(WidgetT *hit);
|
||||
void widgetCanvasOnMouse(WidgetT *hit, int32_t vx, int32_t vy);
|
||||
|
|
|
|||
|
|
@ -136,6 +136,9 @@ void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
|
|||
case WidgetSliderE:
|
||||
widgetSliderCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetAnsiTermE:
|
||||
widgetAnsiTermCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetSeparatorE:
|
||||
if (w->as.separator.vertical) {
|
||||
w->calcMinW = SEPARATOR_THICKNESS;
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFo
|
|||
}
|
||||
return; // handles its own children
|
||||
|
||||
case WidgetAnsiTermE:
|
||||
widgetAnsiTermPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -209,6 +213,9 @@ void wgtDestroy(WidgetT *w) {
|
|||
free(w->as.image.data);
|
||||
} else if (w->type == WidgetCanvasE) {
|
||||
free(w->as.canvas.data);
|
||||
} else if (w->type == WidgetAnsiTermE) {
|
||||
free(w->as.ansiTerm.cells);
|
||||
free(w->as.ansiTerm.scrollback);
|
||||
}
|
||||
|
||||
// Clear static references
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue