diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index f67f52f4c..d2fdc8cf5 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -244,6 +244,12 @@ else() endif() endif() +if(MSVC) + set(CXX_WARNING_FLAGS /W3) +else() + set(CXX_WARNING_FLAGS -Wall -Wpedantic) +endif() + add_library(zwidget STATIC ${ZWIDGET_SOURCES} ${ZWIDGET_INCLUDES}) target_compile_options(zwidget PRIVATE ${ZWIDGET_COMPILE_OPTIONS}) target_compile_definitions(zwidget PRIVATE ${ZWIDGET_DEFINES}) @@ -251,7 +257,51 @@ target_include_directories(zwidget PRIVATE ${ZWIDGET_INCLUDE_DIRS}) target_link_options(zwidget PRIVATE ${ZWIDGET_LINK_OPTIONS}) target_link_libraries(zwidget ${ZWIDGET_LIBS}) set_target_properties(zwidget PROPERTIES CXX_STANDARD 17) +target_compile_options(zwidget PRIVATE ${CXX_WARNING_FLAGS}) if(MSVC) set_property(TARGET zwidget PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + +option(ZWIDGET_BUILD_EXAMPLE "Build the zwidget example application" ON) + +if(ZWIDGET_BUILD_EXAMPLE) + if (UNIX AND NOT APPLE) + find_package(SDL2 REQUIRED) + endif() + + add_executable(zwidget_example WIN32 + example/example.cpp + example/picopng.cpp + example/picopng.h + ) + target_compile_options(zwidget_example PRIVATE ${CXX_WARNING_FLAGS}) + + add_custom_command( + TARGET zwidget_example POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/example/banner.png" + "${CMAKE_CURRENT_BINARY_DIR}/banner.png" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/example/OpenSans.ttf" + "${CMAKE_CURRENT_BINARY_DIR}/OpenSans.ttf" + ) + + target_include_directories(zwidget_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/example) + target_link_libraries(zwidget_example PRIVATE zwidget) + + if(WIN32) + target_compile_definitions(zwidget_example PRIVATE UNICODE _UNICODE) + target_link_libraries(zwidget_example PRIVATE gdi32 user32 shell32 comdlg32) + elseif(APPLE) + target_link_libraries(zwidget_example PRIVATE "-framework Cocoa -framework OpenGL") + else() + target_link_libraries(zwidget_example PRIVATE ${ZWIDGET_LIBS} SDL2::SDL2) + endif() + + set_target_properties(zwidget_example PROPERTIES CXX_STANDARD 17) + + if(MSVC) + set_property(TARGET zwidget_example PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endif() diff --git a/libraries/ZWidget/example/example.cpp b/libraries/ZWidget/example/example.cpp index c14505548..cd2b1b740 100644 --- a/libraries/ZWidget/example/example.cpp +++ b/libraries/ZWidget/example/example.cpp @@ -63,9 +63,17 @@ 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 { diff --git a/libraries/ZWidget/include/zwidget/widgets/dropdown/dropdown.h b/libraries/ZWidget/include/zwidget/widgets/dropdown/dropdown.h index 9091e16d4..fd8a6f403 100644 --- a/libraries/ZWidget/include/zwidget/widgets/dropdown/dropdown.h +++ b/libraries/ZWidget/include/zwidget/widgets/dropdown/dropdown.h @@ -7,14 +7,25 @@ #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); - void RemoveItem(int index); + 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); @@ -22,9 +33,6 @@ public: int GetSelectedItem() const { return selectedItem; } void SetSelectedItem(int index); - std::string GetSelectedText() const; - std::string GetText() const { return text; } - double GetPreferredHeight() const; double GetPreferredWidth() const; @@ -39,8 +47,9 @@ protected: void Notify(Widget* source, const WidgetEvent type) override; private: - void OpenDropdown(); - void CloseDropdown(); + void ItemsChanged(); + bool OpenDropdown(); + bool CloseDropdown(); void OnDropdownActivated(); size_t GetDisplayItems(); @@ -51,8 +60,10 @@ private: bool dropdownOpen = false; Widget* dropdown = nullptr; - ListView* listView = nullptr; + DropdownList* listView = nullptr; int maxDisplayItems = 0; bool dropdownDirection = true; + + friend DropdownList; }; diff --git a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h index 4253eae1f..4e5326413 100644 --- a/libraries/ZWidget/include/zwidget/widgets/listview/listview.h +++ b/libraries/ZWidget/include/zwidget/widgets/listview/listview.h @@ -6,6 +6,7 @@ #include class Scrollbar; +class Dropdown; class ListView : public Widget { @@ -47,4 +48,6 @@ protected: std::vector> items; std::vector columnwidths; int selectedItem = 0; + + friend Dropdown; }; diff --git a/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp b/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp index 074be859b..c40d0c02f 100644 --- a/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp +++ b/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp @@ -13,6 +13,14 @@ Dropdown::Dropdown(Widget* parent) : Widget(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; @@ -28,20 +36,63 @@ void Dropdown::Notify(Widget* source, const WidgetEvent type) } } -void Dropdown::AddItem(const std::string& text) +void Dropdown::ItemsChanged() { - items.push_back(text); - if (selectedItem == -1 && !items.empty()) + if (!CloseDropdown()) { - SetSelectedItem(0); + Update(); } - Update(); } -void Dropdown::RemoveItem(int index) +void Dropdown::AddItem(const std::string& text, int index) { - if (index < 0 || (size_t)index >= items.size()) - return; + 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); @@ -57,38 +108,34 @@ void Dropdown::RemoveItem(int index) { SetSelectedItem(selectedItem - 1); } - Update(); + + ItemsChanged(); + + return true; } void Dropdown::ClearItems() { items.clear(); SetSelectedItem(-1); - Update(); + ItemsChanged(); } void Dropdown::SetSelectedItem(int index) { - if (index >= (int)items.size()) - index = (int)items.size() - 1; + if (index < 0) index = 0; + if (index >= (int)items.size()) index = items.size() - 1; - if (selectedItem != index) - { - selectedItem = index; - text = (selectedItem != -1) ? items[selectedItem] : ""; + if (selectedItem == index) return; - if (OnChanged) - OnChanged(selectedItem); + selectedItem = index; + text = (selectedItem != -1) ? items[selectedItem] : ""; - Update(); - } -} + if (OnChanged) OnChanged(selectedItem); -std::string Dropdown::GetSelectedText() const -{ - if (selectedItem >= 0 && (size_t)selectedItem < items.size()) - return items[selectedItem]; - return {}; + if (dropdownOpen) listView->ScrollToItem(listView->selectedItem = selectedItem); + + Update(); } double Dropdown::GetPreferredHeight() const @@ -144,7 +191,7 @@ bool Dropdown::OnMouseDown(const Point& pos, InputKey key) { if (dropdownOpen) { - // this set focus call will close the popup + CloseDropdown(); SetFocus(); return true; } @@ -160,28 +207,48 @@ bool Dropdown::OnMouseDown(const Point& pos, InputKey key) void Dropdown::OnKeyDown(InputKey key) { - if (key == InputKey::Down) - { - if (selectedItem + 1 < (int)items.size()) - SetSelectedItem(selectedItem + 1); - } - else if (key == InputKey::Up) - { - if (selectedItem > 0) - SetSelectedItem(selectedItem - 1); - } - else if (key == InputKey::Enter || key == InputKey::Space) + 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 = items; + maxDisplayItems = std::max(0, items); } size_t Dropdown::GetDisplayItems() @@ -200,7 +267,7 @@ void Dropdown::OnGeometryChanged() { if (dropdownOpen) { - Point pos = MapToGlobal(Point(0.0, 0.0)); + Point pos = MapTo(Window(), Point(0,0)); double width = GetWidth() + GetNoncontentLeft() + GetNoncontentRight(); double innerH = GetDisplayItems() * 25.0 + 10.0; @@ -229,14 +296,14 @@ void Dropdown::OnLostFocus() CloseDropdown(); } -void Dropdown::OpenDropdown() +bool Dropdown::OpenDropdown() { - if (dropdownOpen || items.empty()) return; + if (dropdownOpen || items.empty()) return false; dropdownOpen = true; dropdown = new Widget(Window()); - listView = new ListView(dropdown); + listView = new DropdownList(dropdown, this); for (const auto& item : items) { listView->AddItem(item); @@ -258,11 +325,13 @@ void Dropdown::OpenDropdown() dropdown->Show(); listView->ScrollToItem(selectedItem); + + return true; } -void Dropdown::CloseDropdown() +bool Dropdown::CloseDropdown() { - if (!dropdownOpen || !dropdown) return; + if (!dropdownOpen || !dropdown) return false; dropdown->Close(); dropdown = nullptr; @@ -270,6 +339,8 @@ void Dropdown::CloseDropdown() dropdownOpen = false; Update(); + + return true; } void Dropdown::OnDropdownActivated() diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp b/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp index ffb394853..d99283248 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp +++ b/libraries/ZWidget/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/libraries/ZWidget/src/window/win32/win32_display_window.cpp b/libraries/ZWidget/src/window/win32/win32_display_window.cpp index f45da1a14..30027e1cc 100644 --- a/libraries/ZWidget/src/window/win32/win32_display_window.cpp +++ b/libraries/ZWidget/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/libraries/ZWidget/src/window/x11/x11_display_window.cpp b/libraries/ZWidget/src/window/x11/x11_display_window.cpp index 5797b0b4f..00887ff94 100644 --- a/libraries/ZWidget/src/window/x11/x11_display_window.cpp +++ b/libraries/ZWidget/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: