mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-01 07:20:42 +01:00
UI code reformatting, add PN532 NFC driver
Some checks failed
Build / Run build (push) Failing after 5m49s
Some checks failed
Build / Run build (push) Failing after 5m49s
This commit is contained in:
parent
7faa1612e0
commit
4667feb240
@ -70,6 +70,7 @@ set(
|
|||||||
set(
|
set(
|
||||||
exthwSources
|
exthwSources
|
||||||
src/common/exthw/lcd.cpp
|
src/common/exthw/lcd.cpp
|
||||||
|
src/common/exthw/nfc.cpp
|
||||||
)
|
)
|
||||||
set(
|
set(
|
||||||
fsSources
|
fsSources
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
* 573in1 - Copyright (C) 2022-2025 spicyjpeg
|
||||||
*
|
*
|
||||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||||
* terms of the GNU General Public License as published by the Free Software
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
@ -40,18 +40,25 @@
|
|||||||
#define CH_RIGHT_ARROW_ALT "\u2192"
|
#define CH_RIGHT_ARROW_ALT "\u2192"
|
||||||
#define CH_INVALID_CHAR "\ufffd"
|
#define CH_INVALID_CHAR "\ufffd"
|
||||||
|
|
||||||
#define CH_CIRCLE_BUTTON "\u25cb"
|
#define CH_PS1_CIRCLE_BUTTON "\u25cb"
|
||||||
#define CH_X_BUTTON "\u2715"
|
#define CH_PS1_X_BUTTON "\u2715"
|
||||||
#define CH_TRIANGLE_BUTTON "\u25b3"
|
#define CH_PS1_TRIANGLE_BUTTON "\u25b3"
|
||||||
#define CH_SQUARE_BUTTON "\u25a1"
|
#define CH_PS1_SQUARE_BUTTON "\u25a1"
|
||||||
#define CH_LEFT_BUTTON "\u25c1"
|
#define CH_PS1_SELECT_BUTTON "\u25ac"
|
||||||
#define CH_RIGHT_BUTTON "\u25b7"
|
#define CH_PS1_START_BUTTON "\u25ba"
|
||||||
#define CH_START_BUTTON "\u25ad"
|
|
||||||
#define CH_CLOSED_LOCK "\U0001f512"
|
#define CH_LEFT_BUTTON "\u25c1"
|
||||||
#define CH_OPEN_LOCK "\U0001f513"
|
#define CH_RIGHT_BUTTON "\u25b7"
|
||||||
#define CH_CDROM_ICON "\U0001f4bf"
|
#define CH_START_BUTTON "\u25ad"
|
||||||
#define CH_HDD_ICON "\U0001f4bd"
|
|
||||||
#define CH_HOST_ICON "\U0001f50c"
|
#define CH_CLOSED_LOCK "\U0001f512"
|
||||||
#define CH_DIR_ICON "\U0001f4c1"
|
#define CH_OPEN_LOCK "\U0001f513"
|
||||||
#define CH_PARENT_DIR_ICON "\U0001f4c2"
|
|
||||||
#define CH_FILE_ICON "\U0001f4c4"
|
#define CH_CDROM_ICON "\U0001f4bf"
|
||||||
|
#define CH_HDD_ICON "\U0001f4bd"
|
||||||
|
#define CH_MEMORY_CARD_ICON "\U0001f4be"
|
||||||
|
#define CH_POCKETSTATION_ICON "\U0001f4f1"
|
||||||
|
#define CH_HOST_ICON "\U0001f50c"
|
||||||
|
#define CH_DIR_ICON "\U0001f4c1"
|
||||||
|
#define CH_PARENT_DIR_ICON "\U0001f4c2"
|
||||||
|
#define CH_FILE_ICON "\U0001f4c4"
|
||||||
|
@ -24,8 +24,8 @@
|
|||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is a simple driver for a character LCD module, wired to the EXT-OUT
|
* This is a simple driver for a standard character LCD module, wired to the
|
||||||
* connector on the 573 main board as follows:
|
* EXT-OUT (CN4) header on the 573 main board as follows:
|
||||||
*
|
*
|
||||||
* | EXT-OUT pin | LCD pin |
|
* | EXT-OUT pin | LCD pin |
|
||||||
* | ----------: | :----------- |
|
* | ----------: | :----------- |
|
||||||
@ -40,6 +40,8 @@
|
|||||||
*
|
*
|
||||||
* The `V0` (bias voltage) pin shall be connected to ground through an
|
* The `V0` (bias voltage) pin shall be connected to ground through an
|
||||||
* appropriate resistor or potentiometer in order to set the display's contrast.
|
* appropriate resistor or potentiometer in order to set the display's contrast.
|
||||||
|
* The backlight is not controlled by this driver and can be left unconnected or
|
||||||
|
* hardwired to power.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace exthw {
|
namespace exthw {
|
||||||
|
411
src/common/exthw/nfc.cpp
Normal file
411
src/common/exthw/nfc.cpp
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 spicyjpeg
|
||||||
|
*
|
||||||
|
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "common/exthw/nfc.hpp"
|
||||||
|
#include "common/util/log.hpp"
|
||||||
|
#include "common/util/templates.hpp"
|
||||||
|
#include "common/io.hpp"
|
||||||
|
#include "ps1/system.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a simple driver for a PN532 NFC reader module connected to the 573's
|
||||||
|
* serial port. Currently the only supported feature is reading the ID of a
|
||||||
|
* Mifare or FeliCa card. The module can be wired to the cartridge slot, or to
|
||||||
|
* the unpopulated CN24 header on main board revisions that have it, as follows:
|
||||||
|
*
|
||||||
|
* | CN24 pin | Cart slot pin | Module pin |
|
||||||
|
* | -------: |- ------------: | :------------------------------------------- |
|
||||||
|
* | | 21, 22, 41, 42 | `VCC` (via 3.3V regulator, see note) |
|
||||||
|
* | 1 | 5 | `SCL`/`HSU_RX` (via level shifter, see note) |
|
||||||
|
* | 2 | 6 | `SDA`/`HSU_TX` (via level shifter, see note) |
|
||||||
|
* | 3, 4 | 1, 2, 8, 9 | `GND`, `I0`, `I1` |
|
||||||
|
* | 5, 6 | 43, 44 | None (short pins together on 573 side) |
|
||||||
|
*
|
||||||
|
* The PN532 operates at 3.3V so a voltage regulator and level shifter are
|
||||||
|
* required to adapt it to the 573's 5V signals (some modules already include
|
||||||
|
* them and can thus be wired directly). The module must be configured for HSU
|
||||||
|
* (serial) mode through the appropriate jumpers or DIP switches, or by
|
||||||
|
* grounding the `I0` and `I1` pins.
|
||||||
|
*
|
||||||
|
* Alternatively the PN532 module may be connected through an RS-232 level
|
||||||
|
* translator to the "network" port on the security cartridge (if any):
|
||||||
|
*
|
||||||
|
* | "Network" pin | Module pin |
|
||||||
|
* | ------------: | :-------------------------------------- |
|
||||||
|
* | 1 | `SCL`/`HSU_RX` (via RS-232 transceiver) |
|
||||||
|
* | 2 | `SDA`/`HSU_TX` (via RS-232 transceiver) |
|
||||||
|
* | 5 | `GND`, `I0`, `I1` |
|
||||||
|
*
|
||||||
|
* The module and transceiver will have to be powered from an external source as
|
||||||
|
* the "network" port is galvanically isolated from the rest of the system.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace exthw {
|
||||||
|
|
||||||
|
/* PN532 packet structures */
|
||||||
|
|
||||||
|
void PN532PacketHeader::updateChecksum(void) {
|
||||||
|
#if 0
|
||||||
|
lengthChecksum = -int(length) & 0xff;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto data = &address;
|
||||||
|
data[length] = -int(util::sum(data, length)) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532PacketHeader::validateChecksum(void) const {
|
||||||
|
if (lengthChecksum != (-int(length) & 0xff))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto data = &address;
|
||||||
|
uint8_t value = -int(util::sum(data, length)) & 0xff;
|
||||||
|
|
||||||
|
if (value != data[length]) {
|
||||||
|
LOG_IO("mismatch, exp=0x%02x, got=0x%02x", value, data[length]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PN532PacketHeader::encodeCommand(int paramLength) {
|
||||||
|
paramLength += 2;
|
||||||
|
|
||||||
|
preamble = PN532_PACKET_PREAMBLE;
|
||||||
|
startCode[0] = PN532_PACKET_START1;
|
||||||
|
startCode[1] = PN532_PACKET_START2;
|
||||||
|
length = paramLength & 0xff;
|
||||||
|
lengthChecksum = -paramLength & 0xff;
|
||||||
|
address = PN532_ADDR_DEVICE;
|
||||||
|
|
||||||
|
updateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532PacketHeader::decodeResponse(void) const {
|
||||||
|
if (
|
||||||
|
(startCode[0] != PN532_PACKET_START1) ||
|
||||||
|
(startCode[1] != PN532_PACKET_START2)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if (address != PN532_ADDR_HOST)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return validateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532PacketHeader::isErrorResponse(void) const {
|
||||||
|
if (
|
||||||
|
(startCode[0] != PN532_PACKET_START1) ||
|
||||||
|
(startCode[1] != PN532_PACKET_START2)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if ((length != 1) || (lengthChecksum != 0xff))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return (address == PN532_ADDR_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PN532ExtPacketHeader::updateChecksum(void) {
|
||||||
|
#if 0
|
||||||
|
lengthChecksum = -int(length[0] + length[1]) & 0xff;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto data = &address;
|
||||||
|
auto length = getDataLength();
|
||||||
|
data[length] = -int(util::sum(data, length)) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532ExtPacketHeader::validateChecksum(void) const {
|
||||||
|
if (lengthChecksum != (-int(length[0] + length[1]) & 0xff))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto data = &address;
|
||||||
|
auto _length = getDataLength();
|
||||||
|
uint8_t value = -int(util::sum(data, _length)) & 0xff;
|
||||||
|
|
||||||
|
if (value != data[_length]) {
|
||||||
|
LOG_IO("mismatch, exp=0x%02x, got=0x%02x", value, data[_length]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PN532ExtPacketHeader::encodeCommand(int paramLength) {
|
||||||
|
paramLength += 2;
|
||||||
|
|
||||||
|
preamble = PN532_PACKET_PREAMBLE;
|
||||||
|
startCode[0] = PN532_PACKET_START1;
|
||||||
|
startCode[1] = PN532_PACKET_START2;
|
||||||
|
packetMagic[0] = 0xff;
|
||||||
|
packetMagic[1] = 0xff;
|
||||||
|
length[0] = (paramLength >> 8) & 0xff;
|
||||||
|
length[1] = (paramLength >> 0) & 0xff;
|
||||||
|
lengthChecksum = -int(length[0] + length[1]) & 0xff;
|
||||||
|
address = PN532_ADDR_DEVICE;
|
||||||
|
|
||||||
|
updateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532ExtPacketHeader::decodeResponse(void) const {
|
||||||
|
if (
|
||||||
|
(startCode[0] != PN532_PACKET_START1) ||
|
||||||
|
(startCode[1] != PN532_PACKET_START2)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if ((packetMagic[0] != 0xff) || (packetMagic[1] != 0xff))
|
||||||
|
return false;
|
||||||
|
if (address != PN532_ADDR_HOST)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return validateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PN532ACKPacket::encodeACK(bool isACK) {
|
||||||
|
preamble = PN532_PACKET_PREAMBLE;
|
||||||
|
startCode[0] = PN532_PACKET_START1;
|
||||||
|
startCode[1] = PN532_PACKET_START2;
|
||||||
|
packetMagic[0] = isACK ? 0x00 : 0xff;
|
||||||
|
packetMagic[1] = isACK ? 0xff : 0x00;
|
||||||
|
postamble = PN532_PACKET_PREAMBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532ACKPacket::decodeACK(void) const {
|
||||||
|
if (
|
||||||
|
(startCode[0] != PN532_PACKET_START1) ||
|
||||||
|
(startCode[1] != PN532_PACKET_START2)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
if ((packetMagic[0] != 0x00) || (packetMagic[1] != 0xff))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PN532 driver */
|
||||||
|
|
||||||
|
static constexpr int _POWER_DOWN_DELAY = 1000;
|
||||||
|
static constexpr int _WAKEUP_DELAY = 1000;
|
||||||
|
static constexpr int _ACK_TIMEOUT = 1000;
|
||||||
|
static constexpr int _RESPONSE_TIMEOUT = 3000000;
|
||||||
|
|
||||||
|
static constexpr int _DEFAULT_BAUD_RATE = 115200;
|
||||||
|
static constexpr int _MAX_SEND_ATTEMPTS = 3;
|
||||||
|
static constexpr int _MAX_RECEIVE_ATTEMPTS = 3;
|
||||||
|
|
||||||
|
bool PN532Driver::_transact(
|
||||||
|
const PN532PacketHeader &request,
|
||||||
|
PN532PacketHeader &response,
|
||||||
|
size_t maxRespLength
|
||||||
|
) {
|
||||||
|
if (!_serial.isConnected()) {
|
||||||
|
LOG_IO("serial port not connected");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_isIdle) {
|
||||||
|
// If the PN532 is powered down, it must be woken up by sending at least
|
||||||
|
// 5 rising edges on TX before it can accept a new command.
|
||||||
|
_serial.writeByte(PN532_PACKET_PREAMBLE);
|
||||||
|
delayMicroseconds(_WAKEUP_DELAY);
|
||||||
|
|
||||||
|
_isIdle = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep sending the request until an acknowledge packet is received.
|
||||||
|
for (int i = _MAX_SEND_ATTEMPTS; i; i--) {
|
||||||
|
_serial.writeBytes(&request.preamble, request.getPacketLength());
|
||||||
|
|
||||||
|
PN532ACKPacket ack;
|
||||||
|
|
||||||
|
if (
|
||||||
|
_serial.readBytes(&ack.preamble, sizeof(ack), _ACK_TIMEOUT)
|
||||||
|
< sizeof(ack)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
if (!ack.decodeACK())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Wait for a response, then validate it and send a NACK to request
|
||||||
|
// retransmission if needed.
|
||||||
|
for (int i = _MAX_RECEIVE_ATTEMPTS; i; i--) {
|
||||||
|
if (_serial.readBytes(
|
||||||
|
&response.preamble,
|
||||||
|
maxRespLength,
|
||||||
|
_RESPONSE_TIMEOUT
|
||||||
|
) >= sizeof(response)) {
|
||||||
|
if (response.decodeResponse())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (response.isErrorResponse()) {
|
||||||
|
LOG_IO("PN532 error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ack.encodeACK(false);
|
||||||
|
_serial.writeBytes(&ack.preamble, sizeof(ack));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_IO("too many receive attempts failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_IO("too many send attempts failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532Driver::init(void) {
|
||||||
|
_serial.init(_DEFAULT_BAUD_RATE);
|
||||||
|
_isIdle = true;
|
||||||
|
|
||||||
|
PN532Packet<4> packet;
|
||||||
|
|
||||||
|
packet.command = PN532_GET_FIRMWARE_VERSION;
|
||||||
|
packet.encodeCommand(0);
|
||||||
|
|
||||||
|
if (!_transact(packet, packet))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (packet.param[0] != 0x32) {
|
||||||
|
LOG_IO("unsupported NFC chip, id=0x%02x", packet.param[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_IO("found PN532 v%d.%d", packet.param[1], packet.param[2]);
|
||||||
|
|
||||||
|
// This command is required to exit "low VBAT" mode.
|
||||||
|
packet.command = PN532_SAM_CONFIG;
|
||||||
|
packet.param[0] = PN532_SAM_MODE_NORMAL;
|
||||||
|
packet.encodeCommand(1);
|
||||||
|
|
||||||
|
if (!_transact(packet, packet))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return setMaxRetries(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532Driver::setMaxRetries(int count) {
|
||||||
|
if (count < 0)
|
||||||
|
count = 0xff;
|
||||||
|
|
||||||
|
PN532Packet<4> packet;
|
||||||
|
|
||||||
|
packet.command = PN532_RF_CONFIG;
|
||||||
|
packet.param[0] = PN532_RF_CONFIG_MAX_RTY_COM;
|
||||||
|
packet.param[1] = count;
|
||||||
|
packet.encodeCommand(2);
|
||||||
|
|
||||||
|
if (!_transact(packet, packet))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
packet.command = PN532_RF_CONFIG;
|
||||||
|
packet.param[0] = PN532_RF_CONFIG_MAX_RETRIES;
|
||||||
|
packet.param[1] = count;
|
||||||
|
packet.param[2] = count;
|
||||||
|
packet.param[3] = count;
|
||||||
|
packet.encodeCommand(4);
|
||||||
|
|
||||||
|
return _transact(packet, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PN532Driver::goIdle(void) {
|
||||||
|
PN532Packet<1> packet;
|
||||||
|
|
||||||
|
packet.command = PN532_POWER_DOWN;
|
||||||
|
packet.param[0] = PN532_WAKEUP_SOURCE_HSU;
|
||||||
|
packet.encodeCommand(1);
|
||||||
|
|
||||||
|
if (!_transact(packet, packet))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
delayMicroseconds(_POWER_DOWN_DELAY);
|
||||||
|
_isIdle = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PN532Driver::readISO14443CardID(uint8_t *output) {
|
||||||
|
PN532Packet<1 + 11> packet;
|
||||||
|
|
||||||
|
packet.command = PN532_IN_LIST_TARGETS;
|
||||||
|
packet.param[0] = 1;
|
||||||
|
packet.param[1] = PN532_LIST_TARGETS_ISO14443A;
|
||||||
|
packet.encodeCommand(2);
|
||||||
|
|
||||||
|
if (!_transact(packet, packet))
|
||||||
|
return 0;
|
||||||
|
if (!packet.param[0])
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto atqa = util::concat2(packet.param[2], packet.param[1]);
|
||||||
|
auto saq = packet.param[3];
|
||||||
|
auto idLength = packet.param[4];
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
char buffer[32];
|
||||||
|
|
||||||
|
util::hexToString(buffer, &packet.param[5], idLength, '-');
|
||||||
|
LOG_IO("%s", buffer);
|
||||||
|
LOG_IO("atqa=0x%04x, saq=0x%02x", atqa, saq);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__builtin_memcpy(output, &packet.param[5], idLength);
|
||||||
|
return idLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PN532Driver::readFeliCaCardID(uint8_t *output, uint16_t systemCode) {
|
||||||
|
PN532Packet<1 + 20> packet;
|
||||||
|
|
||||||
|
packet.command = PN532_IN_LIST_TARGETS;
|
||||||
|
packet.param[0] = 1;
|
||||||
|
packet.param[1] = PN532_LIST_TARGETS_FELICA_212;
|
||||||
|
packet.param[2] = FELICA_POLL;
|
||||||
|
packet.param[3] = (systemCode >> 8) & 0xff;
|
||||||
|
packet.param[4] = (systemCode >> 0) & 0xff;
|
||||||
|
packet.param[5] = FELICA_REQ_CODE_NONE;
|
||||||
|
packet.param[6] = 0;
|
||||||
|
packet.encodeCommand(7);
|
||||||
|
|
||||||
|
if (!_transact(packet, packet))
|
||||||
|
return 0;
|
||||||
|
if (!packet.param[0])
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto respLength = packet.param[1];
|
||||||
|
auto respCode = packet.param[2];
|
||||||
|
|
||||||
|
if ((respLength != 18) && (respLength != 20)) {
|
||||||
|
LOG_IO("invalid response length: 0x%02x", respLength);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (respCode != 0x01) {
|
||||||
|
LOG_IO("invalid response code: 0x%02x", respCode);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
char buffer[24];
|
||||||
|
|
||||||
|
util::hexToString(buffer, &packet.param[3], 8, '-');
|
||||||
|
LOG_IO("%s", buffer);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__builtin_memcpy(output, &packet.param[3], 8);
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
228
src/common/exthw/nfc.hpp
Normal file
228
src/common/exthw/nfc.hpp
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 spicyjpeg
|
||||||
|
*
|
||||||
|
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "common/util/templates.hpp"
|
||||||
|
#include "common/io.hpp"
|
||||||
|
|
||||||
|
namespace exthw {
|
||||||
|
|
||||||
|
/* PN532 command definitions */
|
||||||
|
|
||||||
|
enum PN532Command : uint8_t {
|
||||||
|
PN532_DIAGNOSE = 0x00,
|
||||||
|
PN532_GET_FIRMWARE_VERSION = 0x02,
|
||||||
|
PN532_GET_GENERAL_STATUS = 0x04,
|
||||||
|
PN532_READ_REG = 0x06,
|
||||||
|
PN532_WRITE_REG = 0x08,
|
||||||
|
PN532_READ_GPIO = 0x0c,
|
||||||
|
PN532_WRITE_GPIO = 0x0e,
|
||||||
|
PN532_SET_BAUD_RATE = 0x10,
|
||||||
|
PN532_SET_PARAMETERS = 0x12,
|
||||||
|
PN532_SAM_CONFIG = 0x14,
|
||||||
|
PN532_POWER_DOWN = 0x16,
|
||||||
|
PN532_RF_CONFIG = 0x32,
|
||||||
|
PN532_IN_DATA_EXCHANGE = 0x40,
|
||||||
|
PN532_IN_COMMUNICATE_THRU = 0x42,
|
||||||
|
PN532_IN_DESELECT = 0x44,
|
||||||
|
PN532_IN_JUMP_FOR_PSL = 0x46,
|
||||||
|
PN532_IN_LIST_TARGETS = 0x4a,
|
||||||
|
PN532_IN_PSL = 0x4e,
|
||||||
|
PN532_IN_ATR = 0x50,
|
||||||
|
PN532_IN_RELEASE = 0x52,
|
||||||
|
PN532_IN_SELECT = 0x54,
|
||||||
|
PN532_IN_JUMP_FOR_DEP = 0x56,
|
||||||
|
PN532_RF_REGULATION_TEST = 0x58,
|
||||||
|
PN532_IN_AUTO_POLL = 0x60,
|
||||||
|
PN532_TG_GET_DATA = 0x86,
|
||||||
|
PN532_TG_GET_COMMAND = 0x88,
|
||||||
|
PN532_TG_GET_TARGET_STATUS = 0x8a,
|
||||||
|
PN532_TG_INIT = 0x8c,
|
||||||
|
PN532_TG_SET_DATA = 0x8e,
|
||||||
|
PN532_TG_SEND_RESPONSE = 0x90,
|
||||||
|
PN532_TG_SET_GENERAL_BYTES = 0x92,
|
||||||
|
PN532_TG_SET_METADATA = 0x94
|
||||||
|
};
|
||||||
|
|
||||||
|
/* PN532 parameter definitions */
|
||||||
|
|
||||||
|
enum PN532SAMMode : uint8_t {
|
||||||
|
PN532_SAM_MODE_NORMAL = 0x01,
|
||||||
|
PN532_SAM_MODE_VIRTUAL_CARD = 0x02,
|
||||||
|
PN532_SAM_MODE_WIRED_CARD = 0x03,
|
||||||
|
PN532_SAM_MODE_DUAL_CARD = 0x04
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PN532RFConfigItem : uint8_t {
|
||||||
|
PN532_RF_CONFIG_FIELD = 0x01,
|
||||||
|
PN532_RF_CONFIG_TIMEOUTS = 0x02,
|
||||||
|
PN532_RF_CONFIG_MAX_RTY_COM = 0x04,
|
||||||
|
PN532_RF_CONFIG_MAX_RETRIES = 0x05,
|
||||||
|
PN532_RF_CONFIG_CIU_ISO14443A = 0x0a,
|
||||||
|
PN532_RF_CONFIG_CIU_FELICA = 0x0b,
|
||||||
|
PN532_RF_CONFIG_CIU_ISO14443B = 0x0c,
|
||||||
|
PN532_RF_CONFIG_CIU_ISO14443_4 = 0x0d
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PN532ListTargetsType : uint8_t {
|
||||||
|
PN532_LIST_TARGETS_ISO14443A = 0x00,
|
||||||
|
PN532_LIST_TARGETS_FELICA_212 = 0x01,
|
||||||
|
PN532_LIST_TARGETS_FELICA_414 = 0x02,
|
||||||
|
PN532_LIST_TARGETS_ISO14443B = 0x03
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PN532WakeupSource : uint8_t {
|
||||||
|
PN532_WAKEUP_SOURCE_INT0 = 1 << 0,
|
||||||
|
PN532_WAKEUP_SOURCE_INT1 = 1 << 1,
|
||||||
|
PN532_WAKEUP_SOURCE_RF = 1 << 3,
|
||||||
|
PN532_WAKEUP_SOURCE_HSU = 1 << 4,
|
||||||
|
PN532_WAKEUP_SOURCE_SPI = 1 << 5,
|
||||||
|
PN532_WAKEUP_SOURCE_GPIO = 1 << 6,
|
||||||
|
PN532_WAKEUP_SOURCE_I2C = 1 << 7
|
||||||
|
};
|
||||||
|
|
||||||
|
/* FeliCa definitions */
|
||||||
|
|
||||||
|
enum FeliCaCommand : uint8_t {
|
||||||
|
FELICA_POLL = 0x00,
|
||||||
|
FELICA_REQUEST_SERVICE = 0x02,
|
||||||
|
FELICA_REQUEST_RESPONSE = 0x04,
|
||||||
|
FELICA_READ_WITHOUT_ENC = 0x06,
|
||||||
|
FELICA_WRITE_WITHOUT_ENC = 0x08,
|
||||||
|
FELICA_REQUEST_SYSTEM_CODE = 0x0c
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FeliCaRequestCode : uint8_t {
|
||||||
|
FELICA_REQ_CODE_NONE = 0x00,
|
||||||
|
FELICA_REQ_CODE_SYSTEM_CODE = 0x01,
|
||||||
|
FELICA_REQ_CODE_COMMUNICATION = 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
/* PN532 packet structures */
|
||||||
|
|
||||||
|
static constexpr uint8_t PN532_PACKET_PREAMBLE = 0x55;
|
||||||
|
static constexpr uint8_t PN532_PACKET_START1 = 0x00;
|
||||||
|
static constexpr uint8_t PN532_PACKET_START2 = 0xff;
|
||||||
|
|
||||||
|
enum PN532PacketAddress : uint8_t {
|
||||||
|
PN532_ADDR_ERROR = 0x7f,
|
||||||
|
PN532_ADDR_DEVICE = 0xd4,
|
||||||
|
PN532_ADDR_HOST = 0xd5
|
||||||
|
};
|
||||||
|
|
||||||
|
class PN532PacketHeader {
|
||||||
|
public:
|
||||||
|
uint8_t preamble, startCode[2];
|
||||||
|
uint8_t length, lengthChecksum;
|
||||||
|
uint8_t address;
|
||||||
|
|
||||||
|
inline size_t getPacketLength(void) const {
|
||||||
|
return (sizeof(PN532PacketHeader) - 1) + length + 2;
|
||||||
|
}
|
||||||
|
inline uint8_t *getData(void) {
|
||||||
|
return reinterpret_cast<uint8_t *>(this + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
|
||||||
|
void encodeCommand(int paramLength);
|
||||||
|
bool decodeResponse(void) const;
|
||||||
|
bool isErrorResponse(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t N> class PN532Packet : public PN532PacketHeader {
|
||||||
|
public:
|
||||||
|
uint8_t command;
|
||||||
|
uint8_t param[N];
|
||||||
|
};
|
||||||
|
|
||||||
|
class PN532ExtPacketHeader {
|
||||||
|
public:
|
||||||
|
uint8_t preamble, startCode[2];
|
||||||
|
uint8_t packetMagic[2];
|
||||||
|
uint8_t length[2], lengthChecksum;
|
||||||
|
uint8_t address;
|
||||||
|
|
||||||
|
inline size_t getDataLength(void) const {
|
||||||
|
return util::concat2(length[1], length[0]);
|
||||||
|
}
|
||||||
|
inline size_t getPacketLength(void) const {
|
||||||
|
return (sizeof(PN532PacketHeader) - 1) + getDataLength() + 2;
|
||||||
|
}
|
||||||
|
inline uint8_t *getData(void) {
|
||||||
|
return reinterpret_cast<uint8_t *>(this + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
|
||||||
|
void encodeCommand(int paramLength);
|
||||||
|
bool decodeResponse(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t N> class PN532ExtPacket : public PN532ExtPacketHeader {
|
||||||
|
public:
|
||||||
|
uint8_t command;
|
||||||
|
uint8_t param[N];
|
||||||
|
};
|
||||||
|
|
||||||
|
class PN532ACKPacket {
|
||||||
|
public:
|
||||||
|
uint8_t preamble, startCode[2];
|
||||||
|
uint8_t packetMagic[2];
|
||||||
|
uint8_t postamble;
|
||||||
|
|
||||||
|
void encodeACK(bool isACK = true);
|
||||||
|
bool decodeACK(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* PN532 driver */
|
||||||
|
|
||||||
|
class PN532Driver {
|
||||||
|
private:
|
||||||
|
io::UARTDriver &_serial;
|
||||||
|
bool _isIdle;
|
||||||
|
|
||||||
|
template<size_t N> inline bool _transact(
|
||||||
|
const PN532PacketHeader &request,
|
||||||
|
PN532Packet<N> &response
|
||||||
|
) {
|
||||||
|
_transact(request, response, sizeof(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _transact(
|
||||||
|
const PN532PacketHeader &request,
|
||||||
|
PN532PacketHeader &response,
|
||||||
|
size_t maxRespLength
|
||||||
|
);
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline PN532Driver(io::UARTDriver &serial)
|
||||||
|
: _serial(serial) {}
|
||||||
|
|
||||||
|
bool init(void);
|
||||||
|
bool setMaxRetries(int count);
|
||||||
|
bool goIdle(void);
|
||||||
|
|
||||||
|
size_t readISO14443CardID(uint8_t *output);
|
||||||
|
size_t readFeliCaCardID(uint8_t *output, uint16_t systemCode = 0xffff);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -282,14 +282,24 @@ void TextOverlay::draw(Context &ctx, bool active) const {
|
|||||||
if (leftText) {
|
if (leftText) {
|
||||||
rect.x = 8;
|
rect.x = 8;
|
||||||
rect.w = ctx.gpuCtx.width - 16;
|
rect.w = ctx.gpuCtx.width - 16;
|
||||||
ctx.font.draw(ctx.gpuCtx, leftText, rect, ctx.colors[COLOR_TEXT2]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
leftText,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT2]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (rightText) {
|
if (rightText) {
|
||||||
int width = ctx.font.getStringWidth(rightText);
|
int width = ctx.font.getStringWidth(rightText);
|
||||||
|
|
||||||
rect.x = ctx.gpuCtx.width - (8 + width);
|
rect.x = ctx.gpuCtx.width - (8 + width);
|
||||||
rect.w = width;
|
rect.w = width;
|
||||||
ctx.font.draw(ctx.gpuCtx, rightText, rect, ctx.colors[COLOR_TEXT2]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
rightText,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT2]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +312,8 @@ void SplashOverlay::draw(Context &ctx, bool active) const {
|
|||||||
// Backdrop
|
// Backdrop
|
||||||
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
||||||
ctx.gpuCtx.drawBackdrop(
|
ctx.gpuCtx.drawBackdrop(
|
||||||
gp0_rgb(brightness, brightness, brightness), GP0_BLEND_SUBTRACT
|
gp0_rgb(brightness, brightness, brightness),
|
||||||
|
GP0_BLEND_SUBTRACT
|
||||||
);
|
);
|
||||||
|
|
||||||
if (brightness < 0xff)
|
if (brightness < 0xff)
|
||||||
@ -337,7 +348,10 @@ void LogOverlay::draw(Context &ctx, bool active) const {
|
|||||||
|
|
||||||
// Backdrop
|
// Backdrop
|
||||||
_newLayer(
|
_newLayer(
|
||||||
ctx, 0, offset - ctx.gpuCtx.height, ctx.gpuCtx.width,
|
ctx,
|
||||||
|
0,
|
||||||
|
offset - ctx.gpuCtx.height,
|
||||||
|
ctx.gpuCtx.width,
|
||||||
ctx.gpuCtx.height
|
ctx.gpuCtx.height
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawBackdrop(ctx.colors[COLOR_BACKDROP], GP0_BLEND_SUBTRACT);
|
ctx.gpuCtx.drawBackdrop(ctx.colors[COLOR_BACKDROP], GP0_BLEND_SUBTRACT);
|
||||||
@ -355,7 +369,10 @@ void LogOverlay::draw(Context &ctx, bool active) const {
|
|||||||
|
|
||||||
for (int i = (screenHeight / lineHeight) - 1; i >= 0; i--) {
|
for (int i = (screenHeight / lineHeight) - 1; i >= 0; i--) {
|
||||||
ctx.font.draw(
|
ctx.font.draw(
|
||||||
ctx.gpuCtx, _buffer.getLine(i), rect, ctx.colors[COLOR_TEXT1]
|
ctx.gpuCtx,
|
||||||
|
_buffer.getLine(i),
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT1]
|
||||||
);
|
);
|
||||||
|
|
||||||
rect.y1 = rect.y2;
|
rect.y1 = rect.y2;
|
||||||
@ -378,7 +395,8 @@ void ScreenshotOverlay::draw(Context &ctx, bool active) const {
|
|||||||
|
|
||||||
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
||||||
ctx.gpuCtx.drawBackdrop(
|
ctx.gpuCtx.drawBackdrop(
|
||||||
gp0_rgb(brightness, brightness, brightness), GP0_BLEND_ADD
|
gp0_rgb(brightness, brightness, brightness),
|
||||||
|
GP0_BLEND_ADD
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,7 +441,8 @@ void BackdropScreen::draw(Context &ctx, bool active) const {
|
|||||||
|
|
||||||
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
_newLayer(ctx, 0, 0, ctx.gpuCtx.width, ctx.gpuCtx.height);
|
||||||
ctx.gpuCtx.drawBackdrop(
|
ctx.gpuCtx.drawBackdrop(
|
||||||
gp0_rgb(brightness, brightness, brightness), GP0_BLEND_ADD
|
gp0_rgb(brightness, brightness, brightness),
|
||||||
|
GP0_BLEND_ADD
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,28 +462,47 @@ void ModalScreen::draw(Context &ctx, bool active) const {
|
|||||||
int windowHeight = TITLE_BAR_HEIGHT + _height;
|
int windowHeight = TITLE_BAR_HEIGHT + _height;
|
||||||
|
|
||||||
_newLayer(
|
_newLayer(
|
||||||
ctx, (ctx.gpuCtx.width - _width) / 2,
|
ctx,
|
||||||
(ctx.gpuCtx.height - windowHeight) / 2, _width + SHADOW_OFFSET,
|
(ctx.gpuCtx.width - _width) / 2,
|
||||||
|
(ctx.gpuCtx.height - windowHeight) / 2,
|
||||||
|
_width + SHADOW_OFFSET,
|
||||||
windowHeight + SHADOW_OFFSET
|
windowHeight + SHADOW_OFFSET
|
||||||
);
|
);
|
||||||
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
||||||
|
|
||||||
// Window
|
// Window
|
||||||
ctx.gpuCtx.drawGradientRectD(
|
ctx.gpuCtx.drawGradientRectD(
|
||||||
0, 0, _width, windowHeight, ctx.colors[COLOR_WINDOW1],
|
0,
|
||||||
ctx.colors[COLOR_WINDOW2], ctx.colors[COLOR_WINDOW3]
|
0,
|
||||||
|
_width,
|
||||||
|
windowHeight,
|
||||||
|
ctx.colors[COLOR_WINDOW1],
|
||||||
|
ctx.colors[COLOR_WINDOW2],
|
||||||
|
ctx.colors[COLOR_WINDOW3]
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawGradientRectH(
|
ctx.gpuCtx.drawGradientRectH(
|
||||||
0, 0, _titleBarAnim.getValue(ctx.time), TITLE_BAR_HEIGHT,
|
0,
|
||||||
ctx.colors[COLOR_ACCENT1], ctx.colors[COLOR_ACCENT2]
|
0,
|
||||||
|
_titleBarAnim.getValue(ctx.time),
|
||||||
|
TITLE_BAR_HEIGHT,
|
||||||
|
ctx.colors[COLOR_ACCENT1],
|
||||||
|
ctx.colors[COLOR_ACCENT2]
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
_width, SHADOW_OFFSET, SHADOW_OFFSET, windowHeight,
|
_width,
|
||||||
ctx.colors[COLOR_SHADOW], true
|
SHADOW_OFFSET,
|
||||||
|
SHADOW_OFFSET,
|
||||||
|
windowHeight,
|
||||||
|
ctx.colors[COLOR_SHADOW],
|
||||||
|
true
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
SHADOW_OFFSET, windowHeight, _width - SHADOW_OFFSET, SHADOW_OFFSET,
|
SHADOW_OFFSET,
|
||||||
ctx.colors[COLOR_SHADOW], true
|
windowHeight,
|
||||||
|
_width - SHADOW_OFFSET,
|
||||||
|
SHADOW_OFFSET,
|
||||||
|
ctx.colors[COLOR_SHADOW],
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
@ -474,11 +512,22 @@ void ModalScreen::draw(Context &ctx, bool active) const {
|
|||||||
rect.y1 = TITLE_BAR_PADDING;
|
rect.y1 = TITLE_BAR_PADDING;
|
||||||
rect.x2 = _width - TITLE_BAR_PADDING;
|
rect.x2 = _width - TITLE_BAR_PADDING;
|
||||||
rect.y2 = TITLE_BAR_PADDING + ctx.font.getLineHeight();
|
rect.y2 = TITLE_BAR_PADDING + ctx.font.getLineHeight();
|
||||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_title,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
|
);
|
||||||
|
|
||||||
rect.y1 = TITLE_BAR_HEIGHT + MODAL_PADDING;
|
rect.y1 = TITLE_BAR_HEIGHT + MODAL_PADDING;
|
||||||
rect.y2 = _height - MODAL_PADDING;
|
rect.y2 = _height - MODAL_PADDING;
|
||||||
ctx.font.draw(ctx.gpuCtx, _body, rect, ctx.colors[COLOR_TEXT1], true);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_body,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT1],
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,11 @@ void TextScreen::draw(Context &ctx, bool active) const {
|
|||||||
|
|
||||||
// Top/bottom text
|
// Top/bottom text
|
||||||
_newLayer(
|
_newLayer(
|
||||||
ctx, SCREEN_MARGIN_X, SCREEN_MARGIN_Y, screenWidth, screenHeight
|
ctx,
|
||||||
|
SCREEN_MARGIN_X,
|
||||||
|
SCREEN_MARGIN_Y,
|
||||||
|
screenWidth,
|
||||||
|
screenHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
gpu::Rect rect;
|
gpu::Rect rect;
|
||||||
@ -51,11 +55,22 @@ void TextScreen::draw(Context &ctx, bool active) const {
|
|||||||
rect.y1 = 0;
|
rect.y1 = 0;
|
||||||
rect.x2 = screenWidth;
|
rect.x2 = screenWidth;
|
||||||
rect.y2 = lineHeight;
|
rect.y2 = lineHeight;
|
||||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_title,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
|
);
|
||||||
|
|
||||||
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT_MIN;
|
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT_MIN;
|
||||||
rect.y2 = screenHeight;
|
rect.y2 = screenHeight;
|
||||||
ctx.font.draw(ctx.gpuCtx, _prompt, rect, ctx.colors[COLOR_TEXT1], true);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_prompt,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT1],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
int bodyOffset = lineHeight + SCREEN_BLOCK_MARGIN;
|
int bodyOffset = lineHeight + SCREEN_BLOCK_MARGIN;
|
||||||
int bodyHeight = screenHeight -
|
int bodyHeight = screenHeight -
|
||||||
@ -63,7 +78,10 @@ void TextScreen::draw(Context &ctx, bool active) const {
|
|||||||
|
|
||||||
// Scrollable text
|
// Scrollable text
|
||||||
_newLayer(
|
_newLayer(
|
||||||
ctx, SCREEN_MARGIN_X, SCREEN_MARGIN_Y + bodyOffset, screenWidth,
|
ctx,
|
||||||
|
SCREEN_MARGIN_X,
|
||||||
|
SCREEN_MARGIN_Y + bodyOffset,
|
||||||
|
screenWidth,
|
||||||
bodyHeight
|
bodyHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -75,7 +93,14 @@ void TextScreen::draw(Context &ctx, bool active) const {
|
|||||||
clip.y1 = 0;
|
clip.y1 = 0;
|
||||||
clip.x2 = screenWidth;
|
clip.x2 = screenWidth;
|
||||||
clip.y2 = bodyHeight;
|
clip.y2 = bodyHeight;
|
||||||
ctx.font.draw(ctx.gpuCtx, _body, rect, clip, ctx.colors[COLOR_TEXT1], true);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_body,
|
||||||
|
rect,
|
||||||
|
clip,
|
||||||
|
ctx.colors[COLOR_TEXT1],
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextScreen::update(Context &ctx) {
|
void TextScreen::update(Context &ctx) {
|
||||||
@ -146,14 +171,22 @@ void ImageScreen::draw(Context &ctx, bool active) const {
|
|||||||
int _height = height + _imagePadding;
|
int _height = height + _imagePadding;
|
||||||
|
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
x - _width, y - _height, _width * 2, _height * 2, _backdropColor
|
x - _width,
|
||||||
|
y - _height,
|
||||||
|
_width * 2,
|
||||||
|
_height * 2,
|
||||||
|
_backdropColor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
if (_imageScale > 1)
|
if (_imageScale > 1)
|
||||||
_image->drawScaled(
|
_image->drawScaled(
|
||||||
ctx.gpuCtx, x - width - 1, y - height - 1, width * 2, height * 2
|
ctx.gpuCtx,
|
||||||
|
x - width - 1,
|
||||||
|
y - height - 1,
|
||||||
|
width * 2,
|
||||||
|
height * 2
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
_image->draw(ctx.gpuCtx, x - width, y - height);
|
_image->draw(ctx.gpuCtx, x - width, y - height);
|
||||||
@ -166,11 +199,22 @@ void ImageScreen::draw(Context &ctx, bool active) const {
|
|||||||
rect.y1 = SCREEN_MARGIN_Y;
|
rect.y1 = SCREEN_MARGIN_Y;
|
||||||
rect.x2 = ctx.gpuCtx.width - SCREEN_MARGIN_X;
|
rect.x2 = ctx.gpuCtx.width - SCREEN_MARGIN_X;
|
||||||
rect.y2 = SCREEN_MARGIN_Y + lineHeight;
|
rect.y2 = SCREEN_MARGIN_Y + lineHeight;
|
||||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_title,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
|
);
|
||||||
|
|
||||||
rect.y1 = ctx.gpuCtx.height - (SCREEN_MARGIN_Y + SCREEN_PROMPT_HEIGHT);
|
rect.y1 = ctx.gpuCtx.height - (SCREEN_MARGIN_Y + SCREEN_PROMPT_HEIGHT);
|
||||||
rect.y2 = ctx.gpuCtx.height - SCREEN_MARGIN_Y;
|
rect.y2 = ctx.gpuCtx.height - SCREEN_MARGIN_Y;
|
||||||
ctx.font.draw(ctx.gpuCtx, _prompt, rect, ctx.colors[COLOR_TEXT1], true);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_prompt,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT1],
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ListScreen::ListScreen(void)
|
ListScreen::ListScreen(void)
|
||||||
@ -196,25 +240,37 @@ void ListScreen::_drawItems(Context &ctx) const {
|
|||||||
if ((itemY + itemHeight) >= 0) {
|
if ((itemY + itemHeight) >= 0) {
|
||||||
if (i == _activeItem) {
|
if (i == _activeItem) {
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
LIST_BOX_PADDING, itemY, itemWidth, itemHeight,
|
LIST_BOX_PADDING,
|
||||||
|
itemY,
|
||||||
|
itemWidth,
|
||||||
|
itemHeight,
|
||||||
ctx.colors[COLOR_HIGHLIGHT2]
|
ctx.colors[COLOR_HIGHLIGHT2]
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
LIST_BOX_PADDING, itemY, _itemAnim.getValue(ctx.time),
|
LIST_BOX_PADDING,
|
||||||
itemHeight, ctx.colors[COLOR_HIGHLIGHT1]
|
itemY,
|
||||||
|
_itemAnim.getValue(ctx.time),
|
||||||
|
itemHeight,
|
||||||
|
ctx.colors[COLOR_HIGHLIGHT1]
|
||||||
);
|
);
|
||||||
|
|
||||||
rect.y1 = itemY + LIST_ITEM_PADDING + lineHeight;
|
rect.y1 = itemY + LIST_ITEM_PADDING + lineHeight;
|
||||||
rect.y2 = rect.y1 + lineHeight;
|
rect.y2 = rect.y1 + lineHeight;
|
||||||
ctx.font.draw(
|
ctx.font.draw(
|
||||||
ctx.gpuCtx, _itemPrompt, rect, ctx.colors[COLOR_SUBTITLE]
|
ctx.gpuCtx,
|
||||||
|
_itemPrompt,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_SUBTITLE]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
rect.y1 = itemY + LIST_ITEM_PADDING;
|
rect.y1 = itemY + LIST_ITEM_PADDING;
|
||||||
rect.y2 = rect.y1 + lineHeight;
|
rect.y2 = rect.y1 + lineHeight;
|
||||||
ctx.font.draw(
|
ctx.font.draw(
|
||||||
ctx.gpuCtx, _getItemName(ctx, i), rect, ctx.colors[COLOR_TITLE]
|
ctx.gpuCtx,
|
||||||
|
_getItemName(ctx, i),
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +293,11 @@ void ListScreen::draw(Context &ctx, bool active) const {
|
|||||||
int lineHeight = ctx.font.getLineHeight();
|
int lineHeight = ctx.font.getLineHeight();
|
||||||
|
|
||||||
_newLayer(
|
_newLayer(
|
||||||
ctx, SCREEN_MARGIN_X, SCREEN_MARGIN_Y, screenWidth, screenHeight
|
ctx,
|
||||||
|
SCREEN_MARGIN_X,
|
||||||
|
SCREEN_MARGIN_Y,
|
||||||
|
screenWidth,
|
||||||
|
screenHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
@ -247,25 +307,46 @@ void ListScreen::draw(Context &ctx, bool active) const {
|
|||||||
rect.y1 = 0;
|
rect.y1 = 0;
|
||||||
rect.x2 = screenWidth;
|
rect.x2 = screenWidth;
|
||||||
rect.y2 = lineHeight;
|
rect.y2 = lineHeight;
|
||||||
ctx.font.draw(ctx.gpuCtx, _title, rect, ctx.colors[COLOR_TITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_title,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
|
);
|
||||||
|
|
||||||
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT;
|
rect.y1 = screenHeight - SCREEN_PROMPT_HEIGHT;
|
||||||
rect.y2 = screenHeight;
|
rect.y2 = screenHeight;
|
||||||
ctx.font.draw(ctx.gpuCtx, _prompt, rect, ctx.colors[COLOR_TEXT1], true);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
_prompt,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT1],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
_newLayer(
|
_newLayer(
|
||||||
ctx, SCREEN_MARGIN_X,
|
ctx,
|
||||||
SCREEN_MARGIN_Y + lineHeight + SCREEN_BLOCK_MARGIN, screenWidth,
|
SCREEN_MARGIN_X,
|
||||||
|
SCREEN_MARGIN_Y + lineHeight + SCREEN_BLOCK_MARGIN,
|
||||||
|
screenWidth,
|
||||||
listHeight
|
listHeight
|
||||||
);
|
);
|
||||||
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
||||||
|
|
||||||
// List box
|
// List box
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
0, 0, screenWidth / 2, listHeight, ctx.colors[COLOR_BOX1]
|
0,
|
||||||
|
0,
|
||||||
|
screenWidth / 2,
|
||||||
|
listHeight,
|
||||||
|
ctx.colors[COLOR_BOX1]
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawGradientRectH(
|
ctx.gpuCtx.drawGradientRectH(
|
||||||
screenWidth / 2, 0, screenWidth / 2, listHeight, ctx.colors[COLOR_BOX1],
|
screenWidth / 2,
|
||||||
|
0,
|
||||||
|
screenWidth / 2,
|
||||||
|
listHeight,
|
||||||
|
ctx.colors[COLOR_BOX1],
|
||||||
ctx.colors[COLOR_BOX2]
|
ctx.colors[COLOR_BOX2]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -282,13 +363,19 @@ void ListScreen::draw(Context &ctx, bool active) const {
|
|||||||
if (_activeItem) {
|
if (_activeItem) {
|
||||||
iconRect.y = LIST_BOX_PADDING;
|
iconRect.y = LIST_BOX_PADDING;
|
||||||
ctx.font.draw(
|
ctx.font.draw(
|
||||||
ctx.gpuCtx, CH_UP_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
|
ctx.gpuCtx,
|
||||||
|
CH_UP_ARROW,
|
||||||
|
iconRect,
|
||||||
|
ctx.colors[COLOR_TEXT1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_activeItem < (_listLength - 1)) {
|
if (_activeItem < (_listLength - 1)) {
|
||||||
iconRect.y = listHeight - (lineHeight + LIST_BOX_PADDING);
|
iconRect.y = listHeight - (lineHeight + LIST_BOX_PADDING);
|
||||||
ctx.font.draw(
|
ctx.font.draw(
|
||||||
ctx.gpuCtx, CH_DOWN_ARROW, iconRect, ctx.colors[COLOR_TEXT1]
|
ctx.gpuCtx,
|
||||||
|
CH_DOWN_ARROW,
|
||||||
|
iconRect,
|
||||||
|
ctx.colors[COLOR_TEXT1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,9 +422,17 @@ void ListScreen::update(Context &ctx) {
|
|||||||
int currentOffset = -_scrollAnim.getTargetValue();
|
int currentOffset = -_scrollAnim.getTargetValue();
|
||||||
|
|
||||||
if (topOffset < currentOffset)
|
if (topOffset < currentOffset)
|
||||||
_scrollAnim.setValue(ctx.time, LIST_BOX_PADDING - topOffset, SPEED_FAST);
|
_scrollAnim.setValue(
|
||||||
|
ctx.time,
|
||||||
|
LIST_BOX_PADDING - topOffset,
|
||||||
|
SPEED_FAST
|
||||||
|
);
|
||||||
else if (bottomOffset > currentOffset)
|
else if (bottomOffset > currentOffset)
|
||||||
_scrollAnim.setValue(ctx.time, -(LIST_BOX_PADDING + bottomOffset), SPEED_FAST);
|
_scrollAnim.setValue(
|
||||||
|
ctx.time,
|
||||||
|
-(LIST_BOX_PADDING + bottomOffset),
|
||||||
|
SPEED_FAST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,32 +59,51 @@ void MessageBoxScreen::draw(Context &ctx, bool active) const {
|
|||||||
|
|
||||||
if (_locked) {
|
if (_locked) {
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
|
buttonX,
|
||||||
ctx.colors[COLOR_SHADOW], true
|
buttonY,
|
||||||
|
rect.w,
|
||||||
|
BUTTON_HEIGHT,
|
||||||
|
ctx.colors[COLOR_SHADOW],
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.font.draw(
|
ctx.font.draw(
|
||||||
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TEXT2]
|
ctx.gpuCtx,
|
||||||
|
_buttons[i],
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TEXT2]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (i == activeButton) {
|
if (i == activeButton) {
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
|
buttonX,
|
||||||
|
buttonY,
|
||||||
|
rect.w,
|
||||||
|
BUTTON_HEIGHT,
|
||||||
ctx.colors[COLOR_HIGHLIGHT2]
|
ctx.colors[COLOR_HIGHLIGHT2]
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
buttonX, buttonY, _buttonAnim.getValue(ctx.time),
|
buttonX,
|
||||||
BUTTON_HEIGHT, ctx.colors[COLOR_HIGHLIGHT1]
|
buttonY,
|
||||||
|
_buttonAnim.getValue(ctx.time),
|
||||||
|
BUTTON_HEIGHT,
|
||||||
|
ctx.colors[COLOR_HIGHLIGHT1]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
buttonX, buttonY, rect.w, BUTTON_HEIGHT,
|
buttonX,
|
||||||
|
buttonY,
|
||||||
|
rect.w,
|
||||||
|
BUTTON_HEIGHT,
|
||||||
ctx.colors[COLOR_WINDOW3]
|
ctx.colors[COLOR_WINDOW3]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.font.draw(
|
ctx.font.draw(
|
||||||
ctx.gpuCtx, _buttons[i], rect, ctx.colors[COLOR_TITLE]
|
ctx.gpuCtx,
|
||||||
|
_buttons[i],
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,13 +171,17 @@ void HexEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
if (!active)
|
if (!active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int boxY = TITLE_BAR_HEIGHT + _height -
|
int boxY =
|
||||||
(BUTTON_HEIGHT + MODAL_PADDING) * 2;
|
TITLE_BAR_HEIGHT + _height - (BUTTON_HEIGHT + MODAL_PADDING) * 2;
|
||||||
int boxWidth = _width - MODAL_PADDING * 2;
|
int boxWidth = _width - MODAL_PADDING * 2;
|
||||||
|
|
||||||
// Text box
|
// Text box
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
MODAL_PADDING, boxY, boxWidth, BUTTON_HEIGHT, ctx.colors[COLOR_BOX1]
|
MODAL_PADDING,
|
||||||
|
boxY,
|
||||||
|
boxWidth,
|
||||||
|
BUTTON_HEIGHT,
|
||||||
|
ctx.colors[COLOR_BOX1]
|
||||||
);
|
);
|
||||||
|
|
||||||
char string[128];
|
char string[128];
|
||||||
@ -173,8 +196,11 @@ void HexEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
if (_activeButton < _buttonIndexOffset)
|
if (_activeButton < _buttonIndexOffset)
|
||||||
ctx.gpuCtx.drawGradientRectV(
|
ctx.gpuCtx.drawGradientRectV(
|
||||||
stringOffset + _cursorAnim.getValue(ctx.time),
|
stringOffset + _cursorAnim.getValue(ctx.time),
|
||||||
boxY + BUTTON_HEIGHT / 2, _charWidth, BUTTON_HEIGHT / 2,
|
boxY + BUTTON_HEIGHT / 2,
|
||||||
ctx.colors[COLOR_BOX1], ctx.colors[COLOR_HIGHLIGHT1]
|
_charWidth,
|
||||||
|
BUTTON_HEIGHT / 2,
|
||||||
|
ctx.colors[COLOR_BOX1],
|
||||||
|
ctx.colors[COLOR_HIGHLIGHT1]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Current string
|
// Current string
|
||||||
@ -182,7 +208,12 @@ void HexEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
rect.y1 = boxY + BUTTON_PADDING;
|
rect.y1 = boxY + BUTTON_PADDING;
|
||||||
rect.x2 = _width - MODAL_PADDING;
|
rect.x2 = _width - MODAL_PADDING;
|
||||||
rect.y2 = boxY + BUTTON_PADDING + ctx.font.getLineHeight();
|
rect.y2 = boxY + BUTTON_PADDING + ctx.font.getLineHeight();
|
||||||
ctx.font.draw(ctx.gpuCtx, string, rect, ctx.colors[COLOR_TITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
string,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
|
);
|
||||||
|
|
||||||
// Highlighted field
|
// Highlighted field
|
||||||
if (_activeButton < _buttonIndexOffset) {
|
if (_activeButton < _buttonIndexOffset) {
|
||||||
@ -190,7 +221,12 @@ void HexEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
ptr[1] = 0;
|
ptr[1] = 0;
|
||||||
|
|
||||||
rect.x1 = stringOffset + _cursorAnim.getTargetValue();
|
rect.x1 = stringOffset + _cursorAnim.getTargetValue();
|
||||||
ctx.font.draw(ctx.gpuCtx, ptr, rect, ctx.colors[COLOR_SUBTITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
ptr,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_SUBTITLE]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,13 +350,17 @@ void DateEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
if (!active)
|
if (!active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int boxY = TITLE_BAR_HEIGHT + _height -
|
int boxY =
|
||||||
(BUTTON_HEIGHT + MODAL_PADDING) * 2;
|
TITLE_BAR_HEIGHT + _height - (BUTTON_HEIGHT + MODAL_PADDING) * 2;
|
||||||
int boxWidth = _width - MODAL_PADDING * 2;
|
int boxWidth = _width - MODAL_PADDING * 2;
|
||||||
|
|
||||||
// Text box
|
// Text box
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
MODAL_PADDING, boxY, boxWidth, BUTTON_HEIGHT, ctx.colors[COLOR_BOX1]
|
MODAL_PADDING,
|
||||||
|
boxY,
|
||||||
|
boxWidth,
|
||||||
|
BUTTON_HEIGHT,
|
||||||
|
ctx.colors[COLOR_BOX1]
|
||||||
);
|
);
|
||||||
|
|
||||||
char string[24];
|
char string[24];
|
||||||
@ -342,8 +382,10 @@ void DateEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
if (_activeButton < _buttonIndexOffset)
|
if (_activeButton < _buttonIndexOffset)
|
||||||
ctx.gpuCtx.drawGradientRectV(
|
ctx.gpuCtx.drawGradientRectV(
|
||||||
stringOffset + _cursorAnim.getValue(ctx.time),
|
stringOffset + _cursorAnim.getValue(ctx.time),
|
||||||
boxY + BUTTON_HEIGHT / 2, _charWidth * fieldLength,
|
boxY + BUTTON_HEIGHT / 2,
|
||||||
BUTTON_HEIGHT / 2,ctx.colors[COLOR_BOX1],
|
_charWidth * fieldLength,
|
||||||
|
BUTTON_HEIGHT / 2,
|
||||||
|
ctx.colors[COLOR_BOX1],
|
||||||
ctx.colors[COLOR_HIGHLIGHT1]
|
ctx.colors[COLOR_HIGHLIGHT1]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -352,7 +394,12 @@ void DateEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
rect.y1 = boxY + BUTTON_PADDING;
|
rect.y1 = boxY + BUTTON_PADDING;
|
||||||
rect.x2 = _width - MODAL_PADDING;
|
rect.x2 = _width - MODAL_PADDING;
|
||||||
rect.y2 = boxY + BUTTON_PADDING + ctx.font.getLineHeight();
|
rect.y2 = boxY + BUTTON_PADDING + ctx.font.getLineHeight();
|
||||||
ctx.font.draw(ctx.gpuCtx, string, rect, ctx.colors[COLOR_TITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
string,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_TITLE]
|
||||||
|
);
|
||||||
|
|
||||||
// Highlighted field
|
// Highlighted field
|
||||||
if (_activeButton < _buttonIndexOffset) {
|
if (_activeButton < _buttonIndexOffset) {
|
||||||
@ -360,7 +407,12 @@ void DateEntryScreen::draw(Context &ctx, bool active) const {
|
|||||||
ptr[fieldLength] = 0;
|
ptr[fieldLength] = 0;
|
||||||
|
|
||||||
rect.x1 = stringOffset + _cursorAnim.getTargetValue();
|
rect.x1 = stringOffset + _cursorAnim.getTargetValue();
|
||||||
ctx.font.draw(ctx.gpuCtx, ptr, rect, ctx.colors[COLOR_SUBTITLE]);
|
ctx.font.draw(
|
||||||
|
ctx.gpuCtx,
|
||||||
|
ptr,
|
||||||
|
rect,
|
||||||
|
ctx.colors[COLOR_SUBTITLE]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,7 +474,9 @@ void DateEntryScreen::update(Context &ctx) {
|
|||||||
// Update the cursor's position if necessary.
|
// Update the cursor's position if necessary.
|
||||||
if (oldActive != _activeButton)
|
if (oldActive != _activeButton)
|
||||||
_cursorAnim.setValue(
|
_cursorAnim.setValue(
|
||||||
ctx.time, _fieldOffsets[_activeButton], SPEED_FASTEST
|
ctx.time,
|
||||||
|
_fieldOffsets[_activeButton],
|
||||||
|
SPEED_FASTEST
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -430,6 +484,17 @@ void DateEntryScreen::update(Context &ctx) {
|
|||||||
ProgressScreen::ProgressScreen(void)
|
ProgressScreen::ProgressScreen(void)
|
||||||
: ModalScreen(MODAL_WIDTH, MODAL_HEIGHT_REDUCED) {}
|
: ModalScreen(MODAL_WIDTH, MODAL_HEIGHT_REDUCED) {}
|
||||||
|
|
||||||
|
void ProgressScreen::_setProgress(Context &ctx, int part, int total) {
|
||||||
|
if (!total)
|
||||||
|
total = 1;
|
||||||
|
|
||||||
|
int totalWidth = _width - MODAL_PADDING * 2;
|
||||||
|
int partWidth = (totalWidth * part) / total;
|
||||||
|
|
||||||
|
if (_progressBarAnim.getTargetValue() != partWidth)
|
||||||
|
_progressBarAnim.setValue(ctx.time, partWidth, SPEED_FASTEST);
|
||||||
|
}
|
||||||
|
|
||||||
void ProgressScreen::show(Context &ctx, bool goBack) {
|
void ProgressScreen::show(Context &ctx, bool goBack) {
|
||||||
ModalScreen::show(ctx, goBack);
|
ModalScreen::show(ctx, goBack);
|
||||||
|
|
||||||
@ -445,17 +510,25 @@ void ProgressScreen::draw(Context &ctx, bool active) const {
|
|||||||
int fullBarWidth = _width - MODAL_PADDING * 2;
|
int fullBarWidth = _width - MODAL_PADDING * 2;
|
||||||
|
|
||||||
int barX = (_width - fullBarWidth) / 2;
|
int barX = (_width - fullBarWidth) / 2;
|
||||||
int barY = TITLE_BAR_HEIGHT + _height -
|
int barY =
|
||||||
(PROGRESS_BAR_HEIGHT + MODAL_PADDING);
|
TITLE_BAR_HEIGHT + _height - (PROGRESS_BAR_HEIGHT + MODAL_PADDING);
|
||||||
|
|
||||||
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
_setBlendMode(ctx, GP0_BLEND_SEMITRANS, true);
|
||||||
|
|
||||||
ctx.gpuCtx.drawRect(
|
ctx.gpuCtx.drawRect(
|
||||||
barX, barY, fullBarWidth, PROGRESS_BAR_HEIGHT, ctx.colors[COLOR_WINDOW3]
|
barX,
|
||||||
|
barY,
|
||||||
|
fullBarWidth,
|
||||||
|
PROGRESS_BAR_HEIGHT,
|
||||||
|
ctx.colors[COLOR_WINDOW3]
|
||||||
);
|
);
|
||||||
ctx.gpuCtx.drawGradientRectH(
|
ctx.gpuCtx.drawGradientRectH(
|
||||||
barX, barY, _progressBarAnim.getValue(ctx.time), PROGRESS_BAR_HEIGHT,
|
barX,
|
||||||
ctx.colors[COLOR_PROGRESS2], ctx.colors[COLOR_PROGRESS1]
|
barY,
|
||||||
|
_progressBarAnim.getValue(ctx.time),
|
||||||
|
PROGRESS_BAR_HEIGHT,
|
||||||
|
ctx.colors[COLOR_PROGRESS2],
|
||||||
|
ctx.colors[COLOR_PROGRESS1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,16 +86,7 @@ private:
|
|||||||
util::Tween<int, util::QuadOutEasing> _progressBarAnim;
|
util::Tween<int, util::QuadOutEasing> _progressBarAnim;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
inline void _setProgress(Context &ctx, int part, int total) {
|
void _setProgress(Context &ctx, int part, int total);
|
||||||
if (!total)
|
|
||||||
total = 1;
|
|
||||||
|
|
||||||
int totalWidth = _width - MODAL_PADDING * 2;
|
|
||||||
int partWidth = (totalWidth * part) / total;
|
|
||||||
|
|
||||||
if (_progressBarAnim.getTargetValue() != partWidth)
|
|
||||||
_progressBarAnim.setValue(ctx.time, partWidth, SPEED_FASTEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ProgressScreen(void);
|
ProgressScreen(void);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user