diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index fb2c549ecaf..22f4875d0cb 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -6848,6 +6848,17 @@ def km_3d_view_tool_cursor(params): ) +def km_3d_view_tool_text_select(params): + return ( + "3D View Tool: Edit Text, Select Text", + {"space_type": 'VIEW_3D', "region_type": 'WINDOW'}, + {"items": [ + ("font.selection_set", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None), + ("font.select_word", {"type": 'LEFTMOUSE', "value": 'DOUBLE_CLICK'}, None), + ]}, + ) + + def km_3d_view_tool_select(params, *, fallback): if params.use_tweak_select_passthrough: operator_props = (("vert_without_handles", True),) @@ -8264,6 +8275,7 @@ def generate_keymaps(params=None): *(km_node_editor_tool_select_circle(params, fallback=fallback) for fallback in (False, True)), km_node_editor_tool_links_cut(params), km_3d_view_tool_cursor(params), + km_3d_view_tool_text_select(params), *(km_3d_view_tool_select(params, fallback=fallback) for fallback in (False, True)), *(km_3d_view_tool_select_box(params, fallback=fallback) for fallback in (False, True)), *(km_3d_view_tool_select_circle(params, fallback=fallback) for fallback in (False, True)), diff --git a/scripts/startup/bl_ui/space_toolsystem_toolbar.py b/scripts/startup/bl_ui/space_toolsystem_toolbar.py index dcc0d4e7efe..8864d654b52 100644 --- a/scripts/startup/bl_ui/space_toolsystem_toolbar.py +++ b/scripts/startup/bl_ui/space_toolsystem_toolbar.py @@ -1284,6 +1284,20 @@ class _defs_edit_curve: ) +class _defs_edit_text: + + @ToolDef.from_fn + def select_text(): + return dict( + idname="builtin.select_text", + label="Select Text", + cursor='TEXT', + icon="ops.generic.select_box", + widget=None, + keymap=(), + ) + + class _defs_pose: @ToolDef.from_fn @@ -2980,6 +2994,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel): _defs_transform.shear, ], 'EDIT_TEXT': [ + _defs_edit_text.select_text, _defs_view3d_generic.cursor, None, *_tools_annotate, diff --git a/source/blender/blenkernel/BKE_vfont.h b/source/blender/blenkernel/BKE_vfont.h index f410a0a25b0..851df0a3a1e 100644 --- a/source/blender/blenkernel/BKE_vfont.h +++ b/source/blender/blenkernel/BKE_vfont.h @@ -89,6 +89,9 @@ bool BKE_vfont_to_curve_ex(struct Object *ob, bool *r_text_free, struct CharTrans **r_chartransdata); bool BKE_vfont_to_curve_nubase(struct Object *ob, int mode, struct ListBase *r_nubase); + +int BKE_vfont_cursor_to_text_index(struct Object *ob, float cursor_location[2]); + /** * \warning Expects to have access to evaluated data (i.e. passed object should be evaluated one). */ diff --git a/source/blender/blenkernel/intern/vfont.c b/source/blender/blenkernel/intern/vfont.c index 20b772d6c78..c0a9a1b5854 100644 --- a/source/blender/blenkernel/intern/vfont.c +++ b/source/blender/blenkernel/intern/vfont.c @@ -734,6 +734,25 @@ typedef struct VFontToCurveIter { int status; } VFontToCurveIter; +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name VFont Mouse Cursor to Text Offset + * + * This is an optional argument to `vfont_to_curve` for getting the text + * offset into the string at a mouse cursor location. Used for getting + * text cursor (caret) position or selection range. + * \{ */ +/* Used when translating a mouse cursor location to a position within the string. */ +typedef struct VFontCursor_Params { + /* Mouse cursor location in Object coordinate space as input. */ + float cursor_location[2]; + /* Character position within EditFont::textbuf as output. */ + int r_string_offset; +} VFontCursor_Params; + +/** \} */ + enum { VFONT_TO_CURVE_INIT = 0, VFONT_TO_CURVE_BISECT, @@ -774,6 +793,7 @@ static bool vfont_to_curve(Object *ob, Curve *cu, int mode, VFontToCurveIter *iter_data, + struct VFontCursor_Params *cursor_params, ListBase *r_nubase, const char32_t **r_text, int *r_text_len, @@ -1441,6 +1461,35 @@ static bool vfont_to_curve(Object *ob, } } + if (cursor_params) { + cursor_params->r_string_offset = -1; + for (i = 0; i <= slen; i++, ct++) { + info = &custrinfo[i]; + ascii = mem[i]; + if (info->flag & CU_CHINFO_SMALLCAPS_CHECK) { + ascii = towupper(ascii); + } + ct = &chartransdata[i]; + che = find_vfont_char(vfd, ascii); + float charwidth = char_width(cu, che, info); + float charhalf = (charwidth / 2.0f); + if (cursor_params->cursor_location[1] >= ct->yof - (0.25f * linedist) && + cursor_params->cursor_location[1] <= (ct->yof + (0.75f * linedist))) { + /* On this row. */ + if (cursor_params->cursor_location[0] >= (ct->xof) && + cursor_params->cursor_location[0] <= (ct->xof + charhalf)) { + /* Left half of character. */ + cursor_params->r_string_offset = i; + } + else if (cursor_params->cursor_location[0] >= (ct->xof + charhalf) && + cursor_params->cursor_location[0] <= (ct->xof + charwidth)) { + /* Right half of character. */ + cursor_params->r_string_offset = i + 1; + } + } + } + } + if (ELEM(mode, FO_CURSUP, FO_CURSDOWN, FO_PAGEUP, FO_PAGEDOWN) && iter_data->status == VFONT_TO_CURVE_INIT) { ct = &chartransdata[ef->pos]; @@ -1738,13 +1787,49 @@ bool BKE_vfont_to_curve_ex(Object *ob, }; do { - data.ok &= vfont_to_curve( - ob, cu, mode, &data, r_nubase, r_text, r_text_len, r_text_free, r_chartransdata); + data.ok &= vfont_to_curve(ob, + cu, + mode, + &data, + NULL, + r_nubase, + r_text, + r_text_len, + r_text_free, + r_chartransdata); } while (data.ok && ELEM(data.status, VFONT_TO_CURVE_SCALE_ONCE, VFONT_TO_CURVE_BISECT)); return data.ok; } +int BKE_vfont_cursor_to_text_index(Object *ob, float cursor_location[2]) +{ + Curve *cu = (Curve *)ob->data; + ListBase *r_nubase = &cu->nurb; + + /* TODO: iterating to calculate the scale can be avoided. */ + + VFontToCurveIter data = { + .iteraction = cu->totbox * FONT_TO_CURVE_SCALE_ITERATIONS, + .scale_to_fit = 1.0f, + .word_wrap = true, + .ok = true, + .status = VFONT_TO_CURVE_INIT, + }; + + VFontCursor_Params cursor_params = { + .cursor_location = {cursor_location[0], cursor_location[1]}, + .r_string_offset = -1, + }; + + do { + data.ok &= vfont_to_curve( + ob, cu, FO_CURS, &data, &cursor_params, r_nubase, NULL, NULL, NULL, NULL); + } while (data.ok && ELEM(data.status, VFONT_TO_CURVE_SCALE_ONCE, VFONT_TO_CURVE_BISECT)); + + return cursor_params.r_string_offset; +} + #undef FONT_TO_CURVE_SCALE_ITERATIONS #undef FONT_TO_CURVE_SCALE_THRESHOLD diff --git a/source/blender/editors/curve/curve_intern.h b/source/blender/editors/curve/curve_intern.h index c561d77cf26..783e40d6e6c 100644 --- a/source/blender/editors/curve/curve_intern.h +++ b/source/blender/editors/curve/curve_intern.h @@ -86,6 +86,9 @@ void FONT_OT_text_cut(struct wmOperatorType *ot); void FONT_OT_text_paste(struct wmOperatorType *ot); void FONT_OT_text_paste_from_file(struct wmOperatorType *ot); +void FONT_OT_selection_set(struct wmOperatorType *ot); +void FONT_OT_select_word(struct wmOperatorType *ot); + void FONT_OT_move(struct wmOperatorType *ot); void FONT_OT_move_select(struct wmOperatorType *ot); void FONT_OT_delete(struct wmOperatorType *ot); diff --git a/source/blender/editors/curve/curve_ops.c b/source/blender/editors/curve/curve_ops.c index 4e22008082d..a1eecaece47 100644 --- a/source/blender/editors/curve/curve_ops.c +++ b/source/blender/editors/curve/curve_ops.c @@ -40,6 +40,9 @@ void ED_operatortypes_curve(void) WM_operatortype_append(FONT_OT_text_paste); WM_operatortype_append(FONT_OT_text_paste_from_file); + WM_operatortype_append(FONT_OT_selection_set); + WM_operatortype_append(FONT_OT_select_word); + WM_operatortype_append(FONT_OT_move); WM_operatortype_append(FONT_OT_move_select); WM_operatortype_append(FONT_OT_delete); diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c index 316aa8ed943..5c2f54e5420 100644 --- a/source/blender/editors/curve/editfont.c +++ b/source/blender/editors/curve/editfont.c @@ -1805,6 +1805,143 @@ void FONT_OT_text_insert(wmOperatorType *ot) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Font Selection Operator + * \{ */ + +static int font_cursor_text_index_from_event(bContext *C, Object *obedit, const wmEvent *event) +{ + Curve *cu = obedit->data; + EditFont *ef = cu->editfont; + + /* Calculate a plane from the text object's orientation. */ + float plane[4]; + plane_from_point_normal_v3(plane, obedit->object_to_world[3], obedit->object_to_world[2]); + + /* Convert Mouse location in region to 3D location in world space. */ + float mal_fl[2] = {(float)event->mval[0], (float)event->mval[1]}; + float mouse_loc[3]; + ED_view3d_win_to_3d_on_plane(CTX_wm_region(C), plane, mal_fl, true, mouse_loc); + + /* Convert to object space and scale by font size. */ + mul_m4_v3(obedit->world_to_object, mouse_loc); + + float curs_loc[2] = {mouse_loc[0] / cu->fsize, mouse_loc[1] / cu->fsize}; + return BKE_vfont_cursor_to_text_index(obedit, curs_loc); +} + +static void font_cursor_set_apply(bContext *C, const wmEvent *event) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *ob = DEG_get_evaluated_object(depsgraph, CTX_data_active_object(C)); + Curve *cu = ob->data; + EditFont *ef = cu->editfont; + BLI_assert(ef->len >= 0); + + const int string_offset = font_cursor_text_index_from_event(C, ob, event); + + if (string_offset > ef->len || string_offset < 0) { + return; + } + + cu->curinfo = ef->textbufinfo[ef->pos ? ef->pos - 1 : 0]; + + if (ob->totcol > 0) { + ob->actcol = cu->curinfo.mat_nr; + if (ob->actcol < 1) { + ob->actcol = 1; + } + } + + if (!ef->selboxes && (ef->selstart == 0)) { + if (ef->pos == 0) { + ef->selstart = ef->selend = 1; + } + else { + ef->selstart = ef->selend = string_offset + 1; + } + } + ef->selend = string_offset; + ef->pos = string_offset; + + DEG_id_tag_update(ob->data, ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); +} + +static int font_selection_set_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *obedit = CTX_data_active_object(C); + Curve *cu = obedit->data; + EditFont *ef = cu->editfont; + + font_cursor_set_apply(C, event); + ef->selstart = 0; + ef->selend = 0; + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; +} + +static int font_selection_set_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + switch (event->type) { + case LEFTMOUSE: + if (event->val == KM_RELEASE) { + font_cursor_set_apply(C, event); + return OPERATOR_FINISHED; + } + break; + case MIDDLEMOUSE: + case RIGHTMOUSE: + return OPERATOR_FINISHED; + case MOUSEMOVE: + font_cursor_set_apply(C, event); + break; + } + return OPERATOR_RUNNING_MODAL; +} + +void FONT_OT_selection_set(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Selection"; + ot->idname = "FONT_OT_selection_set"; + ot->description = "Set cursor selection"; + + /* api callbacks */ + ot->invoke = font_selection_set_invoke; + ot->modal = font_selection_set_modal; + ot->poll = ED_operator_editfont; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Word Operator +* \{ */ + +static int font_select_word_exec(bContext *C, wmOperator *UNUSED(op)) +{ + move_cursor(C, NEXT_CHAR, false); + move_cursor(C, PREV_WORD, false); + move_cursor(C, NEXT_WORD, true); + return OPERATOR_FINISHED; +} + +void FONT_OT_select_word(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Word"; + ot->idname = "FONT_OT_select_word"; + ot->description = "Select word under cursor"; + + /* api callbacks */ + ot->exec = font_select_word_exec; + ot->poll = ED_operator_editfont; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Text-Box Add Operator * \{ */ diff --git a/source/blender/windowmanager/intern/wm_toolsystem.c b/source/blender/windowmanager/intern/wm_toolsystem.c index afe876a8317..b742569738f 100644 --- a/source/blender/windowmanager/intern/wm_toolsystem.c +++ b/source/blender/windowmanager/intern/wm_toolsystem.c @@ -729,7 +729,7 @@ static const char *toolsystem_default_tool(const bToolKey *tkey) case CTX_MODE_PARTICLE: return "builtin_brush.Comb"; case CTX_MODE_EDIT_TEXT: - return "builtin.cursor"; + return "builtin.select_text"; } break; case SPACE_IMAGE: