diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index 42fea2e590..dd836cd6de 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -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/.+") diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index 69862fdb49..932a6b5ddc 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -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); diff --git a/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h b/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h new file mode 100644 index 0000000000..56bb345a8e --- /dev/null +++ b/libraries/ZWidget/include/zwidget/widgets/tabwidget/tabwidget.h @@ -0,0 +1,116 @@ + +#pragma once + +#include "../../core/widget.h" +#include +#include + +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& 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 OnCurrentChanged; + +protected: + void OnPaintFrame(Canvas* canvas) override; + void OnGeometryChanged() override; + +private: + void OnBarCurrentChanged(); + + TabBar* Bar = nullptr; + TabWidgetStack* PageStack = nullptr; + std::vector Pages; +}; + +class TabBar : public Widget +{ +public: + TabBar(Widget* parent); + + int AddTab(const std::string& label); + int AddTab(const std::shared_ptr& icon, const std::string& label); + + int GetCurrentIndex() const; + void SetCurrentIndex(int pageIndex); + + double GetPreferredHeight() const { return 30.0; } + + std::function OnCurrentChanged; + +protected: + void OnPaintFrame(Canvas* canvas) override; + void OnGeometryChanged() override; + +private: + void OnTabClicked(TabBarTab* tab); + int GetTabIndex(TabBarTab* tab); + + int CurrentIndex = -1; + std::vector Tabs; +}; + +class TabBarTab : public Widget +{ +public: + TabBarTab(Widget* parent); + + void SetText(const std::string& text); + void SetIcon(const std::shared_ptr& icon); + void SetCurrent(bool value); + + double GetPreferredWidth() const; + + std::function 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; +}; diff --git a/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h b/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h index 32f250c6cc..45515dfcb4 100644 --- a/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h +++ b/libraries/ZWidget/include/zwidget/widgets/textlabel/textlabel.h @@ -21,6 +21,7 @@ public: void SetTextAlignment(TextLabelAlignment alignment); TextLabelAlignment GetTextAlignment() const; + double GetPreferredWidth() const; double GetPreferredHeight() const; protected: diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index 9c6dc63190..f323855838 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -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(); diff --git a/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp new file mode 100644 index 0000000000..adb2e29178 --- /dev/null +++ b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp @@ -0,0 +1,308 @@ + +#include "widgets/tabwidget/tabwidget.h" +#include "widgets/textlabel/textlabel.h" +#include "widgets/imagebox/imagebox.h" +#include + +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& 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& 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) +{ + 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())); +} diff --git a/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp b/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp index 3c034ddb20..55518f0a44 100644 --- a/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp +++ b/libraries/ZWidget/src/widgets/textlabel/textlabel.cpp @@ -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;