SIv3DのGUIでスピンボックス作ってみた


Siv3Dの機能をいくつか組み合わせてスピンボックスを試作しました。
C#のFormだとNumericUpDownですが、名前をどっちにするかが悩みどころ。

Siv3Dのバージョンは2015(June)。

ソース

C++
#include <Siv3D.hpp>
template<typename T>
class GUIValueEditor
{
public:
    typedef std::shared_ptr<s3d::GUITextField> TextFiledPtr;
    typedef std::shared_ptr<s3d::GUIText> TextPtr;
    typedef std::shared_ptr<s3d::GUIButton> ButtonPtr;
    TextFiledPtr textField;
    TextPtr name;
    ButtonPtr plusButton;
    ButtonPtr minusButton;
    T value_ = T();
    T preValue = T();
public:
    T& value(){ return value_; }
    // 初回処理 ただし、複数回呼んでも別にバグらない
    void InitWidget()
    {
        {
            textField = std::make_shared<s3d::GUITextField>();
            textField->m_style.width = 56;
            textField->setMaxLength(6);
            textField->setText(s3d::Format(value_));
        }
        const s3d::int32 buttonSize = 30;
        {
            plusButton = std::make_shared<s3d::GUIButton>();
            plusButton->getText() = L'+';
            plusButton->m_style.margin = -5;
            plusButton->m_style.width = buttonSize;
            plusButton->m_style.padding = 10;
        }
        {
            minusButton = std::make_shared<s3d::GUIButton>();
            minusButton->getText() = L'-';
            minusButton->m_style = plusButton->m_style;
        }
    }
    void LinkGUI(const s3d::String& name, s3d::GUI& gui)
    {
        InitWidget();

        const bool linkOK = (textField != nullptr) && (plusButton != nullptr) && (minusButton != nullptr);
        if (linkOK)
        {
            gui.add(s3d::GUIText::Create(name));
            gui.add(textField);
            gui.add(plusButton);
            gui.addln(minusButton);
        }
    }

    bool InputComplete()const
    {
        return textField->hasChanged && (!textField->getActive());
    }
    // 入力内容に合わせて、数値とテキストを同期させる
    void TextUpdate()
    {
        const auto currentVal = s3d::FromStringOpt<T>(textField->getText());
        if (currentVal)
        {
            this->value_ = *currentVal;
        }
        else{
            textField->setText(s3d::Format(value_));
        }

    }
    void readText()
    {
        if (textField->getActive() && Input::MouseL.clicked)
        {
            if (!textField->mouseOver)
            {
                TextUpdate();
                return;
            }
        }

        if (InputComplete())
        {
            TextUpdate();
        }

        return;
    }


    void update()
    {
        assert(textField);
        assert(plusButton);
        assert(minusButton);
        if (plusButton->pushed){ ++value_; }
        if (minusButton->pushed){ --value_; }

        if (preValue != value_)
        {
            this->textField->setText(s3d::Format(value_));
        }
        readText();
        preValue = value_;
    }
};

void Main()
{
    const s3d::String girdSizeY = L"girdSizeY";
    const s3d::String girdSizeX = L"girdSizeX";
    GUIStyle st;
    GUI gui(GUIStyle::Default);
    gui.setTitle(L"サイズの設定");
    gui.style.movable = true;
    GUIValueEditor<int> gve,gve2;
    gve.LinkGUI(L"sizeX",gui);
    gve2.LinkGUI(L"sizeY",gui);
    while (System::Update())
    {
        gve.update();
        gve2.update();
    }

}

実行結果(数値は実際に入力して変更したもの)

おまけ

GUITextFieldは(textField->hasChanged && (!textField->getActive()))で入力完了のタイミングを拾えた。
本来の入力完了以外に、何らかの入力をしたけどフォーカスを外して他を操作したいパターンで入力を保持するのに使える。
この書き方はGUIValueEditor::InputCompleteで使っている。

数値と文字列の変換はs3d::Formatとs3d::FromStringOptに任せている。
この部分をフックできるようにすれば、さらなる拡張も可能。
ただ、そこまでやるならSiv3D本体にこれを調整して入れられないか考えている。

今後の課題

プラスボタンとマイナスボタンがウィジェットと一体化できればかなりスッキリしそうなので、そこを何とかしたい。
TextFieldを使うのは必須だが、継承で動作を書き換えられないので中間層になるクラスが欲しくなる。