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
This commit is contained in:
parent
d9b2c00228
commit
31c8dde32f
12 changed files with 648 additions and 13 deletions
|
|
@ -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/.+")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include <iostream>
|
||||
#include <zwidget/core/widget.h>
|
||||
#include <zwidget/core/resourcedata.h>
|
||||
#include <zwidget/core/image.h>
|
||||
#include <zwidget/core/theme.h>
|
||||
#include <zwidget/window/window.h>
|
||||
#include <zwidget/widgets/dropdown/dropdown.h>
|
||||
#include <zwidget/widgets/textedit/textedit.h>
|
||||
#include <zwidget/widgets/mainwindow/mainwindow.h>
|
||||
#include <zwidget/widgets/listview/listview.h>
|
||||
|
|
@ -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<uint8_t> ReadAllBytes(const std::string& filename);
|
||||
|
|
@ -156,13 +174,23 @@ std::vector<uint8_t> 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<DarkWidgetTheme>());
|
||||
|
||||
// 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<uint8_t> 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
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <memory>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#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<Widget*> Subscribers;
|
||||
std::unordered_set<Widget*> Subscriptions;
|
||||
|
||||
friend class Timer;
|
||||
friend class OpenFileDialog;
|
||||
friend class OpenFolderDialog;
|
||||
friend class SaveFileDialog;
|
||||
friend class Dropdown;
|
||||
};
|
||||
|
|
|
|||
69
include/zwidget/widgets/dropdown/dropdown.h
Normal file
69
include/zwidget/widgets/dropdown/dropdown.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#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<void(int)> 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<std::string> items;
|
||||
int selectedItem = -1;
|
||||
std::string text;
|
||||
|
||||
bool dropdownOpen = false;
|
||||
Widget* dropdown = nullptr;
|
||||
DropdownList* listView = nullptr;
|
||||
|
||||
int maxDisplayItems = 0;
|
||||
bool dropdownDirection = true;
|
||||
|
||||
friend DropdownList;
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
#include <functional>
|
||||
|
||||
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<void(int)> OnChanged;
|
||||
|
|
@ -43,4 +48,6 @@ protected:
|
|||
std::vector<std::vector<std::string>> items;
|
||||
std::vector<double> columnwidths;
|
||||
int selectedItem = 0;
|
||||
|
||||
friend Dropdown;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ DarkWidgetTheme::DarkWidgetTheme()
|
|||
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
|
||||
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
|
||||
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
|
||||
auto dropdown = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "dropdown");
|
||||
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
|
||||
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
|
||||
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(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<BasicWidgetStyle>(widget), "lineedit");
|
||||
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
|
||||
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
|
||||
auto dropdown = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "dropdown");
|
||||
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
|
||||
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
|
||||
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
354
src/widgets/dropdown/dropdown.cpp
Normal file
354
src/widgets/dropdown/dropdown.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
#include <algorithm>
|
||||
|
||||
#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<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);
|
||||
|
||||
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<int>(0, items);
|
||||
}
|
||||
|
||||
size_t Dropdown::GetDisplayItems()
|
||||
{
|
||||
return (maxDisplayItems < 1)
|
||||
? std::max<size_t>(items.size(), 1ul)
|
||||
: std::min<size_t>(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();
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue