Skip to content

Commit d22d080

Browse files
committed
Add function to show QR Code of an entry's wiki link
1 parent 5189974 commit d22d080

File tree

4 files changed

+179
-0
lines changed

4 files changed

+179
-0
lines changed

include/overlays/overlay.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace Overlays {
3636
void ShowCredits();
3737
std::string SelectDir(const std::string &oldDir, const std::string &msg);
3838
void SelectTheme();
39+
void ShowQrCodeUrl(const std::string &title, const std::string &url);
3940
};
4041

4142
#endif

romfs/lang/en/app.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
"OP_INSTALLING": "Installing",
110110
"OP_MOVING": "Moving",
111111
"OP_WAITING": "Waiting",
112+
"QR_CODE_GEN_FAILED": "Failed to generate QR Code.",
112113
"QUEUE": "Queue",
113114
"QUEUE_POSITION": "Queue position",
114115
"QUEUE_PROGRESS": "Step: %d / %d",

source/menu/entryInfo.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626

2727
#include "common.hpp"
2828
#include "files.hpp"
29+
#include "overlay.hpp"
2930
#include "storeUtils.hpp"
3031
#include "structs.hpp"
3132

3233
extern bool touching(touchPosition touch, Structs::ButtonPos button);
3334
static const Structs::ButtonPos btn = { 45, 215, 24, 24 };
3435
static const Structs::ButtonPos sshot = { 75, 215, 24, 24 };
3536
static const Structs::ButtonPos notes = { 105, 215, 24, 24 };
37+
static const Structs::ButtonPos wikiLink = { 135, 215, 48, 24 };
3638
extern bool checkWifiStatus();
3739
extern bool exiting, QueueRuns;
3840

@@ -59,6 +61,10 @@ void StoreUtils::DrawEntryInfo(const std::unique_ptr<StoreEntry> &entry) {
5961
GFX::DrawBox(btn.x, btn.y, btn.w, btn.h, false);
6062
if (!entry->GetScreenshots().empty()) GFX::DrawIcon(sprites_screenshot_idx, sshot.x, sshot.y, UIThemes->TextColor());
6163
if (entry->GetReleaseNotes() != "") GFX::DrawIcon(sprites_notes_idx, notes.x, notes.y, UIThemes->TextColor());
64+
if (entry->GetWiki() != "") {
65+
GFX::DrawBox(wikiLink.x, wikiLink.y, wikiLink.w, wikiLink.h, false);
66+
Gui::DrawString(wikiLink.x + wikiLink.w / 2, wikiLink.y + 2, 0.6, UIThemes->TextColor(), "Wiki", wikiLink.w, 0, font, C2D_AlignCenter);
67+
}
6268
Gui::DrawString(btn.x + 5, btn.y + 2, 0.6f, UIThemes->TextColor(), "", 0, 0, font);
6369
}
6470
}
@@ -102,6 +108,10 @@ void StoreUtils::EntryHandle(bool &showMark, bool &fetch, bool &sFetch, int &mod
102108
mode = 7;
103109
}
104110
}
111+
112+
if (touching(touch, wikiLink) && entry->GetWiki() != "") {
113+
Overlays::ShowQrCodeUrl(entry->GetTitle() + " Wiki", entry->GetWiki());
114+
}
105115
}
106116

107117
/* Quit UU. */

source/overlays/showQrCode.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* This file is part of Universal-Updater
3+
* Copyright (C) 2019-2021 Universal-Team
4+
* Copyright (C) 2025 Alvin Wong
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
20+
* * Requiring preservation of specified reasonable legal notices or
21+
* author attributions in that material or in the Appropriate Legal
22+
* Notices displayed by works containing it.
23+
* * Prohibiting misrepresentation of the origin of that material,
24+
* or requiring that modified versions of such material be marked in
25+
* reasonable ways as different from the original version.
26+
*/
27+
28+
#include "animation.hpp"
29+
#include "common.hpp"
30+
#include "overlay.hpp"
31+
#include "qrcodegen.h"
32+
33+
extern bool touching(touchPosition touch, Structs::ButtonPos button);
34+
static const Structs::ButtonPos backButton{ 4, 0, 24, 24 };
35+
36+
static std::vector<std::string> wrapUrlLines(std::string_view url) {
37+
// Crudely line wrap the URL:
38+
std::vector<std::string> wrappedUrlLines;
39+
std::string line;
40+
size_t lastWrap = 0;
41+
for (size_t i = 0; i < url.size(); i++) {
42+
char c = url[i];
43+
line.push_back(c);
44+
45+
if (i - lastWrap < 30) continue;
46+
47+
bool wrap = false;
48+
switch (c) {
49+
case '-':
50+
case '/':
51+
case '_':
52+
case '?':
53+
case '=':
54+
case '&':
55+
case ' ':
56+
wrap = true;
57+
break;
58+
default:
59+
if (i - lastWrap >= 40) wrap = true;
60+
break;
61+
}
62+
63+
if (wrap) {
64+
if (wrappedUrlLines.size() >= 9) {
65+
line.append("...");
66+
break;
67+
}
68+
wrappedUrlLines.emplace_back(std::move(line));
69+
lastWrap = i;
70+
}
71+
}
72+
if (!line.empty()) wrappedUrlLines.emplace_back(std::move(line));
73+
return wrappedUrlLines;
74+
}
75+
76+
/* Show a QR Code URL. */
77+
void Overlays::ShowQrCodeUrl(const std::string &title, const std::string &url) {
78+
79+
std::unique_ptr<uint8_t[]> qrcode = std::make_unique<uint8_t[]>(qrcodegen_BUFFER_LEN_MAX);
80+
std::unique_ptr<uint8_t[]> tempBuffer = std::make_unique<uint8_t[]>(qrcodegen_BUFFER_LEN_MAX);
81+
bool ok = qrcodegen_encodeText(url.c_str(), tempBuffer.get(), qrcode.get(), qrcodegen_Ecc_LOW, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
82+
if (!ok) {
83+
Msg::DisplayMsg(Lang::get("QR_CODE_GEN_FAILED"));
84+
return;
85+
}
86+
87+
int size = qrcodegen_getSize(qrcode.get());
88+
if (size <= 0 || size > 256) {
89+
Msg::DisplayMsg(Lang::get("QR_CODE_GEN_FAILED"));
90+
return;
91+
}
92+
93+
C3D_Tex tex;
94+
95+
C3D_TexInit(&tex, 256, 256, GPU_TEXCOLOR::GPU_A8);
96+
C3D_TexSetFilter(&tex, GPU_NEAREST, GPU_NEAREST);
97+
C3D_TexSetWrap(&tex, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
98+
99+
for (u32 x = 0; x < size; x++) {
100+
for (u32 y = 0; y < size; y++) {
101+
const u32 dstPos = ((((y >> 3) * (256 >> 3) + (x >> 3)) << 6) +
102+
((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) |
103+
((x & 4) << 2) | ((y & 4) << 3)));
104+
105+
((uint8_t *)tex.data)[dstPos] = qrcodegen_getModule(qrcode.get(), x, y) ? 255 : 0;
106+
}
107+
}
108+
109+
constexpr int border = 4;
110+
int scale = 240 / (size + border * 2);
111+
int drawSize = size * scale;
112+
int drawX = (400 - drawSize) / 2;
113+
int drawY = (240 - drawSize) / 2;
114+
int drawBorder = border * scale;
115+
116+
C2D_Image qrImage;
117+
Tex3DS_SubTexture subtex{(u16)drawSize, (u16)drawSize, 0.0f, 1.0f, size / 256.f, 1.0f - (size / 256.f)};
118+
qrImage.tex = &tex;
119+
qrImage.subtex = &subtex;
120+
C2D_Tint tint{
121+
.color = BLACK,
122+
.blend = 1.f,
123+
};
124+
C2D_ImageTint imageTint{
125+
.corners = {tint, tint, tint, tint},
126+
};
127+
128+
std::vector<std::string> wrappedUrlLines = wrapUrlLines(url);
129+
130+
bool doOut = false;
131+
132+
while(!doOut) {
133+
Gui::clearTextBufs();
134+
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
135+
C2D_TargetClear(Top, TRANSPARENT);
136+
C2D_TargetClear(Bottom, TRANSPARENT);
137+
138+
GFX::DrawTop();
139+
140+
C2D_DrawRectSolid(drawX - drawBorder, drawY - drawBorder, 0.75f, drawSize + drawBorder * 2, drawSize + drawBorder * 2, WHITE);
141+
C2D_DrawImageAt(qrImage, drawX, drawY, 1.f, &imageTint);
142+
143+
Animation::QueueEntryDone();
144+
GFX::DrawBottom();
145+
Gui::Draw_Rect(0, 0, 320, 25, UIThemes->BarColor());
146+
Gui::Draw_Rect(0, 25, 320, 1, UIThemes->BarOutline());
147+
GFX::DrawIcon(sprites_arrow_idx, backButton.x, backButton.y, UIThemes->TextColor());
148+
Gui::DrawStringCentered(0, 2, 0.6, UIThemes->TextColor(), title, 310, 0, font);
149+
150+
int y = 34;
151+
for (const std::string &urlLine : wrappedUrlLines) {
152+
Gui::DrawStringCentered(0, y, 0.6f, UIThemes->TextColor(), urlLine, 312, 0, font);
153+
y += 20;
154+
if (y >= 240) break;
155+
}
156+
157+
C3D_FrameEnd(0);
158+
159+
hidScanInput();
160+
touchPosition touch;
161+
hidTouchRead(&touch);
162+
Animation::HandleQueueEntryDone();
163+
if ((hidKeysDown() & KEY_START) || (hidKeysDown() & KEY_B) || (hidKeysDown() & KEY_A) || (hidKeysDown() & KEY_TOUCH && touching(touch, backButton))) doOut = true;
164+
}
165+
166+
C3D_TexDelete(&tex);
167+
}

0 commit comments

Comments
 (0)