UI: Blinking Text Cursors #116595

Open
Harley Acheson wants to merge 14 commits from Harley/blender:TextCursors into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
21 changed files with 265 additions and 37 deletions

View File

@ -84,6 +84,7 @@ const UserDef U_default = {
.menuthreshold1 = 5,
.menuthreshold2 = 2,
.app_template = "",
.text_cursor_blink = true,
/** Initialized by #UI_theme_init_default. */
.themes = {NULL},

View File

@ -282,6 +282,7 @@ class USERPREF_PT_interface_editors(InterfacePanel, CenterAlignMixIn, Panel):
col.prop(view, "color_picker_type")
col.row().prop(view, "header_align")
col.prop(view, "factor_display_type")
col.prop(system, "text_cursor_blink")
class USERPREF_PT_interface_temporary_windows(InterfacePanel, CenterAlignMixIn, Panel):

View File

@ -89,8 +89,10 @@ struct SpaceType {
/* Listeners can react to bContext changes */
void (*listener)(const wmSpaceTypeListenerParams *params);
/* Called when the mouse moves into the area. */
void (*activate)(bContext *C, ScrArea *sa);
/* called when the mouse moves out of the area */
void (*deactivate)(ScrArea *area);
void (*deactivate)(bContext *C, ScrArea *area);
/** Refresh context, called after file-reads, #ED_area_tag_refresh(). */
void (*refresh)(const bContext *C, ScrArea *area);

View File

@ -950,6 +950,10 @@ void blo_do_versions_userdef(UserDef *userdef)
}
}
if (!USER_VERSION_ATLEAST(402, 23)) {
userdef->text_cursor_blink = true;
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.

View File

@ -42,6 +42,11 @@ class AbstractViewItem;
void UI_but_func_set(uiBut *but, std::function<void(bContext &)> func);
void UI_but_func_pushed_state_set(uiBut *but, std::function<bool(const uiBut &)> func);
/* Blink value of 530ms is default Windows rate. */
#define TEXT_CURSOR_BLINK_RATE 0.53f
/* Off position of cursor is dimmer, not really off. */
#define TEXT_CURSOR_BLINK_OFF_OPACITY 0.35f
namespace blender::ui {
class AbstractGridView;

View File

@ -3069,6 +3069,7 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, con
/* XXX pass on as arg. */
uiFontStyle fstyle = UI_style_get()->widget;
const float aspect = but->block->aspect;
but->is_cursor_bright = true;
float startx = but->rect.xmin;
float starty_dummy = 0.0f;
@ -3201,6 +3202,7 @@ static void ui_textedit_move(uiBut *but,
const int len = strlen(str);
const int pos_prev = but->pos;
const bool has_sel = (but->selend - but->selsta) > 0;
but->is_cursor_bright = true;
ui_but_update(but);
@ -3413,6 +3415,15 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
MEM_SAFE_FREE(data->str);
if (data->wm->cursor_blink_timer) {
WM_event_timer_remove_notifier(data->wm, win, data->wm->cursor_blink_timer);
}
if (U.text_cursor_blink) {
but->is_cursor_bright = true;
data->wm->cursor_blink_timer = WM_event_timer_add(
data->wm, win, TIMER, TEXT_CURSOR_BLINK_RATE);
}
#ifdef USE_DRAG_MULTINUM
/* this can happen from multi-drag */
if (data->applied_interactive) {
@ -3527,6 +3538,11 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
{
wmWindow *win = data->window;
if (data->wm->cursor_blink_timer) {
WM_event_timer_remove_notifier(data->wm, win, data->wm->cursor_blink_timer);
data->wm->cursor_blink_timer = nullptr;
}
if (but) {
if (UI_but_is_utf8(but)) {
const int strip = BLI_str_utf8_invalid_strip(but->editstr, strlen(but->editstr));
@ -11553,7 +11569,13 @@ static int ui_region_handler(bContext *C, const wmEvent *event, void * /*userdat
if (retval == WM_UI_HANDLER_CONTINUE) {
if (but) {
retval = ui_handle_button_event(C, event, but);
if (event->type == TIMER) {
but->is_cursor_bright = !but->is_cursor_bright;
ED_region_tag_redraw(region);
}
else {
retval = ui_handle_button_event(C, event, but);
}
}
else {
retval = ui_handle_button_over(C, event, region);

View File

@ -185,6 +185,7 @@ struct uiBut {
std::string drawstr;
bool is_cursor_bright = true;
char *placeholder = nullptr;
rctf rect = {}; /* block relative coords */

View File

@ -1998,13 +1998,14 @@ static void widget_draw_text(const uiFontStyle *fstyle,
/* We are drawing on top of widget bases. Flush cache. */
GPU_blend(GPU_BLEND_ALPHA);
UI_widgetbase_draw_cache_flush();
GPU_blend(GPU_BLEND_NONE);
uint pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformThemeColor(TH_WIDGET_TEXT_CURSOR);
immUniformThemeColorAlpha(
TH_WIDGET_TEXT_CURSOR,
(but->is_cursor_bright || !U.text_cursor_blink) ? 1.0f : TEXT_CURSOR_BLINK_OFF_OPACITY);
/* draw cursor */
immRecti(pos,
@ -2014,6 +2015,7 @@ static void widget_draw_text(const uiFontStyle *fstyle,
rect->ymax - U.pixelsize);
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
#ifdef WITH_INPUT_IME
/* IME candidate window uses cursor position. */

View File

@ -982,7 +982,14 @@ void ED_screen_set_active_region(bContext *C, wmWindow *win, const int xy[2])
LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) {
/* Call old area's deactivate if assigned. */
if (region == region_prev && area_iter->type && area_iter->type->deactivate) {
area_iter->type->deactivate(area_iter);
area_iter->type->deactivate(C, area_iter);
}
/* Call new area's activate handler if defined. */
if (region == screen->active_region && region != region_prev && area_iter->type &&
area_iter->type->activate)
{
area_iter->type->activate(C, area_iter);
}
if (region == region_prev && region != screen->active_region) {

View File

@ -18,6 +18,7 @@
#include "GPU_immediate.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_view2d.hh"
@ -133,8 +134,8 @@ static void console_cursor_wrap_offset(
static void console_textview_draw_cursor(TextViewContext *tvc, int cwidth, int columns)
{
int pen[2];
const SpaceConsole *sc = (SpaceConsole *)tvc->arg1;
{
const SpaceConsole *sc = (SpaceConsole *)tvc->arg1;
const ConsoleLine *cl = (ConsoleLine *)sc->history.last;
int offl = 0, offc = 0;
@ -151,13 +152,25 @@ static void console_textview_draw_cursor(TextViewContext *tvc, int cwidth, int c
}
/* cursor */
GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
immUniformThemeColor(TH_CONSOLE_CURSOR);
float color[4];
if (sc->is_area_active) {
UI_GetThemeColor4fv(TH_CONSOLE_CURSOR, color);
color[3] = (sc->is_cursor_bright || !U.text_cursor_blink) ? 1.0f :
TEXT_CURSOR_BLINK_OFF_OPACITY;
}
else {
UI_GetThemeColorBlend4f(TH_BACK, TH_CONSOLE_INPUT, 0.3f, color);
}
immUniformColor4fv(color);
immRectf(pos, pen[0] - U.pixelsize, pen[1], pen[0] + U.pixelsize, pen[1] + tvc->lheight);
GPU_blend(GPU_BLEND_NONE);
immUnbindProgram();
}

View File

@ -386,6 +386,7 @@ static int console_move_exec(bContext *C, wmOperator *op)
ConsoleLine *ci = console_history_verify(C);
ScrArea *area = CTX_wm_area(C);
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
sc->is_cursor_bright = true;
int type = RNA_enum_get(op->ptr, "type");
bool select = RNA_boolean_get(op->ptr, "select");
@ -1286,6 +1287,7 @@ static void console_modal_select_apply(bContext *C, wmOperator *op, const wmEven
/* only redraw if the selection changed */
if (sel_prev[0] != sc->sel_start || sel_prev[1] != sc->sel_end) {
sc->is_cursor_bright = true;
ED_area_tag_redraw(area);
}
}

View File

@ -26,6 +26,7 @@
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_view2d.hh"
@ -298,6 +299,11 @@ static void console_main_region_listener(const wmRegionListenerParams *params)
ED_region_tag_redraw(region);
}
}
else if (wmn->action == NA_CURSOR_BLINK) {
SpaceConsole *sconsole = static_cast<SpaceConsole *>(area->spacedata.first);
sconsole->is_cursor_bright = !bool(sconsole->is_cursor_bright);
ED_region_tag_redraw(region);
}
else {
/* generic redraw request */
ED_region_tag_redraw(region);
@ -343,6 +349,46 @@ static void console_space_blend_write(BlendWriter *writer, SpaceLink *sl)
BLO_write_struct(writer, SpaceConsole, sl);
}
static void console_activate(bContext *C, struct ScrArea *area)
{
SpaceConsole *sc = static_cast<SpaceConsole *>(area->spacedata.first);
sc->is_area_active = true;
sc->is_cursor_bright = true;
if (C) {
wmWindow *win = CTX_wm_window(C);
wmWindowManager *wm = CTX_wm_manager(C);
if (wm->cursor_blink_timer) {
WM_event_timer_remove_notifier(wm, win, wm->cursor_blink_timer);
}
if (U.text_cursor_blink) {
wm->cursor_blink_timer = WM_event_timer_add_notifier(
wm, win, NC_SPACE | ND_SPACE_CONSOLE | NA_CURSOR_BLINK, TEXT_CURSOR_BLINK_RATE);
}
}
/* Redraw to show active caret. */
ED_region_tag_redraw(BKE_area_find_region_type(area, RGN_TYPE_WINDOW));
}
static void console_deactivate(bContext *C, struct ScrArea *area)
{
SpaceConsole *sc = static_cast<SpaceConsole *>(area->spacedata.first);
sc->is_area_active = false;
sc->is_cursor_bright = false;
if (C && area == CTX_wm_area(C)) {
wmWindowManager *wm = CTX_wm_manager(C);
if (wm->cursor_blink_timer) {
WM_event_timer_remove_notifier(wm, CTX_wm_window(C), wm->cursor_blink_timer);
wm->cursor_blink_timer = nullptr;
}
}
/* Redraw to remove active caret. */
ED_region_tag_redraw(BKE_area_find_region_type(area, RGN_TYPE_WINDOW));
}
void ED_spacetype_console()
{
std::unique_ptr<SpaceType> st = std::make_unique<SpaceType>();
@ -360,6 +406,8 @@ void ED_spacetype_console()
st->dropboxes = console_dropboxes;
st->blend_read_data = console_blend_read_data;
st->blend_write = console_space_blend_write;
st->activate = console_activate;
st->deactivate = console_deactivate;
/* regions: main window */
art = static_cast<ARegionType *>(MEM_callocN(sizeof(ARegionType), "spacetype console region"));

View File

@ -488,7 +488,7 @@ static void outliner_foreach_id(SpaceLink *space_link, LibraryForeachIDData *dat
}
}
static void outliner_deactivate(ScrArea *area)
static void outliner_deactivate(bContext * /*C*/, ScrArea *area)
{
/* Remove hover highlights */
SpaceOutliner *space_outliner = static_cast<SpaceOutliner *>(area->spacedata.first);

View File

@ -131,6 +131,10 @@ static void text_listener(const wmSpaceTypeListenerParams *params)
}
switch (wmn->action) {
case NA_CURSOR_BLINK:
st->runtime->is_cursor_bright = !st->runtime->is_cursor_bright;
ED_region_tag_redraw_editor_overlays(BKE_area_find_region_type(area, RGN_TYPE_WINDOW));
break;
case NA_EDITED:
if (st->text) {
space_text_drawcache_tag_update(st, true);
@ -284,6 +288,11 @@ static void text_main_region_draw(const bContext *C, ARegion *region)
/* scrollers? */
}
static void text_main_region_draw_overlay(const bContext *C, ARegion *region)
{
draw_text_cursor(CTX_wm_space_text(C), region);
}
static void text_cursor(wmWindow *win, ScrArea *area, ARegion *region)
{
SpaceText *st = static_cast<SpaceText *>(area->spacedata.first);
@ -414,6 +423,46 @@ static void text_space_blend_write(BlendWriter *writer, SpaceLink *sl)
BLO_write_struct(writer, SpaceText, sl);
}
static void text_activate(bContext *C, struct ScrArea *area)
{
SpaceText *st = static_cast<SpaceText *>(area->spacedata.first);
st->runtime->is_area_active = true;
st->runtime->is_cursor_bright = true;
if (C) {
wmWindow *win = CTX_wm_window(C);
wmWindowManager *wm = CTX_wm_manager(C);
if (wm->cursor_blink_timer) {
WM_event_timer_remove_notifier(wm, win, wm->cursor_blink_timer);
}
if (U.text_cursor_blink) {
wm->cursor_blink_timer = WM_event_timer_add_notifier(
wm, win, NC_TEXT | NA_CURSOR_BLINK, TEXT_CURSOR_BLINK_RATE);
}
/* Redraw to show active text caret. */
ED_region_tag_redraw(BKE_area_find_region_type(area, RGN_TYPE_WINDOW));
}
}
static void text_deactivate(bContext *C, struct ScrArea *area)
{
SpaceText *st = static_cast<SpaceText *>(area->spacedata.first);
st->runtime->is_area_active = false;
st->runtime->is_cursor_bright = false;
if (C) {
wmWindowManager *wm = CTX_wm_manager(C);
if (wm->cursor_blink_timer) {
WM_event_timer_remove_notifier(wm, CTX_wm_window(C), wm->cursor_blink_timer);
wm->cursor_blink_timer = nullptr;
}
}
/* Redraw to remove active text caret. */
ED_region_tag_redraw(BKE_area_find_region_type(area, RGN_TYPE_WINDOW));
}
/********************* registration ********************/
void ED_spacetype_text()
@ -438,12 +487,15 @@ void ED_spacetype_text()
st->blend_read_data = text_space_blend_read_data;
st->blend_read_after_liblink = nullptr;
st->blend_write = text_space_blend_write;
st->activate = text_activate;
st->deactivate = text_deactivate;
/* regions: main window */
art = static_cast<ARegionType *>(MEM_callocN(sizeof(ARegionType), "spacetype text region"));
art->regionid = RGN_TYPE_WINDOW;
art->init = text_main_region_init;
art->draw = text_main_region_draw;
art->draw_overlay = text_main_region_draw_overlay;
art->cursor = text_cursor;
art->event_cursor = true;

View File

@ -1165,12 +1165,87 @@ static void draw_suggestion_list(const SpaceText *st, const TextDrawContext *tdc
/** \name Draw Cursor
* \{ */
void draw_text_cursor(SpaceText *st, ARegion *region)
{
if (st == nullptr || st->text == nullptr) {
return;
}
bool hidden = false;
Text *text = st->text;
int vsell, vselc;
int x, y, w;
int offl, offc;
const int lheight = TXT_LINE_HEIGHT(st);
/* Convert to view space character coordinates to determine if cursor is hidden */
space_text_wrap_offset(st, region, text->sell, text->selc, &offl, &offc);
vsell = txt_get_span(static_cast<TextLine *>(text->lines.first), text->sell) - st->top + offl;
vselc = space_text_get_char_pos(st, text->sell->line, text->selc) - st->left + offc;
if (vselc < 0) {
vselc = 0;
hidden = true;
}
if (text->curl == text->sell && text->curc == text->selc && !st->line_hlight && hidden) {
/* Nothing to draw here */
return;
}
uint pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
if (!hidden) {
/* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
x = TXT_BODY_LEFT(st) + (vselc * st->runtime->cwidth_px);
y = region->winy - vsell * lheight;
if (st->flags & ST_SCROLL_SELECT) {
y += st->runtime->scroll_ofs_px[1];
}
float color[4];
if (st->runtime->is_area_active) {
UI_GetThemeColor4fv(TH_HILITE, color);
color[3] = (st->runtime->is_cursor_bright || !U.text_cursor_blink) ?
1.0f :
TEXT_CURSOR_BLINK_OFF_OPACITY;
}
else {
UI_GetThemeColorBlend4f(TH_BACK, TH_TEXT, 0.3f, color);
}
immUniformColor4fv(color);
GPU_blend(GPU_BLEND_ALPHA);
if (st->overwrite) {
char ch = text->sell->line[text->selc];
y += TXT_LINE_SPACING(st);
w = st->runtime->cwidth_px;
if (ch == '\t') {
w *= st->tabnumber - (vselc + st->left) % st->tabnumber;
}
immRecti(
pos, x, y - lheight - U.pixelsize, x + w + U.pixelsize, y - lheight - (3 * U.pixelsize));
}
else {
immRecti(pos, x - U.pixelsize, y, x + U.pixelsize, y - lheight);
}
GPU_blend(GPU_BLEND_NONE);
}
immUnbindProgram();
}
static void draw_text_decoration(SpaceText *st, ARegion *region)
{
Text *text = st->text;
int vcurl, vcurc, vsell, vselc;
bool hidden = false;
int x, y, w, i;
int x, y, i;
int offl, offc;
const int lheight = TXT_LINE_HEIGHT(st);
@ -1297,33 +1372,6 @@ static void draw_text_decoration(SpaceText *st, ARegion *region)
}
}
if (!hidden) {
/* Draw the cursor itself (we draw the sel. cursor as this is the leading edge) */
x = TXT_BODY_LEFT(st) + (vselc * st->runtime->cwidth_px);
y = region->winy - vsell * lheight;
if (st->flags & ST_SCROLL_SELECT) {
y += st->runtime->scroll_ofs_px[1];
}
immUniformThemeColor(TH_HILITE);
if (st->overwrite) {
char ch = text->sell->line[text->selc];
y += TXT_LINE_SPACING(st);
w = st->runtime->cwidth_px;
if (ch == '\t') {
w *= st->tabnumber - (vselc + st->left) % st->tabnumber;
}
immRecti(
pos, x, y - lheight - U.pixelsize, x + w + U.pixelsize, y - lheight - (3 * U.pixelsize));
}
else {
immRecti(pos, x - U.pixelsize, y, x + U.pixelsize, y - lheight);
}
}
immUnbindProgram();
}
@ -1782,6 +1830,7 @@ void space_text_update_cursor_moved(bContext *C)
ScrArea *area = CTX_wm_area(C);
SpaceText *st = CTX_wm_space_text(C);
st->runtime->is_cursor_bright = true;
space_text_scroll_to_cursor_with_area(st, area, true);
}

View File

@ -24,6 +24,8 @@ struct wmOperatorType;
void draw_text_main(SpaceText *st, ARegion *region);
void draw_text_cursor(SpaceText *st, ARegion *region);
void text_update_line_edited(TextLine *line);
void text_update_edited(Text *text);
@ -208,6 +210,9 @@ struct SpaceText_Runtime {
*/
int scroll_ofs_px[2]{0, 0};
bool is_area_active;
bool is_cursor_bright;
/** Cache for faster drawing. */
void *drawcache = nullptr;
};

View File

@ -1712,6 +1712,10 @@ typedef struct SpaceConsole {
int lheight;
char is_area_active;
char is_cursor_bright;
char _pad[6];
/** Index into history of most recent up/down arrow keys. */
int history_index;

View File

@ -1060,7 +1060,9 @@ typedef struct UserDef {
float collection_instance_empty_size;
char text_flag;
char _pad10[1];
/* Blinking text cursor (caret) in various text inputs and editors. */
char text_cursor_blink;
char file_preview_type; /* eUserpref_File_Preview_Type */
char statusbar_flag; /* eUserpref_StatusBar_Flag */

View File

@ -208,6 +208,8 @@ typedef struct wmWindowManager {
/** Active timers. */
ListBase timers;
/** Timer for text cursor blinking. */
struct wmTimer *cursor_blink_timer;
/** Timer for auto save. */
struct wmTimer *autosavetimer;
/** Auto-save timer was up, but it wasn't possible to auto-save in the current mode. */

View File

@ -5945,6 +5945,11 @@ static void rna_def_userdef_system(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_float_sdna(prop, nullptr, "pixelsize");
prop = RNA_def_property(srna, "text_cursor_blink", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_default(prop, true);
RNA_def_property_ui_text(prop, "Text Cursor Blink", "Blinking cursor when the area is active");
RNA_def_property_update(prop, 0, "rna_userdef_gpu_update");
/* Memory */
prop = RNA_def_property(srna, "memory_cache_limit", PROP_INT, PROP_NONE);

View File

@ -553,6 +553,7 @@ struct wmNotifier {
#define NA_ACTIVATED 7
#define NA_PAINTING 8
#define NA_JOB_FINISHED 9
#define NA_CURSOR_BLINK 10
/* ************** Gesture Manager data ************** */