libgui 3.00
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiPopover.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2026
4 * GuiPopover.cpp
5 *
6 * See GuiPopover.h.
7 ***************************************************************************/
8#include "Gui.h"
9#include <algorithm>
10
11static const int kGap = 6; // between the anchor and the panel
12
13// ---- placement: pure arithmetic on rectangles
14
15int GuiPopover::maxHeight(int screenHeight, const Insets& insets)
16{
17 return screenHeight - insets.top - insets.bottom - 2 * kGap;
18}
19
20GuiPopover::Position GuiPopover::place(const Anchor& a, int w, int h, int screenW, int screenH, const Insets& insets)
21{
22 const int top = insets.top + kGap;
23 const int bottom = screenH - insets.bottom - kGap;
24 Position p;
25 if(!a.valid)
26 {
27 p.x = (screenW - w) / 2;
28 p.y = (screenH - h) / 2;
29 }
30 else if(a.edge == Anchor::BESIDE)
31 {
32 // Level with the anchor, on the side with room (the right if both have), clear of the anchor itself. With
33 // room on neither side it goes against the margin of the roomier one, which is as close as it can get.
34 const int rightX = a.x + a.w + kGap, leftX = a.x - kGap - w;
35 const int rightRoom = screenW - insets.side - rightX, leftRoom = leftX - insets.side + w;
36 if(rightRoom >= w)
37 p.x = rightX;
38 else if(leftX >= insets.side)
39 p.x = leftX;
40 else
41 p.x = (rightRoom >= leftRoom) ? rightX : insets.side;
42 p.y = std::max(top, std::min(a.y + a.h / 2 - h / 2, bottom - h));
43 }
44 else
45 {
46 // Straight under its anchor if that fits above the footer bar, else straight over it; a chip in a bar
47 // gets a panel that comes right out of it. Otherwise (a row in the middle of the page) it keeps to the band.
48 const int below = a.y + a.h + kGap, above = a.y - h - kGap;
49 if(below + h <= bottom)
50 p.y = below;
51 else if(above >= top)
52 p.y = above;
53 else
54 p.y = std::max(top, std::min(below, bottom - h));
55 if(a.edge == Anchor::LEFT_MARGIN)
56 p.x = insets.side;
57 else if(a.edge == Anchor::RIGHT_MARGIN)
58 p.x = screenW - insets.side - w;
59 else
60 p.x = (a.x + a.w / 2 > screenW / 2) ? a.x + a.w - w : a.x;
61 }
62 p.x = std::max(insets.side, std::min(p.x, screenW - insets.side - w));
63 return p;
64}
65
66GuiPopover::Tail GuiPopover::tailAt(const Anchor& a, const Position& at, int panelW, int panelH)
67{
68 const int len = 8, base = 16, edge = 18; // how far it sticks out, how wide it is, how far from a corner it stays
69 Tail t = { false, TAIL::UP, 0, 0, 0, 0 };
70 if(!a.valid)
71 return t;
72 const int ax = a.x + a.w / 2, ay = a.y + a.h / 2;
73 if(at.y >= a.y + a.h) // below it: pointing up, under the anchor's middle
74 {
75 t = { true, TAIL::UP, std::max(at.x + edge, std::min(ax - base / 2, at.x + panelW - edge - base)), at.y - len + 1, base, len };
76 }
77 else if(at.y + panelH <= a.y) // above it: pointing down
78 {
79 t = { true, TAIL::DOWN, std::max(at.x + edge, std::min(ax - base / 2, at.x + panelW - edge - base)), at.y + panelH - 1, base, len };
80 }
81 else if(at.x >= ax) // beside it, on its right (it may cover the anchor's end): pointing left, level with the anchor
82 {
83 t = { true, TAIL::LEFT, at.x - len + 1, std::max(at.y + edge, std::min(ay - base / 2, at.y + panelH - edge - base)), len, base };
84 }
85 else if(at.x + panelW <= ax) // on its left: pointing right
86 {
87 t = { true, TAIL::RIGHT, at.x + panelW - 1, std::max(at.y + edge, std::min(ay - base / 2, at.y + panelH - edge - base)), len, base };
88 }
89 return t;
90}
91
92// ---- the popover
93
94GuiPopover::GuiPopover(const Anchor& anchorAt, const Insets& keepClear) :
95 GuiPopover(anchorAt, keepClear, platform->getVideo()->getScreenWidth(), platform->getVideo()->getScreenHeight())
96{
97}
98
99GuiPopover::GuiPopover(const Anchor& anchorAt, const Insets& keepClear, int screenWidth, int screenHeight) :
100 anchor(anchorAt), insets(keepClear), screenW(screenWidth), screenH(screenHeight), cancel(nullptr), click(nullptr), panel(nullptr), dismissed(false), by(-1)
101{
102 placed.x = 0;
103 placed.y = 0;
104 placedTail = { false, TAIL::UP, 0, 0, 0, 0 };
105 cancel = &ui.own<GuiTrigger>();
106 cancel->setSecondaryTrigger();
107 click = ui.trigger();
108
109 setFixedSize(screenW, screenH);
110 setAlignment(ALIGN_H::LEFT, ALIGN_V::TOP);
111 setPosition(0, 0);
112}
113
114void GuiPopover::dismiss()
115{
116 dismissed = true;
117 by = -1;
118}
119
121{
123 if(dismissed || !c || getState() == STATE::DISABLED)
124 return;
125 if(parentElement && parentElement->getState() == STATE::DISABLED)
126 return;
127
128 // B, or a click anywhere outside the panel. The popover covers the whole screen, so every pointer is always over
129 // it: each controller decides for itself, whichever the screen happens to update first.
130 const int chan = c->getChannel();
131 bool leave = cancel->isClicked(c);
132 if(!leave && chan >= 0 && click->isClicked(c))
133 {
134 const InputPadData& pad = c->getPadData();
135 leave = pad.validPointer && (!panel || !panel->isInside(pad.cursor_x, pad.cursor_y));
136 }
137 if(leave)
138 {
139 dismissed = true;
140 by = chan;
141 }
142}
143
144void GuiPopover::setPanel(GuiCard& card)
145{
146 setPanel(card, GuiTheme::current().surfaceRaised);
147}
148
149void GuiPopover::setPanel(GuiCard& card, PixelColor tailColor)
150{
151 if(panel)
152 return; // once
153 panel = &card;
154 card.layout(); // measure it: a card that sizes itself to its content works as well as a fixed one
155 const int panelW = card.getWidth(), panelH = card.getHeight();
156 placed = place(anchor, panelW, panelH, screenW, screenH, insets);
157
158 GuiLayout& at = ui.column().size(screenW, screenH).addGap((float)placed.y).add(ui.row().size(screenW, panelH).addGap((float)placed.x).add(card));
159 add(&at);
160
161 // the tail, drawn over the panel's edge in the panel's color, pointing at what the popover belongs to
162 placedTail = tailAt(anchor, placed, panelW, panelH);
163 if(placedTail.shown)
164 {
165 static const SHAPE kinds[] = { SHAPE::TRIANGLE_UP, SHAPE::TRIANGLE_DOWN, SHAPE::TRIANGLE_LEFT, SHAPE::TRIANGLE_RIGHT };
166 GuiShape& arrow = ui.own<GuiShape>(placedTail.w, placedTail.h);
167 arrow.setArrow(kinds[(int)placedTail.direction], tailColor, tailColor);
168 GuiLayout& tailPlaced = ui.column().size(screenW, screenH).addGap((float)placedTail.y).add(ui.row().size(screenW, placedTail.h).addGap((float)placedTail.x).add(arrow));
169 add(&tailPlaced);
170 }
171 ui.attachTo(*this);
172}
Umbrella header for the platform-agnostic libgui core.
STATE getState()
Gets the element's current state.
GuiElement * parentElement
Parent element.
Definition GuiElement.h:270
virtual void update(InputController *c)
Called constantly to allow the element to respond to the current input data.
void setPosition(int x, int y)
Sets the element's position.
virtual void setAlignment(ALIGN_H hor, ALIGN_V vert)
Sets the element's alignment respective to its parent element.
GuiLayout & add(GuiElement *e, const GuiItem &item=GuiItem())
Adds a child. Children are laid out in the order they are added.
Definition GuiLayout.cpp:21
GuiLayout & setFixedSize(int w, int h)
Fixes the container's size (0 on an axis = fit the content)
Definition GuiLayout.cpp:97
GuiPopover(const Anchor &anchor=Anchor(), const Insets &insets=Insets())
A popover as large as the screen the platform reports, opening beside anchor (centered with none) and...
static Tail tailAt(const Anchor &anchor, const Position &at, int panelW, int panelH)
Puts the built panel (a card owned by ui, fixed or fit to its content: it is laid out here to measure...
UiScope ui
Owns the panel and everything else the popover is made of; build the panel with it.
Definition GuiPopover.h:115
void setArrow(SHAPE dir, PixelColor rest, PixelColor lit)
Makes this a small arrow: a filled triangle pointing the way of dir (a SHAPE::TRIANGLE_*),...
Definition GuiShape.cpp:56
Input-condition matcher attached to a GuiElement (via setSimpleTrigger() etc.) and evaluated against ...
Definition GuiTrigger.h:34
void setSecondaryTrigger(int ch=-1)
Logical controller for one input channel.
int getChannel() const
const InputPadData & getPadData() const
SHAPE
Basic shape kinds a paint can be applied to.
Definition GuiPaint.h:17
@ TRIANGLE_UP
A triangle pointing up, left or right, apex on the middle of one side and base along the opposite sid...
Definition GuiPaint.h:20
@ TRIANGLE_DOWN
(scroll arrows, the tail of a popover).
Definition GuiPaint.h:21
The control a popover opens beside, in canvas pixels.
Definition GuiPopover.h:40
bool valid
false for a popover with no anchor: it is centered
Definition GuiPopover.h:45
What a popover keeps clear at the screen's edges: the bars at the top and bottom, and the margin at b...
Definition GuiPopover.h:58
Where the panel's top-left corner goes.
Definition GuiPopover.h:64
The little tail on the panel: x,y is its top-left corner, w,h its size, direction the way it points (...
Definition GuiPopover.h:70
static GuiTheme & current()
The theme the rest of the library reads. The first call fills it in with GuiThemeInit().
Definition GuiTheme.cpp:35
bool validPointer
True if the IR pointer or touch is active on screen.
Definition InputData.h:78
8-bit-per-channel RGBA color used by the video interfaces.
Definition Mesh.h:16