Add a tab widget

This commit is contained in:
Magnus Norddahl 2024-01-10 06:06:28 +01:00 committed by Christoph Oelckers
parent c7778b9332
commit 762ce6f14c
7 changed files with 438 additions and 3 deletions

View file

@ -30,6 +30,7 @@ set(ZWIDGET_SOURCES
src/widgets/pushbutton/pushbutton.cpp
src/widgets/checkboxlabel/checkboxlabel.cpp
src/widgets/listview/listview.cpp
src/widgets/tabwidget/tabwidget.cpp
src/window/window.cpp
)
@ -58,6 +59,7 @@ set(ZWIDGET_INCLUDES
include/zwidget/widgets/pushbutton/pushbutton.h
include/zwidget/widgets/checkboxlabel/checkboxlabel.h
include/zwidget/widgets/listview/listview.h
include/zwidget/widgets/tabwidget/tabwidget.h
include/zwidget/window/window.h
)
@ -91,6 +93,7 @@ source_group("src\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURC
source_group("src\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/pushbutton/.+")
source_group("src\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/checkboxlabel/.+")
source_group("src\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/listview/.+")
source_group("src\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/tabwidget/.+")
source_group("src\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/window/.+")
source_group("include" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/.+")
source_group("include\\core" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/core/.+")
@ -107,6 +110,7 @@ source_group("include\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_S
source_group("include\\widgets\\pushbutton" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/pushbutton/.+")
source_group("include\\widgets\\checkboxlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/checkboxlabel/.+")
source_group("include\\widgets\\listview" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/listview/.+")
source_group("include\\widgets\\tabwidget" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/widgets/tabwidget/.+")
source_group("include\\window" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/.+")
source_group("include\\window\\win32" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/win32/.+")
source_group("include\\window\\sdl2" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/include/zwidget/window/sdl2/.+")

View file

@ -86,7 +86,7 @@ public:
void SetClipboardText(const std::string& text);
Widget* Window();
Canvas* GetCanvas();
Canvas* GetCanvas() const;
Widget* ChildAt(double x, double y) { return ChildAt(Point(x, y)); }
Widget* ChildAt(const Point& pos);

View file

@ -0,0 +1,116 @@
#pragma once
#include "../../core/widget.h"
#include <functional>
#include <string>
class TabBar;
class TabBarTab;
class TabWidgetStack;
class TextLabel;
class ImageBox;
class Image;
class TabWidget : public Widget
{
public:
TabWidget(Widget* parent);
int AddTab(Widget* page, const std::string& label);
int AddTab(Widget* page, const std::shared_ptr<Image>& icon, const std::string& label);
int GetCurrentIndex() const;
Widget* GetCurrentWidget() const;
int GetPageIndex(Widget* pageWidget) const;
void SetCurrentIndex(int pageIndex);
void SetCurrentWidget(Widget* pageWidget);
std::function<void()> OnCurrentChanged;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
private:
void OnBarCurrentChanged();
TabBar* Bar = nullptr;
TabWidgetStack* PageStack = nullptr;
std::vector<Widget*> Pages;
};
class TabBar : public Widget
{
public:
TabBar(Widget* parent);
int AddTab(const std::string& label);
int AddTab(const std::shared_ptr<Image>& icon, const std::string& label);
int GetCurrentIndex() const;
void SetCurrentIndex(int pageIndex);
double GetPreferredHeight() const { return 30.0; }
std::function<void()> OnCurrentChanged;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
private:
void OnTabClicked(TabBarTab* tab);
int GetTabIndex(TabBarTab* tab);
int CurrentIndex = -1;
std::vector<TabBarTab*> Tabs;
};
class TabBarTab : public Widget
{
public:
TabBarTab(Widget* parent);
void SetText(const std::string& text);
void SetIcon(const std::shared_ptr<Image>& icon);
void SetCurrent(bool value);
double GetPreferredWidth() const;
std::function<void()> OnClick;
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
void OnMouseMove(const Point& pos) override;
void OnMouseDown(const Point& pos, int key) override;
void OnMouseUp(const Point& pos, int key) override;
void OnMouseLeave() override;
private:
bool IsCurrent = false;
ImageBox* Icon = nullptr;
TextLabel* Label = nullptr;
bool mouseDown = false;
bool hot = false;
};
class TabWidgetStack : public Widget
{
public:
TabWidgetStack(Widget* parent);
void SetCurrentWidget(Widget* widget);
Widget* GetCurrentWidget() const { return CurrentWidget; }
protected:
void OnPaintFrame(Canvas* canvas) override;
void OnGeometryChanged() override;
private:
Widget* CurrentWidget = nullptr;
};

View file

@ -21,6 +21,7 @@ public:
void SetTextAlignment(TextLabelAlignment alignment);
TextLabelAlignment GetTextAlignment() const;
double GetPreferredWidth() const;
double GetPreferredHeight() const;
protected:

View file

@ -427,9 +427,9 @@ Widget* Widget::Window()
return nullptr;
}
Canvas* Widget::GetCanvas()
Canvas* Widget::GetCanvas() const
{
for (Widget* w = this; w != nullptr; w = w->Parent())
for (const Widget* w = this; w != nullptr; w = w->Parent())
{
if (w->DispCanvas)
return w->DispCanvas.get();

View file

@ -0,0 +1,308 @@
#include "widgets/tabwidget/tabwidget.h"
#include "widgets/textlabel/textlabel.h"
#include "widgets/imagebox/imagebox.h"
#include <algorithm>
TabWidget::TabWidget(Widget* parent) : Widget(parent)
{
Bar = new TabBar(this);
PageStack = new TabWidgetStack(this);
Bar->OnCurrentChanged = [=]() { OnBarCurrentChanged(); };
}
int TabWidget::AddTab(Widget* page, const std::string& label)
{
return AddTab(page, nullptr, label);
}
int TabWidget::AddTab(Widget* page, const std::shared_ptr<Image>& icon, const std::string& label)
{
int pageIndex = Bar->AddTab(label);
page->SetParent(PageStack);
page->SetVisible(false);
Pages.push_back(page);
if (Pages.size() == 1)
{
PageStack->SetCurrentWidget(page);
}
return pageIndex;
}
int TabWidget::GetCurrentIndex() const
{
return Bar->GetCurrentIndex();
}
Widget* TabWidget::GetCurrentWidget() const
{
return Pages[Bar->GetCurrentIndex()];
}
void TabWidget::SetCurrentIndex(int pageIndex)
{
if (Bar->GetCurrentIndex() != pageIndex)
{
Bar->SetCurrentIndex(pageIndex);
PageStack->SetCurrentWidget(Pages[pageIndex]);
}
}
void TabWidget::SetCurrentWidget(Widget* pageWidget)
{
int pageIndex = GetPageIndex(pageWidget);
if (pageIndex != -1)
SetCurrentIndex(pageIndex);
}
int TabWidget::GetPageIndex(Widget* pageWidget) const
{
for (size_t i = 0; i < Pages.size(); i++)
{
if (Pages[i] == pageWidget)
return i;
}
return -1;
}
void TabWidget::OnBarCurrentChanged()
{
int pageIndex = Bar->GetCurrentIndex();
PageStack->SetCurrentWidget(Pages[pageIndex]);
if (OnCurrentChanged)
OnCurrentChanged();
}
void TabWidget::OnPaintFrame(Canvas* canvas)
{
}
void TabWidget::OnGeometryChanged()
{
double w = GetWidth();
double h = GetHeight();
double barHeight = Bar->GetPreferredHeight();
Bar->SetFrameGeometry(Rect::xywh(0.0, 0.0, w, barHeight));
PageStack->SetFrameGeometry(Rect::xywh(0.0, barHeight, w, std::max(h - barHeight, 0.0)));
}
/////////////////////////////////////////////////////////////////////////////
TabBar::TabBar(Widget* parent) : Widget(parent)
{
}
int TabBar::AddTab(const std::string& label)
{
return AddTab(nullptr, label);
}
int TabBar::AddTab(const std::shared_ptr<Image>& icon, const std::string& label)
{
TabBarTab* tab = new TabBarTab(this);
tab->SetIcon(icon);
tab->SetText(label);
int pageIndex = Tabs.size();
Tabs.push_back(tab);
if (CurrentIndex == -1)
SetCurrentIndex(pageIndex);
OnGeometryChanged();
return pageIndex;
}
int TabBar::GetCurrentIndex() const
{
return CurrentIndex;
}
void TabBar::SetCurrentIndex(int pageIndex)
{
if (CurrentIndex != pageIndex)
{
if (CurrentIndex != -1)
Tabs[CurrentIndex]->SetCurrent(false);
CurrentIndex = pageIndex;
if (CurrentIndex != -1)
Tabs[CurrentIndex]->SetCurrent(true);
}
}
void TabBar::OnTabClicked(TabBarTab* tab)
{
int pageIndex = GetTabIndex(tab);
if (CurrentIndex != pageIndex)
{
SetCurrentIndex(pageIndex);
if (OnCurrentChanged)
OnCurrentChanged();
}
}
int TabBar::GetTabIndex(TabBarTab* tab)
{
for (size_t i = 0; i < Tabs.size(); i++)
{
if (Tabs[i] == tab)
return i;
}
return -1;
}
void TabBar::OnPaintFrame(Canvas* canvas)
{
}
void TabBar::OnGeometryChanged()
{
double w = GetWidth();
double h = GetHeight();
double x = 0.0;
for (TabBarTab* tab : Tabs)
{
double tabWidth = tab->GetPreferredWidth();
tab->SetFrameGeometry(Rect::xywh(x, 0.0, tabWidth, h));
x += tabWidth;
}
}
/////////////////////////////////////////////////////////////////////////////
TabBarTab::TabBarTab(Widget* parent) : Widget(parent)
{
}
void TabBarTab::SetText(const std::string& text)
{
if (!text.empty())
{
if (!Label)
{
Label = new TextLabel(this);
OnGeometryChanged();
}
Label->SetText(text);
}
else
{
delete Label;
Label = nullptr;
OnGeometryChanged();
}
}
void TabBarTab::SetIcon(const std::shared_ptr<Image>& image)
{
if (image)
{
if (!Icon)
{
Icon = new ImageBox(this);
OnGeometryChanged();
}
Icon->SetImage(image);
}
else
{
delete Icon;
Icon = nullptr;
OnGeometryChanged();
}
}
void TabBarTab::SetCurrent(bool value)
{
if (IsCurrent != value)
{
IsCurrent = value;
Update();
}
}
double TabBarTab::GetPreferredWidth() const
{
double x = Icon ? 32.0 + 5.0 : 0.0;
if (Label) x += Label->GetPreferredWidth();
return x;
}
void TabBarTab::OnPaintFrame(Canvas* canvas)
{
}
void TabBarTab::OnGeometryChanged()
{
double x = 0.0;
double w = GetWidth();
double h = GetHeight();
if (Icon)
{
Icon->SetFrameGeometry(Rect::xywh(x, (h - 32.0) * 0.5, 32.0, 32.0));
x = 32.0 + 5.0;
}
if (Label)
{
Label->SetFrameGeometry(Rect::xywh(x, 0.0, std::max(w - x, 0.0), h));
}
}
void TabBarTab::OnMouseMove(const Point& pos)
{
if (!hot)
{
hot = true;
Update();
}
}
void TabBarTab::OnMouseDown(const Point& pos, int key)
{
mouseDown = true;
Update();
}
void TabBarTab::OnMouseUp(const Point& pos, int key)
{
if (mouseDown)
{
mouseDown = false;
Repaint();
if (OnClick)
OnClick();
}
}
void TabBarTab::OnMouseLeave()
{
hot = false;
mouseDown = false;
Update();
}
/////////////////////////////////////////////////////////////////////////////
TabWidgetStack::TabWidgetStack(Widget* parent) : Widget(parent)
{
}
void TabWidgetStack::SetCurrentWidget(Widget* widget)
{
if (widget != CurrentWidget)
{
if (CurrentWidget)
CurrentWidget->SetVisible(false);
CurrentWidget = widget;
if (CurrentWidget)
CurrentWidget->SetVisible(true);
}
}
void TabWidgetStack::OnPaintFrame(Canvas* canvas)
{
}
void TabWidgetStack::OnGeometryChanged()
{
if (CurrentWidget)
CurrentWidget->SetFrameGeometry(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()));
}

View file

@ -33,6 +33,12 @@ TextLabelAlignment TextLabel::GetTextAlignment() const
return textAlignment;
}
double TextLabel::GetPreferredWidth() const
{
Canvas* canvas = GetCanvas();
return canvas->measureText(text).width;
}
double TextLabel::GetPreferredHeight() const
{
return 20.0;