Skip to content

Commit cbd3d0d

Browse files
authored
Merge pull request #33 from rslite/poke-screen
Added a poke screen accessible by pressing 'P' in the emulator menu (same area with time travel and snapshot saving). Address and bytes can be entered in hex or decimal. Pressing 'H' will open a small help screen for which a separate MessageScreen was added. Smaller changes include extra color definitions and some helper functions for string drawing.
2 parents bd90119 + 8a1a594 commit cbd3d0d

File tree

7 files changed

+375
-4
lines changed

7 files changed

+375
-4
lines changed

firmware/src/Screens/EmulatorScreen.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "AlphabetPicker.h"
1010
#include "GameFilePickerScreen.h"
1111
#include "NavigationStack.h"
12+
#include "PokeScreen.h"
1213
#include "SaveSnapshotScreen.h"
1314
#include "EmulatorScreen/Renderer.h"
1415
#include "EmulatorScreen/Machine.h"
@@ -170,6 +171,9 @@ void EmulatorScreen::pressKey(SpecKeys key)
170171
renderer->isShowingMenu = false;
171172
// show the save snapshot UI
172173
m_navigationStack->push(new SaveSnapshotScreen(m_tft, m_hdmiDisplay, m_audioOutput, machine->getMachine(), m_files));
174+
} else if (key == SPECKEY_P) {
175+
renderer->isShowingMenu = false;
176+
m_navigationStack->push(new PokeScreen(m_tft, m_hdmiDisplay, m_audioOutput, machine->getMachine()));
173177
} else if (key == SPECKEY_SPACE || key == SPECKEY_ENTER) {
174178
renderer->isShowingMenu = false;
175179
machine->resume();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include "MessageScreen.h"
2+
3+
void MessageScreen::updateDisplay()
4+
{
5+
m_tft.startWrite();
6+
m_tft.fillRect(0, 0, m_tft.width(), m_tft.height(), TFT_BLACK);
7+
m_tft.drawRect(0, 0, m_tft.width(), m_tft.height(), TFT_WHITE);
8+
9+
m_tft.loadFont(GillSans_25_vlw);
10+
m_tft.drawCenterString(m_title.c_str(), 5);
11+
12+
m_tft.loadFont(GillSans_15_vlw);
13+
int16_t y = 30;
14+
for (int i=0; i<m_messages.size(); i++){
15+
m_tft.drawString(m_messages[i].c_str(), 5, y);
16+
y += 20;
17+
}
18+
19+
m_tft.endWrite();
20+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#pragma once
2+
3+
#include "NavigationStack.h"
4+
#include "Screen.h"
5+
#include "../TFT/TFTDisplay.h"
6+
#include "fonts/GillSans_25_vlw.h"
7+
#include "fonts/GillSans_15_vlw.h"
8+
9+
class IFiles;
10+
11+
class MessageScreen : public Screen
12+
{
13+
private:
14+
std::string m_title;
15+
std::vector<std::string> m_messages;
16+
public:
17+
MessageScreen(
18+
std::string title,
19+
std::vector<std::string> messages,
20+
Display &tft,
21+
HDMIDisplay *hdmiDisplay,
22+
AudioOutput *audioOutput) : m_title{title}, m_messages{messages}, Screen(tft, hdmiDisplay, audioOutput, nullptr)
23+
{
24+
}
25+
26+
void didAppear()
27+
{
28+
updateDisplay();
29+
}
30+
31+
void pressKey(SpecKeys key)
32+
{
33+
m_navigationStack->pop();
34+
}
35+
36+
void updateDisplay();
37+
};
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#include "MessageScreen.h"
2+
#include "NavigationStack.h"
3+
#include "PokeScreen.h"
4+
#include "Screen.h"
5+
#include "../TFT/TFTDisplay.h"
6+
#include "fonts/GillSans_25_vlw.h"
7+
#include "fonts/GillSans_15_vlw.h"
8+
#include "../Emulator/spectrum.h"
9+
#include "../Emulator/snaps.h"
10+
11+
#include <sstream>
12+
13+
#define CURSOR_COLOR TFT_CYAN
14+
15+
void PokeScreen::pressKey(SpecKeys key)
16+
{
17+
std::string ctrl = "";
18+
size_t l = 0;
19+
int16_t addr = 0;
20+
std::vector<byte> bytes;
21+
int base = 10;
22+
23+
switch (key)
24+
{
25+
case JOYK_FIRE:
26+
case SPECKEY_ENTER:
27+
if (s_addr.length()<3 || s_data.length()==0){
28+
playErrorBeep();
29+
return;
30+
}
31+
addr = get_num(s_addr, 10);
32+
base = (s_addr[1]=='x') ? 16 : 10;
33+
bytes = get_bytes(s_data, base);
34+
for (int i=0; i<bytes.size(); i++){
35+
machine->z80_poke(addr+i, bytes[i]);
36+
}
37+
updateDisplay();
38+
break;
39+
case SPECKEY_H:
40+
{
41+
m_navigationStack->push(new MessageScreen("HELP", {
42+
"Enter the address and the data to be poked.",
43+
"If Addr starts with 0x values are hex, else decimal.",
44+
"Data is space separated.",
45+
"",
46+
"T - switch btween Addr and Data",
47+
"Z/Delete - Delete character",
48+
"Enter - poke data",
49+
"Q/Break - Quit",
50+
}, m_tft, m_hdmiDisplay, m_audioOutput));
51+
break;
52+
}
53+
case SPECKEY_Z:
54+
case SPECKEY_DEL:
55+
ctrl = getControlString();
56+
l = ctrl.length();
57+
if (l == 0){
58+
playErrorBeep();
59+
return;
60+
}
61+
ctrl.pop_back();
62+
setControlString(ctrl);
63+
updateDisplay();
64+
break;
65+
case SPECKEY_T:
66+
active_control = 1 - active_control;
67+
updateDisplay();
68+
break;
69+
case SPECKEY_Q:
70+
case SPECKEY_BREAK:
71+
m_navigationStack->pop();
72+
return;
73+
default:
74+
if (specKeyToLetter.find(key) == specKeyToLetter.end() && key != SPECKEY_SPACE)
75+
break;
76+
char chr = (key==SPECKEY_SPACE) ? ' ' : specKeyToLetter.at(key);
77+
if (chr == 'X'){
78+
bool bad = false;
79+
if (active_control == 1){
80+
// Only needs to be used for address. Bytes will be the same base
81+
bad = true;
82+
} else if (s_addr.length()!=1 || s_addr[0] != '0'){
83+
// Can only use after a 0 in the first position
84+
bad = true;
85+
}
86+
if (bad){
87+
playErrorBeep();
88+
return;
89+
}
90+
// Convert to lowercase
91+
s_addr.push_back('x');
92+
updateDisplay();
93+
return;
94+
}
95+
if (chr == ' '){
96+
// Can only be used to separate data
97+
if (active_control == 0){
98+
playErrorBeep();
99+
return;
100+
}
101+
s_data.push_back(chr);
102+
updateDisplay();
103+
return;
104+
}
105+
if (isHexadecimalDigit(chr)) {
106+
ctrl = getControlString();
107+
ctrl.push_back(chr);
108+
setControlString(ctrl);
109+
playKeyClick();
110+
updateDisplay();
111+
return;
112+
}
113+
}
114+
};
115+
116+
void PokeScreen::updateDisplay()
117+
{
118+
m_tft.loadFont(GillSans_15_vlw);
119+
m_tft.startWrite();
120+
m_tft.fillRect(0, 0, m_tft.width(), m_tft.height(), TFT_BLACK);
121+
m_tft.drawRect(0, 0, m_tft.width(), m_tft.height(), TFT_WHITE);
122+
m_tft.setTextColor(TFT_CYAN, TFT_BLACK);
123+
m_tft.drawString("Poke Screen (H for help)", 2, 2);
124+
125+
m_tft.setTextColor(TFT_WHITE, TFT_BLACK);
126+
const char* c_addr = "Addr:";
127+
auto textSize = m_tft.measureString(c_addr);
128+
const int16_t edit_y = textSize.x + 7;
129+
int16_t cursor_x;
130+
131+
m_tft.drawString(c_addr, 2, 20);
132+
cursor_x = m_tft.drawStringAndMeasure(s_addr.c_str(), edit_y, 20);
133+
if (active_control == 0){
134+
m_tft.fillRect(cursor_x, 20, 2, textSize.y, CURSOR_COLOR);
135+
}
136+
137+
m_tft.drawString("Data:", 2, 40);
138+
cursor_x = m_tft.drawStringAndMeasure(s_data.c_str(), edit_y, 40);
139+
if (active_control == 1){
140+
m_tft.fillRect(cursor_x, 40, 2, textSize.y, CURSOR_COLOR);
141+
}
142+
143+
uint16_t addr;
144+
if (s_addr.length()<3)
145+
addr = 0;
146+
else
147+
addr = get_num(s_addr, 10);
148+
149+
char buf[16];
150+
bool is_hex = s_addr[1] == 'x';
151+
std::vector<byte> pokes;
152+
if (s_data.length()){
153+
// Base doesn't really matter here - doing this just to count the bytes
154+
pokes = get_bytes(s_data, 16);
155+
}
156+
157+
uint16_t poke_len = pokes.size();
158+
if (poke_len == 0){
159+
poke_len = 1;
160+
}
161+
uint16_t start_addr = addr - 4;
162+
uint16_t end_addr = addr + poke_len + 4;
163+
164+
m_tft.setTextColor(TFT_YELLOW, TFT_BLACK);
165+
cursor_x = m_tft.drawStringAndMeasure("Bytes at ", 2, 60);
166+
if (is_hex)
167+
sprintf(buf, "%04X", addr);
168+
else
169+
sprintf(buf, "%d", addr);
170+
cursor_x = m_tft.drawStringAndMeasure(buf, cursor_x, 60);
171+
cursor_x = 2;
172+
173+
for (int i=start_addr; i<end_addr; i++){
174+
auto b = machine->z80_peek(i);
175+
if (is_hex)
176+
sprintf(buf, "%02X", b);
177+
else
178+
sprintf(buf, "%d", b);
179+
if (i<addr || i>=addr+poke_len)
180+
m_tft.setTextColor(TFT_WHITE, TFT_BLACK);
181+
else
182+
m_tft.setTextColor(TFT_YELLOW, TFT_BLACK);
183+
cursor_x = m_tft.drawStringAndMeasure(buf, cursor_x, 80) + 5;
184+
}
185+
186+
m_tft.endWrite();
187+
}
188+
189+
std::vector<std::string> PokeScreen::split(const std::string &s, char delim){
190+
std::vector<std::string> result;
191+
std::stringstream ss(s);
192+
std::string item;
193+
194+
while (getline(ss, item, delim)){
195+
result.push_back(item);
196+
}
197+
return result;
198+
}
199+
200+
// Parse a string as a number with the specified base.
201+
// If the string starts with 0x the base is ignored and the
202+
// string is parsed as hex
203+
int16_t PokeScreen::get_num(const std::string &s, int base){
204+
if (s[0] == '0' && s[1] == 'x'){
205+
base = 16;
206+
}
207+
return std::stoi(s, nullptr, base);
208+
}
209+
210+
// Parse a string of bytes delimited by single spaces
211+
std::vector<byte> PokeScreen::get_bytes(const std::string &s, int base){
212+
std::vector<byte> result;
213+
auto s_bytes = split(s, ' ');
214+
215+
for (const std::string& s_byte: s_bytes){
216+
if (s_byte.length()==0)
217+
continue;
218+
auto b = get_num(s_byte, base) & 0xFF;
219+
result.push_back(b);
220+
}
221+
return result;
222+
}
223+
224+
std::string PokeScreen::getControlString(){
225+
switch(active_control){
226+
case 0:
227+
return s_addr;
228+
default:
229+
return s_data;
230+
}
231+
}
232+
233+
void PokeScreen::setControlString(std::string str){
234+
switch(active_control){
235+
case 0:
236+
s_addr = str;
237+
break;
238+
default:
239+
s_data = str;
240+
break;
241+
}
242+
}

firmware/src/Screens/PokeScreen.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#pragma once
2+
3+
#include "MessageScreen.h"
4+
#include "NavigationStack.h"
5+
#include "Screen.h"
6+
#include "../TFT/TFTDisplay.h"
7+
#include "fonts/GillSans_25_vlw.h"
8+
#include "fonts/GillSans_15_vlw.h"
9+
#include "../Emulator/spectrum.h"
10+
#include "../Emulator/snaps.h"
11+
12+
13+
class IFiles;
14+
15+
class PokeScreen : public Screen
16+
{
17+
private:
18+
ZXSpectrum *machine = nullptr;
19+
std::string s_addr = "0x5800";
20+
std::string s_data = "";
21+
byte active_control = 0;
22+
public:
23+
PokeScreen(
24+
Display &tft,
25+
HDMIDisplay *hdmiDisplay,
26+
AudioOutput *audioOutput,
27+
ZXSpectrum *machine) : machine(machine), Screen(tft, hdmiDisplay, audioOutput, nullptr)
28+
{
29+
}
30+
31+
void didAppear()
32+
{
33+
updateDisplay();
34+
}
35+
36+
void pressKey(SpecKeys key);
37+
void updateDisplay();
38+
39+
private:
40+
std::vector<std::string> split(const std::string &s, char delim);
41+
42+
// Parse a string as a number with the specified base (10 if not specified).
43+
// If the string starts with 0x the base is ignored and the
44+
// string is parsed as hex
45+
int16_t get_num(const std::string &s, int base);
46+
47+
// Parse a string of bytes delimited by single spaces
48+
std::vector<byte> get_bytes(const std::string &s, int base);
49+
50+
std::string getControlString();
51+
void setControlString(std::string str);
52+
};

firmware/src/TFT/Display.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ class Display
8989
// not implemented here - maybe somewhere else?
9090
}
9191
virtual void drawPixel(uint16_t color, int x, int y);
92+
// Draw a centered string at y
93+
void drawCenterString(const char *string, int16_t y){
94+
Point menuSize = measureString(string);
95+
int centerX = (_width - menuSize.x) / 2;
96+
drawString(string, centerX, y);
97+
}
98+
// Draw a string and return the rightmost x
99+
int16_t drawStringAndMeasure(const char *string, int16_t x, int16_t y){
100+
Point size = measureString(string);
101+
drawString(string, x, y);
102+
return x + size.x;
103+
}
92104
protected:
93105
int _width;
94106
int _height;

0 commit comments

Comments
 (0)