libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiControllerDraw.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2026
4 * GuiControllerDraw.cpp
5 ***************************************************************************/
6
7#include "GuiControllerDraw.h"
8#include <cmath>
9#include <algorithm>
10
11static PixelColor rgb(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255)
12{
13 PixelColor c = { r, g, b, a };
14 return c;
15}
16
17static PixelColor withAlpha(PixelColor c, uint8_t a)
18{
19 c.a = a;
20 return c;
21}
22
23static PixelColor mixToWhite(PixelColor c, float t)
24{
25 c.r = (uint8_t)(c.r + (255 - c.r) * t);
26 c.g = (uint8_t)(c.g + (255 - c.g) * t);
27 c.b = (uint8_t)(c.b + (255 - c.b) * t);
28 return c;
29}
30
31void GuiBuildControllerBody(GuiMesh& mesh, const GuiControllerLayout& layout, float scale)
32{
33 const GuiTheme& theme = GuiTheme::current();
34 const float outline = std::max(1.5f, 2.0f * scale);
35 const GuiFill outlineFill = GuiFill::solid(theme.outlineStrong);
36 const GuiFill bodyFill = GuiFill::linear(GuiMixColor(theme.surface, theme.surfaceRaised, 0.3f), GuiMixColor(theme.background, theme.surface, 0.5f), 90);
37
38 std::vector<std::vector<GuiPoint> > pieces;
39 for(size_t i = 0; i < layout.body.size(); i++)
40 {
41 const GuiBodyPiece& b = layout.body[i];
42 GuiRect r = { b.x * scale, b.y * scale, b.w * scale, b.h * scale };
43 pieces.push_back(GuiRoundRectPolygon(r, b.radius * scale, b.angle));
44 }
45
46 // outlines first, fills after: where pieces overlap, the fill of one covers the outline of the other
47 for(size_t i = 0; i < pieces.size(); i++)
48 {
49 mesh.addPolygon(pieces[i], outlineFill);
50 mesh.strokePolygon(pieces[i], outline, STROKE::OUTSIDE, outlineFill);
51 }
52 for(size_t i = 0; i < pieces.size(); i++)
53 mesh.addPolygon(pieces[i], bodyFill);
54
55 // controls that never change appearance
56 for(size_t i = 0; i < layout.spots.size(); i++)
57 if(layout.spots[i].id == 0)
58 GuiBuildControllerSpot(mesh, layout.spots[i], CTL_STATE::NORMAL, scale);
59}
60
61void GuiBuildControllerSpot(GuiMesh& mesh, const GuiControlSpot& spot, CTL_STATE state, float scale)
62{
63 const GuiTheme& theme = GuiTheme::current();
64 const bool lit = state == CTL_STATE::LIT || state == CTL_STATE::LIT_HIGHLIGHT;
65 const bool hl = state == CTL_STATE::HIGHLIGHT || state == CTL_STATE::LIT_HIGHLIGHT;
66
67 const float stroke = std::max(1.2f, 1.5f * scale);
68 GuiRect r = { spot.x * scale, spot.y * scale, spot.w * scale, spot.h * scale };
69
70 PixelColor fill = spot.shape == CTL_SHAPE::STICK ? theme.background : theme.surface;
71 PixelColor edge = theme.outlineStrong;
72 if(lit)
73 {
74 fill = spot.shape == CTL_SHAPE::STICK ? theme.background : theme.good;
75 edge = mixToWhite(theme.good, 0.35f);
76 }
77 if(hl)
78 edge = theme.accent;
79
80 const float edgeWidth = hl ? stroke * 1.8f : stroke;
81 if(hl)
82 {
83 const float blur = 8.0f * std::max(scale, 0.8f);
84 mesh.addShadow(r, spot.shape == CTL_SHAPE::ROUNDRECT ? GuiCorners(spot.radius * scale) : GuiCorners(10000.0f), blur, 1.0f, 0.0f, 0.0f, withAlpha(theme.accent, 110));
85 }
86
87 if(spot.shape == CTL_SHAPE::ROUNDRECT)
88 {
89 mesh.addRoundRect(r, GuiCorners(spot.radius * scale), GuiFill::solid(fill));
90 mesh.strokeRoundRect(r, GuiCorners(spot.radius * scale), edgeWidth, STROKE::INSIDE, GuiFill::solid(edge));
91 }
92 else
93 {
94 mesh.addEllipse(r, GuiFill::solid(fill));
95 mesh.strokeEllipse(r, edgeWidth, STROKE::INSIDE, GuiFill::solid(edge));
96 if(spot.shape == CTL_SHAPE::STICK)
97 {
98 // a second ring inside, as on the real stick's gate
99 const float inset = r.w * 0.11f;
100 GuiRect inner = { r.x + inset, r.y + inset, r.w - 2.0f * inset, r.h - 2.0f * inset };
101 mesh.strokeEllipse(inner, std::max(1.0f, stroke * 0.7f), STROKE::INSIDE, GuiFill::solid(withAlpha(theme.outlineStrong, 150)));
102 }
103 }
104}
105
106void GuiBuildControllerStickCap(GuiMesh& mesh, const GuiControlSpot& spot, float x, float y, bool lit, float scale)
107{
108 const GuiTheme& theme = GuiTheme::current();
109 const float d = spot.w * scale * 0.56f;
110 const float travel = (spot.w * scale - d) * 0.5f * 0.85f;
111 // keep the cap inside the ring when the stick is pushed diagonally
112 float dx = x, dy = y;
113 const float len = sqrtf(dx * dx + dy * dy);
114 if(len > 1.0f)
115 {
116 dx /= len;
117 dy /= len;
118 }
119 const float cx = (spot.x + spot.w * 0.5f) * scale + dx * travel;
120 const float cy = (spot.y + spot.h * 0.5f) * scale + dy * travel;
121 GuiRect r = { cx - d * 0.5f, cy - d * 0.5f, d, d };
122
123 const PixelColor fill = lit ? theme.good : theme.outline;
124 const PixelColor edge = lit ? mixToWhite(theme.good, 0.35f) : theme.outlineStrong;
125 mesh.addEllipse(r, GuiFill::linear(mixToWhite(fill, 0.12f), fill, 90));
126 mesh.strokeEllipse(r, std::max(1.2f, 1.5f * scale), STROKE::INSIDE, GuiFill::solid(edge));
127}
Accumulates shapes as a triangle list of MeshVertex.
void addShadow(const GuiRect &r, const GuiCorners &corners, float blur, float spread, float dx, float dy, PixelColor color)
Adds a soft drop shadow or glow for a rounded rectangle.
void strokePolygon(const std::vector< GuiPoint > &pts, float width, STROKE align, const GuiFill &fill)
Adds a polygon outline with mitered corners (the miter is capped on very sharp corners).
void strokeEllipse(const GuiRect &r, float width, STROKE align, const GuiFill &fill)
Adds an ellipse outline.
void addRoundRect(const GuiRect &r, const GuiCorners &corners, const GuiFill &fill, bool edgeFringe=true)
Adds a filled rounded rectangle.
void addPolygon(const std::vector< GuiPoint > &pts, const GuiFill &fill, bool edgeFringe=true)
Adds a filled polygon (triangle, chevron, a controller silhouette, ...).
void strokeRoundRect(const GuiRect &r, const GuiCorners &corners, float width, STROKE align, const GuiFill &fill)
Adds a rounded-rectangle outline.
void addEllipse(const GuiRect &r, const GuiFill &fill, bool edgeFringe=true)
Adds a filled ellipse inscribed in a rectangle (a circle when w == h)
void GuiBuildControllerSpot(GuiMesh &mesh, const GuiControlSpot &spot, CTL_STATE state, float scale)
Builds one control in one state (an analog stick's cap is separate, see GuiBuildControllerStickCap).
void GuiBuildControllerBody(GuiMesh &mesh, const GuiControllerLayout &layout, float scale)
Builds the resting drawing of a controller (silhouette, then every decorative control) in local coord...
void GuiBuildControllerStickCap(GuiMesh &mesh, const GuiControlSpot &spot, float x, float y, bool lit, float scale)
Builds an analog stick's cap, displaced by (x, y) (each -1 to 1) from the middle of its ring.
CTL_STATE
The look a control is drawn in.
@ HIGHLIGHT
Being edited / pointed at.
@ LIT
Pressed.
@ ROUNDRECT
A rounded rectangle: bumpers, triggers, D-pad arms, small square buttons.
@ STICK
An analog stick: an outer ring with a cap that can move.
std::vector< GuiPoint > GuiRoundRectPolygon(const GuiRect &r, float radius, float angleDeg, float tolerance)
A rounded rectangle as a convex polygon, rotated about its center.
PixelColor GuiMixColor(PixelColor a, PixelColor b, float t)
Blends two colors per channel (alpha included): t = 0 gives a, t = 1 gives b.
Definition GuiPaint.cpp:250
@ INSIDE
Entirely inside the edge (the shape keeps its size)
Definition GuiGeometry.h:53
@ OUTSIDE
Entirely outside the edge.
Definition GuiGeometry.h:55
One rounded rectangle of the silhouette (the pieces are merged into one outline).
float angle
Rotation about its center, degrees clockwise.
float h
Rectangle before rotation.
One control on a controller drawing.
float h
Bounding box.
float radius
Corner radius for ROUNDRECT.
A controller: its drawing size, silhouette and controls.
Per-corner radii, in the order top-left, top-right, bottom-right, bottom-left.
Definition GuiGeometry.h:40
A solid color or a two-color linear gradient.
Definition GuiGeometry.h:60
static GuiFill linear(PixelColor a, PixelColor b, float angleDeg)
A linear gradient across the shape's bounding box.
static GuiFill solid(PixelColor c)
A single flat color.
An axis-aligned rectangle (top-left origin) in design-canvas pixels.
Definition GuiGeometry.h:30
float x
Left edge.
Definition GuiGeometry.h:31
float w
Width.
Definition GuiGeometry.h:33
float h
Height.
Definition GuiGeometry.h:34
float y
Top edge.
Definition GuiGeometry.h:32
Colors, metrics and the style recipes built from them.
Definition GuiTheme.h:16
static GuiTheme & current()
The theme the rest of the library reads. Starts as the default dark theme.
Definition GuiTheme.cpp:35
8-bit-per-channel RGBA color used by the video interfaces.
Definition Mesh.h:16
uint8_t g
Green color component.
Definition Mesh.h:18
uint8_t r
Red color component.
Definition Mesh.h:17
uint8_t b
Blue color component.
Definition Mesh.h:19
uint8_t a
Alpha component.
Definition Mesh.h:20