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
|
resizable windows, dropdown menus, scrollbars, and a declarative widget/layout
|
||||||
system with buttons, checkboxes, radios, text inputs, dropdowns, combo boxes,
|
system with buttons, checkboxes, radios, text inputs, dropdowns, combo boxes,
|
||||||
sliders, progress bars, tab controls, tree views, toolbars, status bars,
|
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
|
## 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
|
color. All operations clip to the canvas bounds. Colors are in display
|
||||||
pixel format (use `packColor()` to create them).
|
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
|
### Spacing and dividers
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ LIBDIR = ../lib
|
||||||
|
|
||||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c
|
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/widgetLayout.c \
|
||||||
widgets/widgetEvent.c \
|
widgets/widgetEvent.c \
|
||||||
widgets/widgetOps.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 file dependencies
|
||||||
WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
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)/widgetCore.o: widgets/widgetCore.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetLayout.o: widgets/widgetLayout.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetLayout.o: widgets/widgetLayout.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetEvent.o: widgets/widgetEvent.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetEvent.o: widgets/widgetEvent.c $(WIDGET_DEPS)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ typedef enum {
|
||||||
WidgetTreeItemE,
|
WidgetTreeItemE,
|
||||||
WidgetImageE,
|
WidgetImageE,
|
||||||
WidgetImageButtonE,
|
WidgetImageButtonE,
|
||||||
WidgetCanvasE
|
WidgetCanvasE,
|
||||||
|
WidgetAnsiTermE
|
||||||
} WidgetTypeE;
|
} WidgetTypeE;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -283,6 +284,34 @@ typedef struct WidgetT {
|
||||||
int32_t lastX;
|
int32_t lastX;
|
||||||
int32_t lastY;
|
int32_t lastY;
|
||||||
} canvas;
|
} 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;
|
} as;
|
||||||
} WidgetT;
|
} 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);
|
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);
|
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
|
// 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);
|
free(child->as.canvas.data);
|
||||||
} else if (child->type == WidgetImageButtonE) {
|
} else if (child->type == WidgetImageButtonE) {
|
||||||
free(child->as.imageButton.data);
|
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
|
// 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) {
|
while (top > 0) {
|
||||||
WidgetT *w = stack[--top];
|
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;
|
focus = w;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +137,13 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle ANSI terminal key input
|
||||||
|
if (focus->type == WidgetAnsiTermE) {
|
||||||
|
widgetAnsiTermOnKey(focus, key);
|
||||||
|
wgtInvalidate(focus);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle text input for TextInput and ComboBox
|
// Handle text input for TextInput and ComboBox
|
||||||
char *buf = NULL;
|
char *buf = NULL;
|
||||||
int32_t bufSize = 0;
|
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);
|
widgetTreeViewOnMouse(hit, root, vx, vy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hit->type == WidgetAnsiTermE && hit->enabled) {
|
||||||
|
AppContextT *actx = (AppContextT *)root->userData;
|
||||||
|
widgetAnsiTermOnMouse(hit, vx, vy, &actx->font);
|
||||||
|
}
|
||||||
|
|
||||||
wgtInvalidate(root);
|
wgtInvalidate(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@ void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const
|
||||||
// Per-widget paint functions
|
// 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 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 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);
|
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
|
// Per-widget calcMinSize functions
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetCanvasCalcMinSize(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
|
// 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 widgetButtonOnMouse(WidgetT *hit);
|
||||||
void widgetImageButtonOnMouse(WidgetT *hit);
|
void widgetImageButtonOnMouse(WidgetT *hit);
|
||||||
void widgetCanvasOnMouse(WidgetT *hit, int32_t vx, int32_t vy);
|
void widgetCanvasOnMouse(WidgetT *hit, int32_t vx, int32_t vy);
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,9 @@ void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
|
||||||
case WidgetSliderE:
|
case WidgetSliderE:
|
||||||
widgetSliderCalcMinSize(w, font);
|
widgetSliderCalcMinSize(w, font);
|
||||||
break;
|
break;
|
||||||
|
case WidgetAnsiTermE:
|
||||||
|
widgetAnsiTermCalcMinSize(w, font);
|
||||||
|
break;
|
||||||
case WidgetSeparatorE:
|
case WidgetSeparatorE:
|
||||||
if (w->as.separator.vertical) {
|
if (w->as.separator.vertical) {
|
||||||
w->calcMinW = SEPARATOR_THICKNESS;
|
w->calcMinW = SEPARATOR_THICKNESS;
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,10 @@ void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFo
|
||||||
}
|
}
|
||||||
return; // handles its own children
|
return; // handles its own children
|
||||||
|
|
||||||
|
case WidgetAnsiTermE:
|
||||||
|
widgetAnsiTermPaint(w, d, ops, font, colors);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -209,6 +213,9 @@ void wgtDestroy(WidgetT *w) {
|
||||||
free(w->as.image.data);
|
free(w->as.image.data);
|
||||||
} else if (w->type == WidgetCanvasE) {
|
} else if (w->type == WidgetCanvasE) {
|
||||||
free(w->as.canvas.data);
|
free(w->as.canvas.data);
|
||||||
|
} else if (w->type == WidgetAnsiTermE) {
|
||||||
|
free(w->as.ansiTerm.cells);
|
||||||
|
free(w->as.ansiTerm.scrollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear static references
|
// Clear static references
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue