/** * @file dl_ui_msg_box.h * @brief 消息框 * * * @version 1.0 * @author lveyou * @date 22-05-11 * * @note 带有标题、内容、按钮、关闭按钮的弹窗 */ #pragma once #ifndef DL_DISABLE_RENDER #include "ui/dl_ui_button_sprite.h" #include "system/dl_system.h" namespace dl::UI { //! 消息框最大按钮数量 constexpr size_t NUM_MSG_BOX_BTN = 10; //! UI消息框 class MsgBox { public: /** * @brief 构造 * @param[in] bg 九图精灵作为背景 * @param[in] txt_title 标题文本 * @param[in] txt 内容文本 * @param[in] btn 普通按钮 * @param[in] btn_close 关闭按钮 * @param[in] dx_btn 按钮(额外)间隔,艺术性调整 * * @note 应该立即调用Set设置内容 */ static MsgBox* Create( Background* bg, gcText txt_title, gcText txt, UI::ButtonSprite* btn, UI::ButtonSprite* btn_close, Float dx_btn = 0.0) { MsgBox* ret = new MsgBox; Coor* coor = Coor::Create(); coor->SetPosition(toPosition(g_system->GetWindowSize() / 2u)); bg->SetUI(true); ret->_coor = coor; ret->_bg = bg; ret->_txtTitle = txt_title; ret->_txt = txt; ret->_btn = btn; ret->_btnClose = btn_close; ret->_dxBtn = dx_btn; ret->_refresh_coor(); ret->SetOrder(Z_DEFAULT_UI); return ret; } MsgBox* Clone() { MsgBox* ret = new MsgBox(*this); ret->_coor = _coor->Clone(); ret->_bg = _bg->Clone(); ret->_btn = _btn->Clone(); ret->_btnClose = _btnClose->Clone(); for (size_t i = 0; i < _allBtn.size(); ++i) { ret->_allBtn[i] = _allBtn[i]->Clone(); } ret->_refresh_coor(); return ret; } /** * @brief 设置内容 * @param[in] title 标题 * @param[in] info 内容 * @param[in] btns 按钮内容 * */ void Set(std::string_view title, std::string_view info, std::vector all_btn) { _txtTitle->SetString(title); if(_bAddTab) _txt->SetString(std::string{ "\t" } + std::string{ info }); else _txt->SetString(info); //少了就新建按钮 _numBtn = all_btn.size(); if (_numBtn > _allBtn.size()) { _allBtn.resize(_numBtn); for (UI::ButtonSprite*& btn : _allBtn) { if (!btn) { btn = _btn->Clone(); } } } for (std::size_t i = 0; i < _numBtn; ++i) { _allBtn[i]->SetString(all_btn[i]); } //--------------------------------更新布局-------------------------------- Float w_btn = _btn->GetSize()[0]; //按钮宽度 Float h_btn = _btn->GetSize()[1]; //整体宽度为 按钮宽度 + 间隔(多一个) Float w = w_btn * _numBtn + _dxBtn * (1 + _numBtn); _txt->SetMaxW(w);//设置文本宽度 Float h_txt = _txt->GetSize()[1]; //文本高度 Float h_title = _txtTitle->GetSize()[1];//标题高度 //整体高度 标题 + 文本 + 按钮 Float h = h_title + h_txt * 1 + h_btn * 1; _txt->SetCenter(); Position size{ w, h }; Position size_half{ size * 0.5f }; _bg->SetSize(size); //设置位置 _bg->GetCoor()->SetPosition(toPosition(_bg->GetOffset()) - size_half); _txtTitle->SetPositionAlign(-size_half, Align::LC); _btnClose->GetCoor()->SetPosition({ size_half[0], -size_half[1] }); if (_numBtn > 0) { //偏移为 负的 按钮总宽度的一半(间隔少一个) + 半个按钮 Float sx_btn = (w_btn * _numBtn + _dxBtn * (_numBtn - 1)) * -0.5_f + w_btn * 0.5_f; for (std::size_t i = 0; i < _numBtn; ++i) { _allBtn[i]->GetCoor()->SetPosition({ sx_btn + i * (w_btn + _dxBtn), size_half[1] }); } } } /** * @brief 设置内容,以语言文件配置的名称 * */ void Set(std::string_view str) { std::string_view str_title = lang("msg_box", std::string{ str } + "_title"); std::string_view str_info = lang("msg_box", std::string{ str } + "_info"); std::vector all_btn; for (std::size_t i = 0; i < NUM_MSG_BOX_BTN; ++i) { std::string str_name = fmt::format("{}_{}", str, i); auto str = g_lang->GetCheck("msg_box", str_name); if (!str.has_value()) { break; } all_btn.push_back(str.value()); } Set(str_title, str_info, all_btn); } void Update() { _btnClose->Update(); for (size_t i = 0; i < _numBtn; ++i) { _allBtn[i]->Update(); } if (_posPick) { if (g_input->KeyUp(MouseCode::LEFT)) _posPick.reset(); } else { if (g_input->KeyDown(MouseCode::LEFT) && IsPickupTitle()) { _posPick = _coor->WorldToThis(toPosition(g_input->GetCursorPosition())); } } //左键拖动 if (_posPick) { Point pos = g_input->GetCursorPosition(); _coor->SetPosition(toPosition(pos) - _posPick.value()); } } void Render() { _bg->Render(); _txtTitle->Render(); _txt->Render(); } /** * @brief 返回点击按钮索引,返回-1代表没有点击 */ size_t GetClick() { for (size_t i = 0; i < _numBtn; ++i) { if (_allBtn[i]->IsClick()) return i; } return -1; } /** * @brief 是否点击了关闭按钮 */ bool IsClickClose() { return _btnClose->IsClick(); } /** * @brief 是否选中标题栏 */ bool IsPickupTitle() { if (_bg->IsPickup()) {//按钮会屏蔽bg的IsPickup Position pos = _txtTitle->GetCoor()->WorldToThis(toPosition(g_input->GetCursorPosition())); if (pos[1] < _txtTitle->GetSize()[1]) return true; } return false; } void SetOrder(float order) { _txt->SetOrder(Math::OffsetOrder(order, 0, 0, -1)); _txtTitle->SetOrder(Math::OffsetOrder(order, 0, 0, -1)); _btnClose->SetOrder(Math::OffsetOrder(order, 0, 0, -1)); _btn->SetOrder(Math::OffsetOrder(order, 0, 0, -1)); for (auto& iter : _allBtn) { iter->SetOrder(Math::OffsetOrder(order, 0, 0, -1)); } _bg->SetOrder(Math::OffsetOrder(order, 0, 0, 0)); } ~MsgBox() { delete _coor; delete _bg; delete _btn; delete _btnClose; for (auto& iter : _allBtn) delete iter; } private: MsgBox(): _coor{nullptr}, _bg{nullptr}, _btn{nullptr}, _btnClose{nullptr}, _numBtn{0}, _bAddTab{true} {} Coor* _coor; Background* _bg; gcText _txtTitle; gcText _txt; UI::ButtonSprite* _btn; UI::ButtonSprite* _btnClose; //动态改变长度,多余的不会自动删除,所以需要标记使用数量 std::vector _allBtn; std::size_t _numBtn; Float _dxBtn; std::optional _posPick;//被拾取位置(差值,相加即可)(直到再抬起) bool _bAddTab;//添加段首缩进 void _refresh_coor() { _bg->GetCoor()->SetParent(_coor); _txtTitle->GetCoor()->SetParent(_coor); _txt->GetCoor()->SetParent(_coor); _btn->GetCoor()->SetParent(_coor); _btnClose->GetCoor()->SetParent(_coor); for (size_t i = 0; i < _allBtn.size(); ++i) { _allBtn[i]->GetCoor()->SetParent(_coor); } } }; } #endif