mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-28 00:35:30 +00:00
Add support to reorder tabs (close #118)
This commit is contained in:
parent
2d64c55661
commit
8c6f55cc41
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#define ANI_ADDING_TAB_TICKS 5
|
#define ANI_ADDING_TAB_TICKS 5
|
||||||
#define ANI_REMOVING_TAB_TICKS 10
|
#define ANI_REMOVING_TAB_TICKS 10
|
||||||
|
#define ANI_REORDER_TABS_TICKS 5
|
||||||
|
|
||||||
#define HAS_ARROWS(tabs) ((m_button_left->getParent() == (tabs)))
|
#define HAS_ARROWS(tabs) ((m_button_left->getParent() == (tabs)))
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ Tabs::Tabs(TabsDelegate* delegate)
|
|||||||
, m_border(2)
|
, m_border(2)
|
||||||
, m_delegate(delegate)
|
, m_delegate(delegate)
|
||||||
, m_timer(1000/60, this)
|
, m_timer(1000/60, this)
|
||||||
|
, m_isDragging(false)
|
||||||
{
|
{
|
||||||
setDoubleBuffered(true);
|
setDoubleBuffered(true);
|
||||||
|
|
||||||
@ -84,6 +86,7 @@ Tabs::~Tabs()
|
|||||||
|
|
||||||
void Tabs::addTab(TabView* tabView)
|
void Tabs::addTab(TabView* tabView)
|
||||||
{
|
{
|
||||||
|
resetOldPositions();
|
||||||
startAni(ANI_ADDING_TAB, ANI_ADDING_TAB_TICKS);
|
startAni(ANI_ADDING_TAB, ANI_ADDING_TAB_TICKS);
|
||||||
|
|
||||||
Tab* tab = new Tab(tabView);
|
Tab* tab = new Tab(tabView);
|
||||||
@ -132,6 +135,7 @@ void Tabs::removeTab(TabView* tabView)
|
|||||||
else
|
else
|
||||||
m_nextTabOfTheRemovedOne = nullptr;
|
m_nextTabOfTheRemovedOne = nullptr;
|
||||||
|
|
||||||
|
resetOldPositions();
|
||||||
startAni(ANI_REMOVING_TAB, ANI_REMOVING_TAB_TICKS);
|
startAni(ANI_REMOVING_TAB, ANI_REMOVING_TAB_TICKS);
|
||||||
updateTabs();
|
updateTabs();
|
||||||
}
|
}
|
||||||
@ -147,6 +151,7 @@ void Tabs::updateTabs()
|
|||||||
tabWidth = MAX(4*ui::guiscale(), tabWidth);
|
tabWidth = MAX(4*ui::guiscale(), tabWidth);
|
||||||
}
|
}
|
||||||
double x = 0.0;
|
double x = 0.0;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
for (Tab* tab : m_list) {
|
for (Tab* tab : m_list) {
|
||||||
tab->text = tab->view->getTabText();
|
tab->text = tab->view->getTabText();
|
||||||
@ -154,6 +159,7 @@ void Tabs::updateTabs()
|
|||||||
tab->x = int(x);
|
tab->x = int(x);
|
||||||
tab->width = int(x+tabWidth) - int(x);
|
tab->width = int(x+tabWidth) - int(x);
|
||||||
x += tabWidth;
|
x += tabWidth;
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
@ -220,8 +226,42 @@ bool Tabs::onProcessMessage(Message* msg)
|
|||||||
switch (msg->type()) {
|
switch (msg->type()) {
|
||||||
|
|
||||||
case kMouseEnterMessage:
|
case kMouseEnterMessage:
|
||||||
|
calculateHot();
|
||||||
|
return true;
|
||||||
|
|
||||||
case kMouseMoveMessage:
|
case kMouseMoveMessage:
|
||||||
calculateHot();
|
calculateHot();
|
||||||
|
|
||||||
|
if (hasCapture() && m_selected) {
|
||||||
|
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||||
|
gfx::Point mousePos = mouseMsg->position();
|
||||||
|
gfx::Point delta = mousePos - m_dragMousePos;
|
||||||
|
|
||||||
|
if (!m_isDragging) {
|
||||||
|
if (!m_clickedCloseButton) {
|
||||||
|
double dist = std::sqrt(delta.x*delta.x + delta.y*delta.y);
|
||||||
|
if (dist > 4.0/ui::guiscale())
|
||||||
|
startDrag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We are drag a tab...
|
||||||
|
else {
|
||||||
|
m_selected->x = m_dragTabX + delta.x;
|
||||||
|
|
||||||
|
int i = (mousePos.x-m_border*guiscale()) / m_selected->width;
|
||||||
|
i = MID(0, i, int(m_list.size())-1);
|
||||||
|
if (i != m_dragTabIndex) {
|
||||||
|
std::swap(m_list[m_dragTabIndex], m_list[i]);
|
||||||
|
m_dragTabIndex = i;
|
||||||
|
|
||||||
|
resetOldPositions(double(m_ani_t) / double(m_ani_T));
|
||||||
|
updateTabs();
|
||||||
|
startAni(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case kMouseLeaveMessage:
|
case kMouseLeaveMessage:
|
||||||
@ -234,6 +274,7 @@ bool Tabs::onProcessMessage(Message* msg)
|
|||||||
case kMouseDownMessage:
|
case kMouseDownMessage:
|
||||||
if (m_hot != NULL) {
|
if (m_hot != NULL) {
|
||||||
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
|
||||||
|
m_dragMousePos = mouseMsg->position();
|
||||||
|
|
||||||
if (m_hotCloseButton) {
|
if (m_hotCloseButton) {
|
||||||
if (!m_clickedCloseButton) {
|
if (!m_clickedCloseButton) {
|
||||||
@ -272,6 +313,9 @@ bool Tabs::onProcessMessage(Message* msg)
|
|||||||
|
|
||||||
releaseMouse();
|
releaseMouse();
|
||||||
|
|
||||||
|
if (m_isDragging)
|
||||||
|
stopDrag();
|
||||||
|
|
||||||
if (m_clickedCloseButton) {
|
if (m_clickedCloseButton) {
|
||||||
m_clickedCloseButton = false;
|
m_clickedCloseButton = false;
|
||||||
invalidate();
|
invalidate();
|
||||||
@ -317,28 +361,66 @@ void Tabs::onPaint(PaintEvent& ev)
|
|||||||
SkinTheme* theme = static_cast<SkinTheme*>(this->getTheme());
|
SkinTheme* theme = static_cast<SkinTheme*>(this->getTheme());
|
||||||
Graphics* g = ev.getGraphics();
|
Graphics* g = ev.getGraphics();
|
||||||
gfx::Rect rect = getClientBounds();
|
gfx::Rect rect = getClientBounds();
|
||||||
gfx::Rect box(rect.x, rect.y,
|
gfx::Rect box(rect.x, rect.y, rect.w,
|
||||||
m_border*guiscale(),
|
|
||||||
(m_list.empty() ? 0:
|
(m_list.empty() ? 0:
|
||||||
theme->dimensions.tabsHeight() - theme->dimensions.tabsEmptyHeight()));
|
theme->dimensions.tabsHeight() - theme->dimensions.tabsEmptyHeight()));
|
||||||
|
|
||||||
g->fillRect(theme->colors.windowFace(), g->getClipBounds());
|
g->fillRect(theme->colors.windowFace(), g->getClipBounds());
|
||||||
|
drawFiller(g, box);
|
||||||
|
|
||||||
skin::Style::State state;
|
int startX = m_border*guiscale();
|
||||||
theme->styles.tabFiller()->paint(g, box, nullptr, state);
|
|
||||||
theme->styles.tabBottom()->paint(g,
|
|
||||||
gfx::Rect(box.x, box.y2(), box.w, rect.y2()-box.y2()), nullptr, state);
|
|
||||||
|
|
||||||
box.x = box.x2();
|
|
||||||
int startX = box.x;
|
|
||||||
double t = double(m_ani_t)/double(m_ani_T);
|
double t = double(m_ani_t)/double(m_ani_T);
|
||||||
Tab* prevTab = nullptr;
|
Tab* prevTab = nullptr;
|
||||||
|
|
||||||
// For each tab...
|
// For each tab...
|
||||||
|
int i = 0;
|
||||||
for (Tab* tab : m_list) {
|
for (Tab* tab : m_list) {
|
||||||
int prevX2 = box.x;
|
if (m_ani == ANI_NONE) {
|
||||||
|
box.x = startX + tab->x;
|
||||||
|
box.w = tab->width;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
box.x = startX + int(inbetween(tab->oldX, tab->x, t));
|
||||||
|
box.w = int(inbetween(tab->oldWidth, tab->width, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_ani == ANI_REMOVING_TAB) {
|
||||||
|
if (m_nextTabOfTheRemovedOne == tab) {
|
||||||
|
// Draw deleted tab
|
||||||
|
if (m_removedTab) {
|
||||||
|
gfx::Rect box2(box.x, box.y, 0, box.h);
|
||||||
|
box2.w = int(startX + inbetween(tab->oldX, tab->x, t)) - box2.x;
|
||||||
|
drawTab(g, box2, m_removedTab, 0, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab != m_selected)
|
||||||
|
drawTab(g, box, tab, 0, (tab == m_hot), false);
|
||||||
|
|
||||||
|
box.x = box.x2();
|
||||||
|
prevTab = tab;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw deleted tab
|
||||||
|
if (m_ani == ANI_REMOVING_TAB && !m_nextTabOfTheRemovedOne && m_removedTab) {
|
||||||
|
if (prevTab) {
|
||||||
|
box.x = int(startX + inbetween(
|
||||||
|
prevTab->oldX+prevTab->oldWidth, prevTab->x+prevTab->width, t));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
box.x = startX;
|
||||||
|
box.w = int(inbetween(m_removedTab->oldWidth, 0, t));
|
||||||
|
drawTab(g, box, m_removedTab, 0, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab that is being dragged
|
||||||
|
if (m_selected) {
|
||||||
|
Tab* tab = m_selected;
|
||||||
|
|
||||||
if (m_ani == ANI_NONE) {
|
if (m_ani == ANI_NONE) {
|
||||||
|
box.x = startX + tab->x;
|
||||||
box.w = tab->width;
|
box.w = tab->width;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -347,52 +429,10 @@ void Tabs::onPaint(PaintEvent& ev)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int dy = 0;
|
int dy = 0;
|
||||||
if (m_ani == ANI_ADDING_TAB) {
|
if (m_ani == ANI_ADDING_TAB)
|
||||||
box.x = prevX2; // To avoid empty spaces in animation between tabs
|
dy = int(box.h - box.h * t);
|
||||||
if (m_selected == tab)
|
|
||||||
dy = int(box.h - box.h * t);
|
|
||||||
}
|
|
||||||
else if (m_ani == ANI_REMOVING_TAB) {
|
|
||||||
if (m_nextTabOfTheRemovedOne == tab) {
|
|
||||||
// Draw deleted tab
|
|
||||||
if (m_removedTab) {
|
|
||||||
gfx::Rect box2(0, box.y, 0, box.h);
|
|
||||||
if (prevTab)
|
|
||||||
box2.x = prevX2;
|
|
||||||
box2.w = int(startX + inbetween(tab->oldX, tab->x, t)) - box2.x;
|
|
||||||
drawTab(g, box2, m_removedTab, 0, false, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
box.x = prevX2; // To avoid empty spaces in animation between tabs
|
|
||||||
}
|
|
||||||
|
|
||||||
drawTab(g, box, tab, dy, (tab == m_hot), (tab == m_selected));
|
drawTab(g, box, m_selected, dy, (tab == m_hot), true);
|
||||||
|
|
||||||
box.x = box.x2();
|
|
||||||
prevTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw deleted tab
|
|
||||||
if (m_ani == ANI_REMOVING_TAB && !m_nextTabOfTheRemovedOne && m_removedTab) {
|
|
||||||
gfx::Rect box2(0, box.y, 0, box.h);
|
|
||||||
if (prevTab) {
|
|
||||||
box2.x = int(startX + inbetween(
|
|
||||||
prevTab->oldX+prevTab->oldWidth, prevTab->x+prevTab->width, t));
|
|
||||||
}
|
|
||||||
box2.w = int(inbetween(m_removedTab->oldWidth, 0, t));
|
|
||||||
drawTab(g, box2, m_removedTab, 0, false, false);
|
|
||||||
|
|
||||||
box.x += box2.w;
|
|
||||||
box.w = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the gap to the right-side
|
|
||||||
if (box.x < rect.x2()) {
|
|
||||||
theme->styles.tabFiller()->paint(g,
|
|
||||||
gfx::Rect(box.x, box.y, rect.x2()-box.x, box.h), nullptr, state);
|
|
||||||
theme->styles.tabBottom()->paint(g,
|
|
||||||
gfx::Rect(box.x, box.y2(), rect.x2()-box.x, rect.y2()-box.y2()), nullptr, state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,6 +569,19 @@ void Tabs::drawTab(Graphics* g, const gfx::Rect& _box, Tab* tab, int dy,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tabs::drawFiller(ui::Graphics* g, const gfx::Rect& box)
|
||||||
|
{
|
||||||
|
SkinTheme* theme = static_cast<SkinTheme*>(this->getTheme());
|
||||||
|
gfx::Rect rect = getClientBounds();
|
||||||
|
skin::Style::State state;
|
||||||
|
|
||||||
|
theme->styles.tabFiller()->paint(g,
|
||||||
|
gfx::Rect(box.x, box.y, rect.x2()-box.x, box.h), nullptr, state);
|
||||||
|
|
||||||
|
theme->styles.tabBottom()->paint(g,
|
||||||
|
gfx::Rect(box.x, box.y2(), rect.x2()-box.x, rect.y2()-box.y2()), nullptr, state);
|
||||||
|
}
|
||||||
|
|
||||||
Tabs::TabsListIterator Tabs::getTabIteratorByView(TabView* tabView)
|
Tabs::TabsListIterator Tabs::getTabIteratorByView(TabView* tabView)
|
||||||
{
|
{
|
||||||
TabsListIterator it, end = m_list.end();
|
TabsListIterator it, end = m_list.end();
|
||||||
@ -557,6 +610,9 @@ void Tabs::makeTabVisible(Tab* thisTab)
|
|||||||
|
|
||||||
void Tabs::calculateHot()
|
void Tabs::calculateHot()
|
||||||
{
|
{
|
||||||
|
if (m_isDragging)
|
||||||
|
return;
|
||||||
|
|
||||||
SkinTheme* theme = static_cast<SkinTheme*>(this->getTheme());
|
SkinTheme* theme = static_cast<SkinTheme*>(this->getTheme());
|
||||||
gfx::Rect rect = getBounds();
|
gfx::Rect rect = getBounds();
|
||||||
gfx::Rect box(rect.x, rect.y, 0, rect.h-1);
|
gfx::Rect box(rect.x, rect.y, 0, rect.h-1);
|
||||||
@ -601,17 +657,28 @@ gfx::Rect Tabs::getTabCloseButtonBounds(Tab* tab, const gfx::Rect& box)
|
|||||||
return gfx::Rect();
|
return gfx::Rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tabs::resetOldPositions()
|
||||||
|
{
|
||||||
|
for (Tab* tab : m_list) {
|
||||||
|
tab->oldX = tab->x;
|
||||||
|
tab->oldWidth = tab->width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tabs::resetOldPositions(double t)
|
||||||
|
{
|
||||||
|
for (Tab* tab : m_list) {
|
||||||
|
tab->oldX = int(inbetween(tab->oldX, tab->x, t));
|
||||||
|
tab->oldWidth = int(inbetween(tab->oldWidth, tab->width, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Tabs::startAni(Ani ani, int T)
|
void Tabs::startAni(Ani ani, int T)
|
||||||
{
|
{
|
||||||
// Stop previous animation
|
// Stop previous animation
|
||||||
if (m_ani != ANI_NONE)
|
if (m_ani != ANI_NONE)
|
||||||
stopAni();
|
stopAni();
|
||||||
|
|
||||||
for (Tab* tab : m_list) {
|
|
||||||
tab->oldX = tab->x;
|
|
||||||
tab->oldWidth = tab->width;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ani = ani;
|
m_ani = ani;
|
||||||
m_ani_t = 0;
|
m_ani_t = 0;
|
||||||
m_ani_T = T;
|
m_ani_T = T;
|
||||||
@ -624,4 +691,26 @@ void Tabs::stopAni()
|
|||||||
m_timer.stop();
|
m_timer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Tabs::startDrag()
|
||||||
|
{
|
||||||
|
ASSERT(m_selected);
|
||||||
|
|
||||||
|
updateTabs();
|
||||||
|
|
||||||
|
m_isDragging = true;
|
||||||
|
m_dragTabX = m_selected->x;
|
||||||
|
m_dragTabIndex = std::find(m_list.begin(), m_list.end(), m_selected) - m_list.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tabs::stopDrag()
|
||||||
|
{
|
||||||
|
m_isDragging = false;
|
||||||
|
m_selected->oldX = m_selected->x;
|
||||||
|
m_selected->oldWidth = m_selected->width;
|
||||||
|
|
||||||
|
resetOldPositions(double(m_ani_t) / double(m_ani_T));
|
||||||
|
updateTabs();
|
||||||
|
startAni(ANI_REORDER_TABS, ANI_REORDER_TABS_TICKS);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -80,7 +80,8 @@ namespace app {
|
|||||||
|
|
||||||
enum Ani { ANI_NONE,
|
enum Ani { ANI_NONE,
|
||||||
ANI_ADDING_TAB,
|
ANI_ADDING_TAB,
|
||||||
ANI_REMOVING_TAB };
|
ANI_REMOVING_TAB,
|
||||||
|
ANI_REORDER_TABS };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Tabs(TabsDelegate* delegate);
|
Tabs(TabsDelegate* delegate);
|
||||||
@ -102,17 +103,22 @@ namespace app {
|
|||||||
void onPreferredSize(ui::PreferredSizeEvent& ev) override;
|
void onPreferredSize(ui::PreferredSizeEvent& ev) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void resetOldPositions();
|
||||||
|
void resetOldPositions(double t);
|
||||||
void startAni(Ani ani, int T);
|
void startAni(Ani ani, int T);
|
||||||
void stopAni();
|
void stopAni();
|
||||||
|
|
||||||
void selectTabInternal(Tab* tab);
|
void selectTabInternal(Tab* tab);
|
||||||
void drawTab(ui::Graphics* g, const gfx::Rect& box, Tab* tab, int dy, bool hover, bool selected);
|
void drawTab(ui::Graphics* g, const gfx::Rect& box, Tab* tab, int dy, bool hover, bool selected);
|
||||||
|
void drawFiller(ui::Graphics* g, const gfx::Rect& box);
|
||||||
TabsListIterator getTabIteratorByView(TabView* tabView);
|
TabsListIterator getTabIteratorByView(TabView* tabView);
|
||||||
Tab* getTabByView(TabView* tabView);
|
Tab* getTabByView(TabView* tabView);
|
||||||
int getMaxScrollX();
|
int getMaxScrollX();
|
||||||
void makeTabVisible(Tab* tab);
|
void makeTabVisible(Tab* tab);
|
||||||
void calculateHot();
|
void calculateHot();
|
||||||
gfx::Rect getTabCloseButtonBounds(Tab* tab, const gfx::Rect& box);
|
gfx::Rect getTabCloseButtonBounds(Tab* tab, const gfx::Rect& box);
|
||||||
|
void startDrag();
|
||||||
|
void stopDrag();
|
||||||
|
|
||||||
int m_border;
|
int m_border;
|
||||||
TabsList m_list;
|
TabsList m_list;
|
||||||
@ -131,6 +137,12 @@ namespace app {
|
|||||||
int m_ani_T; // Number of ticks in total for the current transition/animation
|
int m_ani_T; // Number of ticks in total for the current transition/animation
|
||||||
Tab* m_removedTab;
|
Tab* m_removedTab;
|
||||||
Tab* m_nextTabOfTheRemovedOne;
|
Tab* m_nextTabOfTheRemovedOne;
|
||||||
|
|
||||||
|
// Drag-and-drop
|
||||||
|
bool m_isDragging;
|
||||||
|
int m_dragTabX;
|
||||||
|
gfx::Point m_dragMousePos;
|
||||||
|
int m_dragTabIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
Loading…
x
Reference in New Issue
Block a user