5#include "fifechan/widgets/menupopup.hpp"
12#include <unordered_map>
15#include "fifechan/color.hpp"
16#include "fifechan/focushandler.hpp"
17#include "fifechan/font.hpp"
18#include "fifechan/graphics.hpp"
19#include "fifechan/widgets/menubar.hpp"
20#include "fifechan/widgets/menuitem.hpp"
21#include "fifechan/widgets/modalbackdrop.hpp"
28 constexpr int GAP_ICON_CAPTION = 6;
29 constexpr int GAP_CAPTION_SHORTCUT = 16;
30 constexpr int GAP_SHORTCUT_ARROW = 10;
33 void MenuPopup::layoutItems()
36 if (children.empty()) {
42 if (font ==
nullptr) {
50 std::unordered_map<MenuItem*, MenuItemMetrics> metricsMap;
52 for (
auto* child : children) {
53 if (child ==
nullptr) {
57 auto* mi =
dynamic_cast<MenuItem*
>(child);
62 if (mi->getType() == MenuItem::Type::Separator) {
66 auto m = mi->measure(*font);
71 cols.iconW = std::max(cols.iconW, m.iconW);
72 cols.captionW = std::max(cols.captionW, m.captionW);
73 cols.shortcutW = std::max(cols.shortcutW, m.shortcutW);
74 cols.arrowW = std::max(cols.arrowW, m.arrowW);
78 int const gapIconCaption = cols.iconW > 0 ? GAP_ICON_CAPTION : 0;
79 int const gapCaptionShortcut = (cols.captionW > 0 && cols.shortcutW > 0) ? GAP_CAPTION_SHORTCUT : 0;
80 int const gapShortcutArrow = (cols.shortcutW > 0 && cols.arrowW > 0) ? GAP_SHORTCUT_ARROW : 0;
83 int const contentW = cols.iconW + gapIconCaption + cols.captionW + gapCaptionShortcut + cols.shortcutW +
84 gapShortcutArrow + cols.arrowW;
89 int const xCaption = xIcon + cols.iconW + gapIconCaption;
90 int const xShortcut = xCaption + cols.captionW + gapCaptionShortcut;
91 int const xArrow = xShortcut + cols.shortcutW + gapShortcutArrow;
94 ColumnLayout layout{};
96 layout.xCaption = xCaption;
97 layout.xShortcut = xShortcut;
98 layout.xArrow = xArrow;
105 for (
auto* child : children) {
106 if (child ==
nullptr) {
110 int h = child->getHeight();
112 if (
auto* mi =
dynamic_cast<MenuItem*
>(child)) {
113 auto it = metricsMap.find(mi);
114 if (it != metricsMap.end()) {
116 h = it->second.height;
121 child->setPosition(0, y);
122 child->setSize(contentW, h);
125 if (
auto* mi =
dynamic_cast<MenuItem*
>(child)) {
126 mi->layoutColumns(layout);
136 int const rightBorder =
139 int const bottomBorder =
145 int const popupWidth = contentW + contentPaddingW;
146 int const popupHeight = contentH + contentPaddingH;
149 setSize(popupWidth, popupHeight);
184 if (mParentMenuItem !=
nullptr) {
185 topContainer =
dynamic_cast<Container*
>(mParentMenuItem->getTop());
187 if (topContainer ==
nullptr) {
193 if (currentParent !=
nullptr && currentParent != topContainer) {
194 if (
auto* parentContainer =
dynamic_cast<Container*
>(currentParent)) {
195 parentContainer->remove(
this);
201 if (topContainer !=
nullptr && currentParent != topContainer) {
202 topContainer->
add(
this);
209 if (topContainer !=
nullptr) {
220 mModalScope = std::make_unique<FocusHandler::ModalScope>(
_getFocusHandler(),
this,
this);
225 if (topContainer !=
nullptr) {
227 auto backdrop = std::make_unique<ModalBackdrop>(
this);
228 backdrop->setPosition(0, 0);
230 topContainer->
add(backdrop.get());
235 mBackdrop = backdrop.release();
237 }
else if (mParentMenu !=
nullptr) {
249 if (mParentMenuItem !=
nullptr) {
250 mParentMenuItem->requestFocus();
254 if (mOpenChild !=
nullptr) {
256 mOpenChild =
nullptr;
260 if (mBackdrop !=
nullptr) {
261 if (mBackdrop->getParent() !=
nullptr) {
262 if (
auto* parentContainer =
dynamic_cast<Container*
>(mBackdrop->getParent())) {
263 parentContainer->remove(mBackdrop);
271 if (mModalScope !=
nullptr) {
284 mParentMenuItem = parent;
289 return mParentMenuItem;
299 mParentMenu = parent;
312 std::cerr <<
"[MenuPopup] Warning: widget does not implement adjustSize\n";
325 auto* mi =
dynamic_cast<MenuItem*
>(item);
326 if (mi !=
nullptr && mi->getType() != MenuItem::Type::Separator) {
327 mi->addActionListener(
this);
335 sep->setType(MenuItem::Type::Separator);
349 assert(
"graphics must not be null" && graphics !=
nullptr);
369 int const relx =
event.getX();
370 int const rely =
event.getY();
387 bool clickHitsMenuBar =
false;
389 if (topContainer !=
nullptr) {
392 if (child ==
nullptr || child ==
this) {
398 if (absx >= cx && absx < cx + child->
getWidth() && absy >= cy && absy < cy + child->
getHeight()) {
399 if (
dynamic_cast<MenuBar*
>(child) !=
nullptr ||
dynamic_cast<MenuItem*
>(child) !=
nullptr) {
400 clickHitsMenuBar =
true;
407 if (clickHitsMenuBar) {
424 Widget const * src =
event.getSource();
427 for (
auto* child : children) {
430 child->requestFocus();
434 if (
auto* mi =
dynamic_cast<MenuItem*
>(child)) {
436 if (submenu !=
nullptr) {
438 if (mOpenChild !=
nullptr && mOpenChild != submenu) {
448 mi->getAbsolutePosition(ax, ay);
449 int const sx = ax + mi->getWidth();
451 submenu->
show(sx, sy);
452 mOpenChild = submenu;
468 Key
const key =
event.getKey();
470 if (key.getValue() == fcn::Key::ESCAPE) {
477 if (mParentMenuItem !=
nullptr) {
478 if (key.getValue() == fcn::Key::UP || key.getValue() == fcn::Key::DOWN) {
480 if (children.empty()) {
484 mHoverIndex = std::max(mHoverIndex, 0);
486 if (key.getValue() == fcn::Key::UP) {
488 (mHoverIndex - 1 +
static_cast<int>(children.size())) %
static_cast<int>(children.size());
490 mHoverIndex = (mHoverIndex + 1) %
static_cast<int>(children.size());
495 for (
auto* target : children) {
496 if (i == mHoverIndex) {
497 if (target !=
nullptr) {
498 target->requestFocus();
509 if (key.getValue() == fcn::Key::RIGHT) {
511 if (mHoverIndex >= 0) {
513 for (
auto* child : children) {
514 if (i == mHoverIndex) {
515 if (
auto* mi =
dynamic_cast<MenuItem*
>(child)) {
516 if (mi->getSubmenu() !=
nullptr) {
522 mi->getAbsolutePosition(ax, ay);
523 submenu->
show(ax + mi->getWidth(), ay);
524 mOpenChild = submenu;
536 if (key.getValue() == fcn::Key::LEFT) {
537 if (mParentMenu !=
nullptr) {
561 auto* source =
dynamic_cast<MenuItem*
>(
event.getSource());
562 if (source ==
nullptr) {
567 if (source->getType() == MenuItem::Type::Checkable) {
568 source->setChecked(!source->isChecked());
Represents an action trigger (e.g., button click).
void draw(Graphics *graphics) override
Draws the widget.
virtual void setLayout(LayoutPolicy policy)
Sets the layout of the container.
void resizeToContent(bool recursion=true) override
Resize this container to fit its children.
virtual void add(Widget *widget)
Adds a widget to the container.
Widget * getChild(unsigned int index) const
Gets child by index.
virtual void setOpaque(bool opaque)
Sets the container to be opaque or not.
Base class for all GUI event objects.
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
virtual void setColor(Color const &color)=0
Sets the color to use when drawing.
virtual void fillRectangle(Rectangle const &rectangle)=0
Draws a filled rectangle.
Represents a mouse event.
Represents a rectangular area (X, Y, Width, Height).
Used replacement tokens by configure_file():