libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiUi.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2026
4 * GuiUi.cpp
5 ***************************************************************************/
6
7#include "Gui.h"
8#include <cstdarg>
9#include <cstdio>
10
11std::string GuiFormat(const char * fmt, ...)
12{
13 va_list args;
14 va_start(args, fmt);
15 va_list copy;
16 va_copy(copy, args);
17 const int n = vsnprintf(nullptr, 0, fmt, copy);
18 va_end(copy);
19
20 std::string out;
21 if(n > 0)
22 {
23 out.resize((size_t)n + 1);
24 vsnprintf(&out[0], out.size(), fmt, args);
25 out.resize((size_t)n);
26 }
27 va_end(args);
28 return out;
29}
30
31// An empty string is no text at all: GuiText skips drawing and the translator is not asked for "".
32static const char * textOrNull(const char * t)
33{
34 return (t && *t) ? t : nullptr;
35}
36
37// Shared by the widgets that wrap a GuiButton: report focus when the button becomes selected, run the
38// click callback, and keep the button selected afterwards so the D-pad does not lose its place.
39static void pollButton(GuiButton& b, bool& wasSelected, const UiCallback& click, const UiCallback& focus)
40{
41 const STATE s = b.getState();
42 const bool selected = s == STATE::SELECTED || s == STATE::HELD;
43 if(selected && !wasSelected && focus)
44 focus();
45 wasSelected = selected;
46
47 if(s == STATE::CLICKED && click)
48 {
49 const int chan = b.getStateChan();
50 click();
51 b.resetState();
52 b.setState(STATE::SELECTED, chan);
53 wasSelected = true;
54 }
55}
56
57// ---- UiScope
58
60{
61 public:
62 Runner(UiScope& s) : scope(s) {}
63 void update(InputController *) override { scope.runBindings(); }
64 void draw() override {}
65 private:
66 UiScope& scope;
67};
68
69UiScope::UiScope() : trig(&primary), soundOver(nullptr), soundClick(nullptr), runner(nullptr)
70{
71 primary.setPrimaryTrigger();
72}
73
74UiScope::~UiScope()
75{
76 bindings.clear(); // nothing runs against elements that are about to go
77 for(size_t i = holders.size(); i > 0; i--)
78 delete holders[i - 1];
79}
80
81void UiScope::bind(UiCallback fn)
82{
83 if(!fn)
84 return;
85 fn(); // evaluate once now, so a first layout already sees the values
86 bindings.push_back(fn);
87}
88
89void UiScope::visibleWhen(GuiElement& e, std::function<bool()> fn)
90{
91 GuiElement * target = &e;
92 bind([target, fn]() {
93 const bool v = fn();
94 if(v != target->isVisible())
95 target->setVisible(v);
96 });
97}
98
99void UiScope::enabledWhen(GuiElement& e, std::function<bool()> fn)
100{
101 GuiElement * target = &e;
102 bind([target, fn]() {
103 const bool enabled = fn();
104 const bool off = target->getState() == STATE::DISABLED;
105 if(enabled && off)
106 target->setState(STATE::DEFAULT);
107 else if(!enabled && !off)
108 target->setState(STATE::DISABLED);
109 });
110}
111
112void UiScope::attachTo(GuiWindow& window)
113{
114 if(!runner)
115 runner = &make<Runner>(*this);
116 runner->setVisible(false);
117 window.append(runner);
118}
119
120void UiScope::runBindings()
121{
122 for(size_t i = 0; i < bindings.size(); i++)
123 bindings[i]();
124}
125
126// ---- GuiLabel
127
128GuiLabel::GuiLabel(UiScope& scope, const char * t) :
129 GuiText(textOrNull(t), 20, GuiTheme::current().text), ui(scope)
130{
131}
132
133GuiLabel& GuiLabel::fontSize(int px)
134{
135 setFontSize(px);
136 return *this;
137}
138
139GuiLabel& GuiLabel::textColor(PixelColor c)
140{
141 setColor(c);
142 return *this;
143}
144
145GuiLabel& GuiLabel::muted()
146{
147 return textColor(GuiTheme::current().textMuted);
148}
149
150GuiLabel& GuiLabel::accent()
151{
152 return textColor(GuiTheme::current().accent);
153}
154
155GuiLabel& GuiLabel::bind(std::function<std::string()> fn)
156{
157 GuiLabel * self = this;
158 std::string last = "\x01"; // matches no real text, so the first evaluation always sets it
159 ui.bind([self, fn, last]() mutable {
160 const std::string s = fn();
161 if(s != last)
162 {
163 self->setText(textOrNull(s.c_str()));
164 last = s;
165 }
166 });
167 return *this;
168}
169
170GuiLabel& GuiLabel::visibleWhen(std::function<bool()> fn)
171{
172 ui.visibleWhen(*this, fn);
173 return *this;
174}
175
176// ---- GuiChip
177
178GuiChip::GuiChip(UiScope& scope, const char * t) :
179 ui(scope), backdrop(1, 1), label(scope, t)
180{
181 const GuiTheme& theme = GuiTheme::current();
182
183 backdrop.setCapsule();
184 backdrop.setStyle(theme.chipStyle());
185 label.fontSize(16);
186
187 setPadding(10, 3);
188 GuiLayout::add(&backdrop, GuiItem().background());
189 GuiLayout::add(&label);
190}
191
192GuiChip& GuiChip::bind(std::function<std::string()> fn)
193{
194 label.bind(fn);
195 return *this;
196}
197
198GuiChip& GuiChip::visibleWhen(std::function<bool()> fn)
199{
200 ui.visibleWhen(*this, fn);
201 return *this;
202}
203
204// ---- GuiTextButton
205
206GuiTextButton::GuiTextButton(UiScope& scope, const char * t) :
207 GuiButton(150, 44), ui(scope), shape(150, 44), text(scope, t), textLit(scope, t), wasSelected(false)
208{
209 const GuiTheme& theme = GuiTheme::current();
210
211 shape.setCapsule();
212 shape.setStyle(theme.buttonStyle());
213 textLit.textColor(theme.textOnAccent);
214 text.setAlignment(ALIGN_H::CENTRE, ALIGN_V::MIDDLE);
215 textLit.setAlignment(ALIGN_H::CENTRE, ALIGN_V::MIDDLE);
216
217 setImage(&shape);
218 setLabel(&text);
219 setLabelOver(&textLit);
220 setTrigger(scope.trigger());
221 setSoundOver(scope.getSoundOver());
222 setSoundClick(scope.getSoundClick());
224}
225
226GuiTextButton& GuiTextButton::size(int w, int h)
227{
228 width = w;
229 height = h;
230 shape.setLayoutSize(w, h);
231 return *this;
232}
233
234GuiTextButton& GuiTextButton::fontSize(int px)
235{
236 text.fontSize(px);
237 textLit.fontSize(px);
238 return *this;
239}
240
241void GuiTextButton::setLayoutSize(int w, int h)
242{
244 shape.setLayoutSize(w, h);
245}
246
247GuiTextButton& GuiTextButton::onClick(UiCallback fn)
248{
249 clickCb = fn;
250 return *this;
251}
252
253GuiTextButton& GuiTextButton::onFocus(UiCallback fn)
254{
255 focusCb = fn;
256 return *this;
257}
258
259GuiTextButton& GuiTextButton::style(const GuiShapeStyle& s)
260{
261 shape.setStyle(s);
262 return *this;
263}
264
265GuiTextButton& GuiTextButton::rounded(float radius)
266{
267 shape.setCorners(GuiCorners(radius));
268 return *this;
269}
270
271GuiTextButton& GuiTextButton::disabled(bool d)
272{
273 if(d)
274 setState(STATE::DISABLED);
275 else if(state == STATE::DISABLED)
276 setState(STATE::DEFAULT);
277 return *this;
278}
279
280void GuiTextButton::setText(const char * t)
281{
282 text.setText(textOrNull(t));
283 textLit.setText(textOrNull(t));
284}
285
286GuiTextButton& GuiTextButton::bind(std::function<std::string()> fn)
287{
288 text.bind(fn);
289 textLit.bind(fn);
290 return *this;
291}
292
293GuiTextButton& GuiTextButton::visibleWhen(std::function<bool()> fn)
294{
295 ui.visibleWhen(*this, fn);
296 return *this;
297}
298
299GuiTextButton& GuiTextButton::enabledWhen(std::function<bool()> fn)
300{
301 ui.enabledWhen(*this, fn);
302 return *this;
303}
304
305void GuiTextButton::update(InputController * c)
306{
308 pollButton(*this, wasSelected, clickCb, focusCb);
309}
310
311// ---- GuiCard
312
313GuiCardSurface::GuiCardSurface() : GuiButton(1, 1), shape(1, 1)
314{
315 setImage(&shape);
316}
317
318void GuiCardSurface::setLayoutSize(int w, int h)
319{
321 shape.setLayoutSize(w, h);
322}
323
324GuiCard::GuiCard(UiScope& scope) : ui(scope), wasSelected(false)
325{
326 const GuiTheme& theme = GuiTheme::current();
327
328 surface.shape.setCorners(GuiCorners(theme.radius));
329 surface.shape.setStyle(theme.cardStyle());
330 surface.setTrigger(scope.trigger());
331 surface.setSoundOver(scope.getSoundOver());
332 surface.setSoundClick(scope.getSoundClick());
333
334 setPadding(theme.padding);
335 GuiLayout::add(&surface, GuiItem().background());
336}
337
338GuiCard& GuiCard::onClick(UiCallback fn)
339{
340 clickCb = fn;
341 return *this;
342}
343
344GuiCard& GuiCard::onFocus(UiCallback fn)
345{
346 focusCb = fn;
347 return *this;
348}
349
350GuiCard& GuiCard::style(const GuiShapeStyle& s)
351{
352 surface.shape.setStyle(s);
353 return *this;
354}
355
356GuiCard& GuiCard::corners(const GuiCorners& c)
357{
358 surface.shape.setCorners(c);
359 return *this;
360}
361
362GuiCard& GuiCard::selectable(bool s)
363{
364 surface.setSelectable(s);
365 // an inert surface ignores the pointer too, so a plain panel never lights up
366 if(!s)
367 surface.setState(STATE::DISABLED);
368 else if(surface.getState() == STATE::DISABLED)
369 surface.setState(STATE::DEFAULT);
370 return *this;
371}
372
373GuiCard& GuiCard::disabled(bool d)
374{
375 // only the surface is disabled: a disabled GuiWindow would dim the whole screen
376 const bool off = surface.getState() == STATE::DISABLED;
377 if(d && !off)
378 surface.setState(STATE::DISABLED);
379 else if(!d && off)
380 surface.setState(STATE::DEFAULT);
381 return *this;
382}
383
384GuiCard& GuiCard::visibleWhen(std::function<bool()> fn)
385{
386 ui.visibleWhen(*this, fn);
387 return *this;
388}
389
390GuiCard& GuiCard::enabledWhen(std::function<bool()> fn)
391{
392 GuiCard * self = this;
393 ui.bind([self, fn]() { self->disabled(!fn()); });
394 return *this;
395}
396
397void GuiCard::update(InputController * c)
398{
400 pollButton(surface, wasSelected, clickCb, focusCb);
401}
Umbrella header for the platform-agnostic libgui core.
Display, manage, and manipulate buttons in the GUI.
Definition GuiButton.h:13
void resetState() override
Resets the state and mirrors it into the button's images and icons.
void update(InputController *c)
Constantly called to allow the GuiButton to respond to updated input data.
void setState(STATE s, int c=-1) override
Sets the state and mirrors it into the button's images and icons, so a state-aware element such as Gu...
bool isVisible()
Checks whether or not the element is visible.
int getStateChan()
Gets the controller channel that last changed the element's state.
STATE getState()
Gets the element's current state.
void setTrigger(GuiTrigger *t)
Set a new GuiTrigger for the element.
virtual void setState(STATE s, int c=-1)
Sets the element's state.
virtual void setVisible(bool v)
Sets the element's visibility.
virtual void update(InputController *c)
Called constantly to allow the element to respond to the current input data.
GuiElement()
Constructor.
Definition GuiElement.cpp:9
void setEffectGrow()
Shortcut to SetEffectOnOver(EFFECT::SCALE, 4, 110)
virtual void setLayoutSize(int w, int h)
Gives the element a size chosen by a container (default: sets width and height)
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
void append(GuiElement *e)
Appends a GuiElement to the GuiWindow.
Definition GuiWindow.cpp:31
Logical controller for one input channel.
void update(InputController *) override
Called constantly to allow the element to respond to the current input data.
Definition GuiUi.cpp:63
void draw() override
Called constantly to redraw the element.
Definition GuiUi.cpp:64
std::function< void()> UiCallback
A callback with no arguments.
Definition GuiUi.h:27
STATE
Interaction state of a GuiElement (default, selected, clicked, held, disabled).
Definition GuiElement.h:36
GuiShapeStyle cardStyle() const
A raised card: gradient surface, outline, drop shadow; a glowing accent outline when focused.
Definition GuiTheme.cpp:46
float padding
Space inside a card.
Definition GuiTheme.h:34
GuiShapeStyle chipStyle() const
A small label background (a "chip")
Definition GuiTheme.cpp:84
static GuiTheme & current()
The theme the rest of the library reads. Starts as the default dark theme.
Definition GuiTheme.cpp:35
GuiShapeStyle buttonStyle() const
A capsule or rounded button: lights up in the accent color when focused, darkens when pressed.
Definition GuiTheme.cpp:66
float radius
Corner radius of cards and panels.
Definition GuiTheme.h:32
PixelColor textOnAccent
Text drawn over the accent color.
Definition GuiTheme.h:28