[lua] Add selected tab retrieval/selection through Dialog's data and possibility to set the tabs selector at the bottom

This commit is contained in:
Martín Capello 2023-08-18 17:35:12 -03:00 committed by David Capello
parent 0ae408fe4e
commit d21f47f827
7 changed files with 336 additions and 67 deletions

View File

@ -207,6 +207,7 @@ if(ENABLE_SCRIPTING)
script/slices_class.cpp script/slices_class.cpp
script/sprite_class.cpp script/sprite_class.cpp
script/sprites_class.cpp script/sprites_class.cpp
script/tabs_widget.cpp
script/tag_class.cpp script/tag_class.cpp
script/tags_class.cpp script/tags_class.cpp
script/tile_class.cpp script/tile_class.cpp

View File

@ -18,6 +18,7 @@
#include "app/script/graphics_context.h" #include "app/script/graphics_context.h"
#include "app/script/keys.h" #include "app/script/keys.h"
#include "app/script/luacpp.h" #include "app/script/luacpp.h"
#include "app/script/tabs_widget.h"
#include "app/ui/button_set.h" #include "app/ui/button_set.h"
#include "app/ui/color_button.h" #include "app/ui/color_button.h"
#include "app/ui/color_shades.h" #include "app/ui/color_shades.h"
@ -76,11 +77,10 @@ struct Dialog {
std::map<std::string, ui::Widget*> labelWidgets; std::map<std::string, ui::Widget*> labelWidgets;
int currentRadioGroup = 0; int currentRadioGroup = 0;
// Members used to hold current state about the creation of a group // Member used to hold current state about the creation of a tabs
// of tabs. After creation, these members are reset to their initial // widget. After creation it is reset to null to be ready for the
// empty state to be ready for the next group of tabs. // creation of the next tabs widget.
std::vector<ui::Grid*> tabs; app::script::Tabs* wipTab = nullptr;
HBox* tabsSelector = nullptr;
// Used to create a new row when a different kind of widget is added // Used to create a new row when a different kind of widget is added
// in the dialog. // in the dialog.
@ -581,15 +581,23 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
if (!id.empty()) if (!id.empty())
dlg->labelWidgets[id] = labelWidget; dlg->labelWidgets[id] = labelWidget;
} }
else else {
dlg->currentGrid->addChildInCell(new ui::HBox, 1, 1, ui::LEFT | ui::TOP); // For tabs we don't want the empty space of an unspecified label.
if (widget->type() != Tabs::Type()) {
dlg->currentGrid->addChildInCell(new ui::HBox, 1, 1, ui::LEFT | ui::TOP);
}
}
auto hbox = new ui::HBox; auto hbox = new ui::HBox;
if (widget->type() == ui::kButtonWidget) if (widget->type() == ui::kButtonWidget)
hbox->enableFlags(ui::HOMOGENEOUS); hbox->enableFlags(ui::HOMOGENEOUS);
// For tabs we don't want the empty space of an unspecified label, so
// span 2 columns.
const int hspan = (widget->type() == Tabs::Type() ? 2: 1);
dlg->currentGrid->addChildInCell( dlg->currentGrid->addChildInCell(
hbox, 1, 1, hbox, hspan, 1,
ui::HORIZONTAL | (vexpand ? ui::VERTICAL: ui::TOP)); ui::HORIZONTAL | (vexpand ? ui::VERTICAL: ui::TOP));
dlg->hbox = hbox; dlg->hbox = hbox;
@ -1295,27 +1303,35 @@ int Dialog_tab(lua_State* L)
auto dlg = get_obj<Dialog>(L, 1); auto dlg = get_obj<Dialog>(L, 1);
std::string text; std::string text;
std::string id;
if (lua_istable(L, 2)) { if (lua_istable(L, 2)) {
int type = lua_getfield(L, 2, "text"); int type = lua_getfield(L, 2, "id");
if (type != LUA_TNIL)
id = lua_tostring(L, -1);
lua_pop(L, 1);
type = lua_getfield(L, 2, "text");
if (type != LUA_TNIL) { if (type != LUA_TNIL) {
text = lua_tostring(L, -1); text = lua_tostring(L, -1);
} }
lua_pop(L, 1); lua_pop(L, 1);
// If there was no id set, then use the tab text as the tab id.
if (id.empty()) id = text;
}
if (!dlg->wipTab) {
dlg->wipTab = new app::script::Tabs(ui::CENTER);
} }
auto tabContent = new ui::Grid(2, false); auto tabContent = new ui::Grid(2, false);
tabContent->setExpansive(true);
tabContent->setVisible(false); tabContent->setVisible(false);
tabContent->setText(text); tabContent->setText(text);
dlg->tabs.push_back(tabContent); tabContent->setId(id.c_str());
dlg->wipTab->addTab(tabContent);
dlg->currentGrid = tabContent; dlg->currentGrid = tabContent;
// If this is the first tab create the tabs selector container.
if (dlg->tabs.size() == 1) {
dlg->tabsSelector = new HBox();
dlg->grid.addChildInCell(dlg->tabsSelector, 2, 1, ui::HORIZONTAL);
}
dlg->grid.addChildInCell(tabContent, 2, 1, ui::HORIZONTAL | ui::VERTICAL);
lua_pushvalue(L, 1); lua_pushvalue(L, 1);
return 1; return 1;
} }
@ -1325,7 +1341,7 @@ int Dialog_endtabs(lua_State* L)
auto dlg = get_obj<Dialog>(L, 1); auto dlg = get_obj<Dialog>(L, 1);
// There is no starting :tab(), do nothing then. // There is no starting :tab(), do nothing then.
if (dlg->tabs.empty()) { if (!dlg->wipTab) {
lua_pushvalue(L, 1); lua_pushvalue(L, 1);
return 1; return 1;
} }
@ -1336,70 +1352,35 @@ int Dialog_endtabs(lua_State* L)
int type = lua_getfield(L, 2, "selected"); int type = lua_getfield(L, 2, "selected");
switch (type) { switch (type) {
case LUA_TSTRING: { case LUA_TSTRING: {
// Find the tab index with the specified text. // Find the tab index by id first, if not found try by text then.
std::string selTabText = lua_tostring(L, -1); std::string selTabStr = lua_tostring(L, -1);
for (int i=0; i<dlg->tabs.size(); ++i) { selectedTab = dlg->wipTab->tabIndexById(selTabStr);
if (dlg->tabs[i]->text() == selTabText) { if (selectedTab < 0)
selectedTab = i; selectedTab = dlg->wipTab->tabIndexByText(selTabStr);
break;
}
}
break; break;
} }
case LUA_TNUMBER: case LUA_TNUMBER:
selectedTab = std::clamp<int>(lua_tointeger(L, -1), 1, dlg->tabs.size()) - 1; selectedTab = std::clamp<int>(lua_tointeger(L, -1), 1, dlg->wipTab->size()) - 1;
break; break;
} }
lua_pop(L, 1); lua_pop(L, 1);
type = lua_getfield(L, 2, "align"); type = lua_getfield(L, 2, "align");
if(type != LUA_TNIL) { if (type != LUA_TNIL) {
int v = lua_tointeger(L, -1) & (ui::CENTER | ui::LEFT | ui::RIGHT); // Filter invalid flags.
// Set align only if it has a valid value. int v = lua_tointeger(L, -1) & (ui::CENTER | ui::LEFT | ui::RIGHT | ui::TOP | ui::BOTTOM);
if (v) align = v; if (v) align = v;
} }
lua_pop(L, 1); lua_pop(L, 1);
} }
dlg->wipTab->setSelectorFlags(align);
dlg->wipTab->selectTab(selectedTab);
// Create the tabs selector's buttonset auto newTab = dlg->wipTab;
auto widget = new app::ButtonSet(dlg->tabs.size());
for (int i=0; i<dlg->tabs.size(); ++i) {
auto item = widget->addItem(dlg->tabs[i]->text());
item->setSelected(i == selectedTab);
dlg->tabs[i]->setVisible(i == selectedTab);
}
auto tabs = dlg->tabs;
widget->ItemChange.connect([dlg, tabs](ButtonSet::Item* selItem) {
for (auto tab : tabs) {
tab->setVisible(selItem->text() == tab->text());
}
dlg->window.remapWindow();
});
widget->initTheme();
ui::Separator* sep;
if (align & ui::CENTER || align & ui::RIGHT) {
sep = new ui::Separator("", ui::HORIZONTAL);
sep->setExpansive(true);
dlg->tabsSelector->addChild(sep);
}
dlg->tabsSelector->addChild(widget);
if (align & ui::CENTER || align & ui::LEFT) {
sep = new ui::Separator("", ui::HORIZONTAL);
sep->setExpansive(true);
dlg->tabsSelector->addChild(sep);
}
// Add tab's bottom separator
sep = new ui::Separator("", ui::HORIZONTAL);
sep->setExpansive(true);
dlg->grid.addChildInCell(sep, 2, 1, ui::HORIZONTAL);
// Clear state to be ready for the next tabs creation
dlg->tabsSelector = nullptr;
dlg->tabs.clear();
dlg->currentGrid = &dlg->grid; dlg->currentGrid = &dlg->grid;
dlg->wipTab = nullptr;
lua_pushvalue(L, 1); return Dialog_add_widget(L, newTab);
return 1;
} }
int Dialog_modify(lua_State* L) int Dialog_modify(lua_State* L)
@ -1704,6 +1685,10 @@ int Dialog_get_data(lua_State* L)
else if (auto filenameField = dynamic_cast<const FilenameField*>(widget)) { else if (auto filenameField = dynamic_cast<const FilenameField*>(widget)) {
lua_pushstring(L, filenameField->filename().c_str()); lua_pushstring(L, filenameField->filename().c_str());
} }
else if (auto tabs = dynamic_cast<const app::script::Tabs*>(widget)) {
std::string tabStr = tabs->tabId(tabs->selectedTab());
lua_pushstring(L, tabStr.c_str());
}
else { else {
lua_pushnil(L); lua_pushnil(L);
} }
@ -1794,6 +1779,16 @@ int Dialog_set_data(lua_State* L)
if (auto p = lua_tostring(L, -1)) if (auto p = lua_tostring(L, -1))
filenameField->setFilename(p); filenameField->setFilename(p);
} }
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);
}
}
break; break;
} }

View File

@ -474,6 +474,9 @@ Engine::Engine()
setfield_integer(L, "LEFT", ui::LEFT); setfield_integer(L, "LEFT", ui::LEFT);
setfield_integer(L, "CENTER", ui::CENTER); setfield_integer(L, "CENTER", ui::CENTER);
setfield_integer(L, "RIGHT", ui::RIGHT); setfield_integer(L, "RIGHT", ui::RIGHT);
setfield_integer(L, "TOP", ui::TOP);
setfield_integer(L, "BOTTOM", ui::BOTTOM);
lua_pop(L, 1); lua_pop(L, 1);
// Register classes/prototypes // Register classes/prototypes

View File

@ -0,0 +1,184 @@
// Aseprite
// Copyright (c) 2022-2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#include "app/script/tabs_widget.h"
#ifdef ENABLE_UI
using namespace ui;
namespace app {
namespace script {
// static
ui::WidgetType Tabs::Type()
{
static ui::WidgetType type = ui::kGenericWidget;
if (type == ui::kGenericWidget)
type = ui::register_widget_type();
return type;
}
Tabs::Tabs(int selectorFlags) : m_selectorFlags(selectorFlags)
{
m_sepL.setExpansive(true);
m_sepR.setExpansive(true);
m_pages.setExpansive(true);
setType(Type());
layoutChilds();
}
Tab* Tabs::addTab(Grid* content)
{
m_pages.addChild(content);
if (m_buttons.children().empty()) {
m_buttonsBox.addChild(&m_buttons);
m_buttons.ItemChange.connect([this](ButtonSet::Item* selItem) {
int oldSelectedTabIndex = m_selectedTab;
for (int i=0; i<m_pages.children().size(); ++i) {
auto tab = m_pages.children()[i];
bool isSelectedTab = selItem->text() == tab->text();
tab->setVisible(isSelectedTab);
if (isSelectedTab)
m_selectedTab = i;
}
layout();
if (oldSelectedTabIndex != m_selectedTab) {
TabChanged();
}
});
}
else {
m_buttons.setColumns(m_pages.children().size());
}
auto tab = new Tab();
tab->setText(content->text());
m_buttons.addItem(tab);
// Select the first tab by default.
if (m_buttons.children().size() == 1) {
selectTab(0);
}
m_buttons.initTheme();
return tab;
}
void Tabs::selectTab(int index)
{
if (index >= 0 && index < m_pages.children().size()) {
for (int i=0; i<m_pages.children().size(); ++i) {
m_buttons.getItem(i)->setSelected(i == index);
m_pages.children()[i]->setVisible(i == index);
}
m_selectedTab = index;
}
}
void Tabs::setSelectorFlags(int selectorFlags)
{
if (selectorFlags != m_selectorFlags) {
m_selectorFlags = selectorFlags;
layoutChilds();
}
}
int Tabs::tabIndexById(const std::string& id) const
{
for (int i=0; i<m_pages.children().size(); ++i) {
if (m_pages.children()[i]->id() == id)
return i;
}
return -1;
}
int Tabs::tabIndexByText(const std::string& text) const
{
for (int i=0; i<m_pages.children().size(); ++i) {
if (m_pages.children()[i]->text() == text)
return i;
}
return -1;
}
std::string Tabs::tabId(int index) const
{
return index >= 0 && index < m_pages.children().size() ?
m_pages.children()[index]->id() :
std::string();
}
std::string Tabs::tabText(int index) const
{
return index >= 0 && index < m_pages.children().size() ?
m_pages.children()[index]->text() :
std::string();
}
int Tabs::selectedTab() const
{
return m_selectedTab;
}
void Tabs::layoutChilds()
{
// Put the tab selector at the bottom or at the top.
removeAllChildren();
if (m_selectorFlags & ui::BOTTOM) {
addChild(&m_sepTB);
addChild(&m_pages);
addChild(&m_selector);
}
else {
addChild(&m_selector);
addChild(&m_pages);
addChild(&m_sepTB);
}
// Align the selector buttons.
m_selector.removeAllChildren();
if (m_selectorFlags & ui::LEFT) {
m_selector.addChild(&m_buttonsBox);
m_selector.addChild(&m_sepR);
}
else if (m_selectorFlags & ui::RIGHT) {
m_selector.addChild(&m_sepL);
m_selector.addChild(&m_buttonsBox);
}
else {
m_selector.addChild(&m_sepL);
m_selector.addChild(&m_buttonsBox);
m_selector.addChild(&m_sepR);
}
}
void Pages::onSizeHint(ui::SizeHintEvent& ev)
{
gfx::Size prefSize(0, 0);
for (auto child : children()) {
gfx::Size childSize = child->sizeHint();
prefSize.w = std::max(childSize.w, prefSize.w);
prefSize.h = std::max(childSize.h, prefSize.h);
}
prefSize.w += border().width();
prefSize.h += border().height();
ev.setSizeHint(prefSize);
}
void Tab::onClick()
{
Click();
ButtonSet::Item::onClick();
}
} // namespace script
} // namespace app
#endif // ENABLE_UI

View File

@ -0,0 +1,70 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_SCRIPT_TABS_H_INCLUDED
#define APP_SCRIPT_TABS_H_INCLUDED
#pragma once
#include "app/ui/button_set.h"
#include "ui/box.h"
#include "ui/grid.h"
#include "ui/separator.h"
#include "ui/size_hint_event.h"
#include "ui/widget.h"
namespace app {
namespace script {
class Tab : public app::ButtonSet::Item {
public:
obs::signal<void()> Click;
protected:
virtual void onClick();
};
class Pages : public ui::VBox {
protected:
void onSizeHint(ui::SizeHintEvent& ev);
};
class Tabs : public ui::VBox {
public:
static ui::WidgetType Type();
Tabs(int selectorFlags);
Tab* addTab(ui::Grid* content);
void selectTab(int index);
void setSelectorFlags(int selectorFlags);
int tabIndexById(const std::string& text) const;
int tabIndexByText(const std::string& text) const;
std::string tabId(int index) const;
std::string tabText(int index) const;
int selectedTab() const;
int size() const { return m_pages.children().size(); };
obs::signal<void()> TabChanged;
private:
void layoutChilds();
ui::HBox m_selector;
ui::Separator m_sepL = ui::Separator(std::string(), ui::HORIZONTAL);
ui::Separator m_sepR = ui::Separator(std::string(), ui::HORIZONTAL);
ui::Separator m_sepTB = ui::Separator(std::string(), ui::HORIZONTAL);
ui::HBox m_buttonsBox;
app::ButtonSet m_buttons = app::ButtonSet(1);
Pages m_pages;
int m_selectedTab = 0;
int m_selectorFlags;
};
} // namespace script
} // namespace app
#endif

View File

@ -129,6 +129,21 @@ void Grid::setStyle(Style* style)
setGap(style->gap()); setGap(style->gap());
} }
void Grid::setColumns(int columns)
{
if (columns == m_colstrip.size())
return;
int oldSize = m_colstrip.size();
m_colstrip.resize(columns);
for (int row=0; row<m_rowstrip.size(); ++row) {
m_cells[row].resize(m_colstrip.size());
for (int col=oldSize; col<(int)m_cells[row].size(); ++col) {
m_cells[row][col] = new Cell;
}
}
}
void Grid::setGap(const gfx::Size& gap) void Grid::setGap(const gfx::Size& gap)
{ {
m_colgap = gap.w; m_colgap = gap.w;

View File

@ -34,6 +34,7 @@ namespace ui {
Info getChildInfo(Widget* child); Info getChildInfo(Widget* child);
void setGap(const gfx::Size& gap); void setGap(const gfx::Size& gap);
void setStyle(Style* style) override; void setStyle(Style* style) override;
void setColumns(int columns);
protected: protected:
// Events // Events