This commit is contained in:
Rachael Alexanderson 2025-08-13 17:17:30 -04:00
commit b7d9ddb6a3
No known key found for this signature in database
GPG key ID: 26A8ACCE97115EE0
8 changed files with 252 additions and 56 deletions

View file

@ -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$<$<CONFIG:Debug>: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$<$<CONFIG:Debug>:Debug>")
endif()
endif()

View file

@ -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
{

View file

@ -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;
};

View file

@ -6,6 +6,7 @@
#include <functional>
class Scrollbar;
class Dropdown;
class ListView : public Widget
{
@ -47,4 +48,6 @@ protected:
std::vector<std::vector<std::string>> items;
std::vector<double> columnwidths;
int selectedItem = 0;
friend Dropdown;
};

View file

@ -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<int>(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<int>(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<int>(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()

View file

@ -607,11 +607,12 @@ Size WaylandDisplayBackend::GetScreenSize()
void* WaylandDisplayBackend::StartTimer(int timeoutMilliseconds, std::function<void()> 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

View file

@ -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()

View file

@ -1084,11 +1084,12 @@ Size X11DisplayWindow::GetScreenSize()
void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> 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: