From 718314e8b9301f826f35a05fcd9567572bd62b54 Mon Sep 17 00:00:00 2001 From: Thomas White Date: Sat, 21 Dec 2019 19:43:13 +0100 Subject: Vertical cursor motion in narrative and slide text boxes --- libstorycode/gtk/gtknarrativeview.c | 100 +++++++++++++++++++++++++++++++++++- libstorycode/gtk/gtknarrativeview.h | 1 + libstorycode/gtk/gtkslideview.c | 78 +++++++++++++++++++++++++++- libstorycode/gtk/gtkslideview.h | 2 + 4 files changed, 177 insertions(+), 4 deletions(-) diff --git a/libstorycode/gtk/gtknarrativeview.c b/libstorycode/gtk/gtknarrativeview.c index 97da45b..b26c86b 100644 --- a/libstorycode/gtk/gtknarrativeview.c +++ b/libstorycode/gtk/gtknarrativeview.c @@ -673,6 +673,14 @@ static void sort_positions(struct edit_pos *a, struct edit_pos *b) } +static void set_cursor_h_pos(GtkNarrativeView *e) +{ + double x, y, h; + gtknv_get_cursor_pos(e->n, e->cpos, &x, &y, &h); + e->cursor_h_pos = x; /* Real position, not relative to PangoLayout */ +} + + static void gtknv_cursor_moveh(Narrative *n, struct edit_pos *cp, signed int dir) { struct narrative_item *item = &n->items[cp->para]; @@ -777,6 +785,7 @@ static void gtknv_do_backspace(GtkNarrativeView *e, signed int dir) p2 = e->cpos; p1 = p2; gtknv_cursor_moveh(e->n, &p1, dir); + set_cursor_h_pos(e); } sort_positions(&p1, &p2); @@ -838,6 +847,7 @@ static void gtknv_insert_text(char *t, GtkNarrativeView *e) gtknv_insert_text_in_paragraph(&e->n->items[e->cpos.para], off, t); rewrap_range(e, e->cpos.para, e->cpos.para); gtknv_cursor_moveh(e->n, &e->cpos, +1); + set_cursor_h_pos(e); } update_size(e); @@ -886,6 +896,89 @@ static int gtknv_find_cursor(Narrative *n, double x, double y, struct edit_pos * } +static void gtknv_cursor_movev(GtkNarrativeView *e, signed int dir) +{ + int lineno; + + assert(dir != 0); + + gtknv_unset_selection(e); + + if ( narrative_item_is_text(e->n, e->cpos.para) ) { + + /* Starting in text */ + PangoLayout *layout = e->n->items[e->cpos.para].layout; + + if ( layout == NULL ) return; + pango_layout_index_to_line_x(layout, e->cpos.pos, e->cpos.trail, + &lineno, NULL); + if ( dir > 0 ) { + if ( lineno == pango_layout_get_line_count(layout)-1 ) { + /* Move to next item */ + if ( e->cpos.para == e->n->n_items-1 ) { + /* No next paragraph to move to */ + } else { + e->cpos.para++; + lineno = 0; + } + } else { + lineno++; + } + } else { + if ( lineno == 0 ) { + /* Move to previous paragraph */ + if ( e->cpos.para == 0 ) { + /* No previous paragraph to move to */ + } else { + e->cpos.para--; + if ( narrative_item_is_text(e->n, e->cpos.para) ) { + layout = e->n->items[e->cpos.para].layout; + lineno = pango_layout_get_line_count(layout)-1; + } + } + } else { + lineno--; + } + } + + } else { + + /* Starting in non-text */ + if ( dir > 0 ) { + /* Move to next item */ + if ( e->cpos.para == e->n->n_items-1 ) { + /* No next paragraph to move to */ + } else { + e->cpos.para++; + lineno = 0; + } + } else { + /* Move to previous paragraph */ + if ( e->cpos.para == 0 ) { + /* No previous paragraph to move to */ + } else { + e->cpos.para--; + if ( narrative_item_is_text(e->n, e->cpos.para) ) { + PangoLayout *layout = e->n->items[e->cpos.para].layout; + lineno = pango_layout_get_line_count(layout)-1; + } + } + } + } + + if ( narrative_item_is_text(e->n, e->cpos.para) ) { + /* Use the "virtual" x-position to place the cursor in this line */ + PangoLayoutLine *line; + double x; + line = pango_layout_get_line_readonly(e->n->items[e->cpos.para].layout, lineno); + /* Subtract offset to PangoLayout */ + x = e->cursor_h_pos - e->n->items[e->cpos.para].space_l; + pango_layout_line_x_to_index(line, pango_units_from_double(x), + &e->cpos.pos, &e->cpos.trail); + } /* else do nothing */ +} + + static gboolean gtknv_button_press_sig(GtkWidget *da, GdkEventButton *event, GtkNarrativeView *e) { @@ -900,6 +993,7 @@ static gboolean gtknv_button_press_sig(GtkWidget *da, GdkEventButton *event, gtknv_find_cursor(e->n, x, y, &e->sel_start); e->sel_end = e->sel_start; e->cpos = e->sel_start; + set_cursor_h_pos(e); if ( event->type == GDK_2BUTTON_PRESS ) { struct narrative_item *item = &e->n->items[e->cpos.para]; @@ -977,6 +1071,7 @@ static gboolean gtknv_key_press_sig(GtkWidget *da, GdkEventKey *event, case GDK_KEY_Left : gtknv_cursor_moveh(e->n, &e->cpos, -1); check_cursor_visible(e); + set_cursor_h_pos(e); gtknv_redraw(e); claim = 1; break; @@ -984,19 +1079,20 @@ static gboolean gtknv_key_press_sig(GtkWidget *da, GdkEventKey *event, case GDK_KEY_Right : gtknv_cursor_moveh(e->n, &e->cpos, +1); check_cursor_visible(e); + set_cursor_h_pos(e); gtknv_redraw(e); claim = 1; break; case GDK_KEY_Up : - gtknv_cursor_moveh(e->n, &e->cpos, -1); + gtknv_cursor_movev(e, -1); check_cursor_visible(e); gtknv_redraw(e); claim = 1; break; case GDK_KEY_Down : - gtknv_cursor_moveh(e->n, &e->cpos, +1); + gtknv_cursor_movev(e, +1); check_cursor_visible(e); gtknv_redraw(e); claim = 1; diff --git a/libstorycode/gtk/gtknarrativeview.h b/libstorycode/gtk/gtknarrativeview.h index 18c97bb..02045fd 100644 --- a/libstorycode/gtk/gtknarrativeview.h +++ b/libstorycode/gtk/gtknarrativeview.h @@ -85,6 +85,7 @@ struct _gtknarrativeview /* Location of the cursor */ struct edit_pos cpos; + double cursor_h_pos; /* Place the cursor is trying to be */ /* Rubber band boxes and related stuff */ enum narrative_drag_status drag_status; diff --git a/libstorycode/gtk/gtkslideview.c b/libstorycode/gtk/gtkslideview.c index 986f666..f4f4572 100644 --- a/libstorycode/gtk/gtkslideview.c +++ b/libstorycode/gtk/gtkslideview.c @@ -662,6 +662,24 @@ static void do_resize(GtkSlideView *e, double x, double y, double w, double h) } +static void set_sv_cursor_h_pos(GtkSlideView *e) +{ + double x, y, h; + double slide_w, slide_h; + double pad_l, pad_r, pad_t, pad_b; + Stylesheet *stylesheet; + + stylesheet = narrative_get_stylesheet(e->n); + slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h); + slide_item_get_padding(e->cursor_frame, stylesheet, &pad_l, &pad_r, &pad_t, &pad_b, + slide_w, slide_h); + + gtksv_get_cursor_pos(e->cursor_frame, stylesheet, e->cpos, + slide_w, slide_h, &x, &y, &h); + e->cursor_h_pos = x - pad_l; +} + + static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event, GtkSlideView *e) { @@ -720,6 +738,7 @@ static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event, e->cursor_frame = clicked; gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->cpos, slide_w, slide_h); + set_sv_cursor_h_pos(e); e->start_corner_x = x; e->start_corner_y = y; @@ -732,6 +751,7 @@ static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event, e->drag_reason = DRAG_REASON_TEXTSEL; gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->sel_start, slide_w, slide_h); + set_sv_cursor_h_pos(e); e->sel_end = e->sel_start; } @@ -767,6 +787,7 @@ static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event, e->cursor_frame = clicked; gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->cpos, slide_w, slide_h); + set_sv_cursor_h_pos(e); } @@ -838,6 +859,7 @@ static gboolean gtksv_motion_sig(GtkWidget *da, GdkEventMotion *event, GtkSlideV gtksv_find_cursor(e->cursor_frame, stylesheet, x-frx, y-fry, &e->sel_end, slide_w, slide_h); e->cpos = e->sel_end; + set_sv_cursor_h_pos(e); gtksv_redraw(e); break; @@ -984,6 +1006,56 @@ static size_t gtksv_end_offset_of_para(SlideItem *item, int pnum) } +static void gtksv_cursor_movev(GtkSlideView *e, signed int dir) +{ + int lineno; + PangoLayout *layout; + PangoLayoutLine *line; + + assert(dir != 0); + if ( !is_text(e->cursor_frame->type) ) return; + if ( e->cursor_frame->paras[e->cpos.para].layout == NULL ) return; + gtksv_unset_selection(e); + + layout = e->cursor_frame->paras[e->cpos.para].layout; + pango_layout_index_to_line_x(layout, e->cpos.pos, e->cpos.trail, + &lineno, NULL); + + if ( dir > 0 ) { + if ( lineno == pango_layout_get_line_count(layout)-1 ) { + /* Move to next paragraph */ + if ( e->cpos.para == e->cursor_frame->n_paras-1 ) { + /* No next paragraph to move to */ + } else { + e->cpos.para++; + layout = e->cursor_frame->paras[e->cpos.para].layout; + lineno = 0; + } + } else { + lineno++; + } + } else { + if ( lineno == 0 ) { + /* Move to previous paragraph */ + if ( e->cpos.para == 0 ) { + /* No previous paragraph to move to */ + } else { + e->cpos.para--; + layout = e->cursor_frame->paras[e->cpos.para].layout; + lineno = pango_layout_get_line_count(layout)-1; + } + } else { + lineno--; + } + } + + /* Now, use the "virtual" x-position to place the cursor in this line */ + line = pango_layout_get_line_readonly(layout, lineno); + pango_layout_line_x_to_index(line, pango_units_from_double(e->cursor_h_pos), + &e->cpos.pos, &e->cpos.trail); +} + + static void gtksv_cursor_moveh(GtkSlideView *e, struct slide_pos *cp, signed int dir) { int np = cp->pos; @@ -1175,24 +1247,26 @@ static gboolean gtksv_key_press_sig(GtkWidget *da, GdkEventKey *event, case GDK_KEY_Left : gtksv_cursor_moveh(e, &e->cpos, -1); + set_sv_cursor_h_pos(e); gtksv_redraw(e); claim = 1; break; case GDK_KEY_Right : gtksv_cursor_moveh(e, &e->cpos, +1); + set_sv_cursor_h_pos(e); gtksv_redraw(e); claim = 1; break; case GDK_KEY_Up : - gtksv_cursor_moveh(e, &e->cpos, -1); + gtksv_cursor_movev(e, -1); gtksv_redraw(e); claim = 1; break; case GDK_KEY_Down : - gtksv_cursor_moveh(e, &e->cpos, +1); + gtksv_cursor_movev(e, +1); gtksv_redraw(e); claim = 1; break; diff --git a/libstorycode/gtk/gtkslideview.h b/libstorycode/gtk/gtkslideview.h index a3dadff..aecf44c 100644 --- a/libstorycode/gtk/gtkslideview.h +++ b/libstorycode/gtk/gtkslideview.h @@ -106,6 +106,8 @@ struct _gtkslideview /* Location of the cursor */ SlideItem *cursor_frame; struct slide_pos cpos; + double cursor_h_pos; /* Horizontal position the cursor would have, + * if the current line were long enough. */ /* Border surrounding actual slide within drawingarea */ double border_offs_x; -- cgit v1.2.3