diff --git a/include/TeX/TeX.h b/include/TeX/TeX.h index 8407950..4f9efab 100644 --- a/include/TeX/TeX.h +++ b/include/TeX/TeX.h @@ -6,6 +6,8 @@ #define TEX_TEX #include +#include + #include //--- @@ -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. diff --git a/include/TeX/classes.h b/include/TeX/classes.h index 2d6fe82..91ce683 100644 --- a/include/TeX/classes.h +++ b/include/TeX/classes.h @@ -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 for available modes */ int mode; diff --git a/include/TeX/edit.h b/include/TeX/edit.h new file mode 100644 index 0000000..32547d8 --- /dev/null +++ b/include/TeX/edit.h @@ -0,0 +1,46 @@ +//--- +// edit: Structures related to cursor movement and editing +//--- + +#ifndef TEX_EDIT +#define TEX_EDIT + +#include + +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 */ diff --git a/include/TeX/env.h b/include/TeX/env.h index 9bbf5a7..937bebd 100644 --- a/include/TeX/env.h +++ b/include/TeX/env.h @@ -6,6 +6,7 @@ #define TEX_ENV #include +#include 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; diff --git a/include/TeX/flow.h b/include/TeX/flow.h index 9ad4648..b69b07b 100644 --- a/include/TeX/flow.h +++ b/include/TeX/flow.h @@ -5,6 +5,7 @@ #ifndef TEX_FLOW #define TEX_FLOW +#include #include 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 */ diff --git a/include/TeX/node.h b/include/TeX/node.h index efb866d..bf679fc 100644 --- a/include/TeX/node.h +++ b/include/TeX/node.h @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -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 */ diff --git a/src/TeX.c b/src/TeX.c index 2cc177a..03d0786 100644 --- a/src/TeX.c +++ b/src/TeX.c @@ -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); } diff --git a/src/classes.c b/src/classes.c index db2ff89..0695244 100644 --- a/src/classes.c +++ b/src/classes.c @@ -7,10 +7,12 @@ #include #include #include +#include #include #include +#include #include @@ -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); diff --git a/src/env.c b/src/env.c index 3cabdb8..3354de4 100644 --- a/src/env.c +++ b/src/env.c @@ -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); diff --git a/src/flow.c b/src/flow.c index e740aa1..81dfa45 100644 --- a/src/flow.c +++ b/src/flow.c @@ -1,9 +1,13 @@ #include #include #include +#include +#include +#include #include #include +#include //--- // 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; } } diff --git a/src/node.c b/src/node.c index a93f4eb..98ea3e0 100644 --- a/src/node.c +++ b/src/node.c @@ -3,7 +3,10 @@ #include #include #include +#include +#include +#include #include #include @@ -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); } //---