diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index 43f9094b4..747edc8a4 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -41,10 +41,23 @@ public: // Widget noncontent area void SetNoncontentSizes(double left, double top, double right, double bottom); - double GetNoncontentLeft() const { return GetStyleDouble("noncontent-left"); } - double GetNoncontentTop() const { return GetStyleDouble("noncontent-top"); } - double GetNoncontentRight() const { return GetStyleDouble("noncontent-right"); } - double GetNoncontentBottom() const { return GetStyleDouble("noncontent-bottom"); } + double GetNoncontentLeft() const { return GridFitSize(GetStyleDouble("noncontent-left")); } + double GetNoncontentTop() const { return GridFitSize(GetStyleDouble("noncontent-top")); } + double GetNoncontentRight() const { return GridFitSize(GetStyleDouble("noncontent-right")); } + double GetNoncontentBottom() const { return GridFitSize(GetStyleDouble("noncontent-bottom")); } + + // Get the DPI scale factor for the window the widget is located on + double GetDpiScale() const; + + // Align point to the nearest physical screen pixel + double GridFitPoint(double p) const; + Point GridFitPoint(double x, double y) const { return GridFitPoint(Point(x, y)); } + Point GridFitPoint(Point p) const { return Point(GridFitPoint(p.x), GridFitPoint(p.y)); } + + // Convert size to exactly covering physical screen pixels + double GridFitSize(double s) const; + Size GridFitSize(double w, double h) const { return GridFitSize(Size(w, h)); } + Size GridFitSize(Size s) const { return Size(GridFitSize(s.width), GridFitSize(s.height)); } // Widget frame box Rect GetFrameGeometry() const; @@ -108,7 +121,7 @@ public: std::string GetClipboardText(); void SetClipboardText(const std::string& text); - Widget* Window(); + Widget* Window() const; 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/imagebox/imagebox.h b/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h index e3a724381..7d3aa520e 100644 --- a/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h +++ b/libraries/ZWidget/include/zwidget/widgets/imagebox/imagebox.h @@ -11,6 +11,7 @@ public: void SetImage(std::shared_ptr newImage); + double GetPreferredWidth() const; double GetPreferredHeight() const; protected: diff --git a/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h b/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h index 9c13283cc..d9f084393 100644 --- a/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h +++ b/libraries/ZWidget/include/zwidget/widgets/menubar/menubar.h @@ -2,6 +2,13 @@ #pragma once #include "../../core/widget.h" +#include "../textlabel/textlabel.h" +#include "../imagebox/imagebox.h" + +class Menu; +class MenubarItem; +class MenuItem; +class MenuItemSeparator; class Menubar : public Widget { @@ -9,6 +16,83 @@ public: Menubar(Widget* parent); ~Menubar(); + MenubarItem* AddItem(std::string text, std::function onOpen, bool alignRight = false); + +protected: + void OnGeometryChanged() override; + +private: + void ShowMenu(MenubarItem* item); + void CloseMenu(); + + std::vector menuItems; + Menu* openMenu = nullptr; + + friend class MenubarItem; +}; + +class MenubarItem : public Widget +{ +public: + MenubarItem(Menubar* menubar, std::string text, bool alignRight); + + void SetOpenCallback(std::function callback) { onOpen = std::move(callback); } + const std::function& GetOpenCallback() const { return onOpen; } + + double GetPreferredWidth() const; + + bool AlignRight = false; + protected: void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + bool OnMouseUp(const Point& pos, InputKey key) override; + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + +private: + Menubar* menubar = nullptr; + std::function onOpen; + std::string text; +}; + +class Menu : public Widget +{ +public: + Menu(Widget* parent); + + void SetLeftPosition(const Point& globalPos); + void SetRightPosition(const Point& globalPos); + MenuItem* AddItem(std::shared_ptr icon, std::string text, std::function onClick = {}); + MenuItemSeparator* AddSeparator(); + + double GetPreferredWidth() const; + double GetPreferredHeight() const; + +protected: + void OnGeometryChanged() override; + + std::function onCloseMenu; + + friend class Menubar; +}; + +class MenuItem : public Widget +{ +public: + MenuItem(Widget* parent); + + ImageBox* icon = nullptr; + TextLabel* text = nullptr; + +protected: + void OnMouseMove(const Point& pos) override; + void OnMouseLeave() override; + void OnGeometryChanged() override; +}; + +class MenuItemSeparator : public Widget +{ +public: + MenuItemSeparator(Widget* parent); }; diff --git a/libraries/ZWidget/include/zwidget/window/window.h b/libraries/ZWidget/include/zwidget/window/window.h index 8515f5004..538c7041d 100644 --- a/libraries/ZWidget/include/zwidget/window/window.h +++ b/libraries/ZWidget/include/zwidget/window/window.h @@ -118,7 +118,7 @@ public: class DisplayWindow { public: - static std::unique_ptr Create(DisplayWindowHost* windowHost); + static std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow); static void ProcessEvents(); static void RunLoop(); @@ -157,6 +157,9 @@ public: virtual int GetPixelHeight() const = 0; virtual double GetDpiScale() const = 0; + virtual Point MapFromGlobal(const Point& pos) const = 0; + virtual Point MapToGlobal(const Point& pos) const = 0; + virtual void SetBorderColor(uint32_t bgra8) = 0; virtual void SetCaptionColor(uint32_t bgra8) = 0; virtual void SetCaptionTextColor(uint32_t bgra8) = 0; diff --git a/libraries/ZWidget/src/core/canvas.cpp b/libraries/ZWidget/src/core/canvas.cpp index eab9f0eea..437c25f02 100644 --- a/libraries/ZWidget/src/core/canvas.cpp +++ b/libraries/ZWidget/src/core/canvas.cpp @@ -765,6 +765,95 @@ void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, floa if (x1 <= x0 || y1 <= y0) return; +#if 1 // Use gamma correction + + // To linear + float cred = color.r * color.r; // std::pow(color.r, 2.2f); + float cgreen = color.g * color.g; // std::pow(color.g, 2.2f); + float cblue = color.b * color.b; // std::pow(color.b, 2.2f); +#ifdef USE_SSE2 + __m128 crgba = _mm_set_ps(0.0f, cred, cgreen, cblue); +#endif + + float uscale = uvwidth / width; + float vscale = uvheight / height; + + for (int y = y0; y < y1; y++) + { + float vpix = v + vscale * (y + 0.5f - top); + const uint32_t* sline = src + ((int)vpix) * swidth; + uint32_t* dline = dest + y * dwidth; + + int x = x0; +#ifdef USE_SSE2 + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + __m128i spixel = _mm_cvtsi32_si128(sline[(int)upix]); + spixel = _mm_unpacklo_epi8(spixel, _mm_setzero_si128()); + spixel = _mm_unpacklo_epi16(spixel, _mm_setzero_si128()); + __m128 srgba = _mm_mul_ps(_mm_cvtepi32_ps(spixel), _mm_set_ps1(1.0f / 255.0f)); + + __m128i dpixel = _mm_cvtsi32_si128(dline[x]); + dpixel = _mm_unpacklo_epi8(dpixel, _mm_setzero_si128()); + dpixel = _mm_unpacklo_epi16(dpixel, _mm_setzero_si128()); + __m128 drgba = _mm_mul_ps(_mm_cvtepi32_ps(dpixel), _mm_set_ps1(1.0f / 255.0f)); + + // To linear + drgba = _mm_mul_ps(drgba, drgba); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + __m128 frgba = _mm_add_ps(_mm_mul_ps(crgba, srgba), _mm_mul_ps(drgba, _mm_sub_ps(_mm_set_ps1(1.0f), srgba))); + + // To srgb + frgba = _mm_sqrt_ps(frgba); + + __m128i rgba = _mm_cvtps_epi32(_mm_add_ps(_mm_mul_ps(frgba, _mm_set_ps1(255.0f)), _mm_set_ps1(0.5f))); + rgba = _mm_packs_epi32(rgba, _mm_setzero_si128()); + rgba = _mm_packus_epi16(rgba, _mm_setzero_si128()); + dline[x] = ((uint32_t)_mm_cvtsi128_si32(rgba)) | 0xff000000; + x++; + } +#else + while (x < x1) + { + float upix = u + uscale * (x + 0.5f - left); + uint32_t spixel = sline[(int)upix]; + float sred = ((spixel >> 16) & 0xff) * (1.0f / 255.0f); + float sgreen = ((spixel >> 8) & 0xff) * (1.0f / 255.0f); + float sblue = (spixel & 0xff) * (1.0f / 255.0f); + + uint32_t dpixel = dline[x]; + float dred = ((dpixel >> 16) & 0xff) * (1.0f / 255.0f); + float dgreen = ((dpixel >> 8) & 0xff) * (1.0f / 255.0f); + float dblue = (dpixel & 0xff) * (1.0f / 255.0f); + + // To linear + dred = dred * dred; // std::pow(dred, 2.2f); + dgreen = dgreen * dgreen; // std::pow(dgreen, 2.2f); + dblue = dblue * dblue; // std::pow(dblue, 2.2f); + + // dest.rgb = color.rgb * src.rgb + dest.rgb * (1-src.rgb) + double fr = cred * sred + dred * (1.0f - sred); + double fg = cgreen * sgreen + dgreen * (1.0f - sgreen); + double fb = cblue * sblue + dblue * (1.0f - sblue); + + // To srgb + fr = std::sqrt(fr); // std::pow(fr, 1.0f / 2.2f); + fg = std::sqrt(fg); // std::pow(fg, 1.0f / 2.2f); + fb = std::sqrt(fb); // std::pow(fb, 1.0f / 2.2f); + + uint32_t r = (int)(fr * 255.0f + 0.5f); + uint32_t g = (int)(fg * 255.0f + 0.5f); + uint32_t b = (int)(fb * 255.0f + 0.5f); + dline[x] = 0xff000000 | (r << 16) | (g << 8) | b; + x++; + } +#endif + } + +#else + uint32_t cred = (int32_t)clamp(color.r * 255.0f, 0.0f, 255.0f); uint32_t cgreen = (int32_t)clamp(color.g * 255.0f, 0.0f, 255.0f); uint32_t cblue = (int32_t)clamp(color.b * 255.0f, 0.0f, 255.0f); @@ -832,6 +921,7 @@ void BitmapCanvas::drawGlyph(CanvasTexture* texture, float left, float top, floa x++; } } +#endif } void BitmapCanvas::begin(const Colorf& color) diff --git a/libraries/ZWidget/src/core/theme.cpp b/libraries/ZWidget/src/core/theme.cpp index f30bffdf2..b92839989 100644 --- a/libraries/ZWidget/src/core/theme.cpp +++ b/libraries/ZWidget/src/core/theme.cpp @@ -102,14 +102,16 @@ void BasicWidgetStyle::Paint(Widget* widget, Canvas* canvas, Size size) Colorf borderright = widget->GetStyleColor("border-right-color"); Colorf borderbottom = widget->GetStyleColor("border-bottom-color"); + double borderwidth = widget->GridFitSize(1.0); + if (bordertop.a > 0.0f) - canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, 1.0), bordertop); + canvas->fillRect(Rect::xywh(0.0, 0.0, size.width, borderwidth), bordertop); if (borderbottom.a > 0.0f) - canvas->fillRect(Rect::xywh(0.0, size.height - 1.0, size.width, 1.0), borderbottom); + canvas->fillRect(Rect::xywh(0.0, size.height - borderwidth, size.width, borderwidth), borderbottom); if (borderleft.a > 0.0f) - canvas->fillRect(Rect::xywh(0.0, 0.0, 1.0, size.height - 0.0), borderleft); + canvas->fillRect(Rect::xywh(0.0, 0.0, borderwidth, size.height), borderleft); if (borderright.a > 0.0f) - canvas->fillRect(Rect::xywh(size.width - 1.0, 0.0, 1.0, size.height - 0.0), borderright); + canvas->fillRect(Rect::xywh(size.width - borderwidth, 0.0, borderwidth, size.height), borderright); } ///////////////////////////////////////////////////////////////////////////// @@ -154,6 +156,9 @@ DarkWidgetTheme::DarkWidgetTheme() auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); + auto menu = RegisterStyle(std::make_unique(widget), "menu"); + auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); widget->SetString("font-family", "NotoSans"); widget->SetColor("color", Colorf::fromRgba8(226, 223, 219)); @@ -229,6 +234,21 @@ DarkWidgetTheme::DarkWidgetTheme() checkbox_label->SetColor("checked-color", Colorf::fromRgba8(226, 223, 219)); checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99)); checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); + + menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + + menu->SetDouble("noncontent-left", 5.0); + menu->SetDouble("noncontent-top", 5.0); + menu->SetDouble("noncontent-right", 5.0); + menu->SetDouble("noncontent-bottom", 5.0); + menu->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + + menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); + menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); } ///////////////////////////////////////////////////////////////////////////// @@ -246,6 +266,9 @@ LightWidgetTheme::LightWidgetTheme() auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); + auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); + auto menu = RegisterStyle(std::make_unique(widget), "menu"); + auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); widget->SetString("font-family", "NotoSans"); widget->SetColor("color", Colorf::fromRgba8(0, 0, 0)); @@ -321,4 +344,19 @@ LightWidgetTheme::LightWidgetTheme() checkbox_label->SetColor("checked-color", Colorf::fromRgba8(50, 50, 50)); checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(156, 156, 156)); checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(200, 200, 200)); + + menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); + + menu->SetDouble("noncontent-left", 5.0); + menu->SetDouble("noncontent-top", 5.0); + menu->SetDouble("noncontent-right", 5.0); + menu->SetDouble("noncontent-bottom", 5.0); + menu->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + menu->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + menu->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + menu->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + + menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); + menuitem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); } diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index 299857cfc..fe18d9376 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -4,12 +4,14 @@ #include "core/colorf.h" #include "core/theme.h" #include +#include +#include Widget::Widget(Widget* parent, WidgetType type) : Type(type) { if (type != WidgetType::Child) { - DispWindow = DisplayWindow::Create(this); + DispWindow = DisplayWindow::Create(this, type == WidgetType::Popup); DispCanvas = Canvas::create(DispWindow.get()); SetStyleState("root"); @@ -167,6 +169,10 @@ void Widget::SetFrameGeometry(const Rect& geometry) top = std::min(top, FrameGeometry.bottom()); right = std::max(right, FrameGeometry.left()); bottom = std::max(bottom, FrameGeometry.top()); + left = GridFitPoint(left); + top = GridFitPoint(top); + right = GridFitPoint(right); + bottom = GridFitPoint(bottom); ContentGeometry = Rect::ltrb(left, top, right, bottom); OnGeometryChanged(); } @@ -458,12 +464,12 @@ void Widget::SetClipboardText(const std::string& text) w->DispWindow->SetClipboardText(text); } -Widget* Widget::Window() +Widget* Widget::Window() const { - for (Widget* w = this; w != nullptr; w = w->Parent()) + for (const Widget* w = this; w != nullptr; w = w->Parent()) { if (w->DispWindow) - return w; + return const_cast(w); } return nullptr; } @@ -510,7 +516,7 @@ Point Widget::MapFromGlobal(const Point& pos) const { if (cur->DispWindow) { - return p - cur->GetFrameGeometry().topLeft(); + return cur->DispWindow->MapFromGlobal(p); } p -= cur->ContentGeometry.topLeft(); } @@ -536,7 +542,7 @@ Point Widget::MapToGlobal(const Point& pos) const { if (cur->DispWindow) { - return cur->GetFrameGeometry().topLeft() + p; + return cur->DispWindow->MapToGlobal(p); } p += cur->ContentGeometry.topLeft(); } @@ -705,9 +711,21 @@ void Widget::OnWindowKeyUp(InputKey key) void Widget::OnWindowGeometryChanged() { + if (!DispWindow) + return; Size size = DispWindow->GetClientSize(); FrameGeometry = Rect::xywh(0.0, 0.0, size.width, size.height); - ContentGeometry = FrameGeometry; + + double left = FrameGeometry.left() + GetNoncontentLeft(); + double top = FrameGeometry.top() + GetNoncontentTop(); + double right = FrameGeometry.right() - GetNoncontentRight(); + double bottom = FrameGeometry.bottom() - GetNoncontentBottom(); + left = std::min(left, FrameGeometry.right()); + top = std::min(top, FrameGeometry.bottom()); + right = std::max(right, FrameGeometry.left()); + bottom = std::max(bottom, FrameGeometry.top()); + ContentGeometry = Rect::ltrb(left, top, right, bottom); + OnGeometryChanged(); } @@ -728,6 +746,26 @@ void Widget::OnWindowDpiScaleChanged() { } +double Widget::GetDpiScale() const +{ + Widget* w = Window(); + return w ? w->DispWindow->GetDpiScale() : 1.0; +} + +double Widget::GridFitPoint(double p) const +{ + double dpiscale = GetDpiScale(); + return std::round(p * dpiscale) / dpiscale; +} + +double Widget::GridFitSize(double s) const +{ + if (s <= 0.0) + return 0.0; + double dpiscale = GetDpiScale(); + return std::max(std::floor(s * dpiscale + 0.25), 1.0) / dpiscale; +} + Size Widget::GetScreenSize() { return DisplayWindow::GetScreenSize(); diff --git a/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp b/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp index f78998890..77c1bd645 100644 --- a/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp +++ b/libraries/ZWidget/src/widgets/checkboxlabel/checkboxlabel.cpp @@ -41,16 +41,24 @@ double CheckboxLabel::GetPreferredHeight() const void CheckboxLabel::OnPaint(Canvas* canvas) { + // To do: add and use GetStyleImage for the checkbox + + double center = GridFitPoint(GetHeight() * 0.5); + double borderwidth = GridFitSize(1.0); + double outerboxsize = GridFitSize(10.0); + double innerboxsize = outerboxsize - 2.0 * borderwidth; + double checkedsize = innerboxsize - 2.0 * borderwidth; + if (checked) { - canvas->fillRect(Rect::xywh(0.0, GetHeight() * 0.5 - 6.0, 10.0, 10.0), GetStyleColor("checked-outer-border-color")); - canvas->fillRect(Rect::xywh(1.0, GetHeight() * 0.5 - 5.0, 8.0, 8.0), GetStyleColor("checked-inner-border-color")); - canvas->fillRect(Rect::xywh(2.0, GetHeight() * 0.5 - 4.0, 6.0, 6.0), GetStyleColor("checked-color")); + canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("checked-outer-border-color")); + canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("checked-inner-border-color")); + canvas->fillRect(Rect::xywh(2.0 * borderwidth, center - 4.0 * borderwidth, checkedsize, checkedsize), GetStyleColor("checked-color")); } else { - canvas->fillRect(Rect::xywh(0.0, GetHeight() * 0.5 - 6.0, 10.0, 10.0), GetStyleColor("unchecked-outer-border-color")); - canvas->fillRect(Rect::xywh(1.0, GetHeight() * 0.5 - 5.0, 8.0, 8.0), GetStyleColor("unchecked-inner-border-color")); + canvas->fillRect(Rect::xywh(0.0, center - 6.0 * borderwidth, outerboxsize, outerboxsize), GetStyleColor("unchecked-outer-border-color")); + canvas->fillRect(Rect::xywh(1.0 * borderwidth, center - 5.0 * borderwidth, innerboxsize, innerboxsize), GetStyleColor("unchecked-inner-border-color")); } canvas->drawText(Point(14.0, GetHeight() - 5.0), GetStyleColor("color"), text); diff --git a/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp b/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp index 45d35a160..37253c857 100644 --- a/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp +++ b/libraries/ZWidget/src/widgets/imagebox/imagebox.cpp @@ -5,6 +5,14 @@ ImageBox::ImageBox(Widget* parent) : Widget(parent) { } +double ImageBox::GetPreferredWidth() const +{ + if (image) + return (double)image->GetWidth(); + else + return 0.0; +} + double ImageBox::GetPreferredHeight() const { if (image) diff --git a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp index 2905604af..38b928f95 100644 --- a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp +++ b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp @@ -1,4 +1,4 @@ -#include + #include "widgets/lineedit/lineedit.h" #include "core/utf8reader.h" #include "core/colorf.h" diff --git a/libraries/ZWidget/src/widgets/menubar/menubar.cpp b/libraries/ZWidget/src/widgets/menubar/menubar.cpp index 8a66ce8f3..517128508 100644 --- a/libraries/ZWidget/src/widgets/menubar/menubar.cpp +++ b/libraries/ZWidget/src/widgets/menubar/menubar.cpp @@ -4,13 +4,214 @@ Menubar::Menubar(Widget* parent) : Widget(parent) { + SetStyleClass("menubar"); } Menubar::~Menubar() { } -void Menubar::OnPaint(Canvas* canvas) +MenubarItem* Menubar::AddItem(std::string text, std::function onOpen, bool alignRight) { - canvas->drawText(Point(16.0, 21.0), Colorf::fromRgba8(0, 0, 0), "File Edit View Tools Window Help"); + auto item = new MenubarItem(this, text, alignRight); + item->SetOpenCallback(std::move(onOpen)); + menuItems.push_back(item); + OnGeometryChanged(); + return item; +} + +void Menubar::ShowMenu(MenubarItem* item) +{ + CloseMenu(); + if (item->GetOpenCallback()) + { + openMenu = new Menu(this); + openMenu->onCloseMenu = [=]() { CloseMenu(); }; + item->GetOpenCallback()(openMenu); + if (item->AlignRight) + openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight()))); + else + openMenu->SetLeftPosition(item->MapToGlobal(Point(0.0, item->GetHeight()))); + openMenu->Show(); + } +} + +void Menubar::CloseMenu() +{ + //delete openMenu; + //openMenu = nullptr; +} + +void Menubar::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double left = 0.0; + double right = w; + for (MenubarItem* item : menuItems) + { + double itemwidth = item->GetPreferredWidth(); + itemwidth += 16.0; + if (!item->AlignRight) + { + item->SetFrameGeometry(left, 0.0, itemwidth, h); + left += itemwidth; + } + else + { + right -= itemwidth; + item->SetFrameGeometry(right, 0.0, itemwidth, h); + } + } +} + +///////////////////////////////////////////////////////////////////////////// + +MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight) +{ + SetStyleClass("menubaritem"); +} + +bool MenubarItem::OnMouseDown(const Point& pos, InputKey key) +{ + menubar->ShowMenu(this); + return true; +} + +bool MenubarItem::OnMouseUp(const Point& pos, InputKey key) +{ + return true; +} + +void MenubarItem::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void MenubarItem::OnMouseLeave() +{ + SetStyleState(""); +} + +double MenubarItem::GetPreferredWidth() const +{ + Canvas* canvas = GetCanvas(); + return canvas->measureText(text).width; +} + +void MenubarItem::OnPaint(Canvas* canvas) +{ + double x = (GetWidth() - canvas->measureText(text).width) * 0.5; + canvas->drawText(Point(x, 21.0), GetStyleColor("color"), text); +} + +///////////////////////////////////////////////////////////////////////////// + +Menu::Menu(Widget* parent) : Widget(parent, WidgetType::Popup) +{ + SetStyleClass("menu"); +} + +void Menu::SetLeftPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x, pos.y, GetPreferredWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetPreferredHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +void Menu::SetRightPosition(const Point& pos) +{ + SetFrameGeometry(Rect::xywh(pos.x - GetWidth() - GetNoncontentLeft() - GetNoncontentRight(), pos.y, GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), GetHeight() + GetNoncontentTop() + GetNoncontentBottom())); +} + +MenuItem* Menu::AddItem(std::shared_ptr icon, std::string text, std::function onClick) +{ + auto item = new MenuItem(this); + if (icon) + item->icon->SetImage(icon); + item->text->SetText(text); + /* + item->element->addEventListener("click", [=](Event* event) + { + event->stopPropagation(); + if (onCloseMenu) + onCloseMenu(); + if (onClick) + onClick(); + }); + */ + return item; +} + +MenuItemSeparator* Menu::AddSeparator() +{ + auto sep = new MenuItemSeparator(this); + return sep; +} + +double Menu::GetPreferredWidth() const +{ + return 200.0; +} + +double Menu::GetPreferredHeight() const +{ + double h = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + h += 20.0; + } + return h; +} + +void Menu::OnGeometryChanged() +{ + double w = GetWidth(); + double h = GetHeight(); + double y = 0.0; + for (Widget* item = FirstChild(); item != nullptr; item = item->NextSibling()) + { + item->SetFrameGeometry(Rect::xywh(0.0, y, w, 20.0)); + y += 20.0; + } +} + +///////////////////////////////////////////////////////////////////////////// + +MenuItem::MenuItem(Widget* parent) : Widget(parent) +{ + SetStyleClass("menuitem"); + icon = new ImageBox(this); + text = new TextLabel(this); +} + +void MenuItem::OnMouseMove(const Point& pos) +{ + if (GetStyleState().empty()) + { + SetStyleState("hover"); + } +} + +void MenuItem::OnMouseLeave() +{ + SetStyleState(""); +} + +void MenuItem::OnGeometryChanged() +{ + double iconwidth = icon->GetPreferredWidth(); + double iconheight = icon->GetPreferredHeight(); + double w = GetWidth(); + double h = GetHeight(); + icon->SetFrameGeometry(Rect::xywh(0.0, (h - iconheight) * 0.5, iconwidth, iconheight)); + text->SetFrameGeometry(Rect::xywh(iconwidth, 0.0, w - iconwidth, h)); +} + +///////////////////////////////////////////////////////////////////////////// + +MenuItemSeparator::MenuItemSeparator(Widget* parent) : Widget(parent) +{ + SetStyleClass("menuitemseparator"); } diff --git a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp index f80fe86c0..5e44cb6f1 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp +++ b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.cpp @@ -24,11 +24,14 @@ static void CheckInitSDL() static InitSDL initsdl; } -SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost) : WindowHost(windowHost) +SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow) : WindowHost(windowHost) { CheckInitSDL(); - int result = SDL_CreateWindowAndRenderer(320, 200, SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/, &WindowHandle, &RendererHandle); + unsigned int flags = SDL_WINDOW_HIDDEN /*| SDL_WINDOW_ALLOW_HIGHDPI*/; + if (popupWindow) + flags |= SDL_WINDOW_BORDERLESS; + int result = SDL_CreateWindowAndRenderer(320, 200, flags, &WindowHandle, &RendererHandle); if (result != 0) throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); @@ -174,6 +177,24 @@ Rect SDL2DisplayWindow::GetWindowFrame() const return Rect::xywh(x / uiscale, y / uiscale, w / uiscale, h / uiscale); } +Point SDL2DisplayWindow::MapFromGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(WindowHandle, &x, &y); + return Point(pos.x - x / uiscale, pos.y - y / uiscale); +} + +Point SDL2DisplayWindow::MapToGlobal(const Point& pos) const +{ + int x = 0; + int y = 0; + double uiscale = GetDpiScale(); + SDL_GetWindowPosition(WindowHandle, &x, &y); + return Point(pos.x + x / uiscale, pos.y + y / uiscale); +} + Size SDL2DisplayWindow::GetClientSize() const { int w = 0; diff --git a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h index ca0a61f0e..7f54544bb 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h +++ b/libraries/ZWidget/src/window/sdl2/sdl2displaywindow.h @@ -8,7 +8,7 @@ class SDL2DisplayWindow : public DisplayWindow { public: - SDL2DisplayWindow(DisplayWindowHost* windowHost); + SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow); ~SDL2DisplayWindow(); void SetWindowTitle(const std::string& text) override; @@ -45,6 +45,9 @@ public: std::string GetClipboardText() override; void SetClipboardText(const std::string& text) override; + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + void* GetNativeHandle() override { return WindowHandle; } static void DispatchEvent(const SDL_Event& event); diff --git a/libraries/ZWidget/src/window/win32/win32displaywindow.cpp b/libraries/ZWidget/src/window/win32/win32displaywindow.cpp index 2bf0acff5..6d4be07ff 100644 --- a/libraries/ZWidget/src/window/win32/win32displaywindow.cpp +++ b/libraries/ZWidget/src/window/win32/win32displaywindow.cpp @@ -56,7 +56,7 @@ static std::wstring to_utf16(const std::string& str) return result; } -Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost) : WindowHost(windowHost) +Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow) : WindowHost(windowHost) { Windows.push_front(this); WindowsIterator = Windows.begin(); @@ -74,7 +74,18 @@ Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost) : WindowHo // WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!) // WS_SYSMENU shows the min/max/close buttons // WS_THICKFRAME makes the window resizable - CreateWindowEx(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, L"ZWidgetWindow", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this); + + DWORD style = 0, exstyle = 0; + if (popupWindow) + { + style = WS_POPUP; + } + else + { + exstyle = WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME; + style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; + } + CreateWindowEx(exstyle, L"ZWidgetWindow", L"", style, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this); /* RAWINPUTDEVICE rid; @@ -246,6 +257,26 @@ Rect Win32DisplayWindow::GetWindowFrame() const return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale); } +Point Win32DisplayWindow::MapFromGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x / dpiscale); + point.y = (LONG)std::round(pos.y / dpiscale); + ScreenToClient(WindowHandle, &point); + return Point(point.x * dpiscale, point.y * dpiscale); +} + +Point Win32DisplayWindow::MapToGlobal(const Point& pos) const +{ + double dpiscale = GetDpiScale(); + POINT point = {}; + point.x = (LONG)std::round(pos.x * dpiscale); + point.y = (LONG)std::round(pos.y * dpiscale); + ClientToScreen(WindowHandle, &point); + return Point(point.x / dpiscale, point.y / dpiscale); +} + Size Win32DisplayWindow::GetClientSize() const { RECT box = {}; diff --git a/libraries/ZWidget/src/window/win32/win32displaywindow.h b/libraries/ZWidget/src/window/win32/win32displaywindow.h index 8aa00e42a..7bb481868 100644 --- a/libraries/ZWidget/src/window/win32/win32displaywindow.h +++ b/libraries/ZWidget/src/window/win32/win32displaywindow.h @@ -14,7 +14,7 @@ class Win32DisplayWindow : public DisplayWindow { public: - Win32DisplayWindow(DisplayWindowHost* windowHost); + Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow); ~Win32DisplayWindow(); void SetWindowTitle(const std::string& text) override; @@ -53,6 +53,9 @@ public: std::string GetClipboardText() override; void SetClipboardText(const std::string& text) override; + Point MapFromGlobal(const Point& pos) const override; + Point MapToGlobal(const Point& pos) const override; + Point GetLParamPos(LPARAM lparam) const; void* GetNativeHandle() override { return reinterpret_cast(WindowHandle); } diff --git a/libraries/ZWidget/src/window/window.cpp b/libraries/ZWidget/src/window/window.cpp index 4ceb49469..fa7aaac21 100644 --- a/libraries/ZWidget/src/window/window.cpp +++ b/libraries/ZWidget/src/window/window.cpp @@ -6,9 +6,9 @@ #include "win32/win32displaywindow.h" -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow) { - return std::make_unique(windowHost); + return std::make_unique(windowHost, popupWindow); } void DisplayWindow::ProcessEvents() @@ -43,7 +43,7 @@ void DisplayWindow::StopTimer(void* timerID) #elif defined(__APPLE__) -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow) { throw std::runtime_error("DisplayWindow::Create not implemented"); } @@ -82,9 +82,9 @@ void DisplayWindow::StopTimer(void* timerID) #include "sdl2/sdl2displaywindow.h" -std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost) +std::unique_ptr DisplayWindow::Create(DisplayWindowHost* windowHost, bool popupWindow) { - return std::make_unique(windowHost); + return std::make_unique(windowHost, popupWindow); } void DisplayWindow::ProcessEvents()