From 31c8dde32f41abd2a31de43fa0da22adf3a8e7d7 Mon Sep 17 00:00:00 2001 From: Marisa the Magician Date: Wed, 13 Aug 2025 14:11:06 +0200 Subject: [PATCH] Squashed 'libraries/ZWidget/' changes from 96501b6ef..cecd34301 cecd34301 Delay load DPI-related functions bfc172f70 Added exception to some unimplemented functions 7c8d469bc Added item modifications functions 847d0e6d9 Improved keyboard navigation 8c4270e0f Dropdown can now be closed by re-selecting it 91402c31e Fix dropdown positioning in x11 and win32 02da8cdc0 Added ListView::GetPreferredWidth, ListView::GetPreferredHeight, ListView::GetMinHeight 04bf153d9 Added event subscriber system 1ce55e5da Added Dropdown Known issues: does not hide when parent widget's visibility changes git-subtree-dir: libraries/ZWidget git-subtree-split: cecd34301d369400227eb0f27bad2ab41ad756d2 --- CMakeLists.txt | 2 + example/example.cpp | 53 ++- include/zwidget/core/widget.h | 20 + include/zwidget/widgets/dropdown/dropdown.h | 69 ++++ include/zwidget/widgets/listview/listview.h | 7 + src/core/theme.cpp | 26 ++ src/core/widget.cpp | 31 +- src/widgets/dropdown/dropdown.cpp | 354 ++++++++++++++++++ src/widgets/listview/listview.cpp | 38 +- .../wayland/wayland_display_backend.cpp | 3 +- src/window/win32/win32_display_window.cpp | 55 ++- src/window/x11/x11_display_window.cpp | 3 +- 12 files changed, 648 insertions(+), 13 deletions(-) create mode 100644 include/zwidget/widgets/dropdown/dropdown.h create mode 100644 src/widgets/dropdown/dropdown.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 894da17e2..d2fdc8cf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ set(ZWIDGET_SOURCES src/widgets/textlabel/textlabel.cpp src/widgets/pushbutton/pushbutton.cpp src/widgets/checkboxlabel/checkboxlabel.cpp + src/widgets/dropdown/dropdown.cpp src/widgets/listview/listview.cpp src/widgets/tabwidget/tabwidget.cpp src/window/window.cpp @@ -164,6 +165,7 @@ source_group("src\\widgets\\imagebox" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE source_group("src\\widgets\\textlabel" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/textlabel/.+") 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\\dropdown" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/widgets/dropdown/.+") 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/.+") diff --git a/example/example.cpp b/example/example.cpp index ec4eed91b..cd2b1b740 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -1,8 +1,10 @@ +#include #include #include #include #include #include +#include #include #include #include @@ -34,6 +36,7 @@ public: PlayButton = new PushButton(this); ExitButton = new PushButton(this); GamesList = new ListView(this); + Choices = new Dropdown(this); SetWindowBackground(Colorf::fromRgba8(51, 51, 51)); SetWindowBorderColor(Colorf::fromRgba8(51, 51, 51)); @@ -60,6 +63,18 @@ public: BrightmapsCheckbox->SetText("Brightmaps"); WidescreenCheckbox->SetText("Widescreen"); + Choices->SetMaxDisplayItems(2); + Choices->AddItem("First"); + Choices->AddItem("Second"); + Choices->AddItem("Third"); + Choices->AddItem("Fourth"); + Choices->AddItem("Fifth"); + Choices->AddItem("Sixth"); + + Choices->OnChanged = [this](int index) { + std::cout << "Selected " << index << ":" << Choices->GetItem(index) << std::endl; + }; + try { auto filedata = ReadAllBytes("banner.png"); @@ -93,9 +108,11 @@ public: y += 10.0; - SelectLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, SelectLabel->GetPreferredHeight()); + SelectLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0 - Choices->GetPreferredWidth(), SelectLabel->GetPreferredHeight()); y += SelectLabel->GetPreferredHeight(); + Choices->SetFrameGeometry(GetWidth() - 20.0 - Choices->GetPreferredWidth(), y-Choices->GetPreferredHeight(), Choices->GetPreferredWidth(), Choices->GetPreferredHeight()); + double listViewTop = y + 10.0; y = GetHeight() - 15.0 - PlayButton->GetPreferredHeight(); @@ -140,6 +157,7 @@ public: PushButton* PlayButton = nullptr; PushButton* ExitButton = nullptr; ListView* GamesList = nullptr; + Dropdown* Choices = nullptr; }; static std::vector ReadAllBytes(const std::string& filename); @@ -156,13 +174,23 @@ std::vector LoadWidgetData(const std::string& name) return ReadAllBytes(name); } -int example() +enum class Backend { + Default, Win32, SDL2, X11, Wayland +}; + +int example(Backend backend = Backend::Default) { WidgetTheme::SetTheme(std::make_unique()); - // DisplayBackend::Set(DisplayBackend::TryCreateWin32()); - // DisplayBackend::Set(DisplayBackend::TryCreateSDL2()); - DisplayBackend::Set(DisplayBackend::TryCreateBackend()); + // just for testing backends + switch (backend) + { + case Backend::Default: DisplayBackend::Set(DisplayBackend::TryCreateBackend()); break; + case Backend::Win32: DisplayBackend::Set(DisplayBackend::TryCreateWin32()); break; + case Backend::SDL2: DisplayBackend::Set(DisplayBackend::TryCreateSDL2()); break; + case Backend::X11: DisplayBackend::Set(DisplayBackend::TryCreateX11()); break; + case Backend::Wayland: DisplayBackend::Set(DisplayBackend::TryCreateWayland()); break; + } #if 1 auto launcher = new LauncherWindow(); @@ -275,9 +303,20 @@ static std::vector ReadAllBytes(const std::string& filename) return buffer; } -int main() +int main(int argc, const char** argv) { - example(); + std::string backendStr = argc > 1? argv[1]: ""; + std::transform(backendStr.begin(), backendStr.end(), backendStr.begin(), + [](unsigned char c){ return std::tolower(c); }); + + Backend backend = Backend::Default; + + if (backendStr == "sdl2") backend = Backend::SDL2; + if (backendStr == "x11") backend = Backend::X11; + if (backendStr == "wayland") backend = Backend::Wayland; + if (backendStr == "win32") backend = Backend::Win32; // lol + + example(backend); } #endif diff --git a/include/zwidget/core/widget.h b/include/zwidget/core/widget.h index 6d87f72a0..be41697cd 100644 --- a/include/zwidget/core/widget.h +++ b/include/zwidget/core/widget.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "canvas.h" #include "rect.h" #include "colorf.h" @@ -11,6 +12,7 @@ class Canvas; class Timer; +class Dropdown; enum class WidgetType { @@ -19,6 +21,13 @@ enum class WidgetType Popup }; +enum class WidgetEvent +{ + VisibilityChange, + + // TODO: add more events +}; + class Widget : DisplayWindowHost { public: @@ -93,6 +102,9 @@ public: void ShowNormal(); void Hide(); + void Subscribe(Widget* subscriber); + void Unsubscribe(Widget* subscriber); + void ActivateWindow(); void Close(); @@ -178,6 +190,8 @@ protected: virtual void OnLostFocus() { } virtual void OnEnableChanged() { } + virtual void Notify(Widget* source, const WidgetEvent type) { }; + private: void DetachFromParent(); @@ -201,6 +215,8 @@ private: void OnWindowDeactivated() override; void OnWindowDpiScaleChanged() override; + void NotifySubscribers(const WidgetEvent type); + WidgetType Type = {}; Widget* ParentObj = nullptr; @@ -234,8 +250,12 @@ private: Widget(const Widget&) = delete; Widget& operator=(const Widget&) = delete; + std::unordered_set Subscribers; + std::unordered_set Subscriptions; + friend class Timer; friend class OpenFileDialog; friend class OpenFolderDialog; friend class SaveFileDialog; + friend class Dropdown; }; diff --git a/include/zwidget/widgets/dropdown/dropdown.h b/include/zwidget/widgets/dropdown/dropdown.h new file mode 100644 index 000000000..fd8a6f403 --- /dev/null +++ b/include/zwidget/widgets/dropdown/dropdown.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include "../../core/widget.h" +#include "../../widgets/listview/listview.h" + +class DropdownList : public ListView +{ +public: + DropdownList(Widget* parent, Dropdown* owner); +protected: + void OnKeyDown(InputKey key) override; + Dropdown* owner; +}; + +class Dropdown : public Widget +{ +public: + Dropdown(Widget* parent); + + void AddItem(const std::string& text, int index = -1); + bool UpdateItem(const std::string& text, int index); + bool RemoveItem(int index = -1); + void ClearItems(); + const std::string& GetItem(int index); + + void SetMaxDisplayItems(int items); + void SetDropdownDirection(bool below); + + int GetSelectedItem() const { return selectedItem; } + void SetSelectedItem(int index); + + double GetPreferredHeight() const; + double GetPreferredWidth() const; + + std::function OnChanged; + +protected: + void OnPaint(Canvas* canvas) override; + bool OnMouseDown(const Point& pos, InputKey key) override; + void OnKeyDown(InputKey key) override; + void OnGeometryChanged() override; + void OnLostFocus() override; + void Notify(Widget* source, const WidgetEvent type) override; + +private: + void ItemsChanged(); + bool OpenDropdown(); + bool CloseDropdown(); + void OnDropdownActivated(); + + size_t GetDisplayItems(); + + std::vector items; + int selectedItem = -1; + std::string text; + + bool dropdownOpen = false; + Widget* dropdown = nullptr; + DropdownList* listView = nullptr; + + int maxDisplayItems = 0; + bool dropdownDirection = true; + + friend DropdownList; +}; diff --git a/include/zwidget/widgets/listview/listview.h b/include/zwidget/widgets/listview/listview.h index e1b08192a..4e5326413 100644 --- a/include/zwidget/widgets/listview/listview.h +++ b/include/zwidget/widgets/listview/listview.h @@ -6,6 +6,7 @@ #include class Scrollbar; +class Dropdown; class ListView : public Widget { @@ -22,6 +23,10 @@ public: void SetSelectedItem(int index); void ScrollToItem(int index); + double GetPreferredWidth(); + double GetPreferredHeight(); + double GetMinimumHeight() const; + void Activate(); std::function OnChanged; @@ -43,4 +48,6 @@ protected: std::vector> items; std::vector columnwidths; int selectedItem = 0; + + friend Dropdown; }; diff --git a/src/core/theme.cpp b/src/core/theme.cpp index cd553315c..88e482706 100644 --- a/src/core/theme.cpp +++ b/src/core/theme.cpp @@ -151,6 +151,7 @@ DarkWidgetTheme::DarkWidgetTheme() auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); auto listview = RegisterStyle(std::make_unique(widget), "listview"); + auto dropdown = RegisterStyle(std::make_unique(widget), "dropdown"); auto scrollbar = RegisterStyle(std::make_unique(widget), "scrollbar"); auto tabbar = RegisterStyle(std::make_unique(widget), "tabbar"); auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); @@ -217,6 +218,18 @@ DarkWidgetTheme::DarkWidgetTheme() listview->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); listview->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); + dropdown->SetDouble("noncontent-left", 5.0); + dropdown->SetDouble("noncontent-top", 5.0); + dropdown->SetDouble("noncontent-right", 5.0); + dropdown->SetDouble("noncontent-bottom", 5.0); + dropdown->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); + dropdown->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); + dropdown->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); + dropdown->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); + dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + dropdown->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); + dropdown->SetColor("arrow-color", Colorf::fromRgba8(100, 100, 100)); + scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33)); scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58)); @@ -290,6 +303,7 @@ LightWidgetTheme::LightWidgetTheme() auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); auto listview = RegisterStyle(std::make_unique(widget), "listview"); + auto dropdown = RegisterStyle(std::make_unique(widget), "dropdown"); auto scrollbar = RegisterStyle(std::make_unique(widget), "scrollbar"); auto tabbar = RegisterStyle(std::make_unique(widget), "tabbar"); auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); @@ -353,6 +367,18 @@ LightWidgetTheme::LightWidgetTheme() listview->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); listview->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200)); + dropdown->SetDouble("noncontent-left", 5.0); + dropdown->SetDouble("noncontent-top", 5.0); + dropdown->SetDouble("noncontent-right", 5.0); + dropdown->SetDouble("noncontent-bottom", 5.0); + dropdown->SetColor("background-color", Colorf::fromRgba8(230, 230, 230)); + dropdown->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); + dropdown->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); + dropdown->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); + dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); + dropdown->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200)); + dropdown->SetColor("arrow-color", Colorf::fromRgba8(200, 200, 200)); + scrollbar->SetColor("track-color", Colorf::fromRgba8(210, 210, 220)); scrollbar->SetColor("thumb-color", Colorf::fromRgba8(180, 180, 180)); diff --git a/src/core/widget.cpp b/src/core/widget.cpp index f431a6917..27f6f7b1c 100644 --- a/src/core/widget.cpp +++ b/src/core/widget.cpp @@ -1,4 +1,3 @@ - #include "core/widget.h" #include "core/timer.h" #include "core/colorf.h" @@ -34,6 +33,12 @@ Widget::Widget(Widget* parent, WidgetType type, RenderAPI renderAPI) : Type(type Widget::~Widget() { + for (auto subscription: Subscriptions) + subscription->Subscribers.erase(this); + + for (auto subscriber: Subscribers) + subscriber->Subscriptions.erase(this); + if (DispCanvas) DispCanvas->detach(); @@ -46,6 +51,24 @@ Widget::~Widget() DetachFromParent(); } +void Widget::Subscribe(Widget* subscriber) +{ + Subscribers.insert(subscriber); + subscriber->Subscriptions.insert(this); +} + +void Widget::Unsubscribe(Widget* subscriber) +{ + Subscribers.erase(subscriber); + subscriber->Subscriptions.erase(this); +} + +void Widget::NotifySubscribers(const WidgetEvent type) +{ + for (auto subscriber: Subscribers) + subscriber->Notify(this, type); +} + void Widget::SetCanvas(std::unique_ptr canvas) { if (DispWindow) @@ -206,10 +229,12 @@ void Widget::Show() if (Type != WidgetType::Child) { DispWindow->Show(); + NotifySubscribers(WidgetEvent::VisibilityChange); } else if (HiddenFlag) { HiddenFlag = false; + NotifySubscribers(WidgetEvent::VisibilityChange); Update(); } } @@ -260,11 +285,15 @@ void Widget::Hide() if (Type != WidgetType::Child) { if (DispWindow) + { DispWindow->Hide(); + NotifySubscribers(WidgetEvent::VisibilityChange); + } } else if (!HiddenFlag) { HiddenFlag = true; + NotifySubscribers(WidgetEvent::VisibilityChange); Update(); } } diff --git a/src/widgets/dropdown/dropdown.cpp b/src/widgets/dropdown/dropdown.cpp new file mode 100644 index 000000000..c40d0c02f --- /dev/null +++ b/src/widgets/dropdown/dropdown.cpp @@ -0,0 +1,354 @@ +#include + +#include "widgets/dropdown/dropdown.h" +#include "core/widget.h" + +Dropdown::Dropdown(Widget* parent) : Widget(parent) +{ + SetStyleClass("dropdown"); + SetFocus(); + + // Subscribe to all parent widgets to receive notifications. + for (Widget* p = Parent(); p; p = p->Parent()) + p->Subscribe(this); +} + +DropdownList::DropdownList(Widget* parent, Dropdown* owner) : ListView(parent), owner(owner) +{} + +void DropdownList::OnKeyDown(InputKey key) +{ + owner->OnKeyDown(key); +} + +void Dropdown::Notify(Widget* source, const WidgetEvent type) +{ + if (type != WidgetEvent::VisibilityChange) return; + if (!dropdownOpen) return; + + for (Widget* p = this; p != nullptr; p = p->Parent()) + { + if (p->IsHidden()) + { + CloseDropdown(); + return; + } + } +} + +void Dropdown::ItemsChanged() +{ + if (!CloseDropdown()) + { + Update(); + } +} + +void Dropdown::AddItem(const std::string& text, int index) +{ + if (index < 0) + { + items.push_back(text); + } + else + { + items.insert(items.begin() + index, text); + } + + if (selectedItem == -1 && !items.empty()) + SetSelectedItem(0); + + ItemsChanged(); +} + +bool Dropdown::UpdateItem(const std::string& text, int index) +{ + if (!items.size() || index >= (int)items.size()) + return false; + + if (index < 0) + index = static_cast(items.size()) - 1; + + items[index] = text; + + if (selectedItem == index) + { + this->text = text; + } + + ItemsChanged(); + + return true; +} + +const std::string & Dropdown::GetItem(int index) +{ + return items[index]; +} + +bool Dropdown::RemoveItem(int index) +{ + if (!items.size() || index >= (int)items.size()) + return false; + + if (index < 0) + index = static_cast(items.size()) - 1; + + items.erase(items.begin() + index); + + if ((int)items.size() == 0) + { + SetSelectedItem(-1); + } + else if (selectedItem == index) + { + SetSelectedItem(0); + } + else if (selectedItem > index) + { + SetSelectedItem(selectedItem - 1); + } + + ItemsChanged(); + + return true; +} + +void Dropdown::ClearItems() +{ + items.clear(); + SetSelectedItem(-1); + ItemsChanged(); +} + +void Dropdown::SetSelectedItem(int index) +{ + if (index < 0) index = 0; + if (index >= (int)items.size()) index = items.size() - 1; + + if (selectedItem == index) return; + + selectedItem = index; + text = (selectedItem != -1) ? items[selectedItem] : ""; + + if (OnChanged) OnChanged(selectedItem); + + if (dropdownOpen) listView->ScrollToItem(listView->selectedItem = selectedItem); + + Update(); +} + +double Dropdown::GetPreferredHeight() const +{ + return 20.0 + 2*5.0; // Text plus top/bottom padding +} + +double Dropdown::GetPreferredWidth() const +{ + Canvas* canvas = GetCanvas(); + + double maxWidth = 0.0; + for (const auto& item : items) + { + auto width = canvas->measureText(item).width; + if (width > maxWidth) + { + maxWidth = width; + } + } + + return maxWidth + 2*10.0 + 16.0; // content, pad l/r, scrollbar from dropdown +} + +void Dropdown::OnPaint(Canvas* canvas) +{ + Colorf textColor = GetStyleColor("color"); + Colorf arrowColor = GetStyleColor("arrow-color"); + + double w = GetWidth(); + double h = GetHeight(); + + auto vtp = canvas->verticalTextAlign(); + double textY = (h-(vtp.bottom-vtp.top))/2.0 + vtp.baseline; + canvas->drawText(Point(7.0, textY), textColor, text); + + double arrowS = 8.0; + double arrowX = w - 3.0; // rightmost point, aligned with scrollbar + double arrowY = (h-arrowS)/2; + + Point p1(arrowX, arrowY); + Point p2(arrowX - arrowS, arrowY); + Point p3(arrowX - arrowS/2, arrowY + arrowS); + + canvas->line(p1, p2, arrowColor); + canvas->line(p2, p3, arrowColor); + canvas->line(p3, p1, arrowColor); +} + +bool Dropdown::OnMouseDown(const Point& pos, InputKey key) +{ + if (key == InputKey::LeftMouse) + { + if (dropdownOpen) + { + CloseDropdown(); + SetFocus(); + return true; + } + else + { + SetFocus(); + OpenDropdown(); + return true; + } + } + return false; +} + +void Dropdown::OnKeyDown(InputKey key) +{ + switch (key) + { + case InputKey::Enter: + case InputKey::Space: + if (dropdownOpen) + CloseDropdown(); + else + OpenDropdown(); + break; + + case InputKey::Up: + SetSelectedItem(selectedItem - 1); + break; + + case InputKey::Down: + SetSelectedItem(selectedItem + 1); + break; + + case InputKey::Home: + SetSelectedItem(0); + break; + + case InputKey::End: + SetSelectedItem((int)items.size()); + break; + + case InputKey::PageUp: + SetSelectedItem(selectedItem - (maxDisplayItems? maxDisplayItems: (int)items.size())); + break; + + case InputKey::PageDown: + SetSelectedItem(selectedItem + (maxDisplayItems? maxDisplayItems: (int)items.size())); + break; + + default: + break; + } +} + +void Dropdown::SetMaxDisplayItems(int items) +{ + maxDisplayItems = std::max(0, items); +} + +size_t Dropdown::GetDisplayItems() +{ + return (maxDisplayItems < 1) + ? std::max(items.size(), 1ul) + : std::min(items.size(), (size_t)maxDisplayItems); +} + +void Dropdown::SetDropdownDirection(bool below) +{ + dropdownDirection = below; +} + +void Dropdown::OnGeometryChanged() +{ + if (dropdownOpen) + { + Point pos = MapTo(Window(), Point(0,0)); + + double width = GetWidth() + GetNoncontentLeft() + GetNoncontentRight(); + double innerH = GetDisplayItems() * 25.0 + 10.0; + double outerH = GetHeight(); + + pos.x -= GetNoncontentLeft(); + + // adjust to be above/below box + if (dropdownDirection) + pos.y += outerH + GetNoncontentBottom() - 1.0; + else + pos.y -= innerH + GetNoncontentTop() - 1.0; + + dropdown->SetFrameGeometry(pos.x, pos.y, width, innerH); + } +} + +void Dropdown::OnLostFocus() +{ + if (!dropdownOpen) return; + + // Check if the mouse is over the dropdown itself or one of its children + auto hover = Window()->HoverWidget; + if (hover && (hover == dropdown || dropdown->IsParent(hover))) return; + + CloseDropdown(); +} + +bool Dropdown::OpenDropdown() +{ + if (dropdownOpen || items.empty()) return false; + + dropdownOpen = true; + + dropdown = new Widget(Window()); + listView = new DropdownList(dropdown, this); + for (const auto& item : items) + { + listView->AddItem(item); + } + + listView->SetSelectedItem(selectedItem); + + listView->OnActivated = [=]() { OnDropdownActivated(); }; + listView->OnChanged = [=](int index) { OnDropdownActivated(); }; + + listView->SetFrameGeometry( + 0, + 0, + GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), + GetDisplayItems() * 25.0 + 10.0 + ); + OnGeometryChanged(); + + dropdown->Show(); + + listView->ScrollToItem(selectedItem); + + return true; +} + +bool Dropdown::CloseDropdown() +{ + if (!dropdownOpen || !dropdown) return false; + + dropdown->Close(); + dropdown = nullptr; + listView = nullptr; + dropdownOpen = false; + + Update(); + + return true; +} + +void Dropdown::OnDropdownActivated() +{ + if (listView) + { + SetSelectedItem(listView->GetSelectedItem()); + } + CloseDropdown(); + SetFocus(); +} diff --git a/src/widgets/listview/listview.cpp b/src/widgets/listview/listview.cpp index 1dd9fc9c1..1f771b3b7 100644 --- a/src/widgets/listview/listview.cpp +++ b/src/widgets/listview/listview.cpp @@ -1,4 +1,3 @@ - #include "widgets/listview/listview.h" #include "widgets/scrollbar/scrollbar.h" @@ -284,3 +283,40 @@ void ListView::OnKeyDown(InputKey key) } } } + +double ListView::GetPreferredWidth() +{ + double total = 0.0; + + for (double width : columnwidths) + { + if (width > 0.0) total += width; + } + + if (total == 0.0) + { + auto canvas = GetCanvas(); + + for (const auto& row : items) + { + double wRow = 0.0; + for (const auto& cell : row) + { + wRow += canvas->measureText(cell).width; + } + if (wRow > total) total = wRow; + } + } + + return total + 10.0*2 + scrollbar->GetPreferredWidth(); +} + +double ListView::GetPreferredHeight() +{ + return items.size()*20.0 + 10.0*2; // Items plus top/bottom padding +} + +double ListView::GetMinimumHeight() const +{ + return 20.0 + 10.0*2; // One item plus top/bottom padding +} diff --git a/src/window/wayland/wayland_display_backend.cpp b/src/window/wayland/wayland_display_backend.cpp index ffb394853..d99283248 100644 --- a/src/window/wayland/wayland_display_backend.cpp +++ b/src/window/wayland/wayland_display_backend.cpp @@ -607,11 +607,12 @@ Size WaylandDisplayBackend::GetScreenSize() void* WaylandDisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) { - return nullptr; + throw std::logic_error("unimplemented: WaylandDisplayBackend::StartTimer"); } void WaylandDisplayBackend::StopTimer(void* timerID) { + throw std::logic_error("unimplemented: WaylandDisplayBackend::StopTimer"); } #ifdef USE_DBUS diff --git a/src/window/win32/win32_display_window.cpp b/src/window/win32/win32_display_window.cpp index f45da1a14..30027e1cc 100644 --- a/src/window/win32/win32_display_window.cpp +++ b/src/window/win32/win32_display_window.cpp @@ -28,6 +28,57 @@ #define RIDEV_INPUTSINK (0x100) #endif +// Code for delay loading DPI related functions, needed for continued Windows 7 compatibility. +typedef BOOL (WINAPI *PFN_AdjustWindowRectExForDpi)( + LPRECT lpRect, + DWORD dwStyle, + BOOL bMenu, + DWORD dwExStyle, + UINT dpi +); + +typedef UINT (WINAPI *PFN_GetDpiForWindow)(HWND hwnd); + +static PFN_AdjustWindowRectExForDpi pAdjustWindowRectExForDpi = nullptr; +static PFN_GetDpiForWindow pGetDpiForWindow = nullptr; + +static void DPIDelayLoad() +{ + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); + if (hUser32) { + pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi) + GetProcAddress(hUser32, "AdjustWindowRectExForDpi"); + pGetDpiForWindow = (PFN_GetDpiForWindow) + GetProcAddress(hUser32, "GetDpiForWindow"); + } +} + +static BOOL DelayLoadAdjustWindowRectExForDpi( + LPRECT lpRect, + DWORD dwStyle, + BOOL bMenu, + DWORD dwExStyle, + HWND hwnd +) { + DPIDelayLoad(); + if (pAdjustWindowRectExForDpi) { + return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, + pGetDpiForWindow(hwnd)); + } else { + return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle); + } +} + +static double DelayLoadGetDpiScale(HWND hwnd) +{ + DPIDelayLoad(); + if (pGetDpiForWindow) { + return pGetDpiForWindow(hwnd) / 96.0; + } else { + return 1.0; + } +} + Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow) { Windows.push_front(this); @@ -113,7 +164,7 @@ void Win32DisplayWindow::SetClientFrame(const Rect& box) DWORD style = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_STYLE); DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle.hwnd, GWL_EXSTYLE); - AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle.hwnd)); + DelayLoadAdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, WindowHandle.hwnd); SetWindowPos(WindowHandle.hwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER); } @@ -291,7 +342,7 @@ int Win32DisplayWindow::GetPixelHeight() const double Win32DisplayWindow::GetDpiScale() const { - return GetDpiForWindow(WindowHandle.hwnd) / 96.0; + return DelayLoadGetDpiScale(WindowHandle.hwnd); } std::string Win32DisplayWindow::GetClipboardText() diff --git a/src/window/x11/x11_display_window.cpp b/src/window/x11/x11_display_window.cpp index 5797b0b4f..00887ff94 100644 --- a/src/window/x11/x11_display_window.cpp +++ b/src/window/x11/x11_display_window.cpp @@ -1084,11 +1084,12 @@ Size X11DisplayWindow::GetScreenSize() void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) { - return nullptr; + throw std::logic_error("unimplemented: X11DisplayWindow::StartTimer"); } void X11DisplayWindow::StopTimer(void* timerID) { + throw std::logic_error("unimplemented: X11DisplayWindow::StopTimer"); } // This is to avoid needing all the Vulkan headers and the volk binding library just for this: