多行文本编辑框的原生实现(GMS2) 软件
摸魔抹莫 发表于 2020-01-24 03:48:17 110

通常在GameMaker家族制造的游戏中,获取用户输入的文字的方法是get_string()。这种方法的优点当然是简单、易用,而缺点是: ①弹出窗口会阻断游戏运行,可能中断某些画面特效。 ②GameMaker: Studio版本以后无法自定义弹出窗口风格,可能破坏游戏画风。 ③无法很好地实现多行文字的编辑。(当然可以一行一行get_string(),只不过很麻烦) 如果希望解决这些问题,可以自己组织逻辑,构建一个基于GameMaker原生函数的文本框。Windows上见到的文本框一般是WinAPI组件,用GameMaker原生的绘制函数和交互操作构建,而不调用WinAPI的话,需要做的事情非常多。

本教程基于GMS2实现了一文本框,功能如下: ①文字输入与删除。(废话) ②光标移动。即可以让新输入的文字插入到原有文字的某中间位置,或从原有文字的某中间位置开始删除文字。 ③文字选中。鼠标点击拖动,可以将划过的文字选中。被选中的文字上将会覆盖半透明蓝色矩形。如果此时删除文字,则被选中文字都将被删除。在新输入文字时如当前有文字被选中,则先删除被选中文字。鼠标点击别处或移动光标,则被选中文字会被取消选中。 ④剪切、复制、粘贴、全选操作。剪切:把被选中文字复制到剪贴板,然后删除被选中文字。复制:把被选中文字复制到剪贴板。粘贴:把剪贴板上文字输入。全选:选中文本框的所有内容。目前暂只支持用快捷键完成操作,即Ctrl+X,Ctrl+C,Ctrl+V,Ctrl+A。 ⑤行数显示,背景色、边框色、文字色自定义,焦点获取与丢失。这些属于细节。

未实现的功能主要有: ①撤销功能。 ②文字染色功能。 ③代码提示和自动格式化功能。(一般做代码编辑都要实现这些功能) ④滚动条调整视野功能。

效果演示: 效果演示

如需使用,请下载并导入附件包textbox.yymp,并做如下工作: ①创建一物体,继承objMemoParent。继承Create事件动作,并对需要改变的属性重新赋值。 ②创建该物体以前,创建全局变量global.UIBackgroundColor, global.UIBorderColor, global.UITextColor, global.TopWindow。如:

global.TopWindow = -1;
global.UIBackgroundColor = $232323;
global.UIBorderColor = $535353;
global.UITextColor = $ffffff;

这样就可以创建该物体的实例,并用它完成文本编辑。

主要代码如下: 主角:多行代码框物体objMemoParent

///Object: objMemoParent 
///Event: Create
//创建新物体继承该物体,然后在Create事件继承并改变某些变量值可实现特化。
Width = 556; //宽度
Height = 512; //高度
LineCount = 1; //行数
Lines = ds_list_create(); //每行文字
Lines[| 0] = ""; //初始化
EnableEdit = 1; //是否允许编辑

BackgroundColor = global.UIBackgroundColor; //背景色默认值,可修改为其他值
BorderColor = global.UIBorderColor; //边框色默认值,可修改为其他值
Font = fMSYH12; //字体默认值,可修改为其他值
TextColor = global.UITextColor; //文字颜色默认值,可修改为其他值
SelectionColor = $CC7A00; //文字选中提示色
LineHeight = font_get_height(Font) + 4; //行高计算
Surface = -1; //表面初始化

WindowParent = -1; //所属的窗体,-1表示无,可修改为其他值
IsFocused = 0; //是否有焦点
LinePosition = 0; //光标行位置
ColumnPosition = 1; //光标列位置
IsDrawPosition = 1; //是否绘制光标,闪烁控制
IsHolding = 0; //是否按住键盘,连续删除或输入控制
IsSelecting = 0; //是否正在选中文字
SelectLineBegin = 0; //选中文字开始行位置(鼠标从哪开始点的)
SelectLineHead = 0; //选中文字第一行位置(选中的文字最上面那一行)
SelectLineCount = 0; //选中文字一共几行
SelectFirst = 0; //选中文字第一个字符列位置
SelectLast = 0; //选中文字最后一个字符列位置
IsMultipleSelect = 0; //是否有选中文字

alarm[0] = 30; //光标闪烁计时

///Event: Step
//global.TopWindow:哪个窗体对象在最上层,-1表示无
if(global.TopWindow != WindowParent)
{
    exit;
}

if (!visible)
{
    exit;
}

memo_check_mouse(); //检测并处理鼠标操作
if(!IsFocused)
{
    exit;
}

memo_check_input(); //检测并处理输入操作
memo_check_delete(); //检测并处理删除操作
memo_check_move(); //检测并处理键盘移动光标操作
memo_check_action(); //检测并处理其他操作

if(keyboard_check_released(vk_anykey))
{
    IsHolding = 0;
    alarm[1] = -1;
}

///Event: Alarm 0
//控制光标闪烁
IsDrawPosition = !IsDrawPosition;
alarm[0] = 30;

///Event: Alarm 1
//控制连续按键检测(按住一段时间才算按住)
IsHolding = 1;

///Event: Draw
var _w, _w2;
var i;

//防止表面蒸发的检测
Surface = surface_check(Surface, Width, Height);
surface_set_target(Surface);

//绘制代码边框
draw_set_color(BackgroundColor);
draw_rectangle_wh(0, 0, Width, Height, 0);

draw_set_font(Font);
draw_set_color(TextColor);

//绘制行号
for (i = 0; i < LineCount; i++)
{
    draw_set_halign(fa_right);
    draw_text(42, 2 + i * LineHeight, string(i));
    draw_set_halign(fa_left);
    draw_text(46, 2 + i * LineHeight, Lines[| i]);
}

//绘制光标
_w = string_width(string_copy(Lines[| LinePosition], 1, ColumnPosition - 1)) + 46;
if (IsFocused && IsDrawPosition)
{
    draw_rectangle_wh(_w, 2 + LineHeight * LinePosition, 2, LineHeight - 4, 0);
}

//绘制表示选中文字的蓝色矩形
if (IsMultipleSelect)
{
    draw_set_color_alpha(SelectionColor, 0.5);

    //只有一行,直接画
    if (SelectLineCount == 1)
    {
        _w2 = string_width(string_copy(Lines[| LinePosition], SelectFirst , SelectLast - SelectFirst + 1));
        draw_rectangle_wh(_w, LineHeight * SelectLineHead, _w2, LineHeight - 4, 0);
    }
    //多行,分第一行、中间行、最后行画
    else
    {
        _w2 = string_width(string_copy(Lines[| SelectLineHead], 1, SelectFirst - 1)) + 46;
        draw_rectangle_wh(_w2, LineHeight * SelectLineHead,  Width - _w2, LineHeight, 0);
        for (i = 1; i < SelectLineCount - 1; i++)
        {
            draw_rectangle_wh(46, LineHeight * (SelectLineHead + i), Width - 48, LineHeight, 0);
        }
        _w2 = string_width(string_copy(Lines[| SelectLineHead + SelectLineCount - 1], 1, SelectLast));
        draw_rectangle_wh(46, LineHeight * (SelectLineHead + SelectLineCount - 1), _w2, LineHeight, 0);
    }
}

//绘制边框
draw_set_color_alpha(BorderColor, 1);
draw_rectangle_wh(0, 0, 44, Height, 1);
draw_rectangle_wh(44, 0, Width - 44, Height, 1);

surface_reset_target();

//绘制表面
draw_surface_onezero(Surface, x, y);

draw_reset();

重要脚本:

///Script: memo_check_mouse()
var moul, mouc;
//计算在该字体下字符串宽度,因此先设置上
draw_set_font(Font);
//另外两个脚本,获取鼠标的行位置和列位置
moul = memo_get_mouse_line_position();
mouc = memo_get_mouse_column_position(moul);
if (mouse_check_button_pressed(mb_left))
{
    //另外一个脚本,判断鼠标是否在某矩形内
    if (mouse_is_in_rectangle(x + 46, y, Width, Height))
    {
        //开始选中文字,该赋值的都赋值
        IsFocused = 1;
        LinePosition = moul;
        ColumnPosition = mouc;
        IsSelecting = 1;
        SelectBegin = mouc;
        SelectFirst = mouc;
        SelectLast = mouc;
        SelectLineBegin = LinePosition;
        SelectLineHead = LinePosition;
        SelectLineCount = 1;

        IsDrawPosition = 1;
        alarm[0] = 30;
    }
    else
    {
        //在外部点击,失去焦点
        IsFocused = 0;
        IsDrawPosition = 0;
        alarm[0] = -1;
        //失去焦点时发生,子物体可以创建该事件
        event_user(0);
    }
}
if (IsSelecting)
{
    //分别处理往左拖动、往右拖动的情况
    if (moul < SelectLineBegin)
    {
        //往左拖动的
        SelectFirst = mouc;
        SelectLast = SelectBegin - 1;
        SelectLineHead = moul;
        SelectLineCount = SelectLineBegin - moul + 1;
        LinePosition = moul;
        ColumnPosition = mouc;
    }
    else if (moul == SelectLineBegin)
    {
        SelectLineHead = moul;
        SelectLineCount = 1;
        LinePosition = moul;
        if (mouc < SelectBegin)
        {
            //往左拖动的
            SelectFirst = mouc;
            SelectLast = SelectBegin - 1;
            ColumnPosition = mouc;
        }
        else
        {
            //往右拖动的
            SelectFirst = SelectBegin;
            SelectLast = mouc;
            ColumnPosition = SelectBegin;
        }
    }
    else
    {
        //往右拖动的
        SelectFirst = SelectBegin;
        SelectLast = mouc;
        SelectLineHead = SelectLineBegin;
        SelectLineCount = moul - SelectLineBegin + 1;
        LinePosition = SelectLineBegin;
        ColumnPosition = SelectBegin;
    }
    if (SelectFirst == SelectLast && SelectLineCount == 1)
    {
        //还在原位置
        IsMultipleSelect = 0;
    }
    else
    {
        IsMultipleSelect = 1;
    }
    if (mouse_check_button_released(mb_left))
    {
        IsSelecting = 0;
    }
}

///Script: memo_check_input()
//输入部分核心代码(其实就这么简单)
if (keyboard_string != "")
{
    memo_input(keyboard_string);
    keyboard_string = "";
}

//tab键特殊处理,输入4个空格
if (memo_check_key(vk_tab))
{
    memo_input("    ");
}

//回车键特殊处理,输入换行符
if (memo_check_key(vk_enter))
{
    memo_input("\n");
}

///Script: memo_check_delete()
if (memo_check_key(vk_backspace))
{
    if (IsMultipleSelect)
    {
        //有文字选中,删除选中部分
        memo_delete_selection();
    }
    else
    {
        if (ColumnPosition > 1)
        {
            //光标不在本行行首,删除该处一个字符
            Lines[| LinePosition] = string_delete(Lines[| LinePosition], ColumnPosition - 1, 1);
            ColumnPosition--;
        }
        else
        {
            //光标在行首,把本行内容接到上一行,删除本行
            if (LinePosition > 0)
            {
                ColumnPosition = string_length(Lines[| LinePosition - 1]) + 1;
                Lines[| LinePosition - 1] += Lines[| LinePosition];
                ds_list_delete(Lines, LinePosition);
                LineCount--;
                LinePosition--;
            }
        }
    }

    //删除会使光标立即显示
    IsDrawPosition = 1;
    alarm[0] = 30;
}

//Script: memo_check_move()
var _w;
draw_set_font(Font);

//上、下移动行位置,左、右移动列位置。注意字体可能不等宽,移动行位置时列位置也可能改变。移动时如果有选中文字,则取消选中。
if (memo_check_key(vk_up))
{
    if (IsMultipleSelect)
    {
        IsMultipleSelect = 0;
    }
    IsDrawPosition = 1;
    alarm[0] = 30;
    if (LinePosition > 0)
    {
        _w = string_width(string_copy(Lines[| LinePosition], 1, ColumnPosition - 1));
        ColumnPosition = string_get_position_at_width(Lines[| LinePosition - 1], _w);
        LinePosition--;
    }
}
if (memo_check_key(vk_right))
{
    if (IsMultipleSelect)
    {
        IsMultipleSelect = 0;
    }
    IsDrawPosition = 1;
    alarm[0] = 30;
    if (ColumnPosition <= string_length(Lines[| LinePosition]))
    {
        ColumnPosition++;
    }
    else
    {
        if (LinePosition < LineCount - 1)
        {
            LinePosition++;
        }
    }
}
if (memo_check_key(vk_down))
{
    if (IsMultipleSelect)
    {
        IsMultipleSelect = 0;
    }
    IsDrawPosition = 1;
    alarm[0] = 30;
    if (LinePosition < LineCount - 1)
    {
        _w = string_width(string_copy(Lines[| LinePosition], 1, ColumnPosition - 1));
        ColumnPosition = string_get_position_at_width(Lines[| LinePosition + 1], _w);
        LinePosition++;
    }
}
if (memo_check_key(vk_left))
{
    if (IsMultipleSelect)
    {
        IsMultipleSelect = 0;
    }
    IsDrawPosition = 1;
    alarm[0] = 30;
    if (ColumnPosition > 1)
    {
        ColumnPosition--;
    }
    else
    {
        if (LinePosition > 0)
        {
            LinePosition--;
        }
    }
}

///Script: memo_check_action()
//检测各种操作,如剪切、复制、粘贴、全选。
var ctxt;
if (!IsFocused)
{
    exit;
}

if (keyboard_check(vk_control) && keyboard_check_pressed(ord("C")))
{
    if (IsMultipleSelect)
    {
        clipboard_set_text(memo_get_selection_text());
    }
}
if (keyboard_check(vk_control) && keyboard_check_pressed(ord("X")))
{
    if (IsMultipleSelect)
    {
        clipboard_set_text(memo_get_selection_text());
        memo_delete_selection();
    }
}
if (keyboard_check(vk_control) && keyboard_check_pressed(ord("V")))
{
    ctxt = clipboard_get_text();
    if (IsMultipleSelect)
    {
        memo_delete_selection();
    }
    memo_input(ctxt);
}
if(keyboard_check(vk_control) && keyboard_check_pressed(ord("A")))
{
    SelectBegin = 1;
    SelectFirst = 1;
    SelectLast = string_length(Lines[| LineCount - 1]);
    SelectLineBegin = 0;
    SelectLineHead = 0;
    SelectLineCount = LineCount;
    LinePosition = 0;
    ColumnPosition = 1;
    if (SelectFirst == SelectLast && SelectLineCount == 0)
    {
        IsMultipleSelect = 0;
    }
    else
    {
        IsMultipleSelect = 1;
    }
}

///Script: memo_get_mouse_line_position()
//获取文本框中鼠标位置的行号。每行等宽,因此逻辑简单。
var result;
if (mouse_y <= y)
{
    return 0;   
}
else
{
    result = ceil((mouse_y - y) / LineHeight) - 1;
    if (result > LineCount - 1)
    {
        return LineCount - 1;
    }
    return result;
}
return 0;

///Script: memo_get_mouse_column_position(line)
//获取文本框中鼠标位置在某一行的列号。主要调用另一个脚本。
return string_get_position_at_width(Lines[| argument0], mouse_x - (x + 46));

///Script: mouse_is_in_rectangle(x, y, w, h)
var _l, _t, _r, _b;
_l = min(argument0, argument0 + argument2);
_t = min(argument1, argument1 + argument3);
_r = max(argument0, argument0 + argument2);
_b = max(argument1, argument1 + argument3);
return mouse_x >= _l && mouse_x <= _r - 1 && mouse_y >= _t && mouse_y <= _b - 1;

///Script: string_get_position_at_width(str, w)
//获取字符串在哪一位置达到某一宽度。
var i, _len, _str;
_len = string_length(argument0);
i = 1;
_str = "";

while (i <= _len)
{
    _str += string_char_at(argument0, i);
    if (string_width(_str) > argument1)
    {
        return i;
    }
    ++i;
}
return _len + 1;

///Script: memo_check_key(key)
//获取某一按键是否按下或按住。与一般获取不同的是一按键按住一段时间才算按住。
if (keyboard_check(argument0) && IsHolding)
{
    return 1;
}
else if (keyboard_check_pressed(argument0))
{
    if (alarm[1] < 0)
    {
        alarm[1] = 25;
    }
    return 1;
}
return 0;

///Script: memo_input(str)
var _str, _left, _pos;
_str = argument0;
//有文字选中则删除
if (IsMultipleSelect)
{
    memo_delete_selection();
}

//处理输入文字中有换行符的情况,主要是粘贴中出现
_left = string_delete(Lines[| LinePosition], 1, ColumnPosition - 1);
Lines[| LinePosition] = string_copy(Lines[| LinePosition], 1, ColumnPosition - 1);
while (true)
{
    _pos = string_pos("\n", _str);
    if (_pos != 0)
    {
        Lines[| LinePosition] += string_copy(_str, 1, _pos - 1);
        ds_list_insert(Lines, LinePosition + 1, "");
        LineCount++;
        _str = string_delete(_str, 1, _pos);
        LinePosition++;
        ColumnPosition = 1;
    }
    else
    {
        Lines[| LinePosition] += _str + _left;
        ColumnPosition += string_length(_str);
        break;
    }
}

//输入后立即显示光标
IsDrawPosition = 1;
alarm[0] = 30;

///Script: memo_delete_selection()
//删除选中部分。多行选中需特别处理。
if (SelectLineCount == 1)
{
    Lines[| LinePosition] = string_delete(Lines[| LinePosition], SelectFirst, SelectLast - SelectFirst + 1);
}
else
{
    Lines[| SelectLineHead] = string_copy(Lines[| SelectLineHead], 1, SelectFirst - 1);
    repeat (SelectLineCount - 2)
    {
        ds_list_delete(Lines, SelectLineHead + 1);
    }
    Lines[| SelectLineHead] += string_delete(Lines[| SelectLineHead + 1], 1, SelectLast);
    ds_list_delete(Lines, SelectLineHead + 1);

    LineCount -= SelectLineCount - 1;
}
IsMultipleSelect = 0;

///Script: memo_get_selection_text()
//获取选中文字。主要是拼接和增加换行符。
var i, result;

if (IsMultipleSelect)
{
    if (SelectLineCount == 1)
    {
        return string_copy(Lines[| SelectLineHead], SelectFirst, SelectLast - SelectFirst + 1);
    }
    result = string_delete(Lines[| SelectLineHead], 1, SelectFirst - 1) + "\n";
    for (i = 1; i < SelectLineCount - 1; i += 1)
    {
        result += Lines[| SelectLineHead + i] + "\n";
    }
    result += string_copy(Lines[| SelectLineHead + SelectLineCount - 1], 1, SelectLast);
}
return result;

不影响功能,但开发者可能用到的一些脚本:

///Script: memo_set_font(font)
Font = argument0;
LineHeight = font_get_height(argument0) + 4;

///Script: memo_get_text()
var i, r;
r = "";
for (i = 0; i < LineCount; i++)
{
    r += Lines[| i];
    if (i != LineCount - 1)
    {
        r += "\n";
    }
}
return r;

一些别的辅助脚本,个人习惯:

///Script: font_get_height(font)
var _f, _r;
_f = draw_get_font();
draw_set_font(argument0);
_r = string_height("j");
draw_set_font(_f);
return _r;

///Script: surface_check(surf, w, h)
if (surface_exists(argument0))
{
    return argument0;
}
return surface_create(argument1, argument2);

///Script: draw_rectangle_wh(x, y, w, h, outline)
var _l, _t, _r, _b;
_l = min(argument0, argument0 + argument2);
_t = min(argument1, argument1 + argument3);
_r = max(argument0, argument0 + argument2) - 1;
_b = max(argument1, argument1 + argument3) - 1;
draw_rectangle(_l, _t, _r, _b, argument4);

///Script: draw_reset()
draw_set_color_alpha(c_black, 1);
draw_set_align(fa_left, fa_top);

///Script: draw_set_color_alpha(color, alpha)
draw_set_color(argument0);
draw_set_alpha(argument1);

///Script: draw_set_align(halign, valign)
draw_set_halign(argument0);
draw_set_valign(argument1);

///Script: draw_surface_onezero(surf, x, y)
//用来防止表面腐蚀
gpu_set_blendmode_ext(bm_one, bm_zero);
draw_surface(argument0, argument1, argument2);
gpu_set_blendmode(bm_normal);

感谢您的阅读。如遇到bug,欢迎反馈并协助修复。 EOF

最后于 24天前 被摸魔抹莫编辑 ,原因: 代码修改
上传的附件:
最新回复 (0)