mirror of
https://github.com/opentibiabr/remeres-map-editor
synced 2026-08-15 18:26:04 -04:00
Bug Fixes: - Improved Lua scripting: Lua-visible nil is now consistent for missing/empty values across storage, JSON parsing, item/map/tile APIs, dialogs, app properties, and overlay hover. - Improved macOS graphics reliability (OpenGL context/linking) and safer background/thread handling for streaming and SQLite bootstrap. - Fixed client asset loading paths and improved SQLite migration input paths. UI Improvements: - Replaced “Missing …” modal dialogs with inline text boxes. - Made “Client directory” editable; added macOS app-picker browse flow. CI: - Added compile-scoped macOS builds via reusable workflow and a new build-macos job.
3048 lines
89 KiB
C++
3048 lines
89 KiB
C++
//////////////////////////////////////////////////////////////////////
|
|
// This file is part of Remere's Map Editor
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Remere's Map Editor is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Remere's Map Editor is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "main.h"
|
|
#include "lua_dialog.h"
|
|
#include "lua_api_image.h"
|
|
#include "../gui.h"
|
|
#include "../items.h"
|
|
#include "../map_display.h"
|
|
#include "../map_drawer.h"
|
|
#include "../editor.h"
|
|
#include <wx/msgdlg.h>
|
|
#include "../common_windows.h"
|
|
#include "../find_item_window.h"
|
|
#include "../dcbutton.h"
|
|
#include <wx/statline.h>
|
|
#include <wx/valgen.h>
|
|
#include <wx/aui/aui.h>
|
|
#include <wx/statbmp.h>
|
|
#include <wx/listctrl.h>
|
|
#include <wx/imaglist.h>
|
|
#include <wx/menu.h>
|
|
#include <wx/utils.h>
|
|
#ifdef __WXMSW__
|
|
#include <commctrl.h>
|
|
#endif
|
|
#include <wx/graphics.h>
|
|
#include <wx/dcbuffer.h>
|
|
|
|
using namespace std::string_literals;
|
|
|
|
class CustomButton : public wxControl {
|
|
public:
|
|
CustomButton(wxWindow* parent, wxWindowID id, const wxString &label, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = 0) :
|
|
wxControl(parent, id, pos, size, style | wxBORDER_NONE), m_label(label) {
|
|
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
|
}
|
|
|
|
void SetHoverColors(const wxColour &bg, const wxColour &fg) {
|
|
m_hoverBg = bg;
|
|
m_hoverFg = fg;
|
|
m_hasHover = true;
|
|
}
|
|
|
|
void SetRounded(bool rounded) {
|
|
m_rounded = rounded;
|
|
Refresh();
|
|
}
|
|
|
|
wxSize DoGetBestSize() const override {
|
|
wxScreenDC dc;
|
|
dc.SetFont(GetFont());
|
|
wxSize labelSize = dc.GetTextExtent(m_label);
|
|
return wxSize(labelSize.x + 30, labelSize.y + 16);
|
|
}
|
|
|
|
void SetParentBgColor(const wxColour &c) {
|
|
m_parentBg = c;
|
|
Refresh();
|
|
}
|
|
|
|
private:
|
|
wxString m_label;
|
|
wxColour m_hoverBg;
|
|
wxColour m_hoverFg;
|
|
wxColour m_parentBg;
|
|
bool m_rounded = false;
|
|
bool m_hasHover = false;
|
|
bool m_hovered = false;
|
|
bool m_pressed = false;
|
|
|
|
void OnPaint(wxPaintEvent &evt) {
|
|
wxAutoBufferedPaintDC dc(this);
|
|
|
|
// Use provided parent bg or actual parent bg
|
|
wxColour clearColor = m_parentBg.IsOk() ? m_parentBg : GetParent()->GetBackgroundColour();
|
|
dc.SetBackground(wxBrush(clearColor));
|
|
dc.Clear();
|
|
|
|
std::unique_ptr<wxGraphicsContext> gc(wxGraphicsContext::Create(dc));
|
|
if (gc) {
|
|
wxColour bg = GetBackgroundColour();
|
|
wxColour fg = GetForegroundColour();
|
|
|
|
if (!IsEnabled()) {
|
|
bg = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
|
|
fg = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
|
|
} else {
|
|
if (m_hasHover && m_hovered && m_hoverBg.IsOk()) {
|
|
bg = m_hoverBg;
|
|
}
|
|
if (m_hasHover && m_hovered && m_hoverFg.IsOk()) {
|
|
fg = m_hoverFg;
|
|
}
|
|
if (m_pressed) {
|
|
bg = bg.ChangeLightness(85);
|
|
}
|
|
}
|
|
|
|
gc->SetBrush(wxBrush(bg));
|
|
gc->SetPen(wxPen(bg)); // No border stroke
|
|
|
|
wxDouble w = GetSize().GetWidth();
|
|
wxDouble h = GetSize().GetHeight();
|
|
|
|
wxGraphicsPath path = gc->CreatePath();
|
|
if (m_rounded) {
|
|
path.AddRoundedRectangle(0, 0, w, h, 8.0);
|
|
} else {
|
|
path.AddRectangle(0, 0, w, h);
|
|
}
|
|
gc->FillPath(path);
|
|
|
|
gc->SetFont(GetFont(), fg);
|
|
|
|
wxDouble tw;
|
|
wxDouble th;
|
|
gc->GetTextExtent(m_label, &tw, &th, nullptr, nullptr);
|
|
gc->DrawText(m_label, (w - tw) / 2.0, (h - th) / 2.0);
|
|
}
|
|
}
|
|
|
|
void OnEnter(wxMouseEvent &e) {
|
|
m_hovered = true;
|
|
Refresh();
|
|
e.Skip();
|
|
}
|
|
void OnLeave(wxMouseEvent &e) {
|
|
m_hovered = false;
|
|
m_pressed = false;
|
|
Refresh();
|
|
e.Skip();
|
|
}
|
|
void OnDown(wxMouseEvent &e) {
|
|
m_pressed = true;
|
|
Refresh();
|
|
e.Skip();
|
|
}
|
|
void OnUp(wxMouseEvent &e) {
|
|
if (m_pressed) {
|
|
m_pressed = false;
|
|
Refresh();
|
|
wxCommandEvent clickEvent(wxEVT_BUTTON, GetId());
|
|
clickEvent.SetEventObject(this);
|
|
ProcessWindowEvent(clickEvent);
|
|
}
|
|
}
|
|
|
|
DECLARE_EVENT_TABLE()
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(CustomButton, wxControl)
|
|
EVT_PAINT(CustomButton::OnPaint)
|
|
EVT_ENTER_WINDOW(CustomButton::OnEnter)
|
|
EVT_LEAVE_WINDOW(CustomButton::OnLeave)
|
|
EVT_LEFT_DOWN(CustomButton::OnDown)
|
|
EVT_LEFT_UP(CustomButton::OnUp)
|
|
END_EVENT_TABLE()
|
|
|
|
// Specialized Canvas for Lua Dialogs
|
|
class MapPreviewCanvas : public MapCanvas {
|
|
public:
|
|
MapPreviewCanvas(wxWindow* parent, Editor &editor) :
|
|
MapCanvas(nullptr, editor, nullptr) {
|
|
|
|
ChangeFloor(7);
|
|
MapPreviewCanvas::SetZoom(1.0);
|
|
}
|
|
|
|
~MapPreviewCanvas() override = default;
|
|
|
|
// Overrides to decouple from MapWindow
|
|
void SetZoom(double value) override {
|
|
if (value < 0.125) {
|
|
value = 0.125;
|
|
}
|
|
if (value > 25.00) {
|
|
value = 25.0;
|
|
}
|
|
MapCanvas::SetZoom(value);
|
|
wxGLCanvas::Refresh();
|
|
}
|
|
|
|
void GetViewBox(int* view_scroll_x, int* view_scroll_y, int* screensize_x, int* screensize_y) const override {
|
|
wxSize size = GetClientSize();
|
|
*screensize_x = size.GetWidth();
|
|
*screensize_y = size.GetHeight();
|
|
*view_scroll_x = view_x;
|
|
*view_scroll_y = view_y;
|
|
}
|
|
|
|
void ScreenToMap(int screen_x, int screen_y, int* map_x, int* map_y) override {
|
|
screen_x *= GetContentScaleFactor();
|
|
screen_y *= GetContentScaleFactor();
|
|
|
|
if (screen_x < 0) {
|
|
*map_x = (view_x + screen_x) / 32;
|
|
} else {
|
|
*map_x = int(view_x + (screen_x * GetZoom())) / 32;
|
|
}
|
|
|
|
if (screen_y < 0) {
|
|
*map_y = (view_y + screen_y) / 32;
|
|
} else {
|
|
*map_y = int(view_y + (screen_y * GetZoom())) / 32;
|
|
}
|
|
|
|
if (GetFloor() <= 7) {
|
|
*map_x += 7 - GetFloor();
|
|
*map_y += 7 - GetFloor();
|
|
}
|
|
}
|
|
|
|
void GetScreenCenter(int* map_x, int* map_y) override {
|
|
wxSize size = GetClientSize();
|
|
ScreenToMap(size.GetWidth() / 2, size.GetHeight() / 2, map_x, map_y);
|
|
}
|
|
|
|
void SetPosition(int x, int y, int z) {
|
|
wxSize size = GetClientSize();
|
|
int width = size.GetWidth();
|
|
int height = size.GetHeight();
|
|
|
|
// If not yet laid out, use a default size to avoid bad coordinates
|
|
if (width <= 0) {
|
|
width = 400;
|
|
}
|
|
if (height <= 0) {
|
|
height = 300;
|
|
}
|
|
|
|
view_x = (x * 32) - (width * GetZoom()) / 2;
|
|
view_y = (y * 32) - (height * GetZoom()) / 2;
|
|
ChangeFloor(z);
|
|
Refresh();
|
|
}
|
|
|
|
void UpdatePositionStatus(int = -1, int = -1) override {
|
|
// No-op: preview canvases do not update the status bar
|
|
}
|
|
void UpdateZoomStatus() override {
|
|
// No-op: preview canvases do not update the status bar
|
|
}
|
|
void Refresh() override {
|
|
wxGLCanvas::Refresh();
|
|
}
|
|
|
|
void OnMouseMove(wxMouseEvent &event) {
|
|
event.Skip();
|
|
}
|
|
|
|
void OnWheel(wxMouseEvent &event) {
|
|
// Do nothing - disabled to prevent confusion and potential issues
|
|
}
|
|
|
|
void OnGainMouse(wxMouseEvent &event) {
|
|
Refresh();
|
|
}
|
|
void OnLoseMouse(wxMouseEvent &event) {
|
|
Refresh();
|
|
}
|
|
void OnMouseLeftClick(wxMouseEvent &event) {
|
|
SetFocus();
|
|
}
|
|
void OnMouseLeftRelease(wxMouseEvent &event) {
|
|
// No-op: preview canvas does not handle this mouse event
|
|
}
|
|
void OnMouseRightClick(wxMouseEvent &event) {
|
|
// No-op: preview canvas does not handle this mouse event
|
|
}
|
|
void OnMouseRightRelease(wxMouseEvent &event) {
|
|
// No-op: preview canvas does not handle this mouse event
|
|
}
|
|
void OnMouseCenterClick(wxMouseEvent &event) {
|
|
// No-op: preview canvas does not handle this mouse event
|
|
}
|
|
void OnMouseCenterRelease(wxMouseEvent &event) {
|
|
// No-op: preview canvas does not handle this mouse event
|
|
}
|
|
|
|
void SetViewSize(int w, int h) {
|
|
client_w = w;
|
|
client_h = h;
|
|
|
|
// Auto-resize the window to fit the new client dimensions
|
|
auto tile_pixel_size = static_cast<int>(32 / GetZoom());
|
|
int req_w = w * tile_pixel_size;
|
|
int req_h = h * tile_pixel_size;
|
|
|
|
// Add some padding for the frame borders if needed, but SetClientSize should handle content area
|
|
// We set the MinSize of the canvas to enforce this size
|
|
SetMinSize(wxSize(req_w, req_h));
|
|
|
|
// Attempt to resize the parent dialog to fit the new canvas size
|
|
wxWindow* parent = GetParent();
|
|
if (parent) {
|
|
parent->Fit();
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
void SetLight([[maybe_unused]] bool on) {
|
|
Refresh();
|
|
}
|
|
|
|
void SyncView() {
|
|
Editor* current_editor = g_gui.GetCurrentEditor();
|
|
if (!current_editor) {
|
|
return;
|
|
}
|
|
|
|
MapTab* tab = g_gui.GetCurrentMapTab();
|
|
if (tab) {
|
|
int cx;
|
|
int cy;
|
|
tab->GetCanvas()->GetScreenCenter(&cx, &cy);
|
|
SetPosition(cx, cy, tab->GetCanvas()->GetFloor());
|
|
SetZoom(tab->GetCanvas()->GetZoom());
|
|
}
|
|
}
|
|
|
|
int GetMapX() {
|
|
wxSize size = GetClientSize();
|
|
int width = size.GetWidth() > 0 ? size.GetWidth() : 400;
|
|
return (view_x + (width * (double)GetZoom()) / 2.0) / 32;
|
|
}
|
|
|
|
int GetMapY() {
|
|
wxSize size = GetClientSize();
|
|
int height = size.GetHeight() > 0 ? size.GetHeight() : 300;
|
|
return (view_y + (height * (double)GetZoom()) / 2.0) / 32;
|
|
}
|
|
|
|
int GetClientWidth() const {
|
|
return client_w;
|
|
}
|
|
int GetClientHeight() const {
|
|
return client_h;
|
|
}
|
|
|
|
private:
|
|
int view_x = 0;
|
|
int view_y = 0;
|
|
|
|
int client_w = 25;
|
|
int client_h = 25;
|
|
|
|
DECLARE_EVENT_TABLE()
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(MapPreviewCanvas, MapCanvas)
|
|
EVT_MOTION(MapPreviewCanvas::OnMouseMove)
|
|
EVT_MOUSEWHEEL(MapPreviewCanvas::OnWheel)
|
|
EVT_ENTER_WINDOW(MapPreviewCanvas::OnGainMouse)
|
|
EVT_LEAVE_WINDOW(MapPreviewCanvas::OnLoseMouse)
|
|
EVT_LEFT_DOWN(MapPreviewCanvas::OnMouseLeftClick)
|
|
EVT_LEFT_UP(MapPreviewCanvas::OnMouseLeftRelease)
|
|
EVT_RIGHT_DOWN(MapPreviewCanvas::OnMouseRightClick)
|
|
EVT_RIGHT_UP(MapPreviewCanvas::OnMouseRightRelease)
|
|
EVT_MIDDLE_DOWN(MapPreviewCanvas::OnMouseCenterClick)
|
|
EVT_MIDDLE_UP(MapPreviewCanvas::OnMouseCenterRelease)
|
|
EVT_PAINT(MapCanvas::OnPaint)
|
|
END_EVENT_TABLE()
|
|
|
|
BEGIN_EVENT_TABLE(LuaDialog, wxDialog)
|
|
EVT_CLOSE(LuaDialog::OnClose)
|
|
END_EVENT_TABLE()
|
|
|
|
LuaDialog::LuaDialog(const std::string &title, sol::this_state ts) :
|
|
wxDialog(g_gui.root, wxID_ANY, wxString(title), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
|
lua(ts) {
|
|
createLayout();
|
|
}
|
|
|
|
// Overload constructor to handle options
|
|
LuaDialog::LuaDialog(sol::table options, sol::this_state ts) :
|
|
wxDialog(g_gui.root, wxID_ANY, wxString(options.get_or(std::string("title"), "Script Dialog"s)), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | (options.get_or(std::string("resizable"), true) ? wxRESIZE_BORDER : 0) | (options.get_or(std::string("topmost"), false) ? wxSTAY_ON_TOP : 0)),
|
|
lua(ts) {
|
|
|
|
reqWidth = options.get_or(std::string("width"), -1);
|
|
reqHeight = options.get_or(std::string("height"), -1);
|
|
reqX = options.get_or(std::string("x"), -1);
|
|
reqY = options.get_or(std::string("y"), -1);
|
|
|
|
applyInitialSizeAndPosition();
|
|
setupDockablePanel(options);
|
|
|
|
if (options["onclose"].valid()) {
|
|
oncloseCallback = options["onclose"];
|
|
}
|
|
|
|
createLayout();
|
|
}
|
|
|
|
void LuaDialog::applyInitialSizeAndPosition() {
|
|
if (reqWidth != -1 || reqHeight != -1) {
|
|
SetMinSize(wxSize(reqWidth != -1 ? reqWidth : 150, reqHeight != -1 ? reqHeight : 100));
|
|
SetSize(reqX != -1 ? reqX : -1, reqY != -1 ? reqY : -1, reqWidth, reqHeight);
|
|
} else if (reqX != -1 || reqY != -1) {
|
|
SetPosition(wxPoint(reqX != -1 ? reqX : GetPosition().x, reqY != -1 ? reqY : GetPosition().y));
|
|
}
|
|
}
|
|
|
|
void LuaDialog::setupDockablePanel(sol::table &options) {
|
|
if (!options.get_or(std::string("dockable"), false)) {
|
|
return;
|
|
}
|
|
|
|
dockPanel = new wxPanel(g_gui.root, wxID_ANY);
|
|
|
|
wxAuiPaneInfo info;
|
|
std::string title = options.get_or(std::string("title"), "Script Dialog"s);
|
|
std::string id = options.get_or(std::string("id"), title);
|
|
|
|
info.Name(id);
|
|
info.Caption(title);
|
|
info.Right().Layer(1).Position(1).CloseButton(true).MaximizeButton(true);
|
|
|
|
int minW = options.get_or(std::string("min_width"), reqWidth != -1 ? reqWidth : 200);
|
|
int minH = options.get_or(std::string("min_height"), reqHeight != -1 ? reqHeight : 150);
|
|
info.MinSize(wxSize(minW, minH));
|
|
|
|
if (reqWidth != -1 || reqHeight != -1) {
|
|
info.BestSize(wxSize(reqWidth != -1 ? reqWidth : 300, reqHeight != -1 ? reqHeight : 200));
|
|
} else {
|
|
info.BestSize(wxSize(300, 200));
|
|
}
|
|
|
|
info.Dockable(true).Floatable(true);
|
|
|
|
g_gui.aui_manager->AddPane(dockPanel, info);
|
|
g_gui.aui_manager->Update();
|
|
}
|
|
|
|
LuaDialog::~LuaDialog() {
|
|
while (hotkeySuspendCount > 0) {
|
|
resumeHotkeys();
|
|
}
|
|
if (dockPanel) {
|
|
if (g_gui.aui_manager) {
|
|
wxAuiPaneInfo &info = g_gui.aui_manager->GetPane(dockPanel);
|
|
if (info.IsOk()) {
|
|
g_gui.aui_manager->DetachPane(dockPanel);
|
|
g_gui.aui_manager->Update();
|
|
}
|
|
}
|
|
if (dockPanel->GetParent()) {
|
|
dockPanel->Destroy();
|
|
}
|
|
dockPanel = nullptr;
|
|
}
|
|
}
|
|
|
|
void LuaDialog::createLayout() {
|
|
mainSizer = new wxBoxSizer(wxVERTICAL);
|
|
if (dockPanel) {
|
|
dockPanel->SetSizer(mainSizer);
|
|
} else {
|
|
SetSizer(mainSizer);
|
|
}
|
|
sizerStack.push(mainSizer);
|
|
}
|
|
|
|
void LuaDialog::ensureRowSizer() {
|
|
if (!currentRowSizer) {
|
|
currentRowSizer = new wxBoxSizer(wxHORIZONTAL);
|
|
getSizerForWidget()->Add(currentRowSizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
}
|
|
|
|
void LuaDialog::finishCurrentRow() {
|
|
currentRowSizer = nullptr;
|
|
}
|
|
|
|
wxWindow* LuaDialog::getParentForWidget() {
|
|
if (!panelStack.empty()) {
|
|
return panelStack.top();
|
|
}
|
|
if (currentTabPanel) {
|
|
return currentTabPanel;
|
|
}
|
|
if (dockPanel) {
|
|
return dockPanel;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
wxSizer* LuaDialog::getSizerForWidget() {
|
|
if (!sizerStack.empty()) {
|
|
return sizerStack.top();
|
|
}
|
|
// Fallback mechanism (should shouldn't happen if managed correctly)
|
|
if (currentTabSizer) {
|
|
return currentTabSizer;
|
|
}
|
|
return mainSizer;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::wrap(sol::table options) {
|
|
finishCurrentRow();
|
|
rowSizerStack.push(currentRowSizer);
|
|
currentRowSizer = nullptr;
|
|
|
|
wxBoxSizer* sizer = new wxBoxSizer(options.get_or(std::string("orient"), "horizontal"s) == "horizontal" ? wxHORIZONTAL : wxVERTICAL);
|
|
getSizerForWidget()->Add(sizer, 1, wxEXPAND | wxALL, 0);
|
|
sizerStack.push(sizer);
|
|
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::endwrap() {
|
|
finishCurrentRow();
|
|
if (!sizerStack.empty() && sizerStack.top() != mainSizer && (!currentTabSizer || sizerStack.top() != currentTabSizer)) {
|
|
sizerStack.pop();
|
|
}
|
|
if (!rowSizerStack.empty()) {
|
|
currentRowSizer = rowSizerStack.top();
|
|
rowSizerStack.pop();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::box(sol::table options) {
|
|
finishCurrentRow();
|
|
|
|
std::string orient = options.get_or(std::string("orient"), "vertical"s);
|
|
std::string label = options.get_or(std::string("label"), ""s);
|
|
bool expand = options.get_or("expand", label.empty()); // Defaults to true only if no label (backwards compat)
|
|
if (options["expand"].valid()) {
|
|
expand = options.get<bool>("expand");
|
|
}
|
|
|
|
wxSizer* sizer;
|
|
if (!label.empty()) {
|
|
// Static box sizer
|
|
wxStaticBoxSizer* staticBox = new wxStaticBoxSizer(orient == "horizontal" ? wxHORIZONTAL : wxVERTICAL, getParentForWidget(), wxString(label));
|
|
sizer = staticBox;
|
|
|
|
if (options["bgcolor"].valid() || options["fgcolor"].valid()) {
|
|
applyCommonOptions(staticBox->GetStaticBox(), options);
|
|
}
|
|
} else {
|
|
sizer = new wxBoxSizer(orient == "horizontal" ? wxHORIZONTAL : wxVERTICAL);
|
|
}
|
|
|
|
getSizerForWidget()->Add(sizer, expand ? 1 : 0, getSizerFlags(options, wxEXPAND | wxALL), getSizerBorder(options));
|
|
|
|
int width = options.get_or("width", -1);
|
|
int height = options.get_or("height", -1);
|
|
if (width != -1 || height != -1) {
|
|
wxSize sz(width, height);
|
|
sizer->SetMinSize(sz);
|
|
|
|
// If it's a static box sizer, we can set the max size on the actual window
|
|
if (!expand && !label.empty()) {
|
|
wxStaticBoxSizer* sbSizer = static_cast<wxStaticBoxSizer*>(sizer);
|
|
sbSizer->GetStaticBox()->SetMaxSize(sz);
|
|
}
|
|
}
|
|
|
|
rowSizerStack.push(currentRowSizer);
|
|
currentRowSizer = nullptr;
|
|
sizerStack.push(sizer);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::endbox() {
|
|
finishCurrentRow();
|
|
if (!sizerStack.empty()) {
|
|
sizerStack.pop();
|
|
}
|
|
if (!rowSizerStack.empty()) {
|
|
currentRowSizer = rowSizerStack.top();
|
|
rowSizerStack.pop();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::panel(sol::table options) {
|
|
finishCurrentRow();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("panel_{}", widgets.size()));
|
|
bool expand = options.get_or("expand", false);
|
|
|
|
wxPanel* panel = new wxPanel(getParentForWidget(), wxID_ANY);
|
|
applyCommonOptions(panel, options);
|
|
|
|
wxBoxSizer* panelSizer = new wxBoxSizer(options.get_or<std::string>("orient", "vertical") == "horizontal" ? wxHORIZONTAL : wxVERTICAL);
|
|
panel->SetSizer(panelSizer);
|
|
|
|
getSizerForWidget()->Add(panel, expand ? 1 : 0, getSizerFlags(options, wxEXPAND | wxALL), getSizerBorder(options));
|
|
|
|
rowSizerStack.push(currentRowSizer);
|
|
currentRowSizer = nullptr;
|
|
panelStack.push(panel);
|
|
sizerStack.push(panelSizer);
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "panel";
|
|
widget.widget = panel;
|
|
widgets.push_back(widget);
|
|
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::endpanel() {
|
|
finishCurrentRow();
|
|
if (!panelStack.empty()) {
|
|
panelStack.pop();
|
|
}
|
|
if (!sizerStack.empty()) {
|
|
sizerStack.pop();
|
|
}
|
|
if (!rowSizerStack.empty()) {
|
|
currentRowSizer = rowSizerStack.top();
|
|
rowSizerStack.pop();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::label(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string text = options.get_or(std::string("text"), "label"s);
|
|
std::string id = options.get_or(std::string("id"), ""s);
|
|
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(text));
|
|
currentRowSizer->Add(label, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
if (!id.empty()) {
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "label";
|
|
widget.widget = label;
|
|
widgets.push_back(widget);
|
|
}
|
|
|
|
applyCommonOptions(label, options);
|
|
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::mapCanvas(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
Editor* editor = g_gui.GetCurrentEditor();
|
|
if (!editor) {
|
|
wxMessageBox("You must have a map open to use the Map Canvas widget.", "Error", wxOK | wxICON_ERROR);
|
|
return this;
|
|
}
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("map_c_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
|
}
|
|
|
|
// Create the canvas
|
|
auto canvas = new MapPreviewCanvas(getParentForWidget(), *editor);
|
|
|
|
// Smaller default size to keep the window compact
|
|
canvas->SetMinSize(wxSize(200, 150));
|
|
|
|
// Default to center of map start
|
|
canvas->SetPosition(1000, 1000, 7);
|
|
|
|
currentRowSizer->Add(canvas, 1, getSizerFlags(options, wxEXPAND | wxALL), getSizerBorder(options));
|
|
|
|
if (!id.empty()) {
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "mapCanvas";
|
|
widget.widget = canvas;
|
|
widgets.push_back(widget);
|
|
}
|
|
|
|
applyCommonOptions(canvas, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::input(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("input_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
std::string text = options.get_or(std::string("text"), ""s);
|
|
bool focus = options.get_or(std::string("focus"), false);
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
|
}
|
|
|
|
wxTextCtrl* input = new wxTextCtrl(getParentForWidget(), wxID_ANY, wxString(text), wxDefaultPosition, wxSize(150, -1));
|
|
currentRowSizer->Add(input, 1, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
input->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent &event) {
|
|
suspendHotkeys();
|
|
event.Skip();
|
|
});
|
|
input->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &event) {
|
|
resumeHotkeys();
|
|
event.Skip();
|
|
});
|
|
|
|
if (focus) {
|
|
input->SetFocus();
|
|
}
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "input";
|
|
widget.widget = input;
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
// Store initial value
|
|
values[id] = sol::make_object(lua, text);
|
|
|
|
// Bind change event
|
|
input->Bind(wxEVT_TEXT, [this, id](wxCommandEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(input, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::number(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("number_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
double value = options.get_or(std::string("text"), options.get_or(std::string("value"), 0.0));
|
|
int decimals = options.get_or(std::string("decimals"), 0);
|
|
double minVal = options.get_or(std::string("min"), -999999.0);
|
|
double maxVal = options.get_or(std::string("max"), 999999.0);
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
|
}
|
|
|
|
wxSpinCtrlDouble* spin = new wxSpinCtrlDouble(getParentForWidget(), wxID_ANY, "", wxDefaultPosition, wxSize(100, -1), wxSP_ARROW_KEYS, minVal, maxVal, value, decimals == 0 ? 1 : std::pow(0.1, decimals));
|
|
spin->SetDigits(decimals);
|
|
currentRowSizer->Add(spin, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "number";
|
|
widget.widget = spin;
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, value);
|
|
|
|
spin->Bind(wxEVT_SPINCTRLDOUBLE, [this, id](wxSpinDoubleEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(spin, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::slider(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("slider_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
int value = options.get_or(std::string("value"), 0);
|
|
int minVal = options.get_or(std::string("min"), 0);
|
|
int maxVal = options.get_or(std::string("max"), 100);
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
|
}
|
|
|
|
wxSlider* slider = new wxSlider(getParentForWidget(), wxID_ANY, value, minVal, maxVal, wxDefaultPosition, wxSize(150, -1));
|
|
currentRowSizer->Add(slider, 1, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "slider";
|
|
widget.widget = slider;
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, value);
|
|
|
|
slider->Bind(wxEVT_SLIDER, [this, id](wxCommandEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(slider, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::check(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("check_{}", widgets.size()));
|
|
std::string text = options.get_or(std::string("text"), ""s);
|
|
bool selected = options.get_or(std::string("selected"), false);
|
|
|
|
wxCheckBox* checkbox = new wxCheckBox(getParentForWidget(), wxID_ANY, wxString(text));
|
|
checkbox->SetValue(selected);
|
|
currentRowSizer->Add(checkbox, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "check";
|
|
widget.widget = checkbox;
|
|
if (options["onclick"].valid()) {
|
|
widget.onclick = options["onclick"];
|
|
}
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, selected);
|
|
|
|
checkbox->Bind(wxEVT_CHECKBOX, [this, id](wxCommandEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(checkbox, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::radio(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("radio_{}", widgets.size()));
|
|
std::string text = options.get_or(std::string("text"), ""s);
|
|
bool selected = options.get_or(std::string("selected"), false);
|
|
|
|
wxRadioButton* radio = new wxRadioButton(getParentForWidget(), wxID_ANY, wxString(text));
|
|
radio->SetValue(selected);
|
|
currentRowSizer->Add(radio, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "radio";
|
|
widget.widget = radio;
|
|
if (options["onclick"].valid()) {
|
|
widget.onclick = options["onclick"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, selected);
|
|
|
|
radio->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(radio, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::combobox(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("combobox_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
std::string selected = options.get_or(std::string("option"), ""s);
|
|
|
|
wxArrayString choices;
|
|
if (options["options"].valid()) {
|
|
sol::table opts = options["options"];
|
|
for (size_t i = 1; i <= opts.size(); ++i) {
|
|
if (opts[i].valid()) {
|
|
choices.Add(wxString(opts[i].get<std::string>()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
|
}
|
|
|
|
wxChoice* choice = new wxChoice(getParentForWidget(), wxID_ANY, wxDefaultPosition, wxSize(150, -1), choices);
|
|
|
|
if (!selected.empty()) {
|
|
int idx = choice->FindString(wxString(selected));
|
|
if (idx != wxNOT_FOUND) {
|
|
choice->SetSelection(idx);
|
|
}
|
|
} else if (choices.GetCount() > 0) {
|
|
choice->SetSelection(0);
|
|
selected = choices[0].ToStdString();
|
|
}
|
|
|
|
currentRowSizer->Add(choice, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "combobox";
|
|
widget.widget = choice;
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, selected);
|
|
|
|
choice->Bind(wxEVT_CHOICE, [this, id](wxCommandEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(choice, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::button(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("button_{}", widgets.size()));
|
|
std::string text = options.get_or(std::string("text"), "Button"s);
|
|
bool rounded = options.get_or("rounded", false);
|
|
bool hasHover = options["hover"].valid();
|
|
bool hasCustomColor = options["bgcolor"].valid() || options["fgcolor"].valid();
|
|
|
|
bool useCustom = rounded || hasHover || hasCustomColor;
|
|
|
|
wxWindow* finalWidget = nullptr;
|
|
|
|
if (useCustom) {
|
|
auto btn = new CustomButton(getParentForWidget(), wxID_ANY, wxString(text));
|
|
if (rounded) {
|
|
btn->SetRounded(true);
|
|
}
|
|
|
|
// Anti-aliasing corner fix: check if we are inside a StaticBoxSizer with custom color
|
|
wxStaticBoxSizer* sbs = wxDynamicCast(currentRowSizer, wxStaticBoxSizer);
|
|
if (sbs) {
|
|
wxStaticBox* box = sbs->GetStaticBox();
|
|
if (box && box->GetBackgroundColour().IsOk()) {
|
|
btn->SetParentBgColor(box->GetBackgroundColour());
|
|
}
|
|
}
|
|
|
|
if (hasHover) {
|
|
sol::table hoverOpts = options["hover"];
|
|
auto parseColor = [](sol::object colorObj) -> wxColour {
|
|
if (colorObj.is<sol::table>()) {
|
|
sol::table c = colorObj.as<sol::table>();
|
|
return wxColour(c.get_or("r", 1), c.get_or("g", 2), c.get_or("b", 3));
|
|
} else if (colorObj.is<std::string>()) {
|
|
std::string s = colorObj.as<std::string>();
|
|
return (s.size() > 0 && s[0] == '#') ? wxColour(s) : wxColour(s);
|
|
}
|
|
return wxColour();
|
|
};
|
|
|
|
wxColour hBg;
|
|
wxColour hFg;
|
|
if (hoverOpts["bgcolor"].valid()) {
|
|
hBg = parseColor(hoverOpts["bgcolor"]);
|
|
}
|
|
if (hoverOpts["fgcolor"].valid()) {
|
|
hFg = parseColor(hoverOpts["fgcolor"]);
|
|
}
|
|
btn->SetHoverColors(hBg, hFg);
|
|
}
|
|
finalWidget = btn;
|
|
} else {
|
|
wxButton* btn = new wxButton(getParentForWidget(), wxID_ANY, wxString(text));
|
|
if (options.get_or("focus", false)) {
|
|
btn->SetDefault();
|
|
}
|
|
finalWidget = btn;
|
|
}
|
|
|
|
currentRowSizer->Add(finalWidget, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "button";
|
|
widget.widget = finalWidget;
|
|
if (options["onclick"].valid()) {
|
|
widget.onclick = options["onclick"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, false);
|
|
|
|
finalWidget->Bind(wxEVT_BUTTON, [this, id](wxCommandEvent &) {
|
|
onButtonClick(id);
|
|
});
|
|
|
|
applyCommonOptions(finalWidget, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::color(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("color_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
|
|
wxColour defaultColor = *wxBLACK;
|
|
if (options["color"].valid()) {
|
|
sol::table c = options["color"];
|
|
int r = c.get_or(std::string("red"), c.get_or(1, 0));
|
|
int g = c.get_or(std::string("green"), c.get_or(2, 0));
|
|
int b = c.get_or(std::string("blue"), c.get_or(3, 0));
|
|
defaultColor = wxColour(r, g, b);
|
|
}
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
|
}
|
|
|
|
wxColourPickerCtrl* picker = new wxColourPickerCtrl(getParentForWidget(), wxID_ANY, defaultColor);
|
|
currentRowSizer->Add(picker, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "color";
|
|
widget.widget = picker;
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
// Store color as table
|
|
sol::table colorTable = lua.create_table();
|
|
colorTable["red"] = defaultColor.Red();
|
|
colorTable["green"] = defaultColor.Green();
|
|
colorTable["blue"] = defaultColor.Blue();
|
|
values[id] = colorTable;
|
|
|
|
picker->Bind(wxEVT_COLOURPICKER_CHANGED, [this, id](wxColourPickerEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(picker, options);
|
|
return this;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// LuaDialogListBox - Custom list widget for LuaDialog
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct LuaListBoxItem {
|
|
std::string text;
|
|
int iconId = 0;
|
|
LuaAPI::LuaImage customImage;
|
|
std::string tooltip;
|
|
};
|
|
|
|
class LuaDialogListBox : public wxVListBox {
|
|
public:
|
|
LuaDialogListBox(wxWindow* parent, wxWindowID id, const wxSize &size) :
|
|
wxVListBox(parent, id, wxDefaultPosition, size, wxLB_SINGLE) {
|
|
Bind(wxEVT_LEFT_DOWN, &LuaDialogListBox::OnLeftDown, this);
|
|
Bind(wxEVT_LEFT_DCLICK, &LuaDialogListBox::OnLeftDouble, this);
|
|
Bind(wxEVT_MOTION, &LuaDialogListBox::OnMotion, this);
|
|
Bind(wxEVT_LEAVE_WINDOW, &LuaDialogListBox::OnLeaveWindow, this);
|
|
}
|
|
|
|
void AddItem(const std::string &text, int iconId, const LuaAPI::LuaImage &img, const std::string &tooltip) {
|
|
LuaListBoxItem item;
|
|
item.text = text;
|
|
item.iconId = iconId;
|
|
item.customImage = img;
|
|
item.tooltip = tooltip;
|
|
if (!item.customImage.isValid() && iconId > 0) {
|
|
item.customImage = LuaAPI::LuaImage::loadFromSprite(iconId);
|
|
}
|
|
items.push_back(item);
|
|
SetItemCount(items.size());
|
|
}
|
|
|
|
void Clear() {
|
|
items.clear();
|
|
SetItemCount(0);
|
|
lastTooltipItem = -1;
|
|
}
|
|
|
|
void SetIconSize(int width, int height) {
|
|
iconWidth = width;
|
|
iconHeight = height;
|
|
if (itemHeight < iconHeight + 6) {
|
|
itemHeight = iconHeight + 6;
|
|
}
|
|
}
|
|
|
|
void SetItemHeight(int height) {
|
|
itemHeight = height;
|
|
}
|
|
|
|
void SetShowText(bool value) {
|
|
showText = value;
|
|
}
|
|
|
|
void SetSmooth(bool value) {
|
|
smooth = value;
|
|
}
|
|
|
|
const LuaListBoxItem* GetItem(int index) const {
|
|
if (index < 0 || index >= static_cast<int>(items.size())) {
|
|
return nullptr;
|
|
}
|
|
return &items[index];
|
|
}
|
|
|
|
void OnLeftDown(wxMouseEvent &event) {
|
|
int n = HitTest(event.GetPosition());
|
|
if (n != wxNOT_FOUND) {
|
|
SetSelection(n);
|
|
CallAfter([this, n]() {
|
|
wxCommandEvent ce(wxEVT_LISTBOX, GetId());
|
|
ce.SetInt(n);
|
|
ce.SetEventObject(this);
|
|
ProcessWindowEvent(ce);
|
|
});
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
void OnLeftDouble(wxMouseEvent &event) {
|
|
int n = HitTest(event.GetPosition());
|
|
if (n != wxNOT_FOUND) {
|
|
SetSelection(n);
|
|
SetFocus();
|
|
CallAfter([this, n]() {
|
|
wxCommandEvent ce(wxEVT_LISTBOX_DCLICK, GetId());
|
|
ce.SetInt(n);
|
|
ce.SetEventObject(this);
|
|
ProcessWindowEvent(ce);
|
|
});
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
void OnMotion(wxMouseEvent &event) {
|
|
int n = HitTest(event.GetPosition());
|
|
if (n != wxNOT_FOUND && n < static_cast<int>(items.size())) {
|
|
if (n != lastTooltipItem) {
|
|
const std::string &tip = items[n].tooltip;
|
|
if (!tip.empty()) {
|
|
SetToolTip(wxString(tip));
|
|
} else {
|
|
UnsetToolTip();
|
|
}
|
|
lastTooltipItem = n;
|
|
}
|
|
} else if (lastTooltipItem != -1) {
|
|
UnsetToolTip();
|
|
lastTooltipItem = -1;
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
void OnLeaveWindow(wxMouseEvent &event) {
|
|
UnsetToolTip();
|
|
lastTooltipItem = -1;
|
|
event.Skip();
|
|
}
|
|
|
|
void OnDrawItem(wxDC &dc, const wxRect &rect, size_t n) const override {
|
|
if (n >= items.size()) {
|
|
return;
|
|
}
|
|
const LuaListBoxItem &item = items[n];
|
|
|
|
bool isSelected = IsSelected(n);
|
|
if (isSelected) {
|
|
dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)));
|
|
dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)));
|
|
} else {
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
}
|
|
dc.DrawRectangle(rect);
|
|
|
|
// Icon
|
|
int textX = rect.x + 5;
|
|
if (item.customImage.isValid()) {
|
|
wxBitmap bmp = item.customImage.getBitmap(iconWidth, iconHeight, smooth);
|
|
if (bmp.IsOk()) {
|
|
dc.DrawBitmap(bmp, rect.x + 2, rect.y + (rect.height - iconHeight) / 2, true);
|
|
textX += iconWidth + 4;
|
|
}
|
|
}
|
|
|
|
if (showText) {
|
|
if (isSelected) {
|
|
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
|
|
} else {
|
|
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT));
|
|
}
|
|
|
|
wxString text = wxString(item.text);
|
|
wxSize extent = dc.GetTextExtent(text);
|
|
dc.DrawText(text, textX, rect.y + (rect.height - extent.y) / 2);
|
|
}
|
|
}
|
|
|
|
virtual wxCoord OnMeasureItem(size_t n) const override {
|
|
return itemHeight;
|
|
}
|
|
|
|
int iconWidth = 16;
|
|
int iconHeight = 16;
|
|
int itemHeight = 24;
|
|
bool showText = true;
|
|
bool smooth = true;
|
|
int lastTooltipItem = -1;
|
|
std::vector<LuaListBoxItem> items;
|
|
};
|
|
|
|
// Specialized ListCtrl for Grid with Tooltips
|
|
class LuaGridCtrl : public wxListCtrl {
|
|
public:
|
|
LuaGridCtrl(wxWindow* parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) :
|
|
wxListCtrl(parent, id, pos, size, style) {
|
|
Bind(wxEVT_MOTION, &LuaGridCtrl::OnMotion, this);
|
|
Bind(wxEVT_LEAVE_WINDOW, &LuaGridCtrl::OnLeaveWindow, this);
|
|
}
|
|
|
|
std::map<long, std::string> tooltips;
|
|
long lastTooltipItem = -1;
|
|
int iconWidth = 32;
|
|
int iconHeight = 32;
|
|
int cellWidth = -1;
|
|
int cellHeight = -1;
|
|
|
|
void AddTooltip(long item, std::string_view tip) {
|
|
tooltips[item] = tip;
|
|
}
|
|
|
|
void ClearTooltips() {
|
|
tooltips.clear();
|
|
lastTooltipItem = -1;
|
|
}
|
|
|
|
std::string GetTooltip(long item) const {
|
|
auto it = tooltips.find(item);
|
|
if (it == tooltips.end()) {
|
|
return ""s;
|
|
}
|
|
return it->second;
|
|
}
|
|
|
|
void OnMotion(wxMouseEvent &event) {
|
|
int flags = 0;
|
|
long index = HitTest(event.GetPosition(), flags);
|
|
if (index != wxNOT_FOUND) {
|
|
if (index != lastTooltipItem) {
|
|
if (tooltips.contains(index)) {
|
|
SetToolTip(wxString(tooltips[index]));
|
|
} else {
|
|
UnsetToolTip();
|
|
}
|
|
lastTooltipItem = index;
|
|
}
|
|
} else {
|
|
if (lastTooltipItem != -1) {
|
|
UnsetToolTip();
|
|
lastTooltipItem = -1;
|
|
}
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
void OnLeaveWindow(wxMouseEvent &event) {
|
|
UnsetToolTip();
|
|
lastTooltipItem = -1;
|
|
event.Skip();
|
|
}
|
|
};
|
|
|
|
void LuaDialog::handleListClick(const std::string &id, LuaDialogListBox* listbox, const sol::function &callback, wxMouseEvent &event) {
|
|
if (!callback.valid() || !lua.lua_state() || !callback.lua_state()) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
int index = listbox->HitTest(event.GetPosition());
|
|
if (index == wxNOT_FOUND) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
const LuaListBoxItem* item = listbox->GetItem(index);
|
|
if (!item) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
listbox->SetSelection(index);
|
|
listbox->SetFocus();
|
|
values[id] = sol::make_object(lua, index + 1);
|
|
sol::table info = lua.create_table();
|
|
info["type"] = "list";
|
|
info["index"] = index + 1;
|
|
info["text"] = item->text;
|
|
if (!item->tooltip.empty()) {
|
|
info["tooltip"] = item->tooltip;
|
|
}
|
|
info["widget_id"] = id;
|
|
try {
|
|
callback(this, info);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
void LuaDialog::handleListContextMenu(const std::string &id, LuaDialogListBox* listbox, const sol::function &callback, wxContextMenuEvent &event) {
|
|
if (!callback.valid() || !lua.lua_state() || !callback.lua_state()) {
|
|
return;
|
|
}
|
|
wxPoint screenPos = event.GetPosition();
|
|
if (screenPos == wxDefaultPosition) {
|
|
screenPos = wxGetMousePosition();
|
|
}
|
|
wxPoint clientPos = listbox->ScreenToClient(screenPos);
|
|
int index = listbox->HitTest(clientPos);
|
|
if (index == wxNOT_FOUND) {
|
|
return;
|
|
}
|
|
const LuaListBoxItem* item = listbox->GetItem(index);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
sol::table info = lua.create_table();
|
|
info["type"] = "list";
|
|
info["index"] = index + 1;
|
|
info["text"] = item->text;
|
|
if (!item->tooltip.empty()) {
|
|
info["tooltip"] = item->tooltip;
|
|
}
|
|
info["widget_id"] = id;
|
|
popupContextMenu(callback, info, listbox, screenPos);
|
|
}
|
|
|
|
LuaDialog* LuaDialog::list(sol::table options) {
|
|
finishCurrentRow();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("list_{}", widgets.size()));
|
|
int width = options.get_or(std::string("width"), 200);
|
|
int height = options.get_or(std::string("height"), 150);
|
|
int iconWidth = options.get_or(std::string("icon_width"), 16);
|
|
int iconHeight = options.get_or(std::string("icon_height"), 16);
|
|
int iconSize = options.get_or(std::string("icon_size"), -1);
|
|
int itemHeight = options.get_or(std::string("item_height"), 24);
|
|
bool showText = options.get_or(std::string("show_text"), true);
|
|
bool smooth = options.get_or(std::string("smooth"), true);
|
|
|
|
if (iconSize > 0) {
|
|
iconWidth = iconSize;
|
|
iconHeight = iconSize;
|
|
}
|
|
|
|
auto listbox = new LuaDialogListBox(getParentForWidget(), wxID_ANY, wxSize(width, height));
|
|
listbox->SetIconSize(iconWidth, iconHeight);
|
|
if (itemHeight > 0) {
|
|
listbox->SetItemHeight(itemHeight);
|
|
}
|
|
listbox->SetShowText(showText);
|
|
listbox->SetSmooth(smooth);
|
|
getSizerForWidget()->Add(listbox, 1, getSizerFlags(options, wxEXPAND | wxALL), getSizerBorder(options));
|
|
|
|
// Populate items
|
|
if (options["items"].valid()) {
|
|
sol::table itemsTable = options["items"];
|
|
for (auto &pair : itemsTable) {
|
|
if (pair.second.is<sol::table>()) {
|
|
sol::table itemTable = pair.second;
|
|
std::string text = itemTable.get_or(std::string("text"), ""s);
|
|
int icon = itemTable.get_or(std::string("icon"), 0);
|
|
std::string tooltip = itemTable.get_or(std::string("tooltip"), ""s);
|
|
LuaAPI::LuaImage img;
|
|
if (itemTable["image"].valid()) {
|
|
if (itemTable["image"].is<LuaAPI::LuaImage>()) {
|
|
img = itemTable["image"].get<LuaAPI::LuaImage>();
|
|
} else if (itemTable["image"].is<std::string>()) {
|
|
img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get<std::string>());
|
|
}
|
|
}
|
|
listbox->AddItem(text, icon, img, tooltip);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Selection
|
|
int selection = options.get_or(std::string("selection"), 0);
|
|
if (selection > 0 && selection <= (int)listbox->items.size()) {
|
|
listbox->SetSelection(selection - 1);
|
|
}
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "list";
|
|
widget.widget = listbox;
|
|
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
if (options["ondoubleclick"].valid()) {
|
|
widget.ondoubleclick = options["ondoubleclick"];
|
|
}
|
|
if (options["oncontextmenu"].valid()) {
|
|
widget.oncontextmenu = options["oncontextmenu"];
|
|
}
|
|
if (options["onleftclick"].valid()) {
|
|
widget.onleftclick = options["onleftclick"];
|
|
}
|
|
if (options["onrightclick"].valid()) {
|
|
widget.onrightclick = options["onrightclick"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
// Bind selection event
|
|
listbox->Bind(wxEVT_LISTBOX, [this, id](wxCommandEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
// Bind double click event
|
|
listbox->Bind(wxEVT_LISTBOX_DCLICK, [this, id](wxCommandEvent &) {
|
|
onWidgetDoubleClick(id);
|
|
});
|
|
|
|
sol::function listLeftClick = widget.onleftclick;
|
|
listbox->Bind(wxEVT_LEFT_DOWN, [this, id, listbox, listLeftClick](wxMouseEvent &event) {
|
|
handleListClick(id, listbox, listLeftClick, event);
|
|
});
|
|
|
|
sol::function listRightClick = widget.onrightclick;
|
|
listbox->Bind(wxEVT_RIGHT_DOWN, [this, id, listbox, listRightClick](wxMouseEvent &event) {
|
|
handleListClick(id, listbox, listRightClick, event);
|
|
});
|
|
|
|
// Bind context menu event
|
|
sol::function listContextMenu = widget.oncontextmenu;
|
|
listbox->Bind(wxEVT_CONTEXT_MENU, [this, id, listbox, listContextMenu](wxContextMenuEvent &event) {
|
|
handleListContextMenu(id, listbox, listContextMenu, event);
|
|
});
|
|
|
|
applyCommonOptions(listbox, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::file(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("file_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
std::string filename = options.get_or(std::string("filename"), ""s);
|
|
std::string filetypes = options.get_or(std::string("filetypes"), "*.*"s);
|
|
bool save = options.get_or(std::string("save"), false);
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
}
|
|
|
|
wxFilePickerCtrl* picker;
|
|
if (save) {
|
|
picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), wxFLP_SAVE | wxFLP_USE_TEXTCTRL);
|
|
} else {
|
|
picker = new wxFilePickerCtrl(getParentForWidget(), wxID_ANY, wxString(filename), "Select a file", wxString(filetypes), wxDefaultPosition, wxSize(200, -1), wxFLP_OPEN | wxFLP_FILE_MUST_EXIST | wxFLP_USE_TEXTCTRL);
|
|
}
|
|
currentRowSizer->Add(picker, 1, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "file";
|
|
widget.widget = picker;
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, filename);
|
|
|
|
picker->Bind(wxEVT_FILEPICKER_CHANGED, [this, id](wxFileDirPickerEvent &) {
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
applyCommonOptions(picker, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::image(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("image_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
int width = options.get_or(std::string("width"), -1);
|
|
int height = options.get_or(std::string("height"), -1);
|
|
bool smooth = options.get_or(std::string("smooth"), true); // Default to smooth scaling
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
}
|
|
|
|
wxBitmap bmp;
|
|
LuaAPI::LuaImage luaImage;
|
|
|
|
// Check for different image sources
|
|
if (options["image"].valid()) {
|
|
// Direct LuaImage object
|
|
luaImage = options["image"].get<LuaAPI::LuaImage>();
|
|
} else if (options["path"].valid()) {
|
|
// Load from file path
|
|
std::string path = options.get<std::string>("path");
|
|
luaImage = LuaAPI::LuaImage::loadFromFile(path);
|
|
} else if (options["itemid"].valid()) {
|
|
// Load from item sprite ID
|
|
int itemId = options.get<int>("itemid");
|
|
luaImage = LuaAPI::LuaImage::loadFromItemSprite(itemId);
|
|
} else if (options["spriteid"].valid()) {
|
|
// Load from raw sprite ID
|
|
int spriteId = options.get<int>("spriteid");
|
|
luaImage = LuaAPI::LuaImage::loadFromSprite(spriteId);
|
|
}
|
|
|
|
// Get bitmap with optional resizing
|
|
if (luaImage.isValid()) {
|
|
if (width > 0 && height > 0) {
|
|
bmp = luaImage.getBitmap(width, height, smooth);
|
|
} else if (width > 0) {
|
|
// Scale proportionally based on width
|
|
double factor = static_cast<double>(width) / luaImage.getWidth();
|
|
auto newHeight = static_cast<int>(luaImage.getHeight() * factor);
|
|
bmp = luaImage.getBitmap(width, newHeight, smooth);
|
|
} else if (height > 0) {
|
|
// Scale proportionally based on height
|
|
double factor = static_cast<double>(height) / luaImage.getHeight();
|
|
auto newWidth = static_cast<int>(luaImage.getWidth() * factor);
|
|
bmp = luaImage.getBitmap(newWidth, height, smooth);
|
|
} else {
|
|
bmp = luaImage.getBitmap();
|
|
}
|
|
}
|
|
|
|
// Create placeholder if no valid image
|
|
if (!bmp.IsOk()) {
|
|
int placeholderW = width > 0 ? width : 32;
|
|
int placeholderH = height > 0 ? height : 32;
|
|
bmp.Create(placeholderW, placeholderH);
|
|
wxMemoryDC dc(bmp);
|
|
dc.SetBrush(*wxLIGHT_GREY_BRUSH);
|
|
dc.SetPen(*wxGREY_PEN);
|
|
dc.DrawRectangle(0, 0, placeholderW, placeholderH);
|
|
dc.SelectObject(wxNullBitmap);
|
|
}
|
|
|
|
wxStaticBitmap* staticBmp = new wxStaticBitmap(getParentForWidget(), wxID_ANY, bmp);
|
|
currentRowSizer->Add(staticBmp, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "image";
|
|
widget.widget = staticBmp;
|
|
widgets.push_back(widget);
|
|
|
|
// Store the LuaImage in values for later retrieval/modification
|
|
values[id] = sol::make_object(lua, luaImage);
|
|
|
|
applyCommonOptions(staticBmp, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::item(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("item_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
int itemId = options.get_or(std::string("itemid"), 0);
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
}
|
|
|
|
int clientId = (itemId > 100 && itemId <= g_items.getMaxID()) ? g_items[itemId].clientID : 0;
|
|
auto btn = new ItemButton(getParentForWidget(), RENDER_SIZE_32x32, clientId);
|
|
currentRowSizer->Add(btn, 0, getSizerFlags(options, wxALIGN_CENTER_VERTICAL | wxRIGHT), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "item";
|
|
widget.widget = btn;
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
if (options["onclick"].valid()) {
|
|
widget.onclick = options["onclick"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, itemId);
|
|
|
|
bool readonly = options.get_or(std::string("readonly"), false);
|
|
|
|
// Bind click event
|
|
btn->Bind(wxEVT_BUTTON, [this, id, btn, readonly](wxCommandEvent &) {
|
|
handleItemButtonClick(id, btn, readonly);
|
|
});
|
|
|
|
applyCommonOptions(btn, options);
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::separator(sol::optional<sol::table> options) {
|
|
finishCurrentRow();
|
|
|
|
wxStaticLine* line = new wxStaticLine(getParentForWidget(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
|
|
sol::table opts = options.value_or(lua.create_table());
|
|
getSizerForWidget()->Add(line, 0, getSizerFlags(opts, wxEXPAND | wxALL), getSizerBorder(opts));
|
|
|
|
applyCommonOptions(line, opts);
|
|
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::newrow() {
|
|
finishCurrentRow();
|
|
return this;
|
|
}
|
|
|
|
void LuaDialog::ensureNotebook() {
|
|
if (currentNotebook) {
|
|
return;
|
|
}
|
|
wxWindow* parent = dockPanel ? static_cast<wxWindow*>(dockPanel) : static_cast<wxWindow*>(this);
|
|
currentNotebook = new wxNotebook(parent, wxID_ANY);
|
|
mainSizer->Add(currentNotebook, 1, wxEXPAND | wxALL, 5);
|
|
activeNotebook = currentNotebook;
|
|
tabInfos.clear();
|
|
notebookEventsBound = false;
|
|
}
|
|
|
|
void LuaDialog::onNotebookPageChanging(wxNotebookEvent &event) {
|
|
int newSelection = event.GetSelection();
|
|
if (newSelection == wxNOT_FOUND || newSelection >= static_cast<int>(tabInfos.size())) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
if (!tabInfos[newSelection].isButton) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
if (suppressTabButtonClick && suppressTabButtonIndex == newSelection) {
|
|
suppressTabButtonClick = false;
|
|
suppressTabButtonIndex = -1;
|
|
event.Veto();
|
|
return;
|
|
}
|
|
CallAfter([this, newSelection]() {
|
|
handleTabButtonClick(newSelection);
|
|
});
|
|
event.Veto();
|
|
}
|
|
|
|
void LuaDialog::onNotebookLeftDown(wxMouseEvent &event) {
|
|
if (!activeNotebook || tabInfos.empty()) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
wxPoint pos = event.GetPosition();
|
|
long flags = 0;
|
|
int index = activeNotebook->HitTest(pos, &flags);
|
|
if (index == wxNOT_FOUND || index >= static_cast<int>(tabInfos.size()) || !tabInfos[index].isButton) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
suppressTabButtonClick = true;
|
|
suppressTabButtonIndex = index;
|
|
CallAfter([this, index]() {
|
|
handleTabButtonClick(index);
|
|
});
|
|
CallAfter([this, index]() {
|
|
if (suppressTabButtonClick && suppressTabButtonIndex == index) {
|
|
suppressTabButtonClick = false;
|
|
suppressTabButtonIndex = -1;
|
|
}
|
|
});
|
|
event.StopPropagation();
|
|
}
|
|
|
|
void LuaDialog::onNotebookContextMenu(wxContextMenuEvent &event) {
|
|
if (!activeNotebook || tabInfos.empty()) {
|
|
return;
|
|
}
|
|
wxPoint screenPos = event.GetPosition();
|
|
if (screenPos == wxDefaultPosition) {
|
|
screenPos = wxGetMousePosition();
|
|
}
|
|
wxPoint clientPos = activeNotebook->ScreenToClient(screenPos);
|
|
long flags = 0;
|
|
int index = activeNotebook->HitTest(clientPos, &flags);
|
|
if (index == wxNOT_FOUND || index >= static_cast<int>(tabInfos.size())) {
|
|
return;
|
|
}
|
|
handleTabContextMenu(index, screenPos);
|
|
}
|
|
|
|
void LuaDialog::bindNotebookEvents() {
|
|
if (notebookEventsBound) {
|
|
return;
|
|
}
|
|
notebookEventsBound = true;
|
|
currentNotebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGING, &LuaDialog::onNotebookPageChanging, this);
|
|
currentNotebook->Bind(wxEVT_LEFT_DOWN, &LuaDialog::onNotebookLeftDown, this);
|
|
currentNotebook->Bind(wxEVT_CONTEXT_MENU, &LuaDialog::onNotebookContextMenu, this);
|
|
}
|
|
|
|
int LuaDialog::insertTabPanel(const std::string &text, int insertIndex) {
|
|
currentTabPanel = new wxPanel(currentNotebook);
|
|
currentTabSizer = new wxBoxSizer(wxVERTICAL);
|
|
currentTabPanel->SetSizer(currentTabSizer);
|
|
|
|
if (insertIndex > 0) {
|
|
int zeroBased = std::clamp(insertIndex - 1, 0, static_cast<int>(currentNotebook->GetPageCount()));
|
|
currentNotebook->InsertPage(zeroBased, currentTabPanel, wxString(text));
|
|
return zeroBased;
|
|
}
|
|
|
|
currentNotebook->AddPage(currentTabPanel, wxString(text));
|
|
return static_cast<int>(currentNotebook->GetPageCount()) - 1;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::tab(sol::table options) {
|
|
finishCurrentRow();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("tab_{}", widgets.size()));
|
|
std::string text = options.get_or(std::string("text"), "Tab"s);
|
|
bool isButton = options.get_or(std::string("button"), false) || options.get_or(std::string("is_button"), false);
|
|
int insertIndex = options.get_or(std::string("index"), -1);
|
|
sol::function onclick;
|
|
sol::function oncontextmenu;
|
|
if (options["onclick"].valid()) {
|
|
onclick = options["onclick"];
|
|
}
|
|
if (options["oncontextmenu"].valid()) {
|
|
oncontextmenu = options["oncontextmenu"];
|
|
}
|
|
|
|
ensureNotebook();
|
|
bindNotebookEvents();
|
|
|
|
int pageIndex = insertTabPanel(text, insertIndex);
|
|
|
|
LuaDialogTab tabInfo;
|
|
tabInfo.id = id;
|
|
tabInfo.text = text;
|
|
tabInfo.isButton = isButton;
|
|
tabInfo.onclick = onclick;
|
|
tabInfo.oncontextmenu = oncontextmenu;
|
|
if (pageIndex >= 0 && pageIndex <= static_cast<int>(tabInfos.size())) {
|
|
tabInfos.insert(tabInfos.begin() + pageIndex, tabInfo);
|
|
} else {
|
|
tabInfos.push_back(tabInfo);
|
|
}
|
|
|
|
rowSizerStack.push(currentRowSizer);
|
|
currentRowSizer = nullptr;
|
|
sizerStack.push(currentTabSizer);
|
|
tabSizers.push_back(currentTabSizer);
|
|
|
|
return this;
|
|
}
|
|
|
|
LuaDialog* LuaDialog::endtabs() {
|
|
finishCurrentRow();
|
|
// Pop all tab sizers that were added
|
|
while (!tabSizers.empty()) {
|
|
wxSizer* topSizer = tabSizers.back();
|
|
if (!sizerStack.empty() && sizerStack.top() == topSizer) {
|
|
sizerStack.pop();
|
|
}
|
|
if (!rowSizerStack.empty()) {
|
|
currentRowSizer = rowSizerStack.top();
|
|
rowSizerStack.pop();
|
|
}
|
|
tabSizers.pop_back();
|
|
}
|
|
|
|
currentNotebook = nullptr;
|
|
currentTabPanel = nullptr;
|
|
currentTabSizer = nullptr;
|
|
return this;
|
|
}
|
|
|
|
sol::table LuaDialog::makeTabInfoTable(int index) {
|
|
sol::table info = lua.create_table();
|
|
if (index < 0 || index >= static_cast<int>(tabInfos.size())) {
|
|
return info;
|
|
}
|
|
const LuaDialogTab &tab = tabInfos[index];
|
|
info["index"] = index + 1;
|
|
info["text"] = tab.text;
|
|
info["id"] = tab.id;
|
|
info["is_button"] = tab.isButton;
|
|
return info;
|
|
}
|
|
|
|
void LuaDialog::handleItemButtonClick(const std::string &id, ItemButton* btn, bool readonly) {
|
|
bool handled = false;
|
|
for (auto &w : widgets) {
|
|
if (w.id == id && w.onclick.valid()) {
|
|
onButtonClick(id);
|
|
handled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!handled && !readonly) {
|
|
FindItemDialog dlg(this, "Select Item", false);
|
|
if (dlg.ShowModal() == wxID_OK) {
|
|
int newItemId = dlg.getResultID();
|
|
int newClientId = (newItemId > 100 && newItemId <= g_items.getMaxID()) ? g_items[newItemId].clientID : 0;
|
|
btn->SetSprite(newClientId);
|
|
btn->Refresh();
|
|
values[id] = sol::make_object(lua, newItemId);
|
|
onWidgetChange(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LuaDialog::handleTabButtonClick(int index) {
|
|
if (index < 0 || index >= static_cast<int>(tabInfos.size())) {
|
|
return;
|
|
}
|
|
sol::function onclick = tabInfos[index].onclick;
|
|
if (!onclick.valid()) {
|
|
return;
|
|
}
|
|
if (!lua.lua_state()) {
|
|
return;
|
|
}
|
|
if (!onclick.lua_state()) {
|
|
return;
|
|
}
|
|
sol::table info = makeTabInfoTable(index);
|
|
try {
|
|
onclick(this, info);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
}
|
|
|
|
void LuaDialog::handleTabContextMenu(int index, const wxPoint &screenPos) {
|
|
if (index < 0 || index >= static_cast<int>(tabInfos.size())) {
|
|
return;
|
|
}
|
|
sol::function oncontextmenu = tabInfos[index].oncontextmenu;
|
|
if (!oncontextmenu.valid()) {
|
|
return;
|
|
}
|
|
if (!lua.lua_state()) {
|
|
return;
|
|
}
|
|
if (!oncontextmenu.lua_state()) {
|
|
return;
|
|
}
|
|
|
|
sol::table info = makeTabInfoTable(index);
|
|
info["type"] = "tab";
|
|
popupContextMenu(oncontextmenu, info, activeNotebook, screenPos);
|
|
}
|
|
|
|
void LuaDialog::popupContextMenu(const sol::function &callback, sol::table info, wxWindow* window, const wxPoint &screenPos) {
|
|
if (!callback.valid()) {
|
|
return;
|
|
}
|
|
if (!lua.lua_state() || !callback.lua_state()) {
|
|
return;
|
|
}
|
|
|
|
sol::object result;
|
|
try {
|
|
result = callback(this, info);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
return;
|
|
}
|
|
|
|
if (!result.valid() || !result.is<sol::table>()) {
|
|
return;
|
|
}
|
|
|
|
sol::table items = result.as<sol::table>();
|
|
wxMenu menu;
|
|
std::map<int, sol::function> callbacks;
|
|
int nextId = wxID_HIGHEST + 1;
|
|
|
|
for (auto &pair : items) {
|
|
if (!pair.second.is<sol::table>()) {
|
|
continue;
|
|
}
|
|
sol::table item = pair.second.as<sol::table>();
|
|
bool separator = item.get_or(std::string("separator"), false);
|
|
std::string text = item.get_or(std::string("text"), ""s);
|
|
|
|
if (separator || text.empty()) {
|
|
menu.AppendSeparator();
|
|
continue;
|
|
}
|
|
|
|
int id = nextId++;
|
|
wxMenuItem* menuItem = menu.Append(id, wxString(text));
|
|
bool enabled = item.get_or(std::string("enabled"), true);
|
|
menuItem->Enable(enabled);
|
|
|
|
if (item["onclick"].valid()) {
|
|
sol::function fn = item["onclick"];
|
|
if (fn.lua_state()) {
|
|
callbacks[id] = fn;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (menu.GetMenuItemCount() == 0) {
|
|
return;
|
|
}
|
|
|
|
menu.Bind(wxEVT_MENU, [this, callbacks, info](wxCommandEvent &event) mutable {
|
|
if (!lua.lua_state()) {
|
|
return;
|
|
}
|
|
auto it = callbacks.find(event.GetId());
|
|
if (it == callbacks.end()) {
|
|
return;
|
|
}
|
|
if (!it->second.lua_state()) {
|
|
return;
|
|
}
|
|
try {
|
|
it->second(this, info);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
});
|
|
|
|
if (!window) {
|
|
return;
|
|
}
|
|
wxPoint clientPos = window->ScreenToClient(screenPos);
|
|
window->PopupMenu(&menu, clientPos);
|
|
}
|
|
|
|
LuaDialog* LuaDialog::show(sol::optional<sol::table> options) {
|
|
if (dockPanel) {
|
|
wxAuiPaneInfo &info = g_gui.aui_manager->GetPane(dockPanel);
|
|
if (info.IsOk()) {
|
|
info.Show();
|
|
g_gui.aui_manager->Update();
|
|
}
|
|
isShowing = true;
|
|
return this;
|
|
}
|
|
|
|
// Fit the dialog first to establish baseline size from sizers
|
|
Fit();
|
|
|
|
// Re-apply requested size from constructor if it's larger than fitted size
|
|
if (reqWidth != -1 || reqHeight != -1) {
|
|
wxSize sz = GetSize();
|
|
if (reqWidth != -1) {
|
|
sz.x = std::max(sz.x, reqWidth);
|
|
}
|
|
if (reqHeight != -1) {
|
|
sz.y = std::max(sz.y, reqHeight);
|
|
}
|
|
SetSize(sz);
|
|
}
|
|
|
|
// Process options
|
|
if (options) {
|
|
sol::table opts = *options;
|
|
waitMode = opts.get_or(std::string("wait"), true);
|
|
|
|
if (opts["bounds"].valid()) {
|
|
setBounds(opts["bounds"]);
|
|
} else if (reqX == -1 && reqY == -1) {
|
|
bool wantsCenter = true;
|
|
std::string centerOn = "parent";
|
|
if (opts["center"].valid()) {
|
|
if (opts["center"].is<bool>()) {
|
|
wantsCenter = opts.get<bool>("center");
|
|
} else if (opts["center"].is<std::string>()) {
|
|
centerOn = opts.get<std::string>("center");
|
|
}
|
|
}
|
|
if (opts["center_on"].valid() && opts["center_on"].is<std::string>()) {
|
|
centerOn = opts.get<std::string>("center_on");
|
|
}
|
|
if (wantsCenter) {
|
|
if (centerOn == "screen") {
|
|
Centre();
|
|
} else {
|
|
CentreOnParent();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (reqX == -1 && reqY == -1) {
|
|
CentreOnParent();
|
|
}
|
|
}
|
|
|
|
isShowing = true;
|
|
|
|
if (waitMode) {
|
|
ShowModal();
|
|
isShowing = false;
|
|
} else {
|
|
Show();
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
void LuaDialog::close() {
|
|
while (hotkeySuspendCount > 0) {
|
|
resumeHotkeys();
|
|
}
|
|
if (dockPanel) {
|
|
wxAuiPaneInfo &info = g_gui.aui_manager->GetPane(dockPanel);
|
|
if (info.IsOk()) {
|
|
info.Hide();
|
|
g_gui.aui_manager->Update();
|
|
}
|
|
isShowing = false;
|
|
if (oncloseCallback.valid()) {
|
|
try {
|
|
oncloseCallback();
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error in onclose: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (IsModal()) {
|
|
EndModal(wxID_OK);
|
|
} else {
|
|
Hide();
|
|
}
|
|
isShowing = false;
|
|
if (oncloseCallback.valid()) {
|
|
try {
|
|
oncloseCallback();
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error in onclose: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LuaDialog::OnClose(wxCloseEvent &event) {
|
|
if (IsModal()) {
|
|
EndModal(wxID_CANCEL);
|
|
} else {
|
|
Hide();
|
|
}
|
|
isShowing = false;
|
|
|
|
if (oncloseCallback.valid()) {
|
|
try {
|
|
oncloseCallback();
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error in onclose: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
}
|
|
|
|
if (!IsModal() && event.CanVeto()) {
|
|
// Prevent destruction of modeless dialog so it can be reused
|
|
// event.Veto() is not needed if we don't Skip(), but it's safer
|
|
event.Veto();
|
|
return;
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
LuaDialog* LuaDialog::modify(sol::table options) {
|
|
// Modify widget properties
|
|
for (auto &pair : options) {
|
|
std::string id = pair.first.as<std::string>();
|
|
sol::table props = pair.second.as<sol::table>();
|
|
|
|
for (auto &widget : widgets) {
|
|
if (widget.id == id) {
|
|
// Apply property changes based on widget type
|
|
if (widget.type == "mapCanvas") {
|
|
MapPreviewCanvas* canvas = static_cast<MapPreviewCanvas*>(widget.widget);
|
|
if (props["x"].valid() && props["y"].valid()) {
|
|
canvas->SetPosition(props.get_or(std::string("x"), 1000), props.get_or(std::string("y"), 1000), props.get_or(std::string("z"), (int)canvas->GetFloor()));
|
|
}
|
|
if (props["zoom"].valid()) {
|
|
canvas->SetZoom(props.get_or(std::string("zoom"), 1.0));
|
|
}
|
|
if (props["floor"].valid()) {
|
|
canvas->ChangeFloor(props.get_or(std::string("floor"), 7));
|
|
canvas->Refresh();
|
|
}
|
|
if (props["sync"].valid() && props.get_or(std::string("sync"), false)) {
|
|
canvas->SyncView();
|
|
}
|
|
if (props["client_w"].valid() && props["client_h"].valid()) {
|
|
canvas->SetViewSize(props.get_or(std::string("client_w"), 25), props.get_or(std::string("client_h"), 25));
|
|
}
|
|
if (props["light"].valid()) {
|
|
canvas->SetLight(props.get_or(std::string("light"), false));
|
|
}
|
|
} else if (widget.type == "input") {
|
|
wxTextCtrl* ctrl = static_cast<wxTextCtrl*>(widget.widget);
|
|
if (props["text"].valid()) {
|
|
ctrl->SetValue(wxString(props.get_or(std::string("text"), ""s)));
|
|
}
|
|
} else if (widget.type == "number") {
|
|
wxSpinCtrlDouble* ctrl = static_cast<wxSpinCtrlDouble*>(widget.widget);
|
|
if (props["value"].valid()) {
|
|
ctrl->SetValue(props.get_or(std::string("value"), 0.0));
|
|
}
|
|
} else if (widget.type == "check") {
|
|
wxCheckBox* ctrl = static_cast<wxCheckBox*>(widget.widget);
|
|
if (props["selected"].valid()) {
|
|
ctrl->SetValue(props.get_or(std::string("selected"), false));
|
|
}
|
|
} else if (widget.type == "item") {
|
|
ItemButton* ctrl = static_cast<ItemButton*>(widget.widget);
|
|
if (props["itemid"].valid()) {
|
|
int newItemId = props.get_or(std::string("itemid"), 0);
|
|
ctrl->SetSprite(newItemId);
|
|
ctrl->Refresh();
|
|
}
|
|
} else if (widget.type == "label") {
|
|
wxStaticText* ctrl = static_cast<wxStaticText*>(widget.widget);
|
|
if (props["text"].valid()) {
|
|
ctrl->SetLabel(wxString(props.get_or(std::string("text"), ""s)));
|
|
}
|
|
} else if (widget.type == "combobox") {
|
|
wxChoice* ctrl = static_cast<wxChoice*>(widget.widget);
|
|
if (props["options"].valid()) {
|
|
ctrl->Freeze();
|
|
ctrl->Clear();
|
|
sol::table opts = props["options"];
|
|
for (size_t i = 1; i <= opts.size(); ++i) {
|
|
if (opts[i].valid()) {
|
|
ctrl->Append(wxString(opts[i].get<std::string>()));
|
|
}
|
|
}
|
|
ctrl->Thaw();
|
|
}
|
|
if (props["option"].valid()) {
|
|
std::string selected = props.get_or(std::string("option"), ""s);
|
|
int idx = ctrl->FindString(wxString(selected));
|
|
if (idx != wxNOT_FOUND) {
|
|
ctrl->SetSelection(idx);
|
|
values[id] = sol::make_object(lua, selected);
|
|
}
|
|
} else if (ctrl->GetCount() > 0 && ctrl->GetSelection() == wxNOT_FOUND) {
|
|
ctrl->SetSelection(0);
|
|
values[id] = sol::make_object(lua, ctrl->GetString(0).ToStdString());
|
|
}
|
|
} else if (widget.type == "list") {
|
|
LuaDialogListBox* ctrl = static_cast<LuaDialogListBox*>(widget.widget);
|
|
if (props["icon_width"].valid() || props["icon_height"].valid() || props["icon_size"].valid()) {
|
|
int iconWidth = props.get_or(std::string("icon_width"), ctrl->iconWidth);
|
|
int iconHeight = props.get_or(std::string("icon_height"), ctrl->iconHeight);
|
|
int iconSize = props.get_or(std::string("icon_size"), -1);
|
|
if (iconSize > 0) {
|
|
iconWidth = iconSize;
|
|
iconHeight = iconSize;
|
|
}
|
|
ctrl->SetIconSize(iconWidth, iconHeight);
|
|
}
|
|
if (props["item_height"].valid()) {
|
|
ctrl->SetItemHeight(props.get_or(std::string("item_height"), ctrl->itemHeight));
|
|
}
|
|
if (props["show_text"].valid()) {
|
|
ctrl->SetShowText(props.get_or(std::string("show_text"), true));
|
|
}
|
|
if (props["smooth"].valid()) {
|
|
ctrl->SetSmooth(props.get_or(std::string("smooth"), true));
|
|
}
|
|
if (props["items"].valid()) {
|
|
ctrl->Freeze();
|
|
ctrl->Clear();
|
|
sol::table itemsTable = props["items"];
|
|
for (auto &pair : itemsTable) {
|
|
if (pair.second.is<sol::table>()) {
|
|
sol::table itemTable = pair.second;
|
|
std::string text = itemTable.get_or(std::string("text"), ""s);
|
|
int icon = itemTable.get_or(std::string("icon"), 0);
|
|
std::string tooltip = itemTable.get_or(std::string("tooltip"), ""s);
|
|
LuaAPI::LuaImage img;
|
|
if (itemTable["image"].valid()) {
|
|
if (itemTable["image"].is<LuaAPI::LuaImage>()) {
|
|
img = itemTable["image"].get<LuaAPI::LuaImage>();
|
|
} else if (itemTable["image"].is<std::string>()) {
|
|
img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get<std::string>());
|
|
}
|
|
}
|
|
ctrl->AddItem(text, icon, img, tooltip);
|
|
}
|
|
}
|
|
ctrl->Refresh();
|
|
ctrl->Thaw();
|
|
}
|
|
if (props["selection"].valid()) {
|
|
int selection = props.get_or(std::string("selection"), 0);
|
|
if (selection > 0 && selection <= (int)ctrl->items.size()) {
|
|
ctrl->SetSelection(selection - 1);
|
|
} else {
|
|
ctrl->SetSelection(wxNOT_FOUND);
|
|
}
|
|
}
|
|
|
|
} else if (widget.type == "grid") {
|
|
LuaGridCtrl* ctrl = static_cast<LuaGridCtrl*>(widget.widget);
|
|
int iconWidth = ctrl->iconWidth;
|
|
int iconHeight = ctrl->iconHeight;
|
|
int cellWidth = ctrl->cellWidth;
|
|
int cellHeight = ctrl->cellHeight;
|
|
bool updateIconSize = false;
|
|
bool updateCellSize = false;
|
|
|
|
int iconSize = props.get_or(std::string("icon_size"), -1);
|
|
int itemSize = props.get_or(std::string("item_size"), -1);
|
|
int itemWidth = props.get_or(std::string("item_width"), -1);
|
|
int itemHeight = props.get_or(std::string("item_height"), -1);
|
|
int iconWidthOpt = props.get_or(std::string("icon_width"), -1);
|
|
int iconHeightOpt = props.get_or(std::string("icon_height"), -1);
|
|
int cellSize = props.get_or(std::string("cell_size"), -1);
|
|
int cellWidthOpt = props.get_or(std::string("cell_width"), -1);
|
|
int cellHeightOpt = props.get_or(std::string("cell_height"), -1);
|
|
|
|
if (iconSize > 0) {
|
|
iconWidth = iconSize;
|
|
iconHeight = iconSize;
|
|
updateIconSize = true;
|
|
}
|
|
if (itemSize > 0) {
|
|
iconWidth = itemSize;
|
|
iconHeight = itemSize;
|
|
updateIconSize = true;
|
|
}
|
|
if (itemWidth > 0) {
|
|
iconWidth = itemWidth;
|
|
updateIconSize = true;
|
|
}
|
|
if (itemHeight > 0) {
|
|
iconHeight = itemHeight;
|
|
updateIconSize = true;
|
|
}
|
|
if (iconWidthOpt > 0) {
|
|
iconWidth = iconWidthOpt;
|
|
updateIconSize = true;
|
|
}
|
|
if (iconHeightOpt > 0) {
|
|
iconHeight = iconHeightOpt;
|
|
updateIconSize = true;
|
|
}
|
|
|
|
if (cellSize > 0) {
|
|
cellWidth = cellSize;
|
|
cellHeight = cellSize;
|
|
updateCellSize = true;
|
|
}
|
|
if (cellWidthOpt > 0) {
|
|
cellWidth = cellWidthOpt;
|
|
updateCellSize = true;
|
|
}
|
|
if (cellHeightOpt > 0) {
|
|
cellHeight = cellHeightOpt;
|
|
updateCellSize = true;
|
|
}
|
|
|
|
if (updateCellSize) {
|
|
if (cellWidth <= 0) {
|
|
cellWidth = iconWidth + 8;
|
|
}
|
|
if (cellHeight <= 0) {
|
|
cellHeight = iconHeight + 8;
|
|
}
|
|
#ifdef __WXMSW__
|
|
ListView_SetIconSpacing(static_cast<HWND>(ctrl->GetHandle()), cellWidth, cellHeight);
|
|
#endif
|
|
ctrl->cellWidth = cellWidth;
|
|
ctrl->cellHeight = cellHeight;
|
|
}
|
|
if (props["selection"].valid()) {
|
|
int selection = props.get_or("selection", 0);
|
|
long item = -1;
|
|
while ((item = ctrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
|
|
ctrl->SetItemState(item, 0, wxLIST_STATE_SELECTED);
|
|
}
|
|
if (selection > 0 && selection <= ctrl->GetItemCount()) {
|
|
ctrl->SetItemState(selection - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
|
|
}
|
|
}
|
|
|
|
if (props["items"].valid()) {
|
|
ctrl->Freeze();
|
|
ctrl->DeleteAllItems();
|
|
ctrl->ClearTooltips();
|
|
wxImageList* imageList = ctrl->GetImageList(wxIMAGE_LIST_NORMAL);
|
|
if (!imageList || updateIconSize) {
|
|
imageList = new wxImageList(iconWidth, iconHeight, true);
|
|
ctrl->AssignImageList(imageList, wxIMAGE_LIST_NORMAL);
|
|
ctrl->iconWidth = iconWidth;
|
|
ctrl->iconHeight = iconHeight;
|
|
} else {
|
|
imageList->RemoveAll();
|
|
}
|
|
|
|
sol::table itemsTable = props["items"];
|
|
for (auto &pair : itemsTable) {
|
|
if (pair.second.is<sol::table>()) {
|
|
sol::table itemTable = pair.second;
|
|
std::string text = itemTable.get_or("text", ""s);
|
|
|
|
LuaAPI::LuaImage img;
|
|
int imageIndex = -1;
|
|
|
|
if (itemTable["image"].valid()) {
|
|
if (itemTable["image"].is<LuaAPI::LuaImage>()) {
|
|
img = itemTable["image"].get<LuaAPI::LuaImage>();
|
|
} else if (itemTable["image"].is<std::string>()) {
|
|
img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get<std::string>());
|
|
}
|
|
}
|
|
|
|
if (img.isValid()) {
|
|
imageIndex = imageList->Add(img.getBitmap(iconWidth, iconHeight));
|
|
}
|
|
|
|
long index = ctrl->InsertItem(ctrl->GetItemCount(), text, imageIndex);
|
|
ctrl->SetItemData(index, index + 1);
|
|
|
|
std::string tooltip = itemTable.get_or("tooltip", ""s);
|
|
if (!tooltip.empty()) {
|
|
// Must match the type cast
|
|
LuaGridCtrl* gridCtrl = static_cast<LuaGridCtrl*>(ctrl);
|
|
gridCtrl->AddTooltip(index, tooltip);
|
|
}
|
|
}
|
|
}
|
|
ctrl->Thaw();
|
|
}
|
|
} else if (widget.type == "image") {
|
|
wxStaticBitmap* ctrl = static_cast<wxStaticBitmap*>(widget.widget);
|
|
|
|
LuaAPI::LuaImage luaImage;
|
|
int width = props.get_or(std::string("width"), -1);
|
|
int height = props.get_or(std::string("height"), -1);
|
|
bool smooth = props.get_or(std::string("smooth"), true);
|
|
|
|
if (props["image"].valid()) {
|
|
luaImage = props["image"].get<LuaAPI::LuaImage>();
|
|
} else if (props["path"].valid()) {
|
|
std::string path = props.get<std::string>("path");
|
|
luaImage = LuaAPI::LuaImage::loadFromFile(path);
|
|
} else if (props["itemid"].valid()) {
|
|
int itemId = props.get<int>("itemid");
|
|
luaImage = LuaAPI::LuaImage::loadFromItemSprite(itemId);
|
|
} else if (props["spriteid"].valid()) {
|
|
int spriteId = props.get<int>("spriteid");
|
|
luaImage = LuaAPI::LuaImage::loadFromSprite(spriteId);
|
|
}
|
|
|
|
if (luaImage.isValid()) {
|
|
wxBitmap bmp;
|
|
if (width > 0 && height > 0) {
|
|
bmp = luaImage.getBitmap(width, height, smooth);
|
|
} else if (width > 0) {
|
|
double factor = static_cast<double>(width) / luaImage.getWidth();
|
|
int newHeight = static_cast<int>(luaImage.getHeight() * factor);
|
|
bmp = luaImage.getBitmap(width, newHeight, smooth);
|
|
} else if (height > 0) {
|
|
double factor = static_cast<double>(height) / luaImage.getHeight();
|
|
int newWidth = static_cast<int>(luaImage.getWidth() * factor);
|
|
bmp = luaImage.getBitmap(newWidth, height, smooth);
|
|
} else {
|
|
bmp = luaImage.getBitmap();
|
|
}
|
|
if (bmp.IsOk()) {
|
|
ctrl->SetBitmap(bmp);
|
|
values[id] = sol::make_object(lua, luaImage);
|
|
}
|
|
}
|
|
}
|
|
|
|
applyCommonOptions(widget.widget, props);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
void LuaDialog::handleGridClick(const std::string &id, LuaGridCtrl* grid, const sol::function &callback, wxMouseEvent &event) {
|
|
if (!callback.valid() || !lua.lua_state() || !callback.lua_state()) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
int flags = 0;
|
|
long index = grid->HitTest(event.GetPosition(), flags);
|
|
if (index == wxNOT_FOUND) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
grid->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
|
|
grid->SetFocus();
|
|
values[id] = sol::make_object(lua, static_cast<int>(index + 1));
|
|
sol::table info = lua.create_table();
|
|
info["type"] = "grid";
|
|
info["index"] = static_cast<int>(index + 1);
|
|
info["text"] = grid->GetItemText(index).ToStdString();
|
|
std::string tooltip = grid->GetTooltip(index);
|
|
if (!tooltip.empty()) {
|
|
info["tooltip"] = tooltip;
|
|
}
|
|
info["widget_id"] = id;
|
|
try {
|
|
callback(this, info);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
void LuaDialog::handleGridContextMenu(const std::string &id, LuaGridCtrl* grid, const sol::function &callback, wxContextMenuEvent &event) {
|
|
if (!callback.valid() || !lua.lua_state() || !callback.lua_state()) {
|
|
return;
|
|
}
|
|
wxPoint screenPos = event.GetPosition();
|
|
if (screenPos == wxDefaultPosition) {
|
|
screenPos = wxGetMousePosition();
|
|
}
|
|
wxPoint clientPos = grid->ScreenToClient(screenPos);
|
|
int flags = 0;
|
|
long index = grid->HitTest(clientPos, flags);
|
|
if (index == wxNOT_FOUND) {
|
|
return;
|
|
}
|
|
sol::table info = lua.create_table();
|
|
info["type"] = "grid";
|
|
info["index"] = static_cast<int>(index + 1);
|
|
info["text"] = grid->GetItemText(index).ToStdString();
|
|
std::string tooltip = grid->GetTooltip(index);
|
|
if (!tooltip.empty()) {
|
|
info["tooltip"] = tooltip;
|
|
}
|
|
info["widget_id"] = id;
|
|
popupContextMenu(callback, info, grid, screenPos);
|
|
}
|
|
|
|
LuaDialog* LuaDialog::grid(sol::table options) {
|
|
ensureRowSizer();
|
|
|
|
std::string id = options.get_or(std::string("id"), fmt::format("grid_{}", widgets.size()));
|
|
std::string labelText = options.get_or(std::string("label"), ""s);
|
|
int iconWidth = options.get_or(std::string("icon_width"), 32);
|
|
int iconHeight = options.get_or(std::string("icon_height"), 32);
|
|
int iconSize = options.get_or(std::string("icon_size"), -1);
|
|
int itemSize = options.get_or(std::string("item_size"), -1);
|
|
int itemWidth = options.get_or(std::string("item_width"), -1);
|
|
int itemHeight = options.get_or(std::string("item_height"), -1);
|
|
int cellSize = options.get_or(std::string("cell_size"), -1);
|
|
int cellWidth = options.get_or(std::string("cell_width"), -1);
|
|
int cellHeight = options.get_or(std::string("cell_height"), -1);
|
|
bool labelWrap = options.get_or(std::string("label_wrap"), true);
|
|
bool showText = options.get_or(std::string("show_text"), true);
|
|
|
|
if (iconSize > 0) {
|
|
iconWidth = iconSize;
|
|
iconHeight = iconSize;
|
|
}
|
|
if (itemSize > 0) {
|
|
iconWidth = itemSize;
|
|
iconHeight = itemSize;
|
|
}
|
|
if (itemWidth > 0) {
|
|
iconWidth = itemWidth;
|
|
}
|
|
if (itemHeight > 0) {
|
|
iconHeight = itemHeight;
|
|
}
|
|
if (iconWidth <= 0) {
|
|
iconWidth = 32;
|
|
}
|
|
if (iconHeight <= 0) {
|
|
iconHeight = 32;
|
|
}
|
|
if (cellSize > 0) {
|
|
cellWidth = cellSize;
|
|
cellHeight = cellSize;
|
|
}
|
|
|
|
if (!labelText.empty()) {
|
|
wxStaticText* label = new wxStaticText(getParentForWidget(), wxID_ANY, wxString(labelText));
|
|
currentRowSizer->Add(label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
|
}
|
|
|
|
// Use LuaGridCtrl in Icon mode for grid view
|
|
auto grid = new LuaGridCtrl(getParentForWidget(), wxID_ANY, wxDefaultPosition, wxSize(300, 200), wxLC_ICON | wxLC_AUTOARRANGE | wxLC_SINGLE_SEL);
|
|
|
|
// Create an image list
|
|
wxImageList* imageList = new wxImageList(iconWidth, iconHeight, true);
|
|
grid->AssignImageList(imageList, wxIMAGE_LIST_NORMAL);
|
|
|
|
if (!showText) {
|
|
wxFont font = grid->GetFont();
|
|
font.SetPointSize(1);
|
|
grid->SetFont(font);
|
|
}
|
|
|
|
#ifdef __WXMSW__
|
|
if (!labelWrap) {
|
|
HWND hwnd = static_cast<HWND>(grid->GetHandle());
|
|
LONG_PTR style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
|
|
style |= LVS_NOLABELWRAP;
|
|
::SetWindowLongPtr(hwnd, GWL_STYLE, style);
|
|
::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
|
|
}
|
|
|
|
if (cellWidth > 0 || cellHeight > 0) {
|
|
if (cellWidth <= 0) {
|
|
cellWidth = iconWidth + 8;
|
|
}
|
|
if (cellHeight <= 0) {
|
|
cellHeight = iconHeight + 8;
|
|
}
|
|
ListView_SetIconSpacing(static_cast<HWND>(grid->GetHandle()), cellWidth, cellHeight);
|
|
}
|
|
#endif
|
|
grid->iconWidth = iconWidth;
|
|
grid->iconHeight = iconHeight;
|
|
grid->cellWidth = cellWidth;
|
|
grid->cellHeight = cellHeight;
|
|
|
|
if (options["items"].valid()) {
|
|
sol::table itemsTable = options["items"];
|
|
for (auto &pair : itemsTable) {
|
|
if (pair.second.is<sol::table>()) {
|
|
sol::table itemTable = pair.second;
|
|
std::string text = itemTable.get_or(std::string("text"), ""s);
|
|
if (!showText) {
|
|
text.clear();
|
|
}
|
|
|
|
LuaAPI::LuaImage img;
|
|
int imageIndex = -1;
|
|
|
|
if (itemTable["image"].valid()) {
|
|
if (itemTable["image"].is<LuaAPI::LuaImage>()) {
|
|
img = itemTable["image"].get<LuaAPI::LuaImage>();
|
|
} else if (itemTable["image"].is<std::string>()) {
|
|
img = LuaAPI::LuaImage::loadFromFile(itemTable["image"].get<std::string>());
|
|
}
|
|
}
|
|
|
|
if (img.isValid()) {
|
|
imageIndex = imageList->Add(img.getBitmap(iconWidth, iconHeight));
|
|
}
|
|
|
|
long index = grid->InsertItem(grid->GetItemCount(), text, imageIndex);
|
|
// Store original item index/id in data if needed, or rely on position
|
|
grid->SetItemData(index, index + 1); // 1-based index
|
|
|
|
std::string tooltip = itemTable.get_or(std::string("tooltip"), ""s);
|
|
if (!tooltip.empty()) {
|
|
grid->AddTooltip(index, tooltip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
currentRowSizer->Add(grid, 1, getSizerFlags(options, wxEXPAND | wxALL), getSizerBorder(options));
|
|
|
|
LuaDialogWidget widget;
|
|
widget.id = id;
|
|
widget.type = "grid";
|
|
widget.widget = grid;
|
|
if (options["ondoubleclick"].valid()) {
|
|
widget.ondoubleclick = options["ondoubleclick"];
|
|
}
|
|
if (options["onchange"].valid()) {
|
|
widget.onchange = options["onchange"];
|
|
}
|
|
if (options["onleftclick"].valid()) {
|
|
widget.onleftclick = options["onleftclick"];
|
|
}
|
|
if (options["onrightclick"].valid()) {
|
|
widget.onrightclick = options["onrightclick"];
|
|
}
|
|
if (options["oncontextmenu"].valid()) {
|
|
widget.oncontextmenu = options["oncontextmenu"];
|
|
}
|
|
widgets.push_back(widget);
|
|
|
|
values[id] = sol::make_object(lua, 0);
|
|
|
|
// Bind events
|
|
grid->Bind(wxEVT_LIST_ITEM_SELECTED, [this, id, grid](wxListEvent &event) {
|
|
values[id] = sol::make_object(lua, event.GetIndex() + 1);
|
|
onWidgetChange(id);
|
|
});
|
|
|
|
grid->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this, id, grid](wxListEvent &event) {
|
|
values[id] = sol::make_object(lua, event.GetIndex() + 1);
|
|
onWidgetDoubleClick(id);
|
|
});
|
|
|
|
sol::function gridLeftClick = widget.onleftclick;
|
|
grid->Bind(wxEVT_LEFT_DOWN, [this, id, grid, gridLeftClick](wxMouseEvent &event) {
|
|
handleGridClick(id, grid, gridLeftClick, event);
|
|
});
|
|
|
|
sol::function gridRightClick = widget.onrightclick;
|
|
grid->Bind(wxEVT_RIGHT_DOWN, [this, id, grid, gridRightClick](wxMouseEvent &event) {
|
|
handleGridClick(id, grid, gridRightClick, event);
|
|
});
|
|
|
|
sol::function gridContextMenu = widget.oncontextmenu;
|
|
grid->Bind(wxEVT_CONTEXT_MENU, [this, id, grid, gridContextMenu](wxContextMenuEvent &event) {
|
|
handleGridContextMenu(id, grid, gridContextMenu, event);
|
|
});
|
|
|
|
applyCommonOptions(grid, options);
|
|
return this;
|
|
}
|
|
|
|
void LuaDialog::repaint() {
|
|
Refresh();
|
|
Update();
|
|
}
|
|
|
|
void LuaDialog::clear() {
|
|
// Clear all widgets
|
|
widgets.clear();
|
|
values.clear();
|
|
|
|
// Destroy all children of mainSizer
|
|
mainSizer->Clear(true); // true = delete windows
|
|
|
|
// Reset layout state
|
|
while (!sizerStack.empty()) {
|
|
sizerStack.pop();
|
|
}
|
|
sizerStack.push(mainSizer);
|
|
tabSizers.clear();
|
|
|
|
currentRowSizer = nullptr;
|
|
currentNotebook = nullptr;
|
|
activeNotebook = nullptr;
|
|
currentTabPanel = nullptr;
|
|
currentTabSizer = nullptr;
|
|
tabInfos.clear();
|
|
notebookEventsBound = false;
|
|
suppressTabButtonClick = false;
|
|
suppressTabButtonIndex = -1;
|
|
dockPanel ? dockPanel->Layout() : Layout();
|
|
}
|
|
|
|
void LuaDialog::layout() {
|
|
dockPanel ? dockPanel->Layout() : Layout();
|
|
}
|
|
|
|
sol::table LuaDialog::getData() {
|
|
collectAllValues();
|
|
|
|
sol::table data = lua.create_table();
|
|
for (auto &pair : values) {
|
|
data[pair.first] = pair.second;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
void LuaDialog::setData(sol::table data) {
|
|
for (auto &pair : data) {
|
|
std::string id = pair.first.as<std::string>();
|
|
values[id] = pair.second;
|
|
}
|
|
}
|
|
|
|
sol::table LuaDialog::getBounds() {
|
|
sol::table bounds = lua.create_table();
|
|
wxRect rect = GetRect();
|
|
bounds["x"] = rect.x;
|
|
bounds["y"] = rect.y;
|
|
bounds["width"] = rect.width;
|
|
bounds["height"] = rect.height;
|
|
return bounds;
|
|
}
|
|
|
|
sol::object LuaDialog::getActiveTab() {
|
|
wxNotebook* notebook = activeNotebook ? activeNotebook : currentNotebook;
|
|
if (!notebook) {
|
|
return sol::make_object(lua, sol::lua_nil);
|
|
}
|
|
|
|
int selection = notebook->GetSelection();
|
|
if (selection == wxNOT_FOUND) {
|
|
return sol::make_object(lua, sol::lua_nil);
|
|
}
|
|
|
|
wxString text = notebook->GetPageText(selection);
|
|
return sol::make_object(lua, text.ToStdString());
|
|
}
|
|
|
|
void LuaDialog::setBounds(sol::table bounds) {
|
|
int x = bounds.get_or(std::string("x"), GetPosition().x);
|
|
int y = bounds.get_or(std::string("y"), GetPosition().y);
|
|
int w = bounds.get_or(std::string("width"), GetSize().GetWidth());
|
|
int h = bounds.get_or(std::string("height"), GetSize().GetHeight());
|
|
SetSize(x, y, w, h);
|
|
}
|
|
|
|
void LuaDialog::updateValue(const std::string &id) {
|
|
for (auto &widget : widgets) {
|
|
if (widget.id != id) {
|
|
continue;
|
|
}
|
|
if (widget.type == "input") {
|
|
wxTextCtrl* ctrl = static_cast<wxTextCtrl*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetValue().ToStdString());
|
|
} else if (widget.type == "number") {
|
|
wxSpinCtrlDouble* ctrl = static_cast<wxSpinCtrlDouble*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetValue());
|
|
} else if (widget.type == "slider") {
|
|
wxSlider* ctrl = static_cast<wxSlider*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetValue());
|
|
} else if (widget.type == "check") {
|
|
wxCheckBox* ctrl = static_cast<wxCheckBox*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetValue());
|
|
} else if (widget.type == "radio") {
|
|
wxRadioButton* ctrl = static_cast<wxRadioButton*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetValue());
|
|
} else if (widget.type == "combobox") {
|
|
wxChoice* ctrl = static_cast<wxChoice*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetStringSelection().ToStdString());
|
|
} else if (widget.type == "color") {
|
|
wxColourPickerCtrl* ctrl = static_cast<wxColourPickerCtrl*>(widget.widget);
|
|
wxColour c = ctrl->GetColour();
|
|
sol::table colorTable = lua.create_table();
|
|
colorTable["red"] = c.Red();
|
|
colorTable["green"] = c.Green();
|
|
colorTable["blue"] = c.Blue();
|
|
values[id] = colorTable;
|
|
} else if (widget.type == "file") {
|
|
wxFilePickerCtrl* ctrl = static_cast<wxFilePickerCtrl*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetPath().ToStdString());
|
|
} else if (widget.type == "item") {
|
|
// Value is updated in the event handler
|
|
} else if (widget.type == "mapCanvas") {
|
|
auto ctrl = static_cast<MapPreviewCanvas*>(widget.widget);
|
|
sol::table mapData = lua.create_table();
|
|
mapData["x"] = ctrl->GetMapX();
|
|
mapData["y"] = ctrl->GetMapY();
|
|
mapData["z"] = ctrl->GetFloor();
|
|
mapData["zoom"] = ctrl->GetZoom();
|
|
values[id] = mapData;
|
|
} else if (widget.type == "list") {
|
|
auto ctrl = static_cast<LuaDialogListBox*>(widget.widget);
|
|
values[id] = sol::make_object(lua, ctrl->GetSelection() + 1);
|
|
} else if (widget.type == "grid") {
|
|
wxListCtrl* ctrl = static_cast<wxListCtrl*>(widget.widget);
|
|
long item = -1;
|
|
item = ctrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
|
|
if (item != -1) {
|
|
values[id] = sol::make_object(lua, item + 1);
|
|
} else {
|
|
values[id] = sol::make_object(lua, 0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LuaDialog::collectAllValues() {
|
|
for (const auto &widget : widgets) {
|
|
updateValue(widget.id);
|
|
}
|
|
}
|
|
|
|
int LuaDialog::getSizerFlags(sol::table options, int defaultFlags) {
|
|
int flags = defaultFlags;
|
|
if (options["align"].valid()) {
|
|
std::string align = options.get<std::string>("align");
|
|
if (align == "center") {
|
|
flags = (flags & ~wxALIGN_RIGHT & ~wxALIGN_LEFT) | wxALIGN_CENTER_HORIZONTAL;
|
|
} else if (align == "right") {
|
|
flags = (flags & ~wxALIGN_LEFT & ~wxALIGN_CENTER_HORIZONTAL) | wxALIGN_RIGHT;
|
|
} else if (align == "left") {
|
|
flags = (flags & ~wxALIGN_RIGHT & ~wxALIGN_CENTER_HORIZONTAL) | wxALIGN_LEFT;
|
|
}
|
|
}
|
|
if (options["valign"].valid()) {
|
|
std::string valign = options.get<std::string>("valign");
|
|
if (valign == "center") {
|
|
flags = (flags & ~wxALIGN_TOP & ~wxALIGN_BOTTOM) | wxALIGN_CENTER_VERTICAL;
|
|
} else if (valign == "top") {
|
|
flags = (flags & ~wxALIGN_BOTTOM & ~wxALIGN_CENTER_VERTICAL) | wxALIGN_TOP;
|
|
} else if (valign == "bottom") {
|
|
flags = (flags & ~wxALIGN_TOP & ~wxALIGN_CENTER_VERTICAL) | wxALIGN_BOTTOM;
|
|
}
|
|
}
|
|
if (options.get_or("expand", false)) {
|
|
flags |= wxEXPAND;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
int LuaDialog::getSizerBorder(sol::table options) {
|
|
return options.get_or("padding", 5) + options.get_or("margin", 0);
|
|
}
|
|
|
|
static wxColour parseColor(sol::object colorObj) {
|
|
if (colorObj.is<sol::table>()) {
|
|
sol::table c = colorObj.as<sol::table>();
|
|
int r = c.get_or("r", c.get_or(1, 255));
|
|
int g = c.get_or("g", c.get_or(2, 255));
|
|
int b = c.get_or("b", c.get_or(3, 255));
|
|
return wxColour(r, g, b);
|
|
} else if (colorObj.is<std::string>()) {
|
|
std::string colorStr = colorObj.as<std::string>();
|
|
if (!colorStr.empty() && colorStr[0] == '#') {
|
|
unsigned long hexValue = 0;
|
|
if (colorStr.length() == 7) {
|
|
hexValue = std::stoul(colorStr.substr(1), nullptr, 16);
|
|
} else if (colorStr.length() == 4) {
|
|
char r = colorStr[1];
|
|
char g = colorStr[2];
|
|
char b = colorStr[3];
|
|
std::string expanded = { r, r, g, g, b, b };
|
|
hexValue = std::stoul(expanded, nullptr, 16);
|
|
}
|
|
return wxColour((hexValue >> 16) & 0xFF, (hexValue >> 8) & 0xFF, hexValue & 0xFF);
|
|
}
|
|
return wxColour(wxString(colorStr));
|
|
}
|
|
return wxColour();
|
|
}
|
|
|
|
void LuaDialog::applyFontOptions(wxWindow* widget, sol::table options) {
|
|
wxFont font = widget->GetFont();
|
|
if (options["font_size"].valid()) {
|
|
font.SetPointSize(options.get<int>("font_size"));
|
|
}
|
|
if (options["font_weight"].valid()) {
|
|
std::string weight = options.get<std::string>("font_weight");
|
|
if (weight == "bold") {
|
|
font.SetWeight(wxFONTWEIGHT_BOLD);
|
|
} else if (weight == "light") {
|
|
font.SetWeight(wxFONTWEIGHT_LIGHT);
|
|
} else {
|
|
font.SetWeight(wxFONTWEIGHT_NORMAL);
|
|
}
|
|
}
|
|
if (options["font_style"].valid()) {
|
|
std::string style = options.get<std::string>("font_style");
|
|
if (style == "italic") {
|
|
font.SetStyle(wxFONTSTYLE_ITALIC);
|
|
} else {
|
|
font.SetStyle(wxFONTSTYLE_NORMAL);
|
|
}
|
|
}
|
|
widget->SetFont(font);
|
|
}
|
|
|
|
void LuaDialog::applyCommonOptions(wxWindow* widget, sol::table options) {
|
|
if (options["tooltip"].valid()) {
|
|
widget->SetToolTip(wxString(options.get<std::string>("tooltip")));
|
|
}
|
|
if (options["enabled"].valid()) {
|
|
widget->Enable(options.get<bool>("enabled"));
|
|
}
|
|
if (options["visible"].valid()) {
|
|
widget->Show(options.get<bool>("visible"));
|
|
}
|
|
|
|
if (options["bgcolor"].valid()) {
|
|
wxColour colour = parseColor(options["bgcolor"]);
|
|
if (colour.IsOk()) {
|
|
widget->SetBackgroundColour(colour);
|
|
}
|
|
}
|
|
|
|
if (options["fgcolor"].valid()) {
|
|
wxColour colour = parseColor(options["fgcolor"]);
|
|
if (colour.IsOk()) {
|
|
widget->SetForegroundColour(colour);
|
|
}
|
|
}
|
|
|
|
if (options["font_size"].valid() || options["font_weight"].valid() || options["font_style"].valid()) {
|
|
applyFontOptions(widget, options);
|
|
}
|
|
|
|
if (options["width"].valid() || options["height"].valid()) {
|
|
int w = options.get_or("width", -1);
|
|
int h = options.get_or("height", -1);
|
|
wxSize size(w, h);
|
|
widget->SetMinSize(size);
|
|
if (!options.get_or("expand", true)) {
|
|
widget->SetMaxSize(size);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LuaDialog::suspendHotkeys() {
|
|
if (hotkeySuspendCount == 0) {
|
|
if (g_gui.AreHotkeysEnabled()) {
|
|
g_gui.DisableHotkeys();
|
|
hotkeysDisabledByDialog = true;
|
|
}
|
|
}
|
|
++hotkeySuspendCount;
|
|
}
|
|
|
|
void LuaDialog::resumeHotkeys() {
|
|
if (hotkeySuspendCount <= 0) {
|
|
hotkeySuspendCount = 0;
|
|
return;
|
|
}
|
|
--hotkeySuspendCount;
|
|
if (hotkeySuspendCount == 0 && hotkeysDisabledByDialog) {
|
|
g_gui.EnableHotkeys();
|
|
hotkeysDisabledByDialog = false;
|
|
}
|
|
}
|
|
|
|
void LuaDialog::onWidgetChange(const std::string &id) {
|
|
updateValue(id);
|
|
|
|
// Call onchange callback if set
|
|
sol::function onchange;
|
|
for (auto &widget : widgets) {
|
|
if (widget.id == id && widget.onchange.valid()) {
|
|
onchange = widget.onchange;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (onchange.valid() && lua.lua_state() && onchange.lua_state()) {
|
|
try {
|
|
onchange(this);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LuaDialog::onButtonClick(const std::string &id) {
|
|
// Set button value to true (for detecting which button was clicked)
|
|
values[id] = sol::make_object(lua, true);
|
|
|
|
// Call onclick callback if set
|
|
sol::function onclick;
|
|
for (auto &widget : widgets) {
|
|
if (widget.id == id && widget.onclick.valid()) {
|
|
onclick = widget.onclick;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (onclick.valid() && lua.lua_state() && onclick.lua_state()) {
|
|
try {
|
|
onclick(this);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
return; // onclick handles closing if needed
|
|
}
|
|
|
|
// Default behavior: close dialog on button click (for OK/Cancel buttons)
|
|
std::string lowerId = id;
|
|
std::transform(lowerId.begin(), lowerId.end(), lowerId.begin(), ::tolower);
|
|
if (lowerId == "ok" || lowerId == "cancel" || lowerId == "close") {
|
|
close();
|
|
}
|
|
}
|
|
|
|
void LuaDialog::onWidgetDoubleClick(const std::string &id) {
|
|
updateValue(id);
|
|
|
|
sol::function ondoubleclick;
|
|
for (auto &widget : widgets) {
|
|
if (widget.id == id && widget.ondoubleclick.valid()) {
|
|
ondoubleclick = widget.ondoubleclick;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ondoubleclick.valid() && lua.lua_state() && ondoubleclick.lua_state()) {
|
|
try {
|
|
ondoubleclick(this);
|
|
} catch (const sol::error &e) {
|
|
wxMessageBox(wxString("Script error: ") + e.what(), "Lua Error", wxOK | wxICON_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace LuaAPI {
|
|
|
|
void registerDialog(sol::state &lua) {
|
|
// Register Dialog class with constructor and method chaining
|
|
lua.new_usertype<LuaDialog>("Dialog",
|
|
// Constructor: Dialog{title = "...", topmost = true}
|
|
sol::call_constructor, sol::factories([](sol::table options, sol::this_state ts) { return new LuaDialog(options, ts); }, [](const std::string &title, sol::this_state ts) { return new LuaDialog(title, ts); }),
|
|
|
|
// Widget methods (return self for chaining)
|
|
"label", &LuaDialog::label, "input", &LuaDialog::input, "number", &LuaDialog::number, "slider", &LuaDialog::slider, "check", &LuaDialog::check, "radio", &LuaDialog::radio, "combobox", &LuaDialog::combobox, "button", &LuaDialog::button, "color", &LuaDialog::color, "item", &LuaDialog::item, "file", &LuaDialog::file, "image", &LuaDialog::image, "separator", &LuaDialog::separator, "newrow", &LuaDialog::newrow, "tab", &LuaDialog::tab, "endtabs", &LuaDialog::endtabs, "wrap", &LuaDialog::wrap, "endwrap", &LuaDialog::endwrap, "box", &LuaDialog::box, "endbox", &LuaDialog::endbox, "panel", &LuaDialog::panel, "endpanel", &LuaDialog::endpanel, "mapCanvas", &LuaDialog::mapCanvas, "list", &LuaDialog::list, "grid", &LuaDialog::grid,
|
|
|
|
// Dialog control
|
|
"show", &LuaDialog::show, "close", &LuaDialog::close, "modify", &LuaDialog::modify, "repaint", &LuaDialog::repaint, "clear", &LuaDialog::clear, "layout", &LuaDialog::layout,
|
|
|
|
// Properties
|
|
"data", sol::property(&LuaDialog::getData, &LuaDialog::setData), "bounds", sol::property(&LuaDialog::getBounds, &LuaDialog::setBounds), "dockable", sol::property(&LuaDialog::isDockable), "activeTab", sol::property(&LuaDialog::getActiveTab));
|
|
}
|
|
|
|
} // namespace LuaAPI
|