Cursor movement for equation editing

This commit is contained in:
Heath Mitchell 2023-01-27 14:49:28 +00:00
parent af424d1baa
commit 0a4b91edb0
11 changed files with 355 additions and 35 deletions

View File

@ -6,6 +6,8 @@
#define TEX_TEX
#include <TeX/config.h>
#include <TeX/edit.h>
#include <stddef.h>
//---
@ -85,10 +87,12 @@ void TeX_free(struct TeX_Env *formula);
/* TeX_draw(): Render a parsed formula
@formula Formula to render
@context Used to place the cursor; can be set to NULL is there is none.
This function does not draw the cursor but may its position.
@x x coordinate of the left-side of the bounding box (included)
@y y coordinate of the top-size of the bounding box (included)
@color Requested color for the rendered pixels */
void TeX_draw(struct TeX_Env *formula, int x, int y, int color);
void TeX_draw(struct TeX_Env *formula, struct editContext * context, int x, int y, int color);
/* TeX_interpret(): Parse and render, in sequence
This function parses the provided formula, renders it and then frees it.

View File

@ -43,11 +43,13 @@ struct TeX_Class
void TeX_line(int x1, int y1, int x2, int y2, int color);
void TeX_text(char const *str, int x, int y, int color);
@node A node of the described class, for rendering
@x Horizontal coordinate of the requested rendering position
@y Vertical coordinate of the requested rendering position
@color Requested rendering color */
void (*render)(struct TeX_Node const * node, int x, int y, int color);
@node A node of the described class, for rendering
@context Used to place the cursor; can be set to NULL is there is none.
This function does not draw the cursor but may its position.
@x Horizontal coordinate of the requested rendering position
@y Vertical coordinate of the requested rendering position
@color Requested rendering color */
void (*render)(struct TeX_Node const * node, struct editContext * context, int x, int y, int color);
/* Sub- and superscript mode, see <TeX/flow.h> for available modes */
int mode;

46
include/TeX/edit.h Normal file
View File

@ -0,0 +1,46 @@
//---
// edit: Structures related to cursor movement and editing
//---
#ifndef TEX_EDIT
#define TEX_EDIT
#include <stdbool.h>
struct editContext {
bool elementIsText;
union {
struct TeX_Flow* cursorFlow;
struct TeX_Node* cursorText;
};
int offset;
int cursorX;
int cursorY;
};
// An enum for action that the cursor can take
enum cursorAction {
CURSOR_MOVE_LEFT,
CURSOR_MOVE_RIGHT
};
// An enum of return values for the move_cursor function
enum cursorMoveResult {
// The cursor was found and updated, and the recursive search can end
SUCCESS,
// The cursor was not found, and the recursive search should continue
CURSOR_NOT_HERE,
// For these values where the cursor exits, the parent is responsible for
// moving the cursor out of the flow or node.
// It may also choose to ingore the cursor exit and keep the cursor in the
// flow or node, for example if there is no parent to move the cursor to.
// The cursor exited the flow or node out of the right side
CURSOR_PAST_END,
// The cursor exited the flow or node out of the left side
CURSOR_PAST_START
};
#endif /* TEX_EDIT */

View File

@ -6,6 +6,7 @@
#define TEX_ENV
#include <stddef.h>
#include <stdint.h>
struct TeX_Node;
@ -34,7 +35,7 @@ struct TeX_Env
void (*layout)(TeX_Env *env, int display);
/* render(): Render environment */
void (*render)(TeX_Env *env, int x, int y, int color);
void (*render)(TeX_Env *env, struct editContext * context, int x, int y, int color);
/* Dimensions */
uint16_t width;

View File

@ -5,6 +5,7 @@
#ifndef TEX_FLOW
#define TEX_FLOW
#include <TeX/edit.h>
#include <stdint.h>
struct TeX_Node;
@ -19,6 +20,8 @@ struct TeX_Flow
struct TeX_Node *first;
struct TeX_Node *last;
uint32_t elements;
uint16_t width;
uint16_t height;
uint16_t line;
@ -64,9 +67,17 @@ void TeX_flow_free(struct TeX_Flow *flow);
characters and subscripts/superscripts. Modifies the flow's metadata. */
void TeX_flow_layout(struct TeX_Flow *flow, int display);
/* TeX_flow_cursor_action(): Perform a cursor action on a flow
This function performs a cursor action on a flow, like moving left or right.
This is recursive, and should usually be called on the top-level flow.
If it is called on a non-top-level flow, it will restrict the movement to
within the flow; the cursor will be to able to move within in but not exit.
Returns a cursorMoveResult enum; see edit.h for details of the return value. */
enum cursorMoveResult TeX_flow_cursor_action(struct TeX_Flow * flow, struct editContext * context, enum cursorAction action);
/* TeX_flow_render(): Render a flow and all its components
This function renders all horizontal objects in a flow. The layout of the
flow must have been computed. */
void TeX_flow_render(struct TeX_Flow const * flow, int x, int y, int color);
void TeX_flow_render(struct TeX_Flow const * flow, struct editContext * context, int x, int y, int color);
#endif /* TEX_FLOW */

View File

@ -7,6 +7,8 @@
#include <TeX/config.h>
#include <TeX/defs.h>
#include <TeX/edit.h>
#include <stddef.h>
#include <stdint.h>
@ -97,6 +99,9 @@ void TeX_node_layout(struct TeX_Node *node, int display);
/* TeX_node_render(): Render a node and its children
The node's layout must have been computed first. */
void TeX_node_render(struct TeX_Node const * node, int x, int y, int color);
void TeX_node_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color);
enum cursorMoveResult move_cursor_node(struct TeX_Node *node, struct editContext * context, enum cursorAction action);
enum cursorMoveResult cursor_enter_node(struct TeX_Node *node, struct editContext * context, enum cursorAction action);
#endif /* TEX_NODE */

View File

@ -72,9 +72,9 @@ struct TeX_Env *TeX_parse(char const *formula, int display)
}
/* TeX_draw(): Render a parsed formula */
void TeX_draw(struct TeX_Env *formula, int x, int y, int color)
void TeX_draw(struct TeX_Env *formula, struct editContext * context, int x, int y, int color)
{
formula->render(formula, x, y, color);
formula->render(formula, context, x, y, color);
}
/* TeX_interpret(): Parse and render, in sequence */
@ -83,6 +83,6 @@ void TeX_interpret(char const *formula, int display, int x, int y, int color)
struct TeX_Env *env = TeX_parse(formula, display);
if(!env) return;
TeX_draw(env, x, y, color);
TeX_draw(env, NULL, x, y, color);
TeX_free(env);
}

View File

@ -7,10 +7,12 @@
#include <TeX/flow.h>
#include <TeX/env.h>
#include <TeX/classes.h>
#include <TeX/edit.h>
#include <TeX/interface.h>
#include <TeX/defs.h>
#include <stdbool.h>
#include <string.h>
@ -36,8 +38,22 @@ void text_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = h >> 1;
}
void text_render(struct TeX_Node const * node, int x, int y, int color)
void text_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
if (context != NULL && context->elementIsText && context->cursorText == node) {
// Render a cursor
// Temporarily cap the length of the string to the cursor position
char old = node->text[context->offset + 1];
node->text[context->offset + 1] = '\0';
// Measure the width of the string
int w, h;
TeX_size((char const *)node->text, &w, &h);
// Restore the string
node->text[context->offset + 1] = old;
// Update the cursor position to inside the text
context->cursorX = x + w;
context->cursorY = y;
}
TeX_text((void *)node->text, x, y, color);
}
@ -54,9 +70,9 @@ void env_layout(struct TeX_Node *node, int display)
node->line = node->env->line;
}
void env_render(struct TeX_Node const * node, int x, int y, int color)
void env_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
node->env->render(node->env, x, y, color);
node->env->render(node->env, context, x, y, color);
}
//---
@ -96,7 +112,7 @@ void frac_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = h(0) + spacing + thickness / 2;
}
void frac_render(struct TeX_Node const * node, int x, int y, int color)
void frac_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
/* Fraction bar */
for(int i = 0; i < TEX_FRACTION_BAR_THICKNESS; i++)
@ -107,12 +123,14 @@ void frac_render(struct TeX_Node const * node, int x, int y, int color)
/* First argument */
TeX_flow_render(args[0],
context,
x + ((node->width - w(0)) >> 1),
y,
color);
/* Second argument */
TeX_flow_render(args[1],
context,
x + ((node->width - w(1)) >> 1),
y + h(0) + 2*TEX_FRACTION_SPACING + TEX_FRACTION_BAR_THICKNESS,
color);
@ -165,7 +183,7 @@ void leftright_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->width = 1;
}
void leftright_render(struct TeX_Node const * node, int x, int y, int color)
void leftright_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int h = node->height, w = node->width, l = node->line;
@ -285,10 +303,10 @@ void supsubscript_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = l(0);
}
void supsubscript_render(struct TeX_Node const * node, int x, int y,
int color)
void supsubscript_render(struct TeX_Node const * node, struct editContext * context,
int x, int y, int color)
{
TeX_flow_render(args[0], x, y, color);
TeX_flow_render(args[0], context, x, y, color);
}
//---
@ -306,8 +324,8 @@ void sum_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = 4;
}
void sum_render(TEX_UNUSED struct TeX_Node const * node, int x, int y,
int color)
void sum_render(TEX_UNUSED struct TeX_Node const * node, struct editContext * context,
int x, int y, int color)
{
TeX_line(x, y, x + 8, y, color);
TeX_line(x, y, x + 4, y + 4, color);
@ -330,7 +348,7 @@ void prod_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = 4;
}
void prod_render(TEX_UNUSED struct TeX_Node const * node, int x, int y,
void prod_render(TEX_UNUSED struct TeX_Node const * node, struct editContext * context, int x, int y,
int color)
{
TeX_line(x, y, x + 8, y, color);
@ -353,7 +371,7 @@ void int_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = TEX_INT_HEIGHT >> 1;
}
void int_render(struct TeX_Node const * node, int x, int y, int color)
void int_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int h = node->height;
@ -397,7 +415,7 @@ void vec_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = l(0) + TEX_VEC_SPACING + arrow_height;
}
void vec_render(struct TeX_Node const * node, int x, int y, int color)
void vec_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int length = TEX_VEC_ARROW_LENGTH;
int arrow_height = 2 * length + 1;
@ -411,6 +429,7 @@ void vec_render(struct TeX_Node const * node, int x, int y, int color)
/* First argument */
TeX_flow_render(args[0],
context,
x + TEX_VEC_MARGIN,
y + TEX_VEC_SPACING + arrow_height,
color);
@ -431,7 +450,7 @@ void lim_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = h >> 1;
}
void lim_render(TEX_UNUSED struct TeX_Node const * node, int x, int y,
void lim_render(TEX_UNUSED struct TeX_Node const * node, struct editContext * context, int x, int y,
int color)
{
TeX_text("lim", x, y, color);
@ -492,7 +511,7 @@ void sqrt_layout(struct TeX_Node *node, TEX_UNUSED int display)
node->line = l(0) + TEX_SQRT_MARGIN + 1;
}
void sqrt_render(struct TeX_Node const * node, int x, int y, int color)
void sqrt_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
int slanted = TEX_SQRT_SLANTED && h(0) <= TEX_SQRT_SLANT_MAX;
int w = node->width;
@ -509,6 +528,7 @@ void sqrt_render(struct TeX_Node const * node, int x, int y, int color)
TeX_line(x + w - 1, y, x + w - 1, y + TEX_SQRT_BAR_LENGTH, color);
TeX_flow_render(args[0],
context,
x + base + TEX_SQRT_MARGIN,
y + 1 + TEX_SQRT_MARGIN,
color);

View File

@ -63,10 +63,10 @@ static void primary_layout(struct TeX_Env *env, int display)
env->line = p->flow->line;
}
static void primary_render(struct TeX_Env *env, int x, int y, int color)
static void primary_render(struct TeX_Env *env, struct editContext * context, int x, int y, int color)
{
struct TeX_Env_Primary *p = (void *)env;
TeX_flow_render(p->flow, x, y, color);
TeX_flow_render(p->flow, context, x, y, color);
}
struct TeX_Env *TeX_env_primary(void)
@ -200,7 +200,7 @@ static void matrix_layout(struct TeX_Env *env, int display)
env->line = env->height >> 1;
}
static void matrix_render(struct TeX_Env *env, int x, int y, int color)
static void matrix_render(struct TeX_Env *env, struct editContext * context, int x, int y, int color)
{
struct TeX_Env_Matrix *m = (void *)env;
@ -218,6 +218,7 @@ static void matrix_render(struct TeX_Env *env, int x, int y, int color)
int rh = m->rowline[row] + m->rowdepth[row];
if(flow) TeX_flow_render(flow,
context,
x + dx + ((cw - flow->width) >> 1),
y + dy + ((rh - flow->height) >> 1),
color);

View File

@ -1,9 +1,13 @@
#include <TeX/node.h>
#include <TeX/flow.h>
#include <TeX/classes.h>
#include <TeX/interface.h>
#include <TeX/edit.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//---
// Flow construction and destruction functions
@ -19,6 +23,7 @@ struct TeX_Flow *TeX_flow_add_node(struct TeX_Flow *flow, struct TeX_Node*node)
{
flow->last->next = node;
flow->last = node;
flow->elements++;
return flow;
}
@ -27,6 +32,7 @@ struct TeX_Flow *TeX_flow_add_node(struct TeX_Flow *flow, struct TeX_Node*node)
if(!flow) return NULL;
flow->first = flow->last = node;
flow->elements = 1;
return flow;
}
@ -378,15 +384,125 @@ void TeX_flow_layout(struct TeX_Flow *flow, int display)
flow->width = max(x - TEX_LAYOUT_SPACING, 0);
}
enum cursorMoveResult TeX_flow_cursor_action(struct TeX_Flow * flow, struct editContext * context, enum cursorAction action) {
if (context->cursorFlow != flow)
{
// Search all the nodes in the flow for the cursor
struct TeX_Node* node = flow->first;
int offset = 0;
while (node != NULL)
{
int returnCode = move_cursor_node(node, context, action);
if (returnCode == SUCCESS)
{
return SUCCESS;
}
else if (returnCode == CURSOR_NOT_HERE)
{
// Keep searching
}
else if (returnCode == CURSOR_PAST_END)
{
// Put the cursor in this flow after the node
context->elementIsText = false;
context->cursorFlow = flow;
context->offset = offset + 1;
return SUCCESS;
}
else if (returnCode == CURSOR_PAST_START)
{
// Put the cursor in this flow before the node
context->elementIsText = false;
context->cursorFlow = flow;
context->offset = offset;
return SUCCESS;
}
node = node->next;
offset++;
}
return CURSOR_NOT_HERE;
}
switch (action)
{
case CURSOR_MOVE_LEFT:
if (context->offset <= 0)
{
context->offset = 0;
return CURSOR_PAST_START;
}
else
{
// Find the node at offset
// As this is a linked list we have to iterate through it which is inefficient
// TODO: Change it to a normal array, or store the pointer to the node in the editContext (which will need a doubly linked list)
struct TeX_Node* node = flow->first;
for (int i = 0; i < context->offset - 1; i++)
{
node = node->next;
}
int returncode = cursor_enter_node(node, context, action);
if (returncode == CURSOR_PAST_START)
{
// Put the cursor in this flow before the node
context->offset--;
}
}
break;
case CURSOR_MOVE_RIGHT:
if (context->offset >= flow->elements)
{
context->offset = flow->elements;
return CURSOR_PAST_END;
}
else
{
// Find the node at offset
// As this is a linked list we have to interate through it which is inefficient
// TODO: Change it to a normal array, or store the pointer to the node in the editContext (which will need a doubly linked list)
struct TeX_Node* node = flow->first;
for (int i = 0; i < context->offset; i++)
{
node = node->next;
}
int returncode = cursor_enter_node(node, context, action);
if (returncode == CURSOR_PAST_END)
{
context->offset++;
}
}
break;
}
return SUCCESS;
}
/* TeX_flow_render(): Render a flow and all its components */
void TeX_flow_render(struct TeX_Flow const * flow, int x, int y, int color)
void TeX_flow_render(struct TeX_Flow const * flow, struct editContext * context, int x, int y, int color)
{
struct TeX_Node const * node;
int offset = 0;
for(node = flow->first; node; node = node->next)
{
TeX_node_render(node, x + node->x, y + flow->line -
node->line + node->l, color);
if (context != NULL && !context->elementIsText && context->cursorFlow == flow && context->offset == offset)
{
// Render a cursor
context->cursorX = x + node->x;
context->cursorY = y + flow->line - 4;
}
TeX_node_render(node, context, x + node->x,
y + flow->line - node->line + node->l, color);
offset++;
}
if (context != NULL && !context->elementIsText &&context->cursorFlow == flow && context->offset == offset)
{
// Render a cursor
context->cursorX = x + flow->width;
context->cursorY = y + flow->line - 4;
}
}

View File

@ -3,7 +3,10 @@
#include <TeX/flow.h>
#include <TeX/env.h>
#include <TeX/classes.h>
#include <TeX/interface.h>
#include <TeX/edit.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
@ -15,6 +18,117 @@
((node)->type == TEX_NODECLASS_TEXT || \
(node)->type == TEX_NODECLASS_ENV)
enum cursorMoveResult move_cursor_node(struct TeX_Node *node, struct editContext * context, enum cursorAction action)
{
if (node->type == 0)
{
// Text
if (!context->elementIsText || context->cursorText != node)
return CURSOR_NOT_HERE;
if (action == CURSOR_MOVE_LEFT)
{
if (context->offset <= 0)
return CURSOR_PAST_START;
context->offset--;
return SUCCESS;
}
if (action == CURSOR_MOVE_RIGHT) {
if (context->offset >= strlen(node->text) - 2)
return CURSOR_PAST_END;
context->offset++;
return SUCCESS;
}
}
if (node->type > 1)
{
if (node->args[0] == NULL)
return CURSOR_NOT_HERE;
int returnCode = TeX_flow_cursor_action(node->args[0], context, action);
if (returnCode == SUCCESS)
return returnCode;
if (returnCode == CURSOR_PAST_END)
{
// Go to the denominator
if (node->args[1] == NULL)
return CURSOR_PAST_END;
context->elementIsText = false;
context->cursorFlow = node->args[1];
context->offset = 0;
return SUCCESS;
}
if (returnCode != CURSOR_NOT_HERE)
return returnCode;
if (node->args[1] == NULL)
return CURSOR_NOT_HERE;
returnCode = TeX_flow_cursor_action(node->args[1], context, action);
if (returnCode == CURSOR_PAST_START)
{
// Go to the numerator
context->elementIsText = false;
context->cursorFlow = node->args[0];
context->offset = node->args[0]->elements;
return SUCCESS;
}
return returnCode;
}
// TODO: matrices
return CURSOR_NOT_HERE;
}
enum cursorMoveResult cursor_enter_node(struct TeX_Node *node, struct editContext * context, enum cursorAction action)
{
enum cursorMoveResult skipPast = action == CURSOR_MOVE_RIGHT ? CURSOR_PAST_END : CURSOR_PAST_START;
if (node->type == 0)
{
int len = strlen(node->text);
if (len == 1) return skipPast;
// Text
context->elementIsText = true;
context->cursorText = node;
context->offset = action == CURSOR_MOVE_RIGHT ? 0 : len - 2;
return SUCCESS;
}
if (node->type > 1)
{
if (action == CURSOR_MOVE_RIGHT)
{
// Enter the numerator
if (node->args[0] == NULL)
return CURSOR_PAST_END;
context->elementIsText = false;
context->cursorFlow = node->args[0];
context->offset = 0;
return SUCCESS;
}
else if (action == CURSOR_MOVE_LEFT)
{
// Enter the denominator
struct TeX_Flow *denom = node->args[1];
if (denom == NULL)
denom = node->args[0];
if (denom == NULL)
return CURSOR_PAST_START;
context->elementIsText = false;
context->cursorFlow = denom;
context->offset = denom->elements;
return SUCCESS;
}
}
// TODO: matrices
return skipPast;
}
/* TeX_node_text(): Make a text node */
struct TeX_Node *TeX_node_text(char const *utf8)
{
@ -131,13 +245,13 @@ void TeX_node_layout(struct TeX_Node *node, int display)
}
/* TeX_node_render(): Render a node and its children */
void TeX_node_render(struct TeX_Node const * node, int x, int y, int color)
void TeX_node_render(struct TeX_Node const * node, struct editContext * context, int x, int y, int color)
{
/* Don't render hidden or invalid elements */
if(node->hidden) return;
/* For non trivial classes, use the class' special function */
TeX_class_of(node)->render(node, x, y, color);
TeX_class_of(node)->render(node, context, x, y, color);
}
//---