2018-10-11 23:01:21 +08:00
|
|
|
// Aseprite
|
2025-05-23 01:39:36 +08:00
|
|
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
2018-10-11 23:01:21 +08:00
|
|
|
// Copyright (C) 2018 David Capello
|
|
|
|
//
|
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "app/app.h"
|
|
|
|
#include "app/color.h"
|
|
|
|
#include "app/color_utils.h"
|
2025-01-08 09:47:59 +08:00
|
|
|
#include "app/context.h"
|
|
|
|
#include "app/doc.h"
|
2019-02-14 03:15:04 +08:00
|
|
|
#include "app/file_selector.h"
|
2022-12-14 23:27:24 +08:00
|
|
|
#include "app/script/canvas_widget.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "app/script/engine.h"
|
2022-12-15 04:04:50 +08:00
|
|
|
#include "app/script/graphics_context.h"
|
2023-01-05 01:40:10 +08:00
|
|
|
#include "app/script/keys.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "app/script/luacpp.h"
|
2023-08-19 04:35:12 +08:00
|
|
|
#include "app/script/tabs_widget.h"
|
2023-08-09 04:26:58 +08:00
|
|
|
#include "app/ui/button_set.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "app/ui/color_button.h"
|
2019-12-17 08:17:12 +08:00
|
|
|
#include "app/ui/color_shades.h"
|
2024-11-14 01:36:23 +08:00
|
|
|
#include "app/ui/editor/editor.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "app/ui/expr_entry.h"
|
2019-02-14 03:15:04 +08:00
|
|
|
#include "app/ui/filename_field.h"
|
2021-06-08 04:39:12 +08:00
|
|
|
#include "app/ui/main_window.h"
|
2024-11-14 01:36:23 +08:00
|
|
|
#include "app/ui/window_with_hand.h"
|
2025-01-08 09:47:59 +08:00
|
|
|
#include "base/fs.h"
|
2019-02-14 03:15:04 +08:00
|
|
|
#include "base/paths.h"
|
2020-07-28 21:35:38 +08:00
|
|
|
#include "base/remove_from_container.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "ui/box.h"
|
|
|
|
#include "ui/button.h"
|
|
|
|
#include "ui/combobox.h"
|
2021-06-08 04:39:12 +08:00
|
|
|
#include "ui/display.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "ui/entry.h"
|
2023-07-18 04:22:59 +08:00
|
|
|
#include "ui/fit_bounds.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "ui/grid.h"
|
|
|
|
#include "ui/label.h"
|
2020-04-05 06:42:20 +08:00
|
|
|
#include "ui/manager.h"
|
2023-01-12 22:02:40 +08:00
|
|
|
#include "ui/menu.h"
|
2022-12-15 09:13:49 +08:00
|
|
|
#include "ui/message.h"
|
2023-04-13 04:28:12 +08:00
|
|
|
#include "ui/scale.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
#include "ui/separator.h"
|
|
|
|
#include "ui/slider.h"
|
2022-12-27 02:33:14 +08:00
|
|
|
#include "ui/system.h"
|
2023-07-18 04:22:59 +08:00
|
|
|
#include "ui/view.h"
|
2018-10-11 23:01:21 +08:00
|
|
|
|
|
|
|
#include <map>
|
2022-12-15 08:17:51 +08:00
|
|
|
#include <stack>
|
2019-02-14 03:15:04 +08:00
|
|
|
#include <string>
|
2020-07-28 21:35:38 +08:00
|
|
|
#include <vector>
|
2018-10-11 23:01:21 +08:00
|
|
|
|
2020-07-28 21:35:38 +08:00
|
|
|
#define TRACE_DIALOG(...) // TRACEARGS(__VA_ARGS__)
|
2019-12-12 20:13:21 +08:00
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
namespace app { namespace script {
|
|
|
|
|
|
|
|
using namespace ui;
|
|
|
|
|
2025-08-26 22:08:17 +08:00
|
|
|
static constexpr const int kDefaultAutofit = ui::LEFT | ui::TOP;
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
namespace {
|
2024-11-14 00:23:06 +08:00
|
|
|
|
2024-11-14 01:36:23 +08:00
|
|
|
class DialogWindow : public WindowWithHand {
|
2024-11-14 00:23:06 +08:00
|
|
|
public:
|
|
|
|
DialogWindow(Type type, const std::string& text) : WindowWithHand(type, text), m_handTool(false)
|
|
|
|
{
|
2024-11-22 04:23:50 +08:00
|
|
|
// As scripts can receive the "pressure" information.
|
|
|
|
setNeedsTabletPressure(true);
|
2024-11-14 00:23:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Enables the Hand tool in the active editor.
|
|
|
|
void setHandTool(const bool flag) { m_handTool = flag; }
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void onOpen(Event& ev) override
|
|
|
|
{
|
|
|
|
if (m_handTool && Editor::activeEditor()) {
|
2024-11-14 01:36:23 +08:00
|
|
|
enableHandTool(true);
|
2024-05-13 13:23:22 +08:00
|
|
|
}
|
2024-11-22 03:59:05 +08:00
|
|
|
WindowWithHand::onOpen(ev);
|
2024-11-14 00:23:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void onBeforeClose(CloseEvent& ev) override
|
|
|
|
{
|
2024-11-14 01:36:23 +08:00
|
|
|
if (isHandToolEnabled())
|
|
|
|
enableHandTool(false);
|
2024-11-14 00:23:06 +08:00
|
|
|
}
|
2024-05-13 13:23:22 +08:00
|
|
|
|
2024-11-14 00:23:06 +08:00
|
|
|
private:
|
|
|
|
bool m_handTool;
|
2024-05-13 13:23:22 +08:00
|
|
|
};
|
2018-10-11 23:01:21 +08:00
|
|
|
|
2020-07-28 21:35:38 +08:00
|
|
|
struct Dialog;
|
|
|
|
std::vector<Dialog*> all_dialogs;
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
struct Dialog {
|
2024-05-13 13:23:22 +08:00
|
|
|
DialogWindow window;
|
2023-08-09 04:26:58 +08:00
|
|
|
// Main grid that holds the dialog content.
|
2018-10-11 23:01:21 +08:00
|
|
|
ui::Grid grid;
|
2023-08-09 04:26:58 +08:00
|
|
|
// Pointer to current grid (might be the main grid or a tab's grid).
|
|
|
|
ui::Grid* currentGrid;
|
2018-10-11 23:01:21 +08:00
|
|
|
ui::HBox* hbox = nullptr;
|
2020-04-16 22:48:44 +08:00
|
|
|
bool autoNewRow = false;
|
2023-01-12 22:02:40 +08:00
|
|
|
WidgetsList mainWidgets;
|
2018-10-11 23:01:21 +08:00
|
|
|
std::map<std::string, ui::Widget*> dataWidgets;
|
2020-04-05 06:42:20 +08:00
|
|
|
std::map<std::string, ui::Widget*> labelWidgets;
|
2018-10-11 23:01:21 +08:00
|
|
|
int currentRadioGroup = 0;
|
2025-08-26 22:08:17 +08:00
|
|
|
int autofit = kDefaultAutofit;
|
2018-10-11 23:01:21 +08:00
|
|
|
|
2023-08-19 04:35:12 +08:00
|
|
|
// Member used to hold current state about the creation of a tabs
|
|
|
|
// widget. After creation it is reset to null to be ready for the
|
|
|
|
// creation of the next tabs widget.
|
|
|
|
app::script::Tabs* wipTab = nullptr;
|
2023-08-09 04:26:58 +08:00
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
// Used to create a new row when a different kind of widget is added
|
|
|
|
// in the dialog.
|
|
|
|
ui::WidgetType lastWidgetType = ui::kGenericWidget;
|
|
|
|
|
|
|
|
// Used to keep a reference to the last onclick button pressed, so
|
|
|
|
// then Dialog.data returns true for the button that closed the
|
|
|
|
// dialog.
|
|
|
|
ui::Widget* lastButton = nullptr;
|
|
|
|
|
|
|
|
// Reference used to keep the dialog alive (so it's not garbage
|
|
|
|
// collected) when it's visible.
|
|
|
|
int showRef = LUA_REFNIL;
|
|
|
|
lua_State* L = nullptr;
|
|
|
|
|
2025-08-01 07:52:33 +08:00
|
|
|
Dialog(const ui::Window::Type windowType, const std::string& title, bool sizeable)
|
2023-01-11 20:46:21 +08:00
|
|
|
: window(windowType, title)
|
2023-08-09 04:26:58 +08:00
|
|
|
, grid(2, false)
|
|
|
|
, currentGrid(&grid)
|
|
|
|
{
|
2018-10-11 23:01:21 +08:00
|
|
|
window.addChild(&grid);
|
2025-08-01 07:52:33 +08:00
|
|
|
window.setSizeable(sizeable);
|
2020-07-28 21:35:38 +08:00
|
|
|
all_dialogs.push_back(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
~Dialog() { base::remove_from_container(all_dialogs, this); }
|
2019-12-13 21:21:26 +08:00
|
|
|
|
|
|
|
void unrefShowOnClose()
|
|
|
|
{
|
2018-10-11 23:01:21 +08:00
|
|
|
window.Close.connect([this](ui::CloseEvent&) { unrefShow(); });
|
|
|
|
}
|
|
|
|
|
2019-12-12 20:13:21 +08:00
|
|
|
// When we show the dialog, we reference it from the registry to
|
|
|
|
// keep the dialog alive in case that the user declared it as a
|
|
|
|
// "local" variable but called Dialog:show{wait=false}
|
2018-10-11 23:01:21 +08:00
|
|
|
void refShow(lua_State* L)
|
|
|
|
{
|
|
|
|
if (showRef == LUA_REFNIL) {
|
|
|
|
this->L = L;
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
showRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-12 20:13:21 +08:00
|
|
|
// When the dialog is closed, we unreference it from the registry so
|
|
|
|
// now the dialog can be GC'd if there are no other references to it
|
|
|
|
// (all references to the dialog itself from callbacks are stored in
|
|
|
|
// the same dialog uservalue, so when the dialog+callbacks are not
|
|
|
|
// used anymore they are GC'd as a group)
|
2018-10-11 23:01:21 +08:00
|
|
|
void unrefShow()
|
|
|
|
{
|
|
|
|
if (showRef != LUA_REFNIL) {
|
|
|
|
luaL_unref(this->L, LUA_REGISTRYINDEX, showRef);
|
|
|
|
showRef = LUA_REFNIL;
|
|
|
|
L = nullptr;
|
|
|
|
}
|
|
|
|
}
|
2019-12-12 20:13:21 +08:00
|
|
|
|
2020-04-05 06:42:20 +08:00
|
|
|
Widget* findDataWidgetById(const char* id)
|
|
|
|
{
|
|
|
|
auto it = dataWidgets.find(id);
|
|
|
|
if (it != dataWidgets.end())
|
|
|
|
return it->second;
|
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:01:46 +08:00
|
|
|
void setLabelVisibility(const char* id, bool visible)
|
|
|
|
{
|
2020-04-05 06:42:20 +08:00
|
|
|
auto it = labelWidgets.find(id);
|
|
|
|
if (it != labelWidgets.end())
|
|
|
|
it->second->setVisible(visible);
|
|
|
|
}
|
|
|
|
|
2020-04-07 22:13:30 +08:00
|
|
|
void setLabelText(const char* id, const char* text)
|
|
|
|
{
|
|
|
|
auto it = labelWidgets.find(id);
|
|
|
|
if (it != labelWidgets.end())
|
|
|
|
it->second->setText(text);
|
|
|
|
}
|
|
|
|
|
2025-08-03 08:08:02 +08:00
|
|
|
void setAutofit(int align)
|
|
|
|
{
|
|
|
|
// Accept both 0 or a valid subset of align parameters.
|
|
|
|
if (align == 0 || (align & (ui::LEFT | ui::RIGHT | ui::TOP | ui::BOTTOM)))
|
|
|
|
autofit = align;
|
|
|
|
}
|
|
|
|
|
2023-01-12 22:02:40 +08:00
|
|
|
Display* parentDisplay() const
|
|
|
|
{
|
|
|
|
Display* parentDisplay = window.parentDisplay();
|
|
|
|
if (!parentDisplay) {
|
2025-08-28 02:26:31 +08:00
|
|
|
const auto* mainWindow = App::instance()->mainWindow();
|
|
|
|
if (mainWindow)
|
|
|
|
parentDisplay = mainWindow->display();
|
2023-01-12 22:02:40 +08:00
|
|
|
}
|
|
|
|
return parentDisplay;
|
|
|
|
}
|
|
|
|
|
2021-06-08 04:39:12 +08:00
|
|
|
gfx::Rect getWindowBounds() const
|
|
|
|
{
|
|
|
|
gfx::Rect bounds = window.bounds();
|
2023-01-11 20:38:19 +08:00
|
|
|
// Bounds in scripts will be relative to the parent window
|
|
|
|
// origin/scale (or main window if a parent window wasn't specified).
|
2021-06-08 04:39:12 +08:00
|
|
|
if (window.ownDisplay()) {
|
2023-01-12 22:02:40 +08:00
|
|
|
const Display* parentDisplay = this->parentDisplay();
|
2025-08-28 02:26:31 +08:00
|
|
|
if (!parentDisplay)
|
|
|
|
return bounds;
|
|
|
|
|
2023-01-11 20:38:19 +08:00
|
|
|
const int scale = parentDisplay->scale();
|
2021-06-08 04:39:12 +08:00
|
|
|
const gfx::Point dialogOrigin = window.display()->nativeWindow()->contentRect().origin();
|
2023-01-11 20:38:19 +08:00
|
|
|
const gfx::Point mainOrigin = parentDisplay->nativeWindow()->contentRect().origin();
|
2021-06-08 04:39:12 +08:00
|
|
|
bounds.setOrigin((dialogOrigin - mainOrigin) / scale);
|
|
|
|
}
|
|
|
|
return bounds;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setWindowBounds(const gfx::Rect& rc)
|
|
|
|
{
|
|
|
|
if (window.ownDisplay()) {
|
|
|
|
window.expandWindow(rc.size());
|
|
|
|
|
2023-01-12 22:02:40 +08:00
|
|
|
Display* parentDisplay = this->parentDisplay();
|
2025-08-28 02:26:31 +08:00
|
|
|
if (!parentDisplay)
|
|
|
|
return;
|
|
|
|
|
2023-01-11 20:38:19 +08:00
|
|
|
const int scale = parentDisplay->scale();
|
|
|
|
const gfx::Point mainOrigin = parentDisplay->nativeWindow()->contentRect().origin();
|
2021-06-08 04:39:12 +08:00
|
|
|
gfx::Rect frame = window.display()->nativeWindow()->contentRect();
|
|
|
|
frame.setOrigin(mainOrigin + rc.origin() * scale);
|
|
|
|
window.display()->nativeWindow()->setFrame(frame);
|
|
|
|
}
|
|
|
|
else {
|
2025-08-03 08:08:02 +08:00
|
|
|
gfx::Rect oldBounds(window.bounds());
|
2021-06-08 04:39:12 +08:00
|
|
|
window.setBounds(rc);
|
|
|
|
window.invalidate();
|
2025-08-03 08:08:02 +08:00
|
|
|
parentDisplay()->invalidateRect(oldBounds);
|
2021-06-08 04:39:12 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-15 05:06:55 +08:00
|
|
|
// TODO merge this code with add_scrollbars_if_needed() from
|
|
|
|
// ui/menu.cpp (creating a new function in the ui library)
|
2023-07-18 04:22:59 +08:00
|
|
|
void addScrollbarsIfNeeded(const gfx::Rect& workarea, gfx::Rect& bounds)
|
|
|
|
{
|
|
|
|
gfx::Rect rc = bounds;
|
|
|
|
|
|
|
|
if (rc.x < workarea.x) {
|
|
|
|
rc.w -= (workarea.x - rc.x);
|
|
|
|
rc.x = workarea.x;
|
|
|
|
}
|
|
|
|
if (rc.x2() > workarea.x2()) {
|
|
|
|
rc.w = workarea.x2() - rc.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool vscrollbarsAdded = false;
|
|
|
|
if (rc.y < workarea.y) {
|
|
|
|
rc.h -= (workarea.y - rc.y);
|
|
|
|
rc.y = workarea.y;
|
|
|
|
vscrollbarsAdded = true;
|
|
|
|
}
|
|
|
|
if (rc.y2() > workarea.y2()) {
|
|
|
|
rc.h = workarea.y2() - rc.y;
|
|
|
|
vscrollbarsAdded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
gfx::Rect newRc = rc;
|
|
|
|
if (get_multiple_displays() && window.shouldCreateNativeWindow()) {
|
|
|
|
const os::Window* nativeWindow = const_cast<ui::Display*>(parentDisplay())->nativeWindow();
|
|
|
|
newRc.setOrigin(nativeWindow->pointFromScreen(rc.origin()));
|
|
|
|
newRc.setSize(rc.size() / nativeWindow->scale());
|
|
|
|
}
|
|
|
|
if (newRc == window.bounds())
|
|
|
|
return;
|
|
|
|
|
|
|
|
View* view = new View();
|
|
|
|
view->InitTheme.connect([view] { view->noBorderNoChildSpacing(); });
|
|
|
|
view->initTheme();
|
|
|
|
|
|
|
|
if (vscrollbarsAdded) {
|
|
|
|
int barWidth = view->verticalBar()->getBarWidth();
|
2024-12-17 01:52:19 +08:00
|
|
|
;
|
2023-07-18 04:22:59 +08:00
|
|
|
if (get_multiple_displays())
|
|
|
|
barWidth *= window.display()->scale();
|
|
|
|
|
|
|
|
rc.w += 2 * barWidth;
|
|
|
|
if (rc.x2() > workarea.x2()) {
|
|
|
|
rc.x = workarea.x2() - rc.w;
|
|
|
|
if (rc.x < workarea.x) {
|
|
|
|
rc.x = workarea.x;
|
|
|
|
rc.w = workarea.w;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// New bounds
|
|
|
|
bounds = rc;
|
|
|
|
|
|
|
|
window.removeChild(&grid);
|
|
|
|
view->attachToView(&grid);
|
|
|
|
window.addChild(view);
|
|
|
|
}
|
2018-10-11 23:01:21 +08:00
|
|
|
};
|
|
|
|
|
2020-03-28 03:14:00 +08:00
|
|
|
template<typename... Args, typename Callback>
|
2019-02-14 03:15:04 +08:00
|
|
|
void Dialog_connect_signal(lua_State* L,
|
2019-12-13 21:21:26 +08:00
|
|
|
int dlgIdx,
|
2020-03-28 03:14:00 +08:00
|
|
|
obs::signal<void(Args...)>& signal,
|
2019-02-14 03:15:04 +08:00
|
|
|
Callback callback)
|
|
|
|
{
|
2019-12-13 21:21:26 +08:00
|
|
|
auto dlg = get_obj<Dialog>(L, dlgIdx);
|
2019-02-14 03:15:04 +08:00
|
|
|
|
2019-12-12 20:13:21 +08:00
|
|
|
// Here we get the uservalue of the dlg (the table with
|
|
|
|
// functions/callbacks) and store a copy of the given function in
|
|
|
|
// the stack (index=-1) in that table.
|
2019-12-13 21:21:26 +08:00
|
|
|
lua_getuservalue(L, dlgIdx);
|
2019-02-14 03:15:04 +08:00
|
|
|
lua_len(L, -1);
|
|
|
|
const int n = 1 + lua_tointegerx(L, -1, nullptr);
|
2019-12-12 20:13:21 +08:00
|
|
|
lua_pop(L, 1); // Pop the length of the table
|
|
|
|
lua_pushvalue(L, -2); // Copy the function in stack
|
|
|
|
lua_rawseti(L, -2, n); // Put the copy of the function in the uservalue
|
|
|
|
lua_pop(L, 1); // Pop the uservalue
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2020-03-28 03:14:00 +08:00
|
|
|
signal.connect([=](Args... args) {
|
2019-12-12 20:13:21 +08:00
|
|
|
// In case that the dialog is hidden, we cannot access to the
|
|
|
|
// global LUA_REGISTRYINDEX to get its reference.
|
|
|
|
if (dlg->showRef == LUA_REFNIL)
|
|
|
|
return;
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2023-09-21 23:32:17 +08:00
|
|
|
// Get the function "n" from the uservalue table of the dialog
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, dlg->showRef);
|
|
|
|
lua_getuservalue(L, -1);
|
|
|
|
lua_rawgeti(L, -1, n);
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2023-09-21 23:32:17 +08:00
|
|
|
// Use the callback with a special table in the Lua stack to
|
|
|
|
// send it as parameter to the Lua function in the
|
|
|
|
// lua_pcall() (that table is like an "event data" parameter
|
|
|
|
// for the function).
|
|
|
|
lua_newtable(L);
|
|
|
|
callback(L, std::forward<Args>(args)...);
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2023-09-21 23:32:17 +08:00
|
|
|
if (lua_isfunction(L, -2)) {
|
|
|
|
try {
|
2019-12-17 08:17:12 +08:00
|
|
|
if (lua_pcall(L, 1, 0, 0)) {
|
2019-02-14 03:15:04 +08:00
|
|
|
if (const char* s = lua_tostring(L, -1))
|
2023-09-21 23:32:17 +08:00
|
|
|
App::instance()->scriptEngine()->consolePrint(s);
|
2019-12-12 20:13:21 +08:00
|
|
|
}
|
2019-02-14 03:15:04 +08:00
|
|
|
}
|
2023-09-21 23:32:17 +08:00
|
|
|
catch (const std::exception& ex) {
|
|
|
|
// This is used to catch unhandled exception or for
|
|
|
|
// example, std::runtime_error exceptions when a Tx() is
|
|
|
|
// created without an active sprite.
|
|
|
|
App::instance()->scriptEngine()->handleException(ex);
|
2019-02-14 03:15:04 +08:00
|
|
|
}
|
2024-12-17 01:52:19 +08:00
|
|
|
}
|
|
|
|
else {
|
2023-09-21 23:32:17 +08:00
|
|
|
lua_pop(L, 1); // Pop the value which should have been a function
|
2024-12-17 01:52:19 +08:00
|
|
|
}
|
2023-09-21 23:32:17 +08:00
|
|
|
lua_pop(L, 2); // Pop uservalue & userdata
|
2020-03-28 03:14:00 +08:00
|
|
|
});
|
2019-02-14 03:15:04 +08:00
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
int Dialog_new(lua_State* L)
|
|
|
|
{
|
2022-04-06 07:45:34 +08:00
|
|
|
// If we don't have UI, just return nil
|
|
|
|
if (!App::instance()->isGui())
|
|
|
|
return 0;
|
|
|
|
|
2023-02-23 05:17:26 +08:00
|
|
|
// Get the title and the type of window (with or without title bar)
|
|
|
|
ui::Window::Type windowType = ui::Window::WithTitleBar;
|
|
|
|
std::string title = "Script";
|
2025-08-01 07:52:33 +08:00
|
|
|
bool sizeable = true;
|
2025-08-26 22:08:17 +08:00
|
|
|
int autofit = kDefaultAutofit;
|
2023-01-11 20:46:21 +08:00
|
|
|
if (lua_isstring(L, 1)) {
|
|
|
|
title = lua_tostring(L, 1);
|
|
|
|
}
|
|
|
|
else if (lua_istable(L, 1)) {
|
|
|
|
int type = lua_getfield(L, 1, "title");
|
|
|
|
if (type != LUA_TNIL)
|
|
|
|
title = lua_tostring(L, -1);
|
|
|
|
lua_pop(L, 1);
|
2023-02-23 05:17:26 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 1, "notitlebar");
|
|
|
|
if (type != LUA_TNIL && lua_toboolean(L, -1))
|
|
|
|
windowType = ui::Window::WithoutTitleBar;
|
|
|
|
lua_pop(L, 1);
|
2025-08-01 07:52:33 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 1, "resizeable");
|
|
|
|
if (type != LUA_TNIL && !lua_toboolean(L, -1))
|
|
|
|
sizeable = false;
|
|
|
|
lua_pop(L, 1);
|
2025-08-03 08:08:02 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 1, "autofit");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
autofit = lua_tointeger(L, -1);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2023-01-11 20:46:21 +08:00
|
|
|
}
|
|
|
|
|
2025-08-01 07:52:33 +08:00
|
|
|
auto dlg = push_new<Dialog>(L, windowType, title, sizeable);
|
2025-08-03 08:08:02 +08:00
|
|
|
dlg->setAutofit(autofit);
|
2018-10-11 23:01:21 +08:00
|
|
|
|
2019-12-12 20:13:21 +08:00
|
|
|
// The uservalue of the dialog userdata will contain a table that
|
|
|
|
// stores all the callbacks to handle events. As these callbacks can
|
|
|
|
// reference the dialog itself, it's important to store callbacks in
|
|
|
|
// this table that depends on the dialog lifetime itself
|
|
|
|
// (i.e. uservalue) and in the global registry, because in that case
|
|
|
|
// we could create a cyclic reference that would be not GC'd.
|
2018-10-11 23:01:21 +08:00
|
|
|
lua_newtable(L);
|
2019-12-12 20:13:21 +08:00
|
|
|
lua_setuservalue(L, -2);
|
|
|
|
|
2023-01-11 20:46:21 +08:00
|
|
|
if (lua_istable(L, 1)) {
|
|
|
|
int type = lua_getfield(L, 1, "parent");
|
2023-01-11 20:38:19 +08:00
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto parentDlg = may_get_obj<Dialog>(L, -1))
|
|
|
|
dlg->window.setParentDisplay(parentDlg->window.display());
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2019-12-13 21:21:26 +08:00
|
|
|
type = lua_getfield(L, 1, "onclose");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2020-03-28 03:14:00 +08:00
|
|
|
Dialog_connect_signal(L, -2, dlg->window.Close, [](lua_State*, CloseEvent&) {
|
2019-12-13 21:21:26 +08:00
|
|
|
// Do nothing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The showRef must be the last reference to the dialog to be
|
|
|
|
// unreferenced after the window is closed (that's why this is the
|
|
|
|
// last connection to ui::Window::Close)
|
|
|
|
dlg->unrefShowOnClose();
|
|
|
|
|
2019-12-12 20:13:21 +08:00
|
|
|
TRACE_DIALOG("Dialog_new", dlg);
|
2018-10-11 23:01:21 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_gc(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
2019-12-12 20:13:21 +08:00
|
|
|
TRACE_DIALOG("Dialog_gc", dlg);
|
2018-10-11 23:01:21 +08:00
|
|
|
dlg->~Dialog();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_show(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
dlg->refShow(L);
|
|
|
|
|
|
|
|
bool wait = true;
|
2023-07-18 04:22:59 +08:00
|
|
|
bool autoScrollbars = false;
|
2021-06-08 04:39:12 +08:00
|
|
|
obs::scoped_connection conn;
|
2018-10-11 23:01:21 +08:00
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "wait");
|
|
|
|
if (type == LUA_TBOOLEAN)
|
|
|
|
wait = lua_toboolean(L, -1);
|
|
|
|
lua_pop(L, 1);
|
2024-11-14 00:23:06 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "hand");
|
2024-05-13 13:23:22 +08:00
|
|
|
if (type == LUA_TBOOLEAN)
|
2024-11-14 00:23:06 +08:00
|
|
|
dlg->window.setHandTool(lua_toboolean(L, -1));
|
2024-05-13 13:23:22 +08:00
|
|
|
else
|
2024-11-14 00:23:06 +08:00
|
|
|
dlg->window.setHandTool(false);
|
2024-05-13 13:23:22 +08:00
|
|
|
lua_pop(L, 1);
|
2019-07-13 03:44:54 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "bounds");
|
2019-08-14 05:16:30 +08:00
|
|
|
if (VALID_LUATYPE(type)) {
|
2019-07-13 03:44:54 +08:00
|
|
|
const auto rc = convert_args_into_rect(L, -1);
|
|
|
|
if (!rc.isEmpty()) {
|
2021-06-08 04:39:12 +08:00
|
|
|
conn = dlg->window.Open.connect([dlg, rc] {
|
|
|
|
dlg->setWindowBounds(rc);
|
2023-08-02 05:25:16 +08:00
|
|
|
dlg->window.setAutoRemap(false);
|
2021-06-08 04:39:12 +08:00
|
|
|
});
|
2019-07-13 03:44:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2023-07-18 04:22:59 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "autoscrollbars");
|
|
|
|
if (type == LUA_TBOOLEAN)
|
|
|
|
autoScrollbars = lua_toboolean(L, -1);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (autoScrollbars) {
|
|
|
|
dlg->window.remapWindow();
|
|
|
|
dlg->window.centerWindow();
|
|
|
|
fit_bounds(dlg->parentDisplay(),
|
2024-11-14 00:23:06 +08:00
|
|
|
&dlg->window,
|
|
|
|
dlg->window.bounds(),
|
|
|
|
[dlg](const gfx::Rect& workarea,
|
|
|
|
gfx::Rect& bounds,
|
|
|
|
std::function<gfx::Rect(Widget*)> getWidgetBounds) {
|
|
|
|
dlg->addScrollbarsIfNeeded(workarea, bounds);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
if (wait)
|
|
|
|
dlg->window.openWindowInForeground();
|
|
|
|
else
|
|
|
|
dlg->window.openWindow();
|
|
|
|
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-01-12 22:02:40 +08:00
|
|
|
namespace {
|
2024-11-14 00:23:06 +08:00
|
|
|
class MoveChildren {
|
|
|
|
public:
|
|
|
|
MoveChildren(Dialog* dlg, Widget* to) : m_dlg(dlg), m_to(to)
|
|
|
|
{
|
|
|
|
for (auto child : m_dlg->mainWidgets) {
|
|
|
|
m_oldParents[child] = child->parent();
|
|
|
|
m_to->addChild(child);
|
2023-01-12 22:02:40 +08:00
|
|
|
}
|
2024-11-14 00:23:06 +08:00
|
|
|
}
|
|
|
|
~MoveChildren()
|
|
|
|
{
|
|
|
|
for (auto child : m_dlg->mainWidgets)
|
|
|
|
m_oldParents[child]->addChild(child);
|
|
|
|
}
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2024-11-14 00:23:06 +08:00
|
|
|
private:
|
|
|
|
Dialog* m_dlg;
|
|
|
|
Widget* m_to;
|
|
|
|
std::map<Widget*, Widget*> m_oldParents;
|
|
|
|
};
|
2023-01-12 22:02:40 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
int Dialog_showMenu(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
Menu popup;
|
|
|
|
MoveChildren moveChildren(dlg, &popup);
|
|
|
|
|
|
|
|
// By default show the menu in the mouse position
|
|
|
|
gfx::Point pt = dlg->parentDisplay()->nativeWindow()->pointFromScreen(ui::get_mouse_position());
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
if (lua_getfield(L, 2, "position") != LUA_TNIL) {
|
|
|
|
pt = convert_args_into_point(L, -1);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
dlg->refShow(L);
|
|
|
|
popup.showPopup(pt, dlg->parentDisplay());
|
|
|
|
dlg->unrefShow();
|
|
|
|
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
int Dialog_close(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
dlg->window.closeWindow(nullptr);
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-09-14 02:31:47 +08:00
|
|
|
void set_widget_flags(lua_State* L, int idx, Widget* widget)
|
|
|
|
{
|
2024-11-14 00:23:06 +08:00
|
|
|
// Focus magnet
|
|
|
|
int type = lua_getfield(L, idx, "focus");
|
|
|
|
if (type != LUA_TNIL && lua_toboolean(L, -1))
|
|
|
|
widget->setFocusMagnet(true);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
// Enabled
|
|
|
|
type = lua_getfield(L, idx, "enabled");
|
|
|
|
if (type != LUA_TNIL)
|
|
|
|
widget->setEnabled(lua_toboolean(L, -1));
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
// Visible
|
|
|
|
widget->setVisible(true);
|
|
|
|
type = lua_getfield(L, idx, "visible");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
widget->setVisible(lua_toboolean(L, -1));
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2023-09-14 02:31:47 +08:00
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
int Dialog_add_widget(lua_State* L, Widget* widget)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
const char* label = nullptr;
|
2020-04-05 06:42:20 +08:00
|
|
|
std::string id;
|
2020-05-16 06:03:17 +08:00
|
|
|
bool visible = true;
|
2022-12-14 23:27:24 +08:00
|
|
|
bool hexpand = true;
|
|
|
|
// Canvas is vertically expansive by default too
|
|
|
|
bool vexpand = (widget->type() == Canvas::Type());
|
2018-10-11 23:01:21 +08:00
|
|
|
|
|
|
|
// This is to separate different kind of widgets without label in
|
2025-05-25 23:25:03 +08:00
|
|
|
// different rows. Separator widgets will always create a new row.
|
|
|
|
if (dlg->lastWidgetType != widget->type() || dlg->autoNewRow ||
|
|
|
|
widget->type() == ui::kSeparatorWidget) {
|
2018-10-11 23:01:21 +08:00
|
|
|
dlg->lastWidgetType = widget->type();
|
|
|
|
dlg->hbox = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
// Widget ID (used to fill the Dialog_get_data table then)
|
|
|
|
int type = lua_getfield(L, 2, "id");
|
|
|
|
if (type == LUA_TSTRING) {
|
2020-04-05 06:42:20 +08:00
|
|
|
if (auto s = lua_tostring(L, -1)) {
|
|
|
|
id = s;
|
|
|
|
widget->setId(s);
|
2018-10-11 23:01:21 +08:00
|
|
|
dlg->dataWidgets[id] = widget;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
// Label
|
|
|
|
type = lua_getfield(L, 2, "label");
|
2020-04-07 01:54:11 +08:00
|
|
|
if (type != LUA_TNIL)
|
2018-10-11 23:01:21 +08:00
|
|
|
label = lua_tostring(L, -1);
|
|
|
|
lua_pop(L, 1);
|
2019-01-11 03:20:11 +08:00
|
|
|
|
2023-09-14 02:31:47 +08:00
|
|
|
set_widget_flags(L, 2, widget);
|
2020-05-16 06:03:17 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "visible");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
visible = lua_toboolean(L, -1);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2022-12-14 23:27:24 +08:00
|
|
|
|
|
|
|
// Expand horizontally/vertically, it allows to indicate that a
|
|
|
|
// specific widget is not expansive (e.g. a canvas with a fixed
|
|
|
|
// size)
|
|
|
|
type = lua_getfield(L, 2, "hexpand");
|
2023-03-23 05:02:41 +08:00
|
|
|
if (type != LUA_TNIL)
|
|
|
|
hexpand = lua_toboolean(L, -1);
|
|
|
|
lua_pop(L, 1);
|
2022-12-14 23:27:24 +08:00
|
|
|
type = lua_getfield(L, 2, "vexpand");
|
|
|
|
if (type != LUA_TNIL)
|
|
|
|
vexpand = lua_toboolean(L, -1);
|
2023-03-23 05:02:41 +08:00
|
|
|
lua_pop(L, 1);
|
2018-10-11 23:01:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (label || !dlg->hbox) {
|
2020-04-05 06:42:20 +08:00
|
|
|
if (label) {
|
|
|
|
auto labelWidget = new ui::Label(label);
|
2020-05-16 06:03:17 +08:00
|
|
|
if (!visible)
|
|
|
|
labelWidget->setVisible(false);
|
|
|
|
|
2023-08-09 04:26:58 +08:00
|
|
|
dlg->currentGrid->addChildInCell(labelWidget, 1, 1, ui::LEFT | ui::TOP);
|
2020-04-05 06:42:20 +08:00
|
|
|
if (!id.empty())
|
|
|
|
dlg->labelWidgets[id] = labelWidget;
|
|
|
|
}
|
2023-08-19 04:35:12 +08:00
|
|
|
else {
|
2025-05-25 23:25:03 +08:00
|
|
|
// For tabs and separators, we don't want the empty space of an unspecified label.
|
|
|
|
if (widget->type() != Tabs::Type() && widget->type() != ui::kSeparatorWidget) {
|
2023-08-19 04:35:12 +08:00
|
|
|
dlg->currentGrid->addChildInCell(new ui::HBox, 1, 1, ui::LEFT | ui::TOP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
auto hbox = new ui::HBox;
|
|
|
|
if (widget->type() == ui::kButtonWidget)
|
|
|
|
hbox->enableFlags(ui::HOMOGENEOUS);
|
2022-12-14 23:27:24 +08:00
|
|
|
|
2025-05-25 23:25:03 +08:00
|
|
|
// For tabs and unlabeled separators, we don't want the empty space of an unspecified label, so
|
2023-08-19 04:35:12 +08:00
|
|
|
// span 2 columns.
|
2025-05-25 23:25:03 +08:00
|
|
|
const int hspan =
|
|
|
|
((widget->type() == Tabs::Type()) || (widget->type() == ui::kSeparatorWidget && !label) ? 2 :
|
|
|
|
1);
|
2023-08-09 04:26:58 +08:00
|
|
|
dlg->currentGrid->addChildInCell(hbox,
|
2023-08-19 04:35:12 +08:00
|
|
|
hspan,
|
|
|
|
1,
|
2022-12-14 23:27:24 +08:00
|
|
|
ui::HORIZONTAL | (vexpand ? ui::VERTICAL : ui::TOP));
|
2018-10-11 23:01:21 +08:00
|
|
|
dlg->hbox = hbox;
|
|
|
|
}
|
|
|
|
|
2023-01-12 22:02:40 +08:00
|
|
|
dlg->mainWidgets.push_back(widget);
|
2022-12-14 23:27:24 +08:00
|
|
|
widget->setExpansive(hexpand);
|
2018-10-11 23:01:21 +08:00
|
|
|
dlg->hbox->addChild(widget);
|
|
|
|
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_newrow(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
dlg->hbox = nullptr;
|
2020-04-16 22:48:44 +08:00
|
|
|
|
|
|
|
dlg->autoNewRow = false;
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
// Dialog:newrow{ always }
|
2020-07-29 04:37:31 +08:00
|
|
|
if (lua_is_key_true(L, 2, "always"))
|
|
|
|
dlg->autoNewRow = true;
|
|
|
|
lua_pop(L, 1);
|
2020-04-16 22:48:44 +08:00
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_separator(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
|
2020-05-11 23:26:43 +08:00
|
|
|
std::string id, text;
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
if (lua_isstring(L, 2)) {
|
|
|
|
if (auto p = lua_tostring(L, 2))
|
|
|
|
text = p;
|
|
|
|
}
|
|
|
|
else if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "text");
|
|
|
|
if (type == LUA_TSTRING) {
|
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
text = p;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2020-05-11 23:26:43 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "id");
|
|
|
|
if (type == LUA_TSTRING) {
|
|
|
|
if (auto s = lua_tostring(L, -1))
|
|
|
|
id = s;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2018-10-11 23:01:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new ui::Separator(text, ui::HORIZONTAL);
|
2020-05-11 23:26:43 +08:00
|
|
|
if (!id.empty()) {
|
|
|
|
widget->setId(id.c_str());
|
|
|
|
dlg->dataWidgets[id] = widget;
|
|
|
|
}
|
|
|
|
|
2025-05-25 23:25:03 +08:00
|
|
|
return Dialog_add_widget(L, widget);
|
2018-10-11 23:01:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_label(lua_State* L)
|
|
|
|
{
|
|
|
|
std::string text;
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "text");
|
2020-04-07 01:54:11 +08:00
|
|
|
if (type != LUA_TNIL) {
|
2018-10-11 23:01:21 +08:00
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
text = p;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new ui::Label(text.c_str());
|
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
int Dialog_button_base(lua_State* L, T** outputWidget = nullptr)
|
|
|
|
{
|
2023-01-12 22:02:40 +08:00
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
std::string text;
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "text");
|
2020-04-07 01:54:11 +08:00
|
|
|
if (type != LUA_TNIL) {
|
2018-10-11 23:01:21 +08:00
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
text = p;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new T(text.c_str());
|
|
|
|
if (outputWidget)
|
|
|
|
*outputWidget = widget;
|
|
|
|
|
|
|
|
widget->processMnemonicFromText();
|
|
|
|
|
|
|
|
bool closeWindowByDefault = (widget->type() == ui::kButtonWidget);
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "selected");
|
2020-04-05 06:40:52 +08:00
|
|
|
if (type != LUA_TNIL)
|
2018-10-11 23:01:21 +08:00
|
|
|
widget->setSelected(lua_toboolean(L, -1));
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onclick");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2019-12-13 21:21:26 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->Click, [](lua_State*) {
|
2023-01-12 22:02:40 +08:00
|
|
|
// Do nothing
|
2019-12-13 21:21:26 +08:00
|
|
|
});
|
2018-10-11 23:01:21 +08:00
|
|
|
closeWindowByDefault = false;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
2023-01-12 22:02:40 +08:00
|
|
|
if (closeWindowByDefault) {
|
2023-03-01 00:05:02 +08:00
|
|
|
widget->Click.connect([widget]() { widget->closeWindow(); });
|
2023-01-12 22:02:40 +08:00
|
|
|
}
|
|
|
|
if (widget->type() == ui::kButtonWidget || widget->type() == ui::kMenuItemWidget) {
|
|
|
|
widget->Click.connect([dlg, widget]() { dlg->lastButton = widget; });
|
|
|
|
}
|
2018-10-11 23:01:21 +08:00
|
|
|
|
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_button(lua_State* L)
|
|
|
|
{
|
|
|
|
return Dialog_button_base<ui::Button>(L);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_check(lua_State* L)
|
|
|
|
{
|
|
|
|
return Dialog_button_base<ui::CheckBox>(L);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_radio(lua_State* L)
|
|
|
|
{
|
|
|
|
ui::RadioButton* radio = nullptr;
|
|
|
|
const int res = Dialog_button_base<ui::RadioButton>(L, &radio);
|
|
|
|
if (radio) {
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
bool hasLabelField = false;
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "label");
|
|
|
|
if (type == LUA_TSTRING)
|
|
|
|
hasLabelField = true;
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dlg->currentRadioGroup == 0 || hasLabelField) {
|
|
|
|
++dlg->currentRadioGroup;
|
|
|
|
}
|
|
|
|
|
|
|
|
radio->setRadioGroup(dlg->currentRadioGroup);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2023-01-12 22:02:40 +08:00
|
|
|
int Dialog_menuItem(lua_State* L)
|
|
|
|
{
|
|
|
|
return Dialog_button_base<ui::MenuItem>(L);
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
int Dialog_entry(lua_State* L)
|
|
|
|
{
|
|
|
|
std::string text;
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "text");
|
|
|
|
if (type == LUA_TSTRING) {
|
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
text = p;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new ui::Entry(4096, text.c_str());
|
2020-04-30 05:38:15 +08:00
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "onchange");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
|
|
|
// Do nothing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_number(lua_State* L)
|
|
|
|
{
|
|
|
|
auto widget = new ExprEntry;
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "text");
|
|
|
|
if (type == LUA_TSTRING) {
|
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
widget->setText(p);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "decimals");
|
2020-04-05 06:40:52 +08:00
|
|
|
if (type != LUA_TNIL) {
|
2018-10-11 23:01:21 +08:00
|
|
|
widget->setDecimals(lua_tointegerx(L, -1, nullptr));
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2020-04-30 05:38:15 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onchange");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
|
|
|
// Do nothing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2018-10-11 23:01:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_slider(lua_State* L)
|
|
|
|
{
|
|
|
|
int min = 0;
|
|
|
|
int max = 100;
|
|
|
|
int value = 100;
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "min");
|
2020-04-05 06:40:52 +08:00
|
|
|
if (type != LUA_TNIL) {
|
2018-10-11 23:01:21 +08:00
|
|
|
min = lua_tointegerx(L, -1, nullptr);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "max");
|
2020-04-05 06:40:52 +08:00
|
|
|
if (type != LUA_TNIL) {
|
2018-10-11 23:01:21 +08:00
|
|
|
max = lua_tointegerx(L, -1, nullptr);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "value");
|
2020-04-05 06:40:52 +08:00
|
|
|
if (type != LUA_TNIL) {
|
2018-10-11 23:01:21 +08:00
|
|
|
value = lua_tointegerx(L, -1, nullptr);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new ui::Slider(min, max, value);
|
2020-04-30 05:38:15 +08:00
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "onchange");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
|
|
|
// Do nothing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onrelease");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L, 1, widget->SliderReleased, [](lua_State* L) {
|
|
|
|
// Do nothing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_combobox(lua_State* L)
|
|
|
|
{
|
|
|
|
auto widget = new ui::ComboBox;
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "options");
|
|
|
|
if (type == LUA_TTABLE) {
|
|
|
|
lua_pushnil(L);
|
|
|
|
while (lua_next(L, -2) != 0) {
|
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
widget->addItem(p);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "option");
|
|
|
|
if (type == LUA_TSTRING) {
|
|
|
|
if (auto p = lua_tostring(L, -1)) {
|
|
|
|
int index = widget->findItemIndex(p);
|
|
|
|
if (index >= 0)
|
|
|
|
widget->setSelectedItemIndex(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2020-04-30 05:38:15 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onchange");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
|
|
|
// Do nothing
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2018-10-11 23:01:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_color(lua_State* L)
|
|
|
|
{
|
|
|
|
app::Color color;
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
lua_getfield(L, 2, "color");
|
|
|
|
color = convert_args_into_color(L, -1);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new ColorButton(color, app_get_current_pixel_format(), ColorButtonOptions());
|
2020-04-07 22:25:58 +08:00
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "onchange");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L, const app::Color& color) {
|
|
|
|
push_obj<app::Color>(L, color);
|
|
|
|
lua_setfield(L, -2, "color");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
2019-12-17 08:17:12 +08:00
|
|
|
int Dialog_shades(lua_State* L)
|
|
|
|
{
|
|
|
|
Shade colors;
|
|
|
|
// 'pick' is the default mode anyway
|
|
|
|
ColorShades::ClickType mode = ColorShades::ClickEntries;
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "mode");
|
|
|
|
if (type == LUA_TSTRING) {
|
|
|
|
if (const char* modeStr = lua_tostring(L, -1)) {
|
|
|
|
if (base::utf8_icmp(modeStr, "pick") == 0)
|
|
|
|
mode = ColorShades::ClickEntries;
|
|
|
|
else if (base::utf8_icmp(modeStr, "sort") == 0)
|
|
|
|
mode = ColorShades::DragAndDropEntries;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "colors");
|
|
|
|
if (type == LUA_TTABLE) {
|
|
|
|
lua_pushnil(L);
|
|
|
|
while (lua_next(L, -2) != 0) {
|
|
|
|
app::Color color = convert_args_into_color(L, -1);
|
|
|
|
colors.push_back(color);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new ColorShades(colors, mode);
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "onclick");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L,
|
|
|
|
1,
|
|
|
|
widget->Click,
|
2020-03-28 03:14:00 +08:00
|
|
|
[widget](lua_State* L, ColorShades::ClickEvent& ev) {
|
|
|
|
lua_pushinteger(L, (int)ev.button());
|
|
|
|
lua_setfield(L, -2, "button");
|
2024-12-17 01:52:19 +08:00
|
|
|
|
2019-12-17 08:17:12 +08:00
|
|
|
const int i = widget->getHotEntry();
|
|
|
|
const Shade shade = widget->getShade();
|
|
|
|
if (i >= 0 && i < int(shade.size())) {
|
|
|
|
push_obj<app::Color>(L, shade[i]);
|
|
|
|
lua_setfield(L, -2, "color");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
2019-02-14 03:15:04 +08:00
|
|
|
int Dialog_file(lua_State* L)
|
|
|
|
{
|
|
|
|
std::string title = "Open File";
|
2025-05-23 01:39:36 +08:00
|
|
|
std::string path;
|
2019-02-14 03:15:04 +08:00
|
|
|
std::string fn;
|
|
|
|
base::paths exts;
|
|
|
|
auto dlgType = FileSelectorType::Open;
|
|
|
|
auto fnFieldType = FilenameField::ButtonOnly;
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
lua_getfield(L, 2, "filename");
|
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
fn = p;
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
int type = lua_getfield(L, 2, "save");
|
2020-08-18 19:53:39 +08:00
|
|
|
if (type == LUA_TBOOLEAN && lua_toboolean(L, -1)) {
|
2019-02-14 03:15:04 +08:00
|
|
|
dlgType = FileSelectorType::Save;
|
|
|
|
title = "Save File";
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "title");
|
|
|
|
if (type == LUA_TSTRING)
|
|
|
|
title = lua_tostring(L, -1);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "entry");
|
|
|
|
if (type == LUA_TBOOLEAN) {
|
|
|
|
fnFieldType = FilenameField::EntryAndButton;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "filetypes");
|
|
|
|
if (type == LUA_TTABLE) {
|
|
|
|
lua_pushnil(L);
|
|
|
|
while (lua_next(L, -2) != 0) {
|
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
exts.push_back(p);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2025-01-08 09:47:59 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "basepath");
|
|
|
|
if (type == LUA_TSTRING) {
|
|
|
|
path = lua_tostring(L, -1);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2019-02-14 03:15:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
auto widget = new FilenameField(fnFieldType, fn);
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "onchange");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2019-12-13 21:21:26 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->Change, [](lua_State* L) {
|
|
|
|
// Do nothing
|
|
|
|
});
|
2019-02-14 03:15:04 +08:00
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
2025-01-08 09:47:59 +08:00
|
|
|
// Set file extension from 'exts' if a filename without extension is provided
|
|
|
|
if (!fn.empty() && base::get_file_extension(fn).empty() && !exts.empty())
|
|
|
|
fn = base::replace_extension(fn, exts.front());
|
|
|
|
|
|
|
|
// Set default path if 'basepath' is blank
|
|
|
|
if (path.empty()) {
|
2025-05-23 01:39:36 +08:00
|
|
|
// We use the 'filename' path the relative path if it was given.
|
|
|
|
path = base::get_file_path(fn);
|
|
|
|
if (path.empty()) {
|
|
|
|
if (const auto* doc = App::instance()->context()->activeDocument())
|
|
|
|
path = base::get_file_path(doc->filename());
|
|
|
|
else
|
|
|
|
path = base::get_current_path();
|
|
|
|
}
|
2025-01-08 09:47:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update the widget with the provided filename
|
|
|
|
fn = base::join_path(path, base::get_file_name(fn));
|
|
|
|
widget->setDocFilename(fn);
|
|
|
|
widget->setFilename(fn);
|
|
|
|
|
2024-12-07 03:00:34 +08:00
|
|
|
widget->SelectOutputFile.connect([=]() -> std::string {
|
2019-02-14 03:15:04 +08:00
|
|
|
base::paths newfilename;
|
2024-12-07 03:00:34 +08:00
|
|
|
if (app::show_file_selector(title, widget->fullFilename(), exts, dlgType, newfilename))
|
2019-02-14 03:15:04 +08:00
|
|
|
return newfilename.front();
|
|
|
|
else
|
2024-12-07 03:00:34 +08:00
|
|
|
return widget->fullFilename();
|
2019-02-14 03:15:04 +08:00
|
|
|
});
|
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
2023-01-06 03:13:39 +08:00
|
|
|
// Auxiliary callbacks used in Canvas events
|
|
|
|
static void fill_message_values(lua_State* L, const ui::Message* msg)
|
|
|
|
{
|
|
|
|
// Key modifiers
|
|
|
|
lua_pushboolean(L, msg->modifiers() & ui::kKeyAltModifier);
|
|
|
|
lua_setfield(L, -2, "altKey");
|
|
|
|
|
|
|
|
lua_pushboolean(L, msg->modifiers() & (ui::kKeyCmdModifier | ui::kKeyWinModifier));
|
|
|
|
lua_setfield(L, -2, "metaKey");
|
|
|
|
|
|
|
|
lua_pushboolean(L, msg->modifiers() & ui::kKeyCtrlModifier);
|
|
|
|
lua_setfield(L, -2, "ctrlKey");
|
|
|
|
|
|
|
|
lua_pushboolean(L, msg->modifiers() & ui::kKeyShiftModifier);
|
|
|
|
lua_setfield(L, -2, "shiftKey");
|
|
|
|
|
|
|
|
lua_pushboolean(L, msg->modifiers() & ui::kKeySpaceModifier);
|
|
|
|
lua_setfield(L, -2, "spaceKey");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fill_keymessage_values(lua_State* L, const ui::KeyMessage* msg)
|
|
|
|
{
|
|
|
|
ASSERT(msg->recipient());
|
|
|
|
if (!msg->recipient())
|
|
|
|
return;
|
|
|
|
|
|
|
|
fill_message_values(L, msg);
|
|
|
|
|
|
|
|
// KeyMessage specifics
|
|
|
|
lua_pushinteger(L, msg->repeat());
|
2023-03-17 01:55:30 +08:00
|
|
|
lua_setfield(L, -2, "repeat"); // Only for backward compatibility, remove this in a future
|
|
|
|
|
|
|
|
lua_pushinteger(L, msg->repeat());
|
|
|
|
lua_setfield(L, -2, "repeatCount");
|
2023-01-06 03:13:39 +08:00
|
|
|
|
|
|
|
// TODO improve this (create an Event metatable)
|
|
|
|
lua_pushcfunction(L, [](lua_State*) -> int {
|
|
|
|
Canvas::stopKeyEventPropagation();
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
lua_setfield(L, -2, "stopPropagation");
|
|
|
|
|
|
|
|
std::wstring keyString(1, (wchar_t)msg->unicodeChar());
|
|
|
|
lua_pushstring(L, base::to_utf8(keyString).c_str());
|
|
|
|
lua_setfield(L, -2, "key");
|
|
|
|
|
|
|
|
lua_pushstring(L, vkcode_to_code(msg->scancode()));
|
|
|
|
lua_setfield(L, -2, "code");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fill_mousemessage_values(lua_State* L, const ui::MouseMessage* msg)
|
|
|
|
{
|
|
|
|
ASSERT(msg->recipient());
|
|
|
|
if (!msg->recipient())
|
|
|
|
return;
|
|
|
|
|
|
|
|
fill_message_values(L, msg);
|
|
|
|
|
|
|
|
lua_pushinteger(L, msg->position().x - msg->recipient()->bounds().x);
|
|
|
|
lua_setfield(L, -2, "x");
|
|
|
|
|
|
|
|
lua_pushinteger(L, msg->position().y - msg->recipient()->bounds().y);
|
|
|
|
lua_setfield(L, -2, "y");
|
|
|
|
|
|
|
|
lua_pushinteger(L, int(msg->button()));
|
|
|
|
lua_setfield(L, -2, "button");
|
|
|
|
|
|
|
|
lua_pushnumber(L, msg->pressure());
|
|
|
|
lua_setfield(L, -2, "pressure");
|
|
|
|
|
|
|
|
if (msg->type() == kMouseWheelMessage) {
|
|
|
|
lua_pushnumber(L, msg->wheelDelta().x);
|
|
|
|
lua_setfield(L, -2, "deltaX");
|
|
|
|
|
|
|
|
lua_pushnumber(L, msg->wheelDelta().y);
|
|
|
|
lua_setfield(L, -2, "deltaY");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fill_touchmessage_values(lua_State* L, const ui::TouchMessage* msg)
|
|
|
|
{
|
|
|
|
ASSERT(msg->recipient());
|
|
|
|
if (!msg->recipient())
|
|
|
|
return;
|
|
|
|
|
|
|
|
fill_message_values(L, msg);
|
|
|
|
|
|
|
|
lua_pushinteger(L, msg->position().x - msg->recipient()->bounds().x);
|
|
|
|
lua_setfield(L, -2, "x");
|
|
|
|
|
|
|
|
lua_pushinteger(L, msg->position().y - msg->recipient()->bounds().y);
|
|
|
|
lua_setfield(L, -2, "y");
|
|
|
|
|
|
|
|
lua_pushnumber(L, msg->magnification());
|
|
|
|
lua_setfield(L, -2, "magnification");
|
|
|
|
}
|
|
|
|
|
2022-12-14 23:27:24 +08:00
|
|
|
int Dialog_canvas(lua_State* L)
|
|
|
|
{
|
|
|
|
auto widget = new Canvas;
|
|
|
|
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
gfx::Size sz(0, 0);
|
|
|
|
|
|
|
|
int type = lua_getfield(L, 2, "width");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
sz.w = lua_tointegerx(L, -1, nullptr);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "height");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
sz.h = lua_tointegerx(L, -1, nullptr);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2023-08-22 08:10:51 +08:00
|
|
|
type = lua_getfield(L, 2, "autoscaling");
|
2023-04-13 04:28:12 +08:00
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
widget->setAutoScaling(lua_toboolean(L, -1));
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2023-08-22 08:10:51 +08:00
|
|
|
// Backward compatibility with "autoScaling" parameter.
|
|
|
|
if (type == LUA_TNIL) {
|
|
|
|
type = lua_getfield(L, 2, "autoScaling");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
widget->setAutoScaling(lua_toboolean(L, -1));
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
2023-04-13 04:28:12 +08:00
|
|
|
if (widget->isAutoScaling())
|
|
|
|
sz *= ui::guiscale();
|
|
|
|
|
2022-12-14 23:27:24 +08:00
|
|
|
widget->setSizeHint(sz);
|
|
|
|
|
2023-01-05 01:40:10 +08:00
|
|
|
bool handleKeyEvents = false;
|
2022-12-14 23:27:24 +08:00
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "onpaint");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2022-12-15 04:04:50 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->Paint, [](lua_State* L, GraphicsContext& gc) {
|
|
|
|
push_new<GraphicsContext>(L, std::move(gc));
|
|
|
|
lua_setfield(L, -2, "context");
|
2022-12-14 23:27:24 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2022-12-15 09:13:49 +08:00
|
|
|
|
2023-01-05 01:40:10 +08:00
|
|
|
type = lua_getfield(L, 2, "onkeydown");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2023-01-06 03:13:39 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->KeyDown, fill_keymessage_values);
|
2023-01-05 01:40:10 +08:00
|
|
|
handleKeyEvents = true;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onkeyup");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2023-01-06 03:13:39 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->KeyUp, fill_keymessage_values);
|
2023-01-05 01:40:10 +08:00
|
|
|
handleKeyEvents = true;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2022-12-15 09:13:49 +08:00
|
|
|
type = lua_getfield(L, 2, "onmousemove");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2023-01-06 03:13:39 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->MouseMove, fill_mousemessage_values);
|
2022-12-15 09:13:49 +08:00
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onmousedown");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2023-01-06 03:13:39 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->MouseDown, fill_mousemessage_values);
|
2022-12-15 09:13:49 +08:00
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onmouseup");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2023-01-06 03:13:39 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->MouseUp, fill_mousemessage_values);
|
2022-12-15 09:13:49 +08:00
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2022-12-30 02:12:15 +08:00
|
|
|
|
2023-03-28 01:21:17 +08:00
|
|
|
type = lua_getfield(L, 2, "ondblclick");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
Dialog_connect_signal(L, 1, widget->DoubleClick, fill_mousemessage_values);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2022-12-30 02:12:15 +08:00
|
|
|
type = lua_getfield(L, 2, "onwheel");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2023-01-06 03:13:39 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->Wheel, fill_mousemessage_values);
|
2022-12-30 02:12:15 +08:00
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "ontouchmagnify");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2023-01-06 03:13:39 +08:00
|
|
|
Dialog_connect_signal(L, 1, widget->TouchMagnify, fill_touchmessage_values);
|
2022-12-30 02:12:15 +08:00
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2022-12-14 23:27:24 +08:00
|
|
|
}
|
2023-01-05 01:40:10 +08:00
|
|
|
|
|
|
|
// If this canvas handle keydown/up events, we set it as a focus
|
|
|
|
// stop.
|
|
|
|
if (handleKeyEvents)
|
|
|
|
widget->setFocusStop(true);
|
2022-12-14 23:27:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return Dialog_add_widget(L, widget);
|
|
|
|
}
|
|
|
|
|
2023-08-09 04:26:58 +08:00
|
|
|
int Dialog_tab(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
|
|
|
|
std::string text;
|
2023-08-19 04:35:12 +08:00
|
|
|
std::string id;
|
2023-08-30 04:49:02 +08:00
|
|
|
bool hasId = false;
|
2023-08-09 04:26:58 +08:00
|
|
|
if (lua_istable(L, 2)) {
|
2023-08-19 04:35:12 +08:00
|
|
|
int type = lua_getfield(L, 2, "id");
|
2023-08-30 04:49:02 +08:00
|
|
|
if (type != LUA_TNIL) {
|
2023-08-19 04:35:12 +08:00
|
|
|
id = lua_tostring(L, -1);
|
2023-08-30 04:49:02 +08:00
|
|
|
hasId = !id.empty();
|
|
|
|
}
|
2023-08-19 04:35:12 +08:00
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "text");
|
2023-08-09 04:26:58 +08:00
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
text = lua_tostring(L, -1);
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2023-08-19 04:35:12 +08:00
|
|
|
|
|
|
|
// If there was no id set, then use the tab text as the tab id.
|
2023-08-30 04:49:02 +08:00
|
|
|
if (!hasId)
|
|
|
|
id = text;
|
2023-08-19 04:35:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!dlg->wipTab) {
|
|
|
|
dlg->wipTab = new app::script::Tabs(ui::CENTER);
|
2023-08-09 04:26:58 +08:00
|
|
|
}
|
|
|
|
|
2024-01-20 04:04:21 +08:00
|
|
|
auto tab = dlg->wipTab->addTab(id, text);
|
|
|
|
dlg->currentGrid = tab->content();
|
2023-08-09 04:26:58 +08:00
|
|
|
|
2024-01-20 04:04:21 +08:00
|
|
|
if (hasId)
|
|
|
|
dlg->dataWidgets[id] = tab;
|
2023-08-30 04:49:02 +08:00
|
|
|
|
2023-08-30 04:13:01 +08:00
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "onclick");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
2024-01-20 04:04:21 +08:00
|
|
|
Dialog_connect_signal(L, 1, tab->Click, [id](lua_State* L) {
|
2024-11-14 00:23:06 +08:00
|
|
|
lua_pushstring(L, id.c_str());
|
|
|
|
lua_setfield(L, -2, "tab");
|
|
|
|
});
|
2023-08-30 04:13:01 +08:00
|
|
|
}
|
2023-09-14 02:31:47 +08:00
|
|
|
|
2024-01-20 04:04:21 +08:00
|
|
|
set_widget_flags(L, 2, tab);
|
2023-08-30 04:13:01 +08:00
|
|
|
}
|
|
|
|
|
2023-08-09 04:26:58 +08:00
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_endtabs(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
|
|
|
|
// There is no starting :tab(), do nothing then.
|
2023-08-19 04:35:12 +08:00
|
|
|
if (!dlg->wipTab) {
|
2023-08-09 04:26:58 +08:00
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int selectedTab = 0;
|
|
|
|
int align = ui::CENTER;
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
int type = lua_getfield(L, 2, "selected");
|
|
|
|
switch (type) {
|
|
|
|
case LUA_TSTRING: {
|
2023-08-19 04:35:12 +08:00
|
|
|
// Find the tab index by id first, if not found try by text then.
|
|
|
|
std::string selTabStr = lua_tostring(L, -1);
|
|
|
|
selectedTab = dlg->wipTab->tabIndexById(selTabStr);
|
|
|
|
if (selectedTab < 0)
|
|
|
|
selectedTab = dlg->wipTab->tabIndexByText(selTabStr);
|
2023-08-09 04:26:58 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case LUA_TNUMBER:
|
2023-08-19 04:35:12 +08:00
|
|
|
selectedTab = std::clamp<int>(lua_tointeger(L, -1), 1, dlg->wipTab->size()) - 1;
|
2023-08-09 04:26:58 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "align");
|
2023-08-19 04:35:12 +08:00
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
// Filter invalid flags.
|
|
|
|
int v = lua_tointeger(L, -1) & (ui::CENTER | ui::LEFT | ui::RIGHT | ui::TOP | ui::BOTTOM);
|
2023-08-09 04:26:58 +08:00
|
|
|
if (v)
|
|
|
|
align = v;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
2023-08-30 04:13:01 +08:00
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "onchange");
|
|
|
|
if (type == LUA_TFUNCTION) {
|
|
|
|
auto tab = dlg->wipTab;
|
|
|
|
Dialog_connect_signal(L, 1, dlg->wipTab->TabChanged, [tab](lua_State* L) {
|
2024-11-14 00:23:06 +08:00
|
|
|
lua_pushstring(L, tab->tabId(tab->selectedTab()).c_str());
|
|
|
|
lua_setfield(L, -2, "tab");
|
|
|
|
});
|
2023-08-30 04:13:01 +08:00
|
|
|
}
|
2023-08-09 04:26:58 +08:00
|
|
|
}
|
2023-08-19 04:35:12 +08:00
|
|
|
dlg->wipTab->setSelectorFlags(align);
|
|
|
|
dlg->wipTab->selectTab(selectedTab);
|
2023-08-09 04:26:58 +08:00
|
|
|
|
2023-08-19 04:35:12 +08:00
|
|
|
auto newTab = dlg->wipTab;
|
2023-08-09 04:26:58 +08:00
|
|
|
dlg->currentGrid = &dlg->grid;
|
2023-08-19 04:35:12 +08:00
|
|
|
dlg->wipTab = nullptr;
|
2023-08-09 04:26:58 +08:00
|
|
|
|
2023-08-19 04:35:12 +08:00
|
|
|
return Dialog_add_widget(L, newTab);
|
2023-08-09 04:26:58 +08:00
|
|
|
}
|
|
|
|
|
2020-04-05 06:42:20 +08:00
|
|
|
int Dialog_modify(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
if (lua_istable(L, 2)) {
|
|
|
|
const char* id = nullptr;
|
|
|
|
bool relayout = false;
|
|
|
|
|
|
|
|
int type = lua_getfield(L, 2, "id");
|
|
|
|
if (type != LUA_TNIL)
|
|
|
|
id = lua_tostring(L, -1);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2020-04-07 22:14:06 +08:00
|
|
|
// Modify window itself when no ID is specified
|
|
|
|
if (id == nullptr) {
|
|
|
|
// "title" or "text" is the same for dialogs
|
|
|
|
type = lua_getfield(L, 2, "title");
|
|
|
|
if (type == LUA_TNIL) {
|
|
|
|
lua_pop(L, 1);
|
|
|
|
type = lua_getfield(L, 2, "text");
|
|
|
|
}
|
|
|
|
if (const char* s = lua_tostring(L, -1)) {
|
|
|
|
dlg->window.setText(s);
|
|
|
|
relayout = true;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
return 0;
|
|
|
|
}
|
2020-04-05 06:42:20 +08:00
|
|
|
|
|
|
|
// Here we could use dlg->window.findChild(id) but why not use the
|
|
|
|
// map directly (it should be faster than iterating over all
|
|
|
|
// children).
|
|
|
|
Widget* widget = dlg->findDataWidgetById(id);
|
|
|
|
if (!widget)
|
|
|
|
return luaL_error(L, "Given id=\"%s\" in Dialog:modify{} not found in dialog", id);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "enabled");
|
|
|
|
if (type != LUA_TNIL)
|
|
|
|
widget->setEnabled(lua_toboolean(L, -1));
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "selected");
|
|
|
|
if (type != LUA_TNIL)
|
|
|
|
widget->setSelected(lua_toboolean(L, -1));
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "visible");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
bool state = lua_toboolean(L, -1);
|
|
|
|
widget->setVisible(state);
|
2020-04-07 22:01:46 +08:00
|
|
|
dlg->setLabelVisibility(id, state);
|
2020-04-05 06:42:20 +08:00
|
|
|
relayout = true;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "text");
|
|
|
|
if (const char* s = lua_tostring(L, -1)) {
|
|
|
|
widget->setText(s);
|
2025-08-03 11:10:58 +08:00
|
|
|
|
|
|
|
// Re-process mnemonics for buttons
|
|
|
|
if (widget->type() == WidgetType::kButtonWidget)
|
|
|
|
widget->processMnemonicFromText();
|
2020-04-05 06:42:20 +08:00
|
|
|
relayout = true;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2020-04-07 22:13:30 +08:00
|
|
|
type = lua_getfield(L, 2, "label");
|
|
|
|
if (const char* s = lua_tostring(L, -1)) {
|
|
|
|
dlg->setLabelText(id, s);
|
|
|
|
relayout = true;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "focus");
|
|
|
|
if (type != LUA_TNIL && lua_toboolean(L, -1)) {
|
|
|
|
widget->requestFocus();
|
|
|
|
relayout = true;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2020-05-01 04:47:00 +08:00
|
|
|
type = lua_getfield(L, 2, "decimals");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto expr = dynamic_cast<ExprEntry*>(widget)) {
|
|
|
|
expr->setDecimals(lua_tointegerx(L, -1, nullptr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "min");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
|
|
|
|
slider->setRange(lua_tointegerx(L, -1, nullptr), slider->getMaxValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "max");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
|
|
|
|
slider->setRange(slider->getMinValue(), lua_tointegerx(L, -1, nullptr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "value");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
|
|
|
|
slider->setValue(lua_tointegerx(L, -1, nullptr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2021-12-01 05:52:01 +08:00
|
|
|
// Handling options before option should support
|
|
|
|
// using both or only one of them at the same time
|
|
|
|
type = lua_getfield(L, 2, "options");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (lua_istable(L, -1)) {
|
|
|
|
if (auto combobox = dynamic_cast<ui::ComboBox*>(widget)) {
|
|
|
|
combobox->deleteAllItems();
|
|
|
|
lua_pushnil(L);
|
|
|
|
bool empty = true;
|
|
|
|
while (lua_next(L, -2) != 0) {
|
|
|
|
if (auto p = lua_tostring(L, -1)) {
|
|
|
|
combobox->addItem(p);
|
|
|
|
empty = false;
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
if (empty)
|
|
|
|
combobox->getEntryWidget()->setText("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2020-05-01 04:47:00 +08:00
|
|
|
type = lua_getfield(L, 2, "option");
|
|
|
|
if (auto p = lua_tostring(L, -1)) {
|
|
|
|
if (auto combobox = dynamic_cast<ui::ComboBox*>(widget)) {
|
|
|
|
int index = combobox->findItemIndex(p);
|
|
|
|
if (index >= 0)
|
|
|
|
combobox->setSelectedItemIndex(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "color");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto colorButton = dynamic_cast<ColorButton*>(widget)) {
|
|
|
|
colorButton->setColor(convert_args_into_color(L, -1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "colors");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto colorShade = dynamic_cast<ColorShades*>(widget)) {
|
|
|
|
Shade shade;
|
|
|
|
if (lua_istable(L, -1)) {
|
|
|
|
lua_pushnil(L);
|
|
|
|
while (lua_next(L, -2) != 0) {
|
|
|
|
app::Color color = convert_args_into_color(L, -1);
|
|
|
|
shade.push_back(color);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
colorShade->setShade(shade);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
|
|
|
type = lua_getfield(L, 2, "filename");
|
|
|
|
if (auto p = lua_tostring(L, -1)) {
|
|
|
|
if (auto filenameField = dynamic_cast<FilenameField*>(widget)) {
|
|
|
|
filenameField->setFilename(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2022-12-27 02:33:14 +08:00
|
|
|
type = lua_getfield(L, 2, "mouseCursor");
|
|
|
|
if (type != LUA_TNIL) {
|
|
|
|
if (auto canvas = dynamic_cast<Canvas*>(widget)) {
|
|
|
|
auto cursor = (ui::CursorType)lua_tointeger(L, -1);
|
|
|
|
canvas->setMouseCursor(cursor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
|
|
|
2021-12-01 05:52:01 +08:00
|
|
|
// TODO shades mode? file title / open / save / filetypes? on* events?
|
2020-04-07 22:13:30 +08:00
|
|
|
|
2023-03-23 19:34:07 +08:00
|
|
|
// Relayout only if the the dialog window is not being resized.
|
|
|
|
// This is due to the possibility of a script calling a method
|
|
|
|
// to modify dialog's properties (which might generate a relayout)
|
|
|
|
// during an ongoing resize event.
|
|
|
|
if (relayout && !dlg->window.isResizing()) {
|
2020-04-05 06:42:20 +08:00
|
|
|
dlg->window.layout();
|
|
|
|
|
2025-08-03 08:08:02 +08:00
|
|
|
if (dlg->autofit > 0) {
|
|
|
|
gfx::Rect oldBounds = dlg->window.bounds();
|
|
|
|
gfx::Size resize(oldBounds.size());
|
|
|
|
|
|
|
|
if (dlg->autofit & ui::TOP || dlg->autofit & ui::BOTTOM)
|
|
|
|
resize.h = dlg->window.sizeHint().h;
|
|
|
|
if (dlg->autofit & ui::LEFT || dlg->autofit & ui::RIGHT)
|
|
|
|
resize.w = dlg->window.sizeHint().w;
|
|
|
|
|
|
|
|
gfx::Size difference = resize - oldBounds.size();
|
|
|
|
const auto& bounds = dlg->getWindowBounds();
|
|
|
|
gfx::Rect newBounds(bounds.x, bounds.y, resize.w, resize.h);
|
|
|
|
|
|
|
|
if (dlg->autofit & ui::BOTTOM)
|
|
|
|
newBounds.y = bounds.y - difference.h;
|
|
|
|
if (dlg->autofit & ui::RIGHT)
|
|
|
|
newBounds.x = bounds.x - difference.w;
|
|
|
|
|
|
|
|
dlg->setWindowBounds(newBounds);
|
|
|
|
}
|
2020-04-05 06:42:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2022-12-15 08:17:51 +08:00
|
|
|
int Dialog_repaint(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
std::stack<ui::Widget*> widgets;
|
|
|
|
widgets.push(&dlg->grid);
|
|
|
|
|
|
|
|
while (!widgets.empty()) {
|
|
|
|
auto child = widgets.top();
|
|
|
|
widgets.pop();
|
|
|
|
|
|
|
|
if (child->type() == Canvas::Type()) {
|
|
|
|
static_cast<Canvas*>(child)->callPaint();
|
|
|
|
child->invalidate();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto subchild : child->children())
|
|
|
|
widgets.push(subchild);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
int Dialog_get_data(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
lua_newtable(L);
|
|
|
|
for (const auto& kv : dlg->dataWidgets) {
|
|
|
|
const ui::Widget* widget = kv.second;
|
|
|
|
switch (widget->type()) {
|
2020-05-11 23:26:43 +08:00
|
|
|
case ui::kSeparatorWidget:
|
|
|
|
// Do nothing
|
|
|
|
continue;
|
2018-10-11 23:01:21 +08:00
|
|
|
case ui::kButtonWidget:
|
|
|
|
case ui::kCheckWidget:
|
|
|
|
case ui::kRadioWidget:
|
2023-01-12 22:02:40 +08:00
|
|
|
case ui::kMenuItemWidget:
|
2018-10-11 23:01:21 +08:00
|
|
|
lua_pushboolean(
|
|
|
|
L,
|
|
|
|
widget->isSelected() || dlg->window.closer() == widget || dlg->lastButton == widget);
|
|
|
|
break;
|
|
|
|
case ui::kEntryWidget:
|
|
|
|
if (auto expr = dynamic_cast<const ExprEntry*>(widget)) {
|
|
|
|
if (expr->decimals() == 0)
|
|
|
|
lua_pushinteger(L, widget->textInt());
|
|
|
|
else
|
|
|
|
lua_pushnumber(L, widget->textDouble());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
lua_pushstring(L, widget->text().c_str());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ui::kLabelWidget: lua_pushstring(L, widget->text().c_str()); break;
|
|
|
|
case ui::kSliderWidget:
|
|
|
|
if (auto slider = dynamic_cast<const ui::Slider*>(widget)) {
|
|
|
|
lua_pushinteger(L, slider->getValue());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ui::kComboBoxWidget:
|
|
|
|
if (auto combobox = dynamic_cast<const ui::ComboBox*>(widget)) {
|
|
|
|
if (auto sel = combobox->getSelectedItem())
|
|
|
|
lua_pushstring(L, sel->text().c_str());
|
|
|
|
else
|
|
|
|
lua_pushnil(L);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2019-12-17 08:17:12 +08:00
|
|
|
if (auto colorButton = dynamic_cast<const ColorButton*>(widget)) {
|
2018-10-11 23:01:21 +08:00
|
|
|
push_obj<app::Color>(L, colorButton->getColor());
|
2019-12-17 08:17:12 +08:00
|
|
|
}
|
|
|
|
else if (auto colorShade = dynamic_cast<const ColorShades*>(widget)) {
|
|
|
|
switch (colorShade->clickType()) {
|
|
|
|
case ColorShades::ClickEntries: {
|
|
|
|
Shade shade = colorShade->getShade();
|
|
|
|
int i = colorShade->getHotEntry();
|
|
|
|
if (i >= 0 && i < int(shade.size()))
|
|
|
|
push_obj<app::Color>(L, shade[i]);
|
|
|
|
else
|
|
|
|
lua_pushnil(L);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ColorShades::DragAndDropEntries: {
|
|
|
|
lua_newtable(L);
|
|
|
|
Shade shade = colorShade->getShade();
|
|
|
|
for (int i = 0; i < int(shade.size()); ++i) {
|
|
|
|
push_obj<app::Color>(L, shade[i]);
|
|
|
|
lua_rawseti(L, -2, i + 1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: lua_pushnil(L); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (auto filenameField = dynamic_cast<const FilenameField*>(widget)) {
|
2024-12-07 03:00:34 +08:00
|
|
|
lua_pushstring(L, filenameField->fullFilename().c_str());
|
2019-12-17 08:17:12 +08:00
|
|
|
}
|
2023-08-19 04:35:12 +08:00
|
|
|
else if (auto tabs = dynamic_cast<const app::script::Tabs*>(widget)) {
|
|
|
|
std::string tabStr = tabs->tabId(tabs->selectedTab());
|
|
|
|
lua_pushstring(L, tabStr.c_str());
|
|
|
|
}
|
2019-12-17 08:17:12 +08:00
|
|
|
else {
|
2018-10-11 23:01:21 +08:00
|
|
|
lua_pushnil(L);
|
2019-12-17 08:17:12 +08:00
|
|
|
}
|
2018-10-11 23:01:21 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
lua_setfield(L, -2, kv.first.c_str());
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_set_data(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
if (!lua_istable(L, 2))
|
|
|
|
return 0;
|
|
|
|
for (const auto& kv : dlg->dataWidgets) {
|
|
|
|
lua_getfield(L, 2, kv.first.c_str());
|
|
|
|
|
|
|
|
ui::Widget* widget = kv.second;
|
|
|
|
switch (widget->type()) {
|
2020-05-11 23:26:43 +08:00
|
|
|
case ui::kSeparatorWidget:
|
|
|
|
// Do nothing
|
|
|
|
break;
|
2018-10-11 23:01:21 +08:00
|
|
|
case ui::kButtonWidget:
|
|
|
|
case ui::kCheckWidget:
|
|
|
|
case ui::kRadioWidget: widget->setSelected(lua_toboolean(L, -1)); break;
|
|
|
|
case ui::kEntryWidget:
|
|
|
|
if (auto expr = dynamic_cast<ExprEntry*>(widget)) {
|
|
|
|
if (expr->decimals() == 0)
|
|
|
|
expr->setTextf("%d", lua_tointeger(L, -1));
|
|
|
|
else
|
|
|
|
expr->setTextf("%.*g", expr->decimals(), lua_tonumber(L, -1));
|
|
|
|
}
|
|
|
|
else if (auto p = lua_tostring(L, -1)) {
|
|
|
|
widget->setText(p);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ui::kLabelWidget:
|
|
|
|
if (auto p = lua_tostring(L, -1)) {
|
|
|
|
widget->setText(p);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ui::kSliderWidget:
|
|
|
|
if (auto slider = dynamic_cast<ui::Slider*>(widget)) {
|
|
|
|
slider->setValue(lua_tointeger(L, -1));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ui::kComboBoxWidget:
|
|
|
|
if (auto combobox = dynamic_cast<ui::ComboBox*>(widget)) {
|
|
|
|
if (auto p = lua_tostring(L, -1)) {
|
|
|
|
int index = combobox->findItemIndex(p);
|
|
|
|
if (index >= 0)
|
|
|
|
combobox->setSelectedItemIndex(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2019-12-17 08:17:12 +08:00
|
|
|
if (auto colorButton = dynamic_cast<ColorButton*>(widget)) {
|
2018-10-11 23:01:21 +08:00
|
|
|
colorButton->setColor(convert_args_into_color(L, -1));
|
2019-12-17 08:17:12 +08:00
|
|
|
}
|
|
|
|
else if (auto colorShade = dynamic_cast<ColorShades*>(widget)) {
|
|
|
|
switch (colorShade->clickType()) {
|
|
|
|
case ColorShades::ClickEntries: {
|
|
|
|
// TODO change hot entry?
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ColorShades::DragAndDropEntries: {
|
|
|
|
Shade shade;
|
|
|
|
if (lua_istable(L, -1)) {
|
|
|
|
lua_pushnil(L);
|
|
|
|
while (lua_next(L, -2) != 0) {
|
|
|
|
app::Color color = convert_args_into_color(L, -1);
|
|
|
|
shade.push_back(color);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
colorShade->setShade(shade);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-14 03:15:04 +08:00
|
|
|
else if (auto filenameField = dynamic_cast<FilenameField*>(widget)) {
|
|
|
|
if (auto p = lua_tostring(L, -1))
|
|
|
|
filenameField->setFilename(p);
|
|
|
|
}
|
2023-08-19 04:35:12 +08:00
|
|
|
else if (auto tabs = dynamic_cast<app::script::Tabs*>(widget)) {
|
|
|
|
int type = lua_type(L, -1);
|
|
|
|
if (type == LUA_TNUMBER)
|
|
|
|
tabs->selectTab(lua_tointeger(L, -1) - 1);
|
|
|
|
else if (type == LUA_TSTRING) {
|
|
|
|
std::string tabStr = lua_tostring(L, -1);
|
|
|
|
int tabIndex = tabs->tabIndexById(tabStr);
|
|
|
|
tabs->selectTab(tabIndex);
|
|
|
|
}
|
|
|
|
}
|
2018-10-11 23:01:21 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-11-29 20:46:37 +08:00
|
|
|
int Dialog_get_bounds(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
2023-08-02 05:25:16 +08:00
|
|
|
if (!dlg->window.isVisible() && dlg->window.bounds().isEmpty()) {
|
2018-11-29 20:46:37 +08:00
|
|
|
dlg->window.remapWindow();
|
2023-08-02 05:25:16 +08:00
|
|
|
dlg->window.centerWindow(dlg->parentDisplay());
|
|
|
|
}
|
2021-06-08 04:39:12 +08:00
|
|
|
push_new<gfx::Rect>(L, dlg->getWindowBounds());
|
2018-11-29 20:46:37 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2025-08-03 08:08:02 +08:00
|
|
|
int Dialog_get_sizeHint(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
push_new<gfx::Size>(L, dlg->window.sizeHint());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_get_autofit(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
lua_pushinteger(L, dlg->autofit);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Dialog_set_autofit(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
dlg->setAutofit(lua_tointeger(L, 2));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-29 20:46:37 +08:00
|
|
|
int Dialog_set_bounds(lua_State* L)
|
|
|
|
{
|
|
|
|
auto dlg = get_obj<Dialog>(L, 1);
|
|
|
|
const auto rc = get_obj<gfx::Rect>(L, 2);
|
2021-06-08 04:39:12 +08:00
|
|
|
if (rc) {
|
2023-08-02 05:25:16 +08:00
|
|
|
if (*rc != dlg->getWindowBounds()) {
|
2021-06-08 04:39:12 +08:00
|
|
|
dlg->setWindowBounds(*rc);
|
2023-08-02 05:25:16 +08:00
|
|
|
dlg->window.setAutoRemap(false);
|
|
|
|
}
|
2019-07-13 03:42:43 +08:00
|
|
|
}
|
2018-11-29 20:46:37 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-11 23:01:21 +08:00
|
|
|
const luaL_Reg Dialog_methods[] = {
|
|
|
|
{ "__gc", Dialog_gc },
|
|
|
|
{ "show", Dialog_show },
|
2023-01-12 22:02:40 +08:00
|
|
|
{ "showMenu", Dialog_showMenu },
|
2018-10-11 23:01:21 +08:00
|
|
|
{ "close", Dialog_close },
|
|
|
|
{ "newrow", Dialog_newrow },
|
|
|
|
{ "separator", Dialog_separator },
|
|
|
|
{ "label", Dialog_label },
|
|
|
|
{ "button", Dialog_button },
|
|
|
|
{ "check", Dialog_check },
|
|
|
|
{ "radio", Dialog_radio },
|
2023-01-12 22:02:40 +08:00
|
|
|
{ "menuItem", Dialog_menuItem },
|
2018-10-11 23:01:21 +08:00
|
|
|
{ "entry", Dialog_entry },
|
|
|
|
{ "number", Dialog_number },
|
|
|
|
{ "slider", Dialog_slider },
|
|
|
|
{ "combobox", Dialog_combobox },
|
|
|
|
{ "color", Dialog_color },
|
2019-12-17 08:17:12 +08:00
|
|
|
{ "shades", Dialog_shades },
|
2019-02-14 03:15:04 +08:00
|
|
|
{ "file", Dialog_file },
|
2022-12-14 23:27:24 +08:00
|
|
|
{ "canvas", Dialog_canvas },
|
2023-08-09 04:26:58 +08:00
|
|
|
{ "tab", Dialog_tab },
|
|
|
|
{ "endtabs", Dialog_endtabs },
|
2020-04-05 06:42:20 +08:00
|
|
|
{ "modify", Dialog_modify },
|
2022-12-15 08:17:51 +08:00
|
|
|
{ "repaint", Dialog_repaint },
|
2018-10-11 23:01:21 +08:00
|
|
|
{ nullptr, nullptr }
|
|
|
|
};
|
|
|
|
|
|
|
|
const Property Dialog_properties[] = {
|
2025-08-03 08:08:02 +08:00
|
|
|
{ "data", Dialog_get_data, Dialog_set_data },
|
|
|
|
{ "bounds", Dialog_get_bounds, Dialog_set_bounds },
|
|
|
|
{ "autofit", Dialog_get_autofit, Dialog_set_autofit },
|
|
|
|
{ "sizeHint", Dialog_get_sizeHint, nullptr },
|
|
|
|
{ nullptr, nullptr, nullptr }
|
2018-10-11 23:01:21 +08:00
|
|
|
};
|
|
|
|
|
2024-11-14 00:23:06 +08:00
|
|
|
} // anonymous namespace
|
2018-10-11 23:01:21 +08:00
|
|
|
|
|
|
|
DEF_MTNAME(Dialog);
|
|
|
|
|
|
|
|
void register_dialog_class(lua_State* L)
|
|
|
|
{
|
|
|
|
REG_CLASS(L, Dialog);
|
|
|
|
REG_CLASS_NEW(L, Dialog);
|
|
|
|
REG_CLASS_PROPERTIES(L, Dialog);
|
2020-07-28 21:35:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// close all opened Dialogs before closing the UI
|
|
|
|
void close_all_dialogs()
|
|
|
|
{
|
|
|
|
for (Dialog* dlg : all_dialogs) {
|
|
|
|
ASSERT(dlg);
|
|
|
|
if (dlg)
|
|
|
|
dlg->window.closeWindow(nullptr);
|
|
|
|
}
|
2018-10-11 23:01:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
}} // namespace app::script
|