refactor(settings): migrate widget add popup to DialogPopupHost and remove duplicate PopupWindow plumbing

This commit is contained in:
Ly-sec
2026-05-07 13:31:01 +02:00
parent dc6331519e
commit 0eb6f5e79b
10 changed files with 225 additions and 505 deletions
-1
View File
@@ -551,7 +551,6 @@ _noctalia_sources = files(
'src/ui/controls/label.cpp',
'src/ui/controls/list_editor.cpp',
'src/ui/controls/progress_bar.cpp',
'src/ui/controls/popup_window.cpp',
'src/ui/controls/radio_button.cpp',
'src/ui/controls/scroll_view.cpp',
'src/ui/controls/search_picker.cpp',
+1 -6
View File
@@ -30,7 +30,6 @@
#include "system/distro_info.h"
#include "time/time_format.h"
#include "ui/controls/input.h"
#include "ui/controls/popup_window.h"
#include "ui/dialogs/color_picker_dialog.h"
#include "ui/dialogs/file_dialog.h"
#include "ui/dialogs/glyph_picker_dialog.h"
@@ -809,11 +808,7 @@ void Application::initUi() {
m_fileDialogPopup.onKeyboardEvent(event);
return;
}
if (PopupWindow::dispatchKeyboardEvent(m_wayland.lastKeyboardSurface(), event)) {
return;
}
if (m_settingsWindow.isOpen() && m_settingsWindow.wlSurface() != nullptr &&
m_wayland.lastKeyboardSurface() == m_settingsWindow.wlSurface()) {
if (m_settingsWindow.ownsKeyboardSurface(m_wayland.lastKeyboardSurface())) {
m_settingsWindow.onKeyboardEvent(event);
return;
}
+14 -3
View File
@@ -199,6 +199,16 @@ float SettingsWindow::uiScale() const {
return std::max(0.1f, m_config->config().shell.uiScale);
}
bool SettingsWindow::ownsKeyboardSurface(wl_surface* surface) const noexcept {
if (!isOpen() || surface == nullptr || m_surface == nullptr) {
return false;
}
if (surface == m_surface->wlSurface()) {
return true;
}
return m_widgetAddPopup != nullptr && m_widgetAddPopup->wlSurface() == surface;
}
void SettingsWindow::open() {
if (m_wayland == nullptr || m_renderContext == nullptr || !m_wayland->hasXdgShell()) {
return;
@@ -483,7 +493,8 @@ void SettingsWindow::openBarWidgetAddPopup(const std::vector<std::string>& laneP
}
if (m_widgetAddPopup == nullptr) {
m_widgetAddPopup = std::make_unique<settings::WidgetAddPopup>(*m_wayland, *m_renderContext);
m_widgetAddPopup = std::make_unique<settings::WidgetAddPopup>();
m_widgetAddPopup->initialize(*m_wayland, *m_config, *m_renderContext);
m_widgetAddPopup->setOnSelect([this](const std::vector<std::string>& selectedLanePath, const std::string& value) {
if (value.empty() || m_config == nullptr) {
return;
@@ -516,8 +527,8 @@ void SettingsWindow::openBarWidgetAddPopup(const std::vector<std::string>& laneP
output = m_output;
}
m_widgetAddPopup->open(m_surface->xdgSurface(), output, m_wayland->lastInputSerial(), anchorButton, lanePath,
m_config->config(), uiScale(), PopupWindow::AnchorMode::CenterOnAnchor);
m_widgetAddPopup->open(m_surface->xdgSurface(), output, m_wayland->lastInputSerial(), anchorButton,
m_surface->wlSurface(), lanePath, m_config->config(), uiScale());
}
void SettingsWindow::saveSupportReport() {
+1
View File
@@ -41,6 +41,7 @@ public:
[[nodiscard]] wl_surface* wlSurface() const noexcept {
return m_surface != nullptr ? m_surface->wlSurface() : nullptr;
}
[[nodiscard]] bool ownsKeyboardSurface(wl_surface* surface) const noexcept;
[[nodiscard]] bool onPointerEvent(const PointerEvent& event);
void onKeyboardEvent(const KeyboardEvent& event);
+116 -94
View File
@@ -1,7 +1,10 @@
#include "shell/settings/widget_add_popup.h"
#include "config/config_service.h"
#include "core/deferred_call.h"
#include "i18n/i18n.h"
#include "render/render_context.h"
#include "render/scene/input_area.h"
#include "render/scene/node.h"
#include "shell/settings/widget_settings_registry.h"
#include "ui/controls/button.h"
@@ -10,9 +13,10 @@
#include "ui/palette.h"
#include "ui/style.h"
#include "wayland/wayland_connection.h"
#include "wayland/wayland_seat.h"
#include "xdg-shell-client-protocol.h"
#include <algorithm>
#include <cmath>
#include <string>
#include <string_view>
#include <utility>
@@ -44,31 +48,51 @@ namespace settings {
return label;
}
PopupSurfaceConfig makePopupConfig(std::int32_t anchorX, std::int32_t anchorY, std::int32_t anchorWidth,
std::int32_t anchorHeight, std::uint32_t width, std::uint32_t height,
std::uint32_t serial, std::int32_t offsetX = 0, std::int32_t offsetY = 0,
bool grab = true) {
PopupSurfaceConfig cfg{
.anchorX = anchorX,
.anchorY = anchorY,
.anchorWidth = std::max(1, anchorWidth),
.anchorHeight = std::max(1, anchorHeight),
.width = std::max<std::uint32_t>(1, width),
.height = std::max<std::uint32_t>(1, height),
.anchor = XDG_POSITIONER_ANCHOR_BOTTOM,
.gravity = XDG_POSITIONER_GRAVITY_BOTTOM,
.constraintAdjustment =
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y |
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y,
.offsetX = offsetX,
.offsetY = offsetY,
.serial = serial,
.grab = grab,
};
cfg.anchorX += cfg.anchorWidth / 2;
cfg.anchorY += cfg.anchorHeight / 2;
cfg.anchorWidth = 1;
cfg.anchorHeight = 1;
return cfg;
}
} // namespace
WidgetAddPopup::WidgetAddPopup(WaylandConnection& wayland, RenderContext& renderContext)
: m_wayland(wayland), m_renderContext(renderContext),
m_popup(std::make_unique<PopupWindow>(wayland, renderContext)) {
m_popup->setOnDismissed([this]() {
m_lanePath.clear();
m_searchPicker = nullptr;
m_focusSearchOnOpen = false;
if (m_onDismissed) {
m_onDismissed();
}
});
}
WidgetAddPopup::~WidgetAddPopup() { destroyPopup(); }
WidgetAddPopup::~WidgetAddPopup() = default;
void WidgetAddPopup::initialize(WaylandConnection& wayland, ConfigService& config, RenderContext& renderContext) {
initializeBase(wayland, config, renderContext);
}
void WidgetAddPopup::setOnSelect(SelectCallback callback) { m_onSelect = std::move(callback); }
void WidgetAddPopup::setOnDismissed(std::function<void()> callback) { m_onDismissed = std::move(callback); }
void WidgetAddPopup::open(xdg_surface* parentXdgSurface, wl_output* output, std::uint32_t serial,
Button* anchorButton, const std::vector<std::string>& lanePath, const Config& config,
float scale, PopupWindow::AnchorMode anchorMode) {
if (m_popup == nullptr || parentXdgSurface == nullptr || anchorButton == nullptr) {
Button* anchorButton, wl_surface* parentWlSurface, const std::vector<std::string>& lanePath,
const Config& config, float scale) {
if (parentXdgSurface == nullptr || parentWlSurface == nullptr || anchorButton == nullptr) {
return;
}
@@ -103,15 +127,51 @@ namespace settings {
return;
}
if (isOpen()) {
close();
}
m_scale = std::max(0.1f, scale);
m_options = std::move(options);
m_lanePath = lanePath;
m_searchPicker = nullptr;
m_root = nullptr;
const float panelPadding = Style::spaceSm * scale;
const float panelGap = Style::spaceSm * scale;
const float panelWidth = 520.0f * scale;
const float panelHeight = 420.0f * scale;
const float panelWidth = 520.0f * m_scale;
const float panelHeight = 420.0f * m_scale;
float anchorAbsX = 0.0f;
float anchorAbsY = 0.0f;
Node::absolutePosition(anchorButton, anchorAbsX, anchorAbsY);
const auto cfg = makePopupConfig(static_cast<std::int32_t>(anchorAbsX), static_cast<std::int32_t>(anchorAbsY),
std::max(1, static_cast<std::int32_t>(anchorButton->width())),
std::max(1, static_cast<std::int32_t>(anchorButton->height())),
static_cast<std::uint32_t>(std::max(1.0f, panelWidth)),
static_cast<std::uint32_t>(std::max(1.0f, panelHeight)), serial, 0,
static_cast<std::int32_t>(Style::spaceXs * m_scale), true);
(void)openPopupAsChild(cfg, parentXdgSurface, parentWlSurface, output);
}
void WidgetAddPopup::close() { destroyPopup(); }
bool WidgetAddPopup::isOpen() const noexcept { return DialogPopupHost::isOpen(); }
bool WidgetAddPopup::onPointerEvent(const PointerEvent& event) { return DialogPopupHost::onPointerEvent(event); }
void WidgetAddPopup::onKeyboardEvent(const KeyboardEvent& event) { DialogPopupHost::onKeyboardEvent(event); }
wl_surface* WidgetAddPopup::wlSurface() const noexcept { return DialogPopupHost::wlSurface(); }
void WidgetAddPopup::requestLayout() { DialogPopupHost::requestLayout(); }
void WidgetAddPopup::requestRedraw() { DialogPopupHost::requestRedraw(); }
void WidgetAddPopup::populateContent(Node* contentParent, std::uint32_t /*width*/, std::uint32_t /*height*/) {
const float panelPadding = Style::spaceSm * m_scale;
const float panelGap = Style::spaceSm * m_scale;
m_popup->setContentBuilder([this, options, panelPadding, panelGap, scale](float width, float height) {
auto root = std::make_unique<Flex>();
root->setDirection(FlexDirection::Vertical);
root->setAlign(FlexAlign::Stretch);
@@ -120,15 +180,15 @@ namespace settings {
root->setFill(colorSpecFromRole(ColorRole::Surface));
root->setBorder(colorSpecFromRole(ColorRole::Outline, 0.35f), Style::borderWidth);
root->setRadius(Style::radiusLg);
root->setSize(width, height);
m_root = root.get();
auto header = std::make_unique<Flex>();
header->setDirection(FlexDirection::Horizontal);
header->setAlign(FlexAlign::Center);
header->setGap(Style::spaceSm * scale);
header->setGap(Style::spaceSm * m_scale);
header->addChild(makeLabel(i18n::tr("settings.entities.widget.inspector.add-title", "lane",
laneLabel(m_lanePath.empty() ? "" : m_lanePath.back())),
Style::fontSizeBody * scale, colorSpecFromRole(ColorRole::OnSurface), true));
Style::fontSizeBody * m_scale, colorSpecFromRole(ColorRole::OnSurface), true));
auto spacer = std::make_unique<Flex>();
spacer->setFlexGrow(1.0f);
@@ -137,25 +197,19 @@ namespace settings {
auto closeBtn = std::make_unique<Button>();
closeBtn->setGlyph("close");
closeBtn->setVariant(ButtonVariant::Ghost);
closeBtn->setGlyphSize(Style::fontSizeBody * scale);
closeBtn->setMinWidth(Style::controlHeightSm * scale);
closeBtn->setMinHeight(Style::controlHeightSm * scale);
closeBtn->setPadding(Style::spaceXs * scale);
closeBtn->setRadius(Style::radiusSm * scale);
closeBtn->setOnClick([this]() {
if (m_popup != nullptr) {
m_popup->close();
}
});
closeBtn->setGlyphSize(Style::fontSizeBody * m_scale);
closeBtn->setMinWidth(Style::controlHeightSm * m_scale);
closeBtn->setMinHeight(Style::controlHeightSm * m_scale);
closeBtn->setPadding(Style::spaceXs * m_scale);
closeBtn->setRadius(Style::radiusSm * m_scale);
closeBtn->setOnClick([this]() { DeferredCall::callLater([this]() { close(); }); });
header->addChild(std::move(closeBtn));
root->addChild(std::move(header));
auto picker = std::make_unique<SearchPicker>();
picker->setPlaceholder(i18n::tr("settings.entities.widget.picker.placeholder"));
picker->setEmptyText(i18n::tr("settings.entities.widget.picker.empty"));
picker->setOptions(options);
picker->setSize(std::max(1.0f, width - panelPadding * 2.0f),
std::max(1.0f, height - panelPadding * 2.0f - Style::controlHeightSm * scale));
picker->setOptions(m_options);
picker->setOnActivated([this](const SearchPickerOption& option) {
if (option.value.empty()) {
return;
@@ -163,73 +217,41 @@ namespace settings {
if (m_onSelect) {
m_onSelect(m_lanePath, option.value);
}
if (m_popup != nullptr) {
m_popup->close();
}
});
picker->setOnCancel([this]() {
if (m_popup != nullptr) {
m_popup->close();
}
DeferredCall::callLater([this]() { close(); });
});
picker->setOnCancel([this]() { DeferredCall::callLater([this]() { close(); }); });
m_searchPicker = picker.get();
root->addChild(std::move(picker));
return root;
});
m_popup->setSceneReadyCallback([this](InputDispatcher& dispatcher) {
if (!m_focusSearchOnOpen || m_searchPicker == nullptr) {
contentParent->addChild(std::move(root));
}
void WidgetAddPopup::layoutSheet(float contentWidth, float contentHeight) {
if (m_root == nullptr || m_searchPicker == nullptr || renderContext() == nullptr) {
return;
}
auto* area = m_searchPicker->filterInputArea();
if (area != nullptr) {
dispatcher.setFocus(area);
m_focusSearchOnOpen = false;
}
});
float anchorAbsX = 0.0f;
float anchorAbsY = 0.0f;
Node::absolutePosition(anchorButton, anchorAbsX, anchorAbsY);
auto cfg = PopupWindow::makeConfig(static_cast<std::int32_t>(anchorAbsX), static_cast<std::int32_t>(anchorAbsY),
std::max(1, static_cast<std::int32_t>(anchorButton->width())),
std::max(1, static_cast<std::int32_t>(anchorButton->height())),
static_cast<std::uint32_t>(std::max(1.0f, panelWidth)),
static_cast<std::uint32_t>(std::max(1.0f, panelHeight)), serial, anchorMode, 0,
static_cast<std::int32_t>(Style::spaceXs * scale), true);
m_focusSearchOnOpen = true;
m_popup->openAsChild(cfg, parentXdgSurface, output);
const float panelPadding = Style::spaceSm * m_scale;
m_root->setSize(contentWidth, contentHeight);
const float pickerWidth = std::max(1.0f, contentWidth - panelPadding * 2.0f);
const float pickerHeight = std::max(1.0f, contentHeight - panelPadding * 2.0f - Style::controlHeightSm * m_scale);
m_searchPicker->setSize(pickerWidth, pickerHeight);
m_root->layout(*renderContext());
}
void WidgetAddPopup::close() {
if (m_popup != nullptr) {
m_popup->close();
}
void WidgetAddPopup::cancelToFacade() {}
InputArea* WidgetAddPopup::initialFocusArea() {
return m_searchPicker != nullptr ? m_searchPicker->filterInputArea() : nullptr;
}
bool WidgetAddPopup::isOpen() const noexcept { return m_popup != nullptr && m_popup->isOpen(); }
bool WidgetAddPopup::onPointerEvent(const PointerEvent& event) {
return m_popup != nullptr && m_popup->onPointerEvent(event);
}
void WidgetAddPopup::onKeyboardEvent(const KeyboardEvent& event) {
if (m_popup != nullptr) {
m_popup->onKeyboardEvent(event);
}
}
void WidgetAddPopup::requestLayout() {
if (m_popup != nullptr) {
m_popup->requestLayout();
}
}
void WidgetAddPopup::requestRedraw() {
if (m_popup != nullptr) {
m_popup->requestRedraw();
void WidgetAddPopup::onSheetClose() {
m_options.clear();
m_lanePath.clear();
m_root = nullptr;
m_searchPicker = nullptr;
if (m_onDismissed) {
m_onDismissed();
}
}
+19 -10
View File
@@ -1,7 +1,7 @@
#pragma once
#include "ui/controls/popup_window.h"
#include "ui/controls/search_picker.h"
#include "ui/dialogs/dialog_popup_host.h"
#include <functional>
#include <memory>
@@ -9,7 +9,8 @@
#include <vector>
class Button;
class InputDispatcher;
class Node;
class ConfigService;
class RenderContext;
class WaylandConnection;
struct Config;
@@ -20,34 +21,42 @@ struct xdg_surface;
namespace settings {
class WidgetAddPopup {
class WidgetAddPopup final : public DialogPopupHost {
public:
using SelectCallback = std::function<void(const std::vector<std::string>& lanePath, const std::string& value)>;
WidgetAddPopup(WaylandConnection& wayland, RenderContext& renderContext);
WidgetAddPopup() = default;
~WidgetAddPopup();
void initialize(WaylandConnection& wayland, ConfigService& config, RenderContext& renderContext);
void setOnSelect(SelectCallback callback);
void setOnDismissed(std::function<void()> callback);
void open(xdg_surface* parentXdgSurface, wl_output* output, std::uint32_t serial, Button* anchorButton,
const std::vector<std::string>& lanePath, const Config& config, float scale,
PopupWindow::AnchorMode anchorMode = PopupWindow::AnchorMode::CenterOnAnchor);
wl_surface* parentWlSurface, const std::vector<std::string>& lanePath, const Config& config, float scale);
void close();
[[nodiscard]] bool isOpen() const noexcept;
[[nodiscard]] bool onPointerEvent(const PointerEvent& event);
void onKeyboardEvent(const KeyboardEvent& event);
[[nodiscard]] wl_surface* wlSurface() const noexcept;
void requestLayout();
void requestRedraw();
protected:
void populateContent(Node* contentParent, std::uint32_t width, std::uint32_t height) override;
void layoutSheet(float contentWidth, float contentHeight) override;
void cancelToFacade() override;
[[nodiscard]] InputArea* initialFocusArea() override;
void onSheetClose() override;
private:
WaylandConnection& m_wayland;
RenderContext& m_renderContext;
std::unique_ptr<PopupWindow> m_popup;
std::vector<SearchPickerOption> m_options;
float m_scale = 1.0f;
std::vector<std::string> m_lanePath;
Flex* m_root = nullptr;
SearchPicker* m_searchPicker = nullptr;
bool m_focusSearchOnOpen = false;
SelectCallback m_onSelect;
std::function<void()> m_onDismissed;
};
-280
View File
@@ -1,280 +0,0 @@
#include "ui/controls/popup_window.h"
#include "core/deferred_call.h"
#include "core/log.h"
#include "core/ui_phase.h"
#include "render/render_context.h"
#include "render/scene/node.h"
#include "wayland/wayland_connection.h"
#include "wayland/wayland_seat.h"
#include "xdg-shell-client-protocol.h"
#include <algorithm>
#include <cmath>
namespace {
constexpr Logger kLog("popup-window");
} // namespace
std::vector<PopupWindow*>& PopupWindow::openPopups() {
static std::vector<PopupWindow*> popups;
return popups;
}
PopupWindow::PopupWindow(WaylandConnection& wayland, RenderContext& renderContext)
: m_wayland(wayland), m_renderContext(renderContext) {}
PopupWindow::~PopupWindow() { close(); }
void PopupWindow::setContentBuilder(ContentBuilder builder) { m_contentBuilder = std::move(builder); }
void PopupWindow::setSceneReadyCallback(SceneReadyCallback callback) { m_sceneReadyCallback = std::move(callback); }
void PopupWindow::setOnDismissed(std::function<void()> callback) { m_onDismissed = std::move(callback); }
void PopupWindow::open(PopupSurfaceConfig config, zwlr_layer_surface_v1* parentLayerSurface, wl_output* output) {
openCommon(config, parentLayerSurface, nullptr, output);
}
void PopupWindow::openAsChild(PopupSurfaceConfig config, xdg_surface* parentXdgSurface, wl_output* output) {
openCommon(config, nullptr, parentXdgSurface, output);
}
void PopupWindow::openCommon(PopupSurfaceConfig config, zwlr_layer_surface_v1* parentLayerSurface,
xdg_surface* parentXdgSurface, wl_output* output) {
close();
m_surface = std::make_unique<PopupSurface>(m_wayland);
m_surface->setRenderContext(&m_renderContext);
auto* self = this;
m_surface->setConfigureCallback(
[self](std::uint32_t /*w*/, std::uint32_t /*h*/) { self->m_surface->requestLayout(); });
m_surface->setPrepareFrameCallback([self](bool /*needsUpdate*/, bool needsLayout) {
if (self->m_surface == nullptr) {
return;
}
const auto width = self->m_surface->width();
const auto height = self->m_surface->height();
if (width == 0 || height == 0) {
return;
}
self->m_renderContext.makeCurrent(self->m_surface->renderTarget());
const bool needsSceneBuild = self->m_sceneRoot == nullptr ||
static_cast<std::uint32_t>(std::round(self->m_sceneRoot->width())) != width ||
static_cast<std::uint32_t>(std::round(self->m_sceneRoot->height())) != height;
if (needsSceneBuild) {
UiPhaseScope layoutPhase(UiPhase::Layout);
self->buildScene(width, height);
return;
}
if (needsLayout && self->m_sceneRoot != nullptr) {
UiPhaseScope layoutPhase(UiPhase::Layout);
self->m_sceneRoot->setSize(static_cast<float>(width), static_cast<float>(height));
self->m_sceneRoot->layout(self->m_renderContext);
self->m_surface->setSceneRoot(self->m_sceneRoot.get());
}
});
m_surface->setDismissedCallback([self]() { DeferredCall::callLater([self]() { self->close(); }); });
const bool initialized = parentXdgSurface != nullptr ? m_surface->initializeAsChild(parentXdgSurface, output, config)
: m_surface->initialize(parentLayerSurface, output, config);
if (!initialized) {
kLog.warn("failed to create popup window");
m_surface.reset();
return;
}
m_wlSurface = m_surface->wlSurface();
openPopups().push_back(this);
}
void PopupWindow::buildScene(std::uint32_t width, std::uint32_t height) {
const float fw = static_cast<float>(width);
const float fh = static_cast<float>(height);
if (m_contentBuilder) {
m_sceneRoot = m_contentBuilder(fw, fh);
}
if (m_sceneRoot == nullptr) {
m_sceneRoot = std::make_unique<Node>();
}
m_sceneRoot->setSize(fw, fh);
m_sceneRoot->layout(m_renderContext);
m_inputDispatcher.setSceneRoot(m_sceneRoot.get());
m_inputDispatcher.setCursorShapeCallback(
[this](std::uint32_t serial, std::uint32_t shape) { m_wayland.setCursorShape(serial, shape); });
m_surface->setSceneRoot(m_sceneRoot.get());
if (m_sceneReadyCallback) {
m_sceneReadyCallback(m_inputDispatcher);
}
}
void PopupWindow::close() {
const bool wasOpen = m_surface != nullptr;
auto& popups = openPopups();
popups.erase(std::remove(popups.begin(), popups.end(), this), popups.end());
m_sceneRoot.reset();
m_surface.reset();
m_inputDispatcher.setSceneRoot(nullptr);
m_wlSurface = nullptr;
m_pointerInside = false;
if (wasOpen && m_onDismissed) {
m_onDismissed();
}
}
bool PopupWindow::isOpen() const noexcept { return m_surface != nullptr; }
wl_surface* PopupWindow::wlSurface() const noexcept { return m_wlSurface; }
bool PopupWindow::onPointerEvent(const PointerEvent& event) {
if (!isOpen()) {
return false;
}
const bool onPopup = (event.surface != nullptr && event.surface == m_wlSurface);
switch (event.type) {
case PointerEvent::Type::Enter:
if (onPopup) {
m_pointerInside = true;
m_inputDispatcher.pointerEnter(static_cast<float>(event.sx), static_cast<float>(event.sy), event.serial);
}
break;
case PointerEvent::Type::Leave:
if (onPopup) {
m_pointerInside = false;
m_inputDispatcher.pointerLeave();
}
break;
case PointerEvent::Type::Motion:
if (onPopup || m_pointerInside) {
if (onPopup) {
m_pointerInside = true;
}
m_inputDispatcher.pointerMotion(static_cast<float>(event.sx), static_cast<float>(event.sy), 0);
return true;
}
break;
case PointerEvent::Type::Button:
if (onPopup || m_pointerInside) {
if (onPopup) {
m_pointerInside = true;
}
const bool pressed = (event.state == 1);
(void)m_inputDispatcher.pointerButton(static_cast<float>(event.sx), static_cast<float>(event.sy), event.button,
pressed);
return true;
}
break;
case PointerEvent::Type::Axis:
if (onPopup || m_pointerInside) {
return m_inputDispatcher.pointerAxis(static_cast<float>(event.sx), static_cast<float>(event.sy), event.axis,
event.axisSource, event.axisValue, event.axisDiscrete, event.axisValue120,
event.axisLines);
}
break;
}
if (m_surface != nullptr && m_sceneRoot != nullptr && m_surface->isRunning()) {
if (m_sceneRoot->layoutDirty()) {
m_surface->requestLayout();
} else if (m_sceneRoot->paintDirty()) {
m_surface->requestRedraw();
}
}
return onPopup;
}
void PopupWindow::onKeyboardEvent(const KeyboardEvent& event) {
if (!isOpen()) {
return;
}
m_inputDispatcher.keyEvent(event.sym, event.utf32, event.modifiers, event.pressed, event.preedit);
if (m_surface != nullptr && m_sceneRoot != nullptr && m_surface->isRunning()) {
if (m_sceneRoot->layoutDirty()) {
m_surface->requestLayout();
} else if (m_sceneRoot->paintDirty()) {
m_surface->requestRedraw();
}
}
}
void PopupWindow::requestLayout() {
if (m_surface != nullptr) {
m_surface->requestLayout();
}
}
void PopupWindow::requestRedraw() {
if (m_surface != nullptr) {
m_surface->requestRedraw();
}
}
bool PopupWindow::dispatchKeyboardEvent(wl_surface* keyboardSurface, const KeyboardEvent& event) {
if (keyboardSurface == nullptr) {
return false;
}
auto& popups = openPopups();
for (auto it = popups.rbegin(); it != popups.rend(); ++it) {
auto* popup = *it;
if (popup == nullptr || !popup->isOpen()) {
continue;
}
if (popup->wlSurface() == keyboardSurface) {
popup->onKeyboardEvent(event);
return true;
}
}
return false;
}
PopupSurfaceConfig PopupWindow::makeConfig(std::int32_t anchorX, std::int32_t anchorY, std::int32_t anchorWidth,
std::int32_t anchorHeight, std::uint32_t width, std::uint32_t height,
std::uint32_t serial, AnchorMode mode, std::int32_t offsetX,
std::int32_t offsetY, bool grab) {
PopupSurfaceConfig cfg{
.anchorX = anchorX,
.anchorY = anchorY,
.anchorWidth = std::max(1, anchorWidth),
.anchorHeight = std::max(1, anchorHeight),
.width = std::max<std::uint32_t>(1, width),
.height = std::max<std::uint32_t>(1, height),
.anchor = XDG_POSITIONER_ANCHOR_BOTTOM,
.gravity = XDG_POSITIONER_GRAVITY_BOTTOM,
.constraintAdjustment = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X |
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y |
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y,
.offsetX = offsetX,
.offsetY = offsetY,
.serial = serial,
.grab = grab,
};
if (mode == AnchorMode::CenterOnAnchor) {
cfg.anchor = XDG_POSITIONER_ANCHOR_BOTTOM;
cfg.gravity = XDG_POSITIONER_GRAVITY_BOTTOM;
cfg.anchorX += cfg.anchorWidth / 2;
cfg.anchorY += cfg.anchorHeight / 2;
cfg.anchorWidth = 1;
cfg.anchorHeight = 1;
}
return cfg;
}
-78
View File
@@ -1,78 +0,0 @@
#pragma once
#include "render/scene/input_dispatcher.h"
#include "wayland/popup_surface.h"
#include <functional>
#include <memory>
#include <vector>
class Node;
class PopupSurface;
class RenderContext;
class WaylandConnection;
struct KeyboardEvent;
struct PointerEvent;
struct wl_output;
struct wl_surface;
struct xdg_surface;
struct zwlr_layer_surface_v1;
class PopupWindow {
public:
enum class AnchorMode : std::uint8_t {
BelowAnchor,
CenterOnAnchor,
};
using ContentBuilder = std::function<std::unique_ptr<Node>(float width, float height)>;
using SceneReadyCallback = std::function<void(InputDispatcher&)>;
PopupWindow(WaylandConnection& wayland, RenderContext& renderContext);
~PopupWindow();
void setContentBuilder(ContentBuilder builder);
void setSceneReadyCallback(SceneReadyCallback callback);
void setOnDismissed(std::function<void()> callback);
void open(PopupSurfaceConfig config, zwlr_layer_surface_v1* parentLayerSurface, wl_output* output);
void openAsChild(PopupSurfaceConfig config, xdg_surface* parentXdgSurface, wl_output* output);
void close();
[[nodiscard]] bool isOpen() const noexcept;
[[nodiscard]] wl_surface* wlSurface() const noexcept;
[[nodiscard]] bool onPointerEvent(const PointerEvent& event);
void onKeyboardEvent(const KeyboardEvent& event);
void requestLayout();
void requestRedraw();
// Dispatch keyboard events to the popup that currently owns the provided Wayland keyboard surface.
[[nodiscard]] static bool dispatchKeyboardEvent(wl_surface* keyboardSurface, const KeyboardEvent& event);
[[nodiscard]] static PopupSurfaceConfig makeConfig(std::int32_t anchorX, std::int32_t anchorY,
std::int32_t anchorWidth, std::int32_t anchorHeight,
std::uint32_t width, std::uint32_t height, std::uint32_t serial,
AnchorMode mode, std::int32_t offsetX = 0,
std::int32_t offsetY = 0, bool grab = true);
private:
void openCommon(PopupSurfaceConfig config, zwlr_layer_surface_v1* parentLayerSurface, xdg_surface* parentXdgSurface,
wl_output* output);
void buildScene(std::uint32_t width, std::uint32_t height);
WaylandConnection& m_wayland;
RenderContext& m_renderContext;
std::unique_ptr<PopupSurface> m_surface;
std::unique_ptr<Node> m_sceneRoot;
InputDispatcher m_inputDispatcher;
wl_surface* m_wlSurface = nullptr;
bool m_pointerInside = false;
ContentBuilder m_contentBuilder;
SceneReadyCallback m_sceneReadyCallback;
std::function<void()> m_onDismissed;
static std::vector<PopupWindow*>& openPopups();
};
+32
View File
@@ -34,6 +34,13 @@ DialogPopupHost::~DialogPopupHost() {
assert(m_surface == nullptr && "subclass must call destroyPopup() in its destructor");
}
void DialogPopupHost::initializeBase(WaylandConnection& wayland, ConfigService& config, RenderContext& renderContext) {
m_wayland = &wayland;
m_config = &config;
m_renderContext = &renderContext;
m_popupHosts = nullptr;
}
void DialogPopupHost::initializeBase(WaylandConnection& wayland, ConfigService& config, RenderContext& renderContext,
LayerPopupHostRegistry& popupHosts) {
m_wayland = &wayland;
@@ -75,6 +82,31 @@ bool DialogPopupHost::openPopup(std::uint32_t width, std::uint32_t height) {
return true;
}
bool DialogPopupHost::openPopupAsChild(PopupSurfaceConfig config, xdg_surface* parentXdgSurface,
wl_surface* parentWlSurface, wl_output* output) {
if (m_wayland == nullptr || m_renderContext == nullptr || parentXdgSurface == nullptr || parentWlSurface == nullptr) {
return false;
}
destroyPopup();
m_parentSurface = parentWlSurface;
auto surface = std::make_unique<PopupSurface>(*m_wayland);
surface->setAnimationManager(&m_animations);
surface->setRenderContext(m_renderContext);
surface->setConfigureCallback([this](std::uint32_t /*w*/, std::uint32_t /*h*/) { requestLayout(); });
surface->setPrepareFrameCallback(
[this](bool needsUpdate, bool needsLayout) { prepareFrame(needsUpdate, needsLayout); });
surface->setDismissedCallback([this]() { cancel(); });
m_surface = std::move(surface);
if (!m_surface->initializeAsChild(parentXdgSurface, output, config)) {
destroyPopup();
return false;
}
return true;
}
void DialogPopupHost::destroyPopup() {
if (m_attachedToHost && m_popupHosts != nullptr) {
m_popupHosts->endAttachedPopup(m_parentSurface);
+9
View File
@@ -18,6 +18,8 @@ class WaylandConnection;
struct KeyboardEvent;
struct PointerEvent;
struct wl_surface;
struct wl_output;
struct xdg_surface;
// Shared base for the three xdg_popup-backed dialog popups (GlyphPicker,
// ColorPicker, FileDialog). Owns the PopupSurface, the scene scaffolding
@@ -62,6 +64,7 @@ protected:
DialogPopupHost();
// Called once during subclass `initialize` to capture the shared deps.
void initializeBase(WaylandConnection& wayland, ConfigService& config, RenderContext& renderContext);
void initializeBase(WaylandConnection& wayland, ConfigService& config, RenderContext& renderContext,
LayerPopupHostRegistry& popupHosts);
@@ -72,6 +75,12 @@ protected:
// automatically calls `destroyPopup()`.
[[nodiscard]] bool openPopup(std::uint32_t width, std::uint32_t height);
// Build the PopupSurface as a child of an xdg parent. Uses the same scene/
// input/prepareFrame plumbing as openPopup() but bypasses LayerPopupHostRegistry
// parent resolution.
[[nodiscard]] bool openPopupAsChild(PopupSurfaceConfig config, xdg_surface* parentXdgSurface,
wl_surface* parentWlSurface, wl_output* output);
// Tear the popup down — endAttachedPopup, invoke `onSheetClose()` hook,
// reset the scene tree, drop the PopupSurface. Safe to call repeatedly.
void destroyPopup();