Merge branch 'omega-hotfix' into omega-dev

This commit is contained in:
Quentin Guidée
2020-10-18 09:59:17 +02:00
53 changed files with 781 additions and 225 deletions

View File

@@ -299,6 +299,7 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded, b
m_calculationDisplayOutput = calculation->displayOutput(context);
// We must set which subviews are displayed before setLayouts to mark the right rectangle as dirty
m_scrollableOutputView.setDisplayableCenter(m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate || m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximateToggle);
m_scrollableOutputView.setDisplayCenter(m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate || m_calculationExpanded);
m_scrollableOutputView.setLayouts(Poincare::Layout(), exactOutputLayout, approximateOutputLayout);
I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual;

View File

@@ -451,30 +451,6 @@ void ConsoleController::printText(const char * text, size_t length) {
flushOutputAccumulationBufferToStore();
micropython_port_vm_hook_refresh_print();
}
// #if __EMSCRIPTEN__
/* If we called micropython_port_interrupt_if_needed here, we would need to
* put in the WHITELIST all the methods that call
* ConsoleController::printText, which means all the MicroPython methods that
* call print... This is a lot of work + might reduce the performance as
* emterpreted code is slower.
*
* We thus do not allow print interruption on the web simulator. It would be
* better to allow it, but the biggest problem was on the device anyways
* -> It is much quicker to interrupt Python on the web simulator than on the
* device.
*
* TODO: Allow print interrpution on emscripten -> maybe by using WASM=1 ? */
/*
* This can be run in Omega, since it uses WebASM.
*/
// #else
/* micropython_port_vm_hook_loop is not enough to detect user interruptions,
* because it calls micropython_port_interrupt_if_needed every 20000
* operations, and a print operation is quite long. We thus explicitely call
* micropython_port_interrupt_if_needed here. */
micropython_port_interrupt_if_needed();
// #endif
}
void ConsoleController::autoImportScript(Script script, bool force) {

View File

@@ -336,28 +336,28 @@ PHilaireLeRoux = "@0Babass2"
SpeedOfLight = "2.99792458·10^8_m_s^-1"
YearLight = "9.461·10^15_m"
Boltzmann = "1.380649·10^-23_J_K^-1"
StefanBoltzmann = "5.670374·10^-8_W_m^-2_K^-4"
VacuumImpedance = "376.730313461_Ω"
StefanBoltzmann = "5.670374419·10^-8_W_m^-2_K^-4"
VacuumImpedance = "376.730313668_Ω"
WaterTriplePoint = "273.16_K"
AtmosphericPressure = "101325_Pa"
AtomicMassUnit = "1.660539040·10^-27_kg"
Wien = "2.8977729·10^-3_K_m"
BohrRadius = "5.2917721067·10^-11_m"
BohrMagneton = "9.274009994·10^-24_A_m^2"
NuclearMagneton = "5.050783699·10^-27_A_m^2"
MuonMass = "1.883531594·10^-28_kg"
AtomicMassUnit = "1.66053906660·10^-27_kg"
Wien = "2.897771955·10^-3_K_m"
BohrRadius = "5.29177210903·10^-11_m"
BohrMagneton = "9.2740100783·10^-24_A_m^2"
NuclearMagneton = "5.0507837461·10^-27_A_m^2"
MuonMass = "1.883531627·10^-28_kg"
Avogadro = "6.02214076·10^23_mol^-1"
Gas = "8.31446261815324_J_mol^-1_K^-1"
Coulomb = "8.9875517887·10^9_N_m^2_C^-2"
Gas = "8.314462618_J_mol^-1_K^-1"
Coulomb = "8.9875517923·10^9_N_m^2_C^-2"
Vacuum_permittivity = "8.8541878128·10^-12_F_m^-1"
Vacuum_permeability = "·10^-7_H_m^-1"
Vacuum_permeability = "1.25663706212·10^-6_H_m^-1"
Planck = "6.62607015·10^-34_J_s"
ElectronMass = "9.109383·10^-31_kg"
ProtonMass = "1.672649·10^-27_kg"
NeutronMass = "1.67493·10^-27_kg"
ElementalCharge = "1.60217662·10^-19_C"
ElectronMass = "9.1093837015·10^-31_kg"
ProtonMass = "1.67262192369·10^-27_kg"
NeutronMass = "1.67492749804·10^-27_kg"
ElementalCharge = "1.602176634·10^-19_C"
GAcceleration = "9.80665_m_s^-2"
GConstant = "6.674·10^-11_m^3_kg^-1_s^-2"
GConstant = "6.67430·10^-11_m^3_kg^-1_s^-2"
SpeedOfSound0 = "343.4_m_s^-1"
SpeedOfSoundWater = "1480_m_s^-1"
SpeedOfSoundSteel = "5600_m_s^-1"
@@ -370,7 +370,7 @@ EarthRadius = "6378140_m"
MoonRadius = "3474600_m"
EarthMoonDistance = "384400000_m"
EarthSunDistance = "149597870000_m"
FaradayConstant = "96484_C_mol^-1"
FaradayConstant = "96485.33212_C_mol^-1"
EscapeVelocityOfEarth = "11186_m_s^-1"
EscapeVelocityOfMoon = "2380_m_s^-1"
EscapeVelocityOfSun = "42100_m_s^-1"
@@ -409,11 +409,11 @@ Pka14Value = "2.1"
Pka15Value = "1.9"
Pka16Value = "0"
Pka = "pKa"
PlanckReduce = "1.504571800·10^-34_J_s"
PlanckMass = "2.176470·10^-8_kg"
PlanckLength = "1.616229·10^-35_m"
PlanckTime = "5.39116·10^-44_s"
PlanckTemperature = "1.416808·10^32_K"
PlanckReduce = "1.054571817·10^-34_J_s"
PlanckMass = "2.176434·10^-8_kg"
PlanckLength = "1.616255·10^-35_m"
PlanckTime = "5.391247·10^-44_s"
PlanckTemperature = "1.416784·10^32_K"
PlanckCharge = "1.875·10^-18_C"
PlanckForce = "1.210·10^44_N"
PlanckEnergy = "1.956·10^9_J"
@@ -425,12 +425,12 @@ PlanckTension = "1.0432·10^27_V"
PlanckCurrent = "3.479·10^25_A"
PlanckPressure = "4.635·10^113_Pa"
PlanckImpedance = "29.986_Ω"
TauonMass = "3.16747·10^-27_kg"
TauonMass = "3.16754·10^-27_kg"
WBosonMass = "1.4334·10^-25_kg"
ZBosonMass = "1.62556·10^-25_kg"
FineStructure = "7.2973525664·10^-3"
RydbergConstant = "1.0973731568508·10^7_m^-1"
HartreeConstant = "4.359744650·10^-18_J"
MagneticFluxQuantum = "2.067833831·10^-15_Wb"
ConductanceQuantum = "7.7480917310·10^-5_S"
CirculationQuantum = "3.6369475486·10^-4_m^2_s^-1"
FineStructure = "7.2973525693·10^-3"
RydbergConstant = "10973731.568160_m^-1"
HartreeConstant = "4.3597447222071·10^-18_J"
MagneticFluxQuantum = "2.067833848·10^-15_Wb"
ConductanceQuantum = "7.748091729·10^-5_S"
CirculationQuantum = "3.6369475516·10^-4_m^2_s^-1"

View File

@@ -172,7 +172,7 @@ KDSize AbstractScrollableMultipleExpressionsView::ContentCell::privateMinimalSiz
}
KDSize centerSize = KDSizeZero;
if (displayCenter() || (forceFullDisplay && !m_centeredExpressionView.layout().isUninitialized())) {
if (displayCenter() || (forceFullDisplay && displayableCenter())) {
centerSize = m_centeredExpressionView.minimalSizeForOptimalDisplay();
width += centerSize.width() + 2 * AbstractScrollableMultipleExpressionsView::k_horizontalMargin + m_approximateSign.minimalSizeForOptimalDisplay().width();
}

View File

@@ -33,6 +33,7 @@ public:
}
bool displayCenter() const { return constContentCell()->displayCenter(); }
void setDisplayCenter(bool display);
void setDisplayableCenter(bool displayable) { contentCell()->setDisplayableCenter(displayable); }
void reloadScroll();
bool handleEvent(Ion::Events::Event event) override;
Poincare::Layout layout() const { return constContentCell()->layout(); }
@@ -63,7 +64,9 @@ protected:
}
void setSelectedSubviewPosition(SubviewPosition subviewPosition);
bool displayCenter() const { return m_displayCenter && !m_centeredExpressionView.layout().isUninitialized(); }
bool displayableCenter() const { return m_displayableCenter && !m_centeredExpressionView.layout().isUninitialized(); }
void setDisplayCenter(bool display);
void setDisplayableCenter(bool displayable) {m_displayableCenter = displayable;}
void layoutSubviews(bool force = false) override;
int numberOfSubviews() const override;
virtual Poincare::Layout layout() const override;
@@ -79,6 +82,7 @@ protected:
ExpressionView m_centeredExpressionView;
SubviewPosition m_selectedSubviewPosition;
bool m_displayCenter;
bool m_displayableCenter;
};
virtual ContentCell * contentCell() = 0;
virtual const ContentCell * constContentCell() const = 0;

View File

@@ -3,7 +3,7 @@
$(eval $(call rule_for, \
AS, %.o, %.s, \
$$(CC) $$(SFLAGS) -c $$< -o $$@, \
global \
global local \
))
$(eval $(call rule_for, \
@@ -56,7 +56,7 @@ $(eval $(call rule_for, \
$(eval $(call rule_for, \
WINDRES, %.o, %.rc, \
$$(WINDRES) $$< -O coff -o $$@, \
$$(WINDRES) $$(WRFLAGS) $$< -O coff -o $$@, \
global \
))

2
docs/build/index.md vendored
View File

@@ -11,7 +11,7 @@ breadcrumb: SDK
We recommend using the [Msys2](https://www.msys2.org/) environment to install most of the required tools. We support Windows 7 and up. Once Msys2 has been installed, launch the Msys2 terminal application, and enter the following commands
```
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-fltk git make python
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python
echo "export PATH=/mingw64/bin:$PATH" >> .bashrc
```

View File

@@ -60,6 +60,7 @@ bool isLockActive();
void setLongRepetition(bool longRepetition);
bool isLongRepetition();
void updateModifiersFromEvent(Event e);
void didPressNewKey();
// Plain

View File

@@ -43,7 +43,9 @@ public:
}
operator uint64_t() const { return m_bitField; }
void setKey(Key k) {
m_bitField |= (uint64_t)1 << (uint8_t)k;
if (k != Key::None) {
m_bitField |= (uint64_t)1 << (uint8_t)k;
}
}
void clearKey(Key k) {
m_bitField &= ~((uint64_t)1 << (uint8_t)k);

View File

@@ -3,5 +3,6 @@ include ion/src/device/shared/usb/Makefile
include ion/src/device/shared/drivers/Makefile
ion_device_src += $(addprefix ion/src/device/shared/, \
events.cpp \
stack.cpp \
)

View File

@@ -0,0 +1,10 @@
#include <ion/events.h>
namespace Ion {
namespace Events {
void didPressNewKey() {
}
}
}

View File

@@ -66,7 +66,7 @@ public:
0, // bInterfaceNumber
k_dfuInterfaceAlternateSetting, // bAlternateSetting
0, // bNumEndpoints: Other than endpoint 0
0xFE, // bInterfaceClass: DFU (http://www.usb.org/developers/defined_class)
0xFE, // bInterfaceClass: DFU (https://www.usb.org/defined-class-codes)
1, // bInterfaceSubClass: DFU
2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1)
4, // iInterface: Index of the Interface string, see m_descriptor

View File

@@ -65,6 +65,7 @@ Event getEvent(int * timeout) {
if (keysSeenTransitionningFromUpToDown != 0) {
sEventIsRepeating = false;
resetLongRepetition();
didPressNewKey();
/* The key that triggered the event corresponds to the first non-zero bit
* in "match". This is a rather simple logic operation for the which many
* processors have an instruction (ARM thumb uses CLZ).

View File

@@ -12,6 +12,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \
rtc.cpp \
crc32.cpp \
display.cpp:-headless \
events.cpp \
events_keyboard.cpp:-headless \
events_stdin.cpp:+headless \
framebuffer_base.cpp \
@@ -26,5 +27,8 @@ ion_src += $(addprefix ion/src/simulator/shared/, \
timing.cpp \
)
ion_simulator_assets = background.jpg horizontal_arrow.png vertical_arrow.png round.png small_squircle.png large_squircle.png
ion_simulator_assets_paths = $(add_prefix ion/src/simulator/assets/,$(ion_simulator_assets))
include ion/src/simulator/$(TARGET)/Makefile
include ion/src/simulator/external/Makefile

View File

@@ -1,10 +1,12 @@
ion_src += $(addprefix ion/src/simulator/android/src/cpp/, \
images.cpp \
haptics_enabled.cpp \
)
ion_src += $(addprefix ion/src/simulator/shared/, \
dummy/callback.cpp \
dummy/language.cpp \
haptics.cpp \
)
ion_src += ion/src/shared/collect_registers.cpp

View File

@@ -0,0 +1,20 @@
#include "../../../shared/haptics.h"
#include <jni.h>
#include <SDL.h>
namespace Ion {
namespace Simulator {
namespace Haptics {
bool isEnabled() {
JNIEnv * env = static_cast<JNIEnv *>(SDL_AndroidGetJNIEnv());
jobject activity = static_cast<jobject>(SDL_AndroidGetActivity());
jclass j_class = env->FindClass("com/numworks/calculator/EpsilonActivity");
jmethodID j_methodId = env->GetMethodID(j_class,"hapticFeedbackIsEnabled", "()Z");
return env->CallObjectMethod(activity, j_methodId);
}
}
}
}

View File

@@ -25,6 +25,8 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
AndroidBitmap_lockPixels(env, j_bitmap, &bitmapPixels);
// TODO: Handle the case where lockPixels fails
size_t bytesPerPixel = 4;
SDL_Texture * texture = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_ABGR8888,
@@ -37,9 +39,11 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
texture,
nullptr,
bitmapPixels,
4 * bitmapInfo.width
bytesPerPixel * bitmapInfo.width
);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
AndroidBitmap_unlockPixels(env, j_bitmap);
return texture;

View File

@@ -3,9 +3,12 @@ package io.github.omega.simulator;
import java.util.Locale;
import android.app.Activity;
import android.content.Context;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import com.google.android.gms.analytics.GoogleAnalytics;
@@ -13,6 +16,7 @@ import com.google.android.gms.analytics.Tracker;
import com.google.android.gms.analytics.HitBuilders;
import org.libsdl.app.SDLActivity;
import org.libsdl.app.SDL;
public class OmegaActivity extends SDLActivity {
protected String[] getLibraries() {
@@ -40,6 +44,12 @@ public class OmegaActivity extends SDLActivity {
return bitmap;
}
public boolean hapticFeedbackIsEnabled() {
ContentResolver contentResolver = SDL.getContext().getContentResolver();
int val = Settings.System.getInt(contentResolver, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0);
return val != 0;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
/* This is done to hide the status bar and the bottom navigation buttons.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -5,6 +5,8 @@ ion_src += $(addprefix ion/src/simulator/ios/, \
ion_src += $(addprefix ion/src/simulator/shared/, \
apple/language.m \
dummy/callback.cpp \
dummy/haptics_enabled.cpp \
haptics.cpp \
)
ion_src += ion/src/shared/collect_registers.cpp

View File

@@ -16,7 +16,9 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
size_t bytesPerRow = bytesPerPixel * width;
size_t bitsPerComponent = 8;
void * bitmapData = malloc(height * width * bytesPerPixel);
size_t size = height * width * bytesPerPixel;
void * bitmapData = malloc(size);
memset(bitmapData, 0, size);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(
@@ -42,9 +44,11 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
texture,
NULL,
bitmapData,
4 * width
bytesPerPixel * width
);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
free(bitmapData);
return texture;

View File

@@ -10,13 +10,15 @@ SFLAGS += -Iion/src/simulator/linux/include
ion_src += $(addprefix ion/src/simulator/linux/, \
images.cpp \
language.cpp \
background.s \
assets.s \
)
ion_src += $(addprefix ion/src/simulator/shared/, \
dummy/callback.cpp \
dummy/haptics_enabled.cpp \
collect_registers_x86_64.s \
collect_registers.cpp \
haptics.cpp \
)
ifeq ($(EPSILON_TELEMETRY),1)
@@ -25,3 +27,16 @@ ion_src += ion/src/shared/telemetry_console.cpp
endif
LDFLAGS += -ljpeg
$(eval $(call rule_for, \
INCBIN, \
ion/src/simulator/linux/assets.s ion/src/simulator/linux/images.h, \
$(ion_simulator_assets_paths), \
$$(PYTHON) ion/src/simulator/linux/incbin.py $(ion_simulator_assets) -o $$@, \
global \
))
$(call object_for,ion/src/simulator/linux/images.cpp): $(BUILD_DIR)/ion/src/simulator/linux/images.h
# The header is refered to as <ion/src/simulator/linux/images.h> so make sure it's findable this way
SFLAGS += -I$(BUILD_DIR)

View File

@@ -1,5 +0,0 @@
.global _ion_simulator_background_start
.global _ion_simulator_background_end
_ion_simulator_background_start:
.incbin "ion/src/simulator/assets/background.jpg"
_ion_simulator_background_end:

View File

@@ -2,49 +2,147 @@
#include <SDL.h>
#include <jpeglib.h>
#include <png.h>
#include <assert.h>
extern unsigned char _ion_simulator_background_start;
extern unsigned char _ion_simulator_background_end;
#include <ion/src/simulator/linux/images.h>
SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) {
enum class AssetFormat {
JPG,
PNG
};
png_size_t readSize = 0;
void ReadDataFromMemory(png_structp png, png_bytep outBytes, png_size_t byteSize) {
png_voidp io = png_get_io_ptr(png);
memcpy((char *)outBytes, (char *)io + readSize, byteSize);
readSize += byteSize;
}
bool readPNG(unsigned char * start, size_t size, unsigned char ** bitmapData, unsigned int * width, unsigned int * height, unsigned int * bytesPerPixel, Uint32 * pixelFormat) {
readSize = 0;
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png) {
// Memory failure
return false;
}
png_infop info = png_create_info_struct(png);
if (!info) {
// Memory failure
png_destroy_read_struct(&png, NULL, NULL);
return false;
}
static constexpr size_t k_pngSignatureLength = 8;
if (png_sig_cmp((png_const_bytep)start, 0, k_pngSignatureLength) != 0) {
// Corrupted PNG
return false;
}
png_set_read_fn(png, start, ReadDataFromMemory);
png_read_info(png, info);
int bitDepth = 0;
int colorType = -1;
png_get_IHDR(png, info, width, height, &bitDepth, &colorType, nullptr, nullptr, nullptr);
*bytesPerPixel = bitDepth*4/8;
*pixelFormat = SDL_PIXELFORMAT_ARGB8888;
assert(colorType == PNG_COLOR_TYPE_RGB_ALPHA);
const png_uint_32 bytesPerRow = png_get_rowbytes(png, info);
*bitmapData = new unsigned char[*height * bytesPerRow];
unsigned char * rowData = *bitmapData;
for (unsigned int rowIndex = 0; rowIndex < *height; rowIndex++) {
png_read_row(png, static_cast<png_bytep>(rowData), nullptr);
rowData += bytesPerRow;
}
png_destroy_read_struct(&png, &info, nullptr);
return true;
}
bool readJPG(const unsigned char * start, size_t size, unsigned char ** bitmapData, unsigned int * width, unsigned int * height, unsigned int * bytesPerPixel, Uint32 * pixelFormat) {
*pixelFormat = SDL_PIXELFORMAT_RGB24;
struct jpeg_decompress_struct info;
struct jpeg_error_mgr err;
info.err = jpeg_std_error(&err);
jpeg_create_decompress(&info);
unsigned char * jpegStart = &_ion_simulator_background_start;
unsigned long jpegSize = &_ion_simulator_background_end - &_ion_simulator_background_start;
jpeg_mem_src(&info, jpegStart, jpegSize);
jpeg_mem_src(&info, start, size);
if (jpeg_read_header(&info, TRUE) != 1) {
return nullptr;
return false;
}
jpeg_start_decompress(&info);
int width = info.output_width;
int height = info.output_height;
int bytesPerPixel = info.output_components;
unsigned char * bitmapData = new unsigned char[height * width * bytesPerPixel];
*width = info.output_width;
*height = info.output_height;
*bytesPerPixel = info.output_components;
*bitmapData = new unsigned char[*height * *width * *bytesPerPixel];
while (info.output_scanline < info.output_height) {
unsigned char * buffer_array[1];
buffer_array[0] = bitmapData + info.output_scanline * width * bytesPerPixel;
buffer_array[0] = *bitmapData + info.output_scanline * *width * *bytesPerPixel;
jpeg_read_scanlines(&info, buffer_array, 1);
}
jpeg_finish_decompress(&info);
jpeg_destroy_decompress(&info);
return true;
}
Uint32 texturePixelFormat = SDL_PIXELFORMAT_RGB24;
assert(bytesPerPixel == SDL_BYTESPERPIXEL(texturePixelFormat));
SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identifier) {
static constexpr const char * jpgExtension = ".jpg";
static constexpr const char * pngExtension = ".png";
unsigned char * assetStart = nullptr;
unsigned long assertSize = 0;
AssetFormat format;
// Find the asset corresponding to identifier
for (size_t i = 0; i < sizeof(resources_addresses)/sizeof(resources_addresses[0]); i++) {
if (strcmp(identifier, resources_addresses[i].identifier()) == 0) {
if (strcmp(jpgExtension, identifier + strlen(identifier) - strlen(jpgExtension)) == 0) {
format = AssetFormat::JPG;
} else {
assert(strcmp(pngExtension, identifier + strlen(identifier) - strlen(pngExtension)) == 0);
format = AssetFormat::PNG;
}
assetStart = resources_addresses[i].start();
assertSize = resources_addresses[i].end() - resources_addresses[i].start();
break;
}
}
assert(assetStart);
unsigned int width;
unsigned int height;
unsigned int bytesPerPixel;
unsigned char * bitmapData;
Uint32 pixelFormat;
switch (format) {
case AssetFormat::JPG:
if (!readJPG(assetStart, assertSize, &bitmapData, &width, &height, &bytesPerPixel, &pixelFormat)) {
return nullptr;
}
break;
default:
assert(format == AssetFormat::PNG);
if (!readPNG(assetStart, assertSize, &bitmapData, &width, &height, &bytesPerPixel, &pixelFormat)) {
return nullptr;
}
}
assert(bytesPerPixel == SDL_BYTESPERPIXEL(pixelFormat));
SDL_Texture * texture = SDL_CreateTexture(
renderer,
texturePixelFormat,
pixelFormat,
SDL_TEXTUREACCESS_STATIC,
width,
height
@@ -52,11 +150,13 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
SDL_UpdateTexture(
texture,
NULL,
nullptr,
bitmapData,
width * bytesPerPixel
);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
delete[] bitmapData;
return texture;

View File

@@ -0,0 +1,70 @@
# This script generates:
# - .s file representing assets in order to access them from C code
# - .h representing the mapping between symbols and assets
import sys
import re
import argparse
import io
import os
parser = argparse.ArgumentParser(description="Process some asset files.")
parser.add_argument('assets', metavar='asset', type=str, nargs='+', help='The list of assets to include')
parser.add_argument('-o', help='The file to generate')
args = parser.parse_args()
def print_asset(f, asset):
asset_basename = os.path.splitext(asset)[0]
f.write(".global _ion_simulator_" + asset_basename + "_start\n")
f.write(".global _ion_simulator_" + asset_basename + "_end\n")
f.write("_ion_simulator_" + asset_basename + "_start:\n")
f.write(" .incbin \"ion/src/simulator/assets/" + asset + "\"\n")
f.write("_ion_simulator_" + asset_basename + "_end:\n\n")
def print_assembly(files, path):
f = open(path, "w")
for asset in files:
print_asset(f, asset)
f.close()
def print_declaration(f, asset):
asset_basename = os.path.splitext(asset)[0]
f.write("extern unsigned char _ion_simulator_" + asset_basename + "_start;\n")
f.write("extern unsigned char _ion_simulator_" + asset_basename + "_end;\n")
def print_mapping(f, asset):
asset_basename = os.path.splitext(asset)[0]
f.write('ResourceMap("' + asset + '", &_ion_simulator_' + asset_basename +'_start, &_ion_simulator_' + asset_basename + '_end),\n')
def print_header(files, path):
f = open(path, "w")
f.write("#ifndef ION_SIMULATOR_LINUX_IMAGES_H\n")
f.write("#define ION_SIMULATOR_LINUX_IMAGES_H\n\n")
f.write("// This file is auto-generated by incbin.py\n\n")
for asset in files:
print_declaration(f, asset)
f.write("\nclass ResourceMap {\n")
f.write("public:\n")
f.write(" constexpr ResourceMap(const char * identifier, unsigned char * start, unsigned char * end) : m_identifier(identifier), m_start(start), m_end(end) {}\n")
f.write(" const char * identifier() const { return m_identifier; }\n")
f.write(" unsigned char * start() const { return m_start; }\n")
f.write(" unsigned char * end() const { return m_end; }\n")
f.write("private:\n")
f.write(" const char * m_identifier;\n")
f.write(" unsigned char * m_start;\n")
f.write(" unsigned char * m_end;\n")
f.write("};\n\n")
f.write("static constexpr ResourceMap resources_addresses[] = {\n")
for asset in files:
print_mapping(f, asset)
f.write("};\n\n")
f.write("#endif\n")
f.close()
if (args.o.endswith(".s")):
print_assembly(args.assets, args.o)
if (args.o.endswith(".h")):
print_header(args.assets, args.o)

View File

@@ -5,8 +5,10 @@ ion_src += $(addprefix ion/src/simulator/macos/, \
ion_src += $(addprefix ion/src/simulator/shared/, \
apple/language.m \
dummy/callback.cpp \
dummy/haptics_enabled.cpp \
collect_registers_x86_64.s \
collect_registers.cpp \
haptics.cpp \
)
ifeq ($(EPSILON_TELEMETRY),1)

View File

@@ -14,12 +14,13 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
size_t bytesPerPixel = 4;
size_t bytesPerRow = bytesPerPixel * width;
size_t bitsPerComponent = 8;
void * bitmapData = malloc(height * width * bytesPerPixel);
size_t size = height * width * bytesPerPixel;
void * bitmapData = malloc(size);
memset(bitmapData, 0, size);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(
@@ -45,9 +46,11 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
texture,
NULL,
bitmapData,
4 * width
bytesPerPixel * width
);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
free(bitmapData);
return texture;

View File

@@ -14,11 +14,16 @@ $(simulator_app_binary): $(foreach arch,$(ARCHS),$(BUILD_DIR)/$(arch)/%.bin) | $
$(call rule_label,LIPO)
$(Q) $(LIPO) -create $^ -output $@
# Background image
# Background & Keys images
$(call simulator_app_resource,background.jpg): ion/src/simulator/assets/background.jpg | $$(@D)/.
define rule_for_asset
simulator_app_deps += $(call simulator_app_resource,$(1))
$(call simulator_app_resource,$(1)): ion/src/simulator/assets/$(1) | $$$$(@D)/.
$(call rule_label,COPY)
$(Q) cp $^ $@
$(Q) cp $$^ $$@
endef
$(foreach asset,$(ion_simulator_assets),$(eval $(call rule_for_asset,$(asset))))
# Process icons
@@ -40,4 +45,3 @@ $(addprefix $(SIMULATOR_ICONSET)/,icon_%.png): ion/src/simulator/assets/logo.svg
simulator_app_deps += $(simulator_app_binary)
simulator_app_deps += $(call simulator_app_plist,Info.plist)
simulator_app_deps += $(call simulator_app_resource,background.jpg)

View File

@@ -32,7 +32,7 @@ void draw(SDL_Renderer * renderer, SDL_Rect * rect) {
int pitch = 0;
void * pixels = nullptr;
SDL_LockTexture(sFramebufferTexture, nullptr, &pixels, &pitch);
assert(pitch == 2*Ion::Display::Width);
assert(pitch == sizeof(KDColor)*Ion::Display::Width);
memcpy(pixels, Framebuffer::address(), sizeof(KDColor)*Ion::Display::Width*Ion::Display::Height);
SDL_UnlockTexture(sFramebufferTexture);

View File

@@ -0,0 +1,15 @@
#include "../haptics.h"
namespace Ion {
namespace Simulator {
namespace Haptics {
bool isEnabled() {
/* Dummy default to false as failsafe.
* Avoid to get haptics feedback that are not desactivable. */
return false;
}
}
}
}

View File

@@ -0,0 +1,13 @@
#include <ion/events.h>
#include "haptics.h"
#include <SDL.h>
namespace Ion {
namespace Events {
void didPressNewKey() {
Simulator::Haptics::rumble();
}
}
}

View File

@@ -1,5 +1,6 @@
#include "main.h"
#include "platform.h"
#include "layout.h"
#include <assert.h>
#include <ion/events.h>
@@ -193,6 +194,18 @@ Event getPlatformEvent() {
result = eventFromSDLTextInputEvent(event.text);
break;
}
#if !EPSILON_SDL_SCREEN_ONLY
if (event.type == SDL_MOUSEMOTION) {
SDL_Point p;
SDL_GetMouseState(&p.x, &p.y);
Simulator::Layout::highlightKeyAt(&p);
}
/* On smarphones, don't forget to unhighlight the key when the finger is up.
* (finger up doesn't imply a mouse motion!) */
if (event.type == SDL_FINGERUP) {
Simulator::Layout::unhighlightKey();
}
#endif
}
if (result != None) {
/* When events are not being processed - for instance when a Python script

View File

@@ -0,0 +1,35 @@
#include "haptics.h"
#include <SDL.h>
namespace Ion {
namespace Simulator {
namespace Haptics {
static SDL_Haptic * sSDLHaptic = nullptr;
void init() {
if (SDL_Init(SDL_INIT_HAPTIC) == 0) {
sSDLHaptic = SDL_HapticOpen(0);
if (sSDLHaptic) {
if (SDL_HapticRumbleInit(sSDLHaptic) != 0) {
sSDLHaptic = nullptr;
}
}
}
}
void shutdown() {
if (sSDLHaptic) {
SDL_HapticClose(sSDLHaptic);
}
}
void rumble() {
if (isEnabled() && sSDLHaptic) {
SDL_HapticRumblePlay(sSDLHaptic, 1.0, 40);
}
}
}
}
}

View File

@@ -0,0 +1,17 @@
#ifndef ION_SIMULATOR_HAPTICS_H
#define ION_SIMULATOR_HAPTICS_H
namespace Ion {
namespace Simulator {
namespace Haptics {
void init();
bool isEnabled();
void rumble();
void shutdown();
}
}
}
#endif

View File

@@ -69,15 +69,9 @@ State scan() {
state = sKeyboardState;
#else
// Register a key for the mouse, if any
SDL_Point p;
Uint32 mouseState = SDL_GetMouseState(&p.x, &p.y);
if (!(previousState) && mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)){
Key k = Simulator::Layout::keyAt(&p);
Key k = Simulator::Layout::getHighlightedKey();
if (SDL_GetMouseState(nullptr, nullptr) && SDL_BUTTON(SDL_BUTTON_LEFT)) {
state.setKey(k);
previousState = true;
}
if (!(mouseState & SDL_BUTTON(SDL_BUTTON_LEFT))) {
previousState = false;
}
#endif

View File

@@ -1,9 +1,16 @@
#include "layout.h"
#include "main.h"
#include "platform.h"
#include <ion.h>
#include <limits.h>
#include <cmath>
namespace Ion {
namespace Simulator {
namespace Layout {
#if !EPSILON_SDL_SCREEN_ONLY
static constexpr int backgroundWidth = 1160;
static constexpr int backgroundHeight = 2220;
@@ -15,16 +22,16 @@ static constexpr SDL_FRect screenRect = {X(192), Y(191), X(776), Y(582)};
static SDL_Rect sFrame;
static void makeAbsolute(const SDL_FRect * f, SDL_Rect * r) {
r->x = f->x * sFrame.w + sFrame.x;
r->y = f->y * sFrame.h + sFrame.y;
r->w = f->w * sFrame.w;
r->h = f->h * sFrame.h;
static void makeAbsolute(const SDL_FRect f, SDL_Rect * r) {
r->x = std::round(f.x * static_cast<float>(sFrame.w) + static_cast<float>(sFrame.x));
r->y = std::round(f.y * static_cast<float>(sFrame.h) + static_cast<float>(sFrame.y));
r->w = std::round(f.w * static_cast<float>(sFrame.w));
r->h = std::round(f.h * static_cast<float>(sFrame.h));
}
static void makeAbsolute(const SDL_FPoint * f, SDL_Point * p) {
p->x = f->x * sFrame.w + sFrame.x;
p->y = f->y * sFrame.h + sFrame.y;
static void makeAbsolute(const SDL_FPoint f, SDL_Point * p) {
p->x = f.x * sFrame.w + sFrame.x;
p->y = f.y * sFrame.h + sFrame.y;
}
void recompute(int width, int height) {
@@ -74,7 +81,7 @@ void recompute(int width, int height) {
}
void getScreenRect(SDL_Rect * rect) {
makeAbsolute(&screenRect, rect);
makeAbsolute(screenRect, rect);
}
void getBackgroundRect(SDL_Rect * rect) {
@@ -84,86 +91,188 @@ void getBackgroundRect(SDL_Rect * rect) {
rect->h = sFrame.h;
}
static constexpr SDL_FPoint sKeyCenters[Keyboard::NumberOfValidKeys] = {
{X(185), Y(1029)}, // A1, Left
{X(273), Y(941)}, // A2, Up
{X(273), Y(1117)}, // A3, Down
{X(361), Y(1029)}, // A4, Right
{X(810), Y(1029)}, // A5, OK
{X(963), Y(1029)}, // A6, Back
class KeyLayout {
public:
enum class Shape : uint8_t {
HorizontalArrow,
VerticalArrow,
Round,
SmallSquircle,
LargeSquircle,
NumberOfShapes
};
static constexpr size_t NumberOfShapes = (size_t)Shape::NumberOfShapes;
static constexpr const char * assetName[KeyLayout::NumberOfShapes] = {
"horizontal_arrow.png",
"vertical_arrow.png",
"round.png",
"small_squircle.png",
"large_squircle.png"
};
{X(580), Y(958)}, // B1, Home
{X(580), Y(1094)}, // B2, Power
constexpr KeyLayout(float x, float y, Shape shape) :
m_center{X(x), Y(y)},
m_shape(shape) {}
SDL_FPoint center() const { return m_center; }
Shape shape() const { return m_shape; }
{X(198), Y(1252)}, // C1, Shift
{X(352), Y(1252)}, // C2, Alpha
{X(506), Y(1252)}, // C3, xnt
{X(656), Y(1252)}, // C4, var
{X(810), Y(1252)}, // C5, toolbox
{X(963), Y(1252)}, // C6, Delete
private:
SDL_FPoint m_center;
Shape m_shape;
};
{X(198), Y(1375)}, // D1, exp
{X(352), Y(1375)}, // D2, ln
{X(506), Y(1375)}, // D3, log
{X(656), Y(1375)}, // D4, i
{X(810), Y(1375)}, // D5, comma
{X(963), Y(1375)}, // D6, power
constexpr const char * const KeyLayout::assetName[KeyLayout::NumberOfShapes];
{X(198), Y(1498)}, // E1, sin
{X(352), Y(1498)}, // E2, cos
{X(506), Y(1498)}, // E3, tan
{X(656), Y(1498)}, // E4, pi
{X(810), Y(1498)}, // E5, sqrt
{X(963), Y(1498)}, // E6, square
static constexpr KeyLayout sKeyLayouts[Keyboard::NumberOfValidKeys] = {
KeyLayout(195, 1029, KeyLayout::Shape::HorizontalArrow), // A1, Left
KeyLayout(273, 948, KeyLayout::Shape::VerticalArrow), // A2, Up
KeyLayout(273, 1108, KeyLayout::Shape::VerticalArrow), // A3, Down
KeyLayout(353, 1029, KeyLayout::Shape::HorizontalArrow), // A4, Right
KeyLayout(810, 1029, KeyLayout::Shape::Round), // A5, OK
KeyLayout(963, 1029, KeyLayout::Shape::Round), // A6, Back
{X(210), Y(1629)}, // F1, 7
{X(395), Y(1629)}, // F2, 8
{X(580), Y(1629)}, // F3, 9
{X(765), Y(1629)}, // F4, (
{X(950), Y(1629)}, // F5, )
KeyLayout(580, 958, KeyLayout::Shape::LargeSquircle), // B1, Home
KeyLayout(580, 1094, KeyLayout::Shape::LargeSquircle), // B2, Power
{X(210), Y(1766)}, // G1, 4
{X(395), Y(1766)}, // G2, 5
{X(580), Y(1766)}, // G3, 6
{X(765), Y(1766)}, // G4, *
{X(950), Y(1766)}, // G5, /
KeyLayout(198, 1253, KeyLayout::Shape::SmallSquircle), // C1, Shift
KeyLayout(352, 1253, KeyLayout::Shape::SmallSquircle), // C2, Alpha
KeyLayout(506, 1253, KeyLayout::Shape::SmallSquircle), // C3, xnt
KeyLayout(656, 1253, KeyLayout::Shape::SmallSquircle), // C4, var
KeyLayout(810, 1253, KeyLayout::Shape::SmallSquircle), // C5, toolbox
KeyLayout(963, 1253, KeyLayout::Shape::SmallSquircle), // C6, Delete
{X(210), Y(1902)}, // H1, 1
{X(395), Y(1902)}, // H2, 2
{X(580), Y(1902)}, // H3, 3
{X(765), Y(1902)}, // H4, +
{X(950), Y(1902)}, // H5, -
KeyLayout(198, 1375, KeyLayout::Shape::SmallSquircle), // D1, exp
KeyLayout(352, 1375, KeyLayout::Shape::SmallSquircle), // D2, ln
KeyLayout(506, 1375, KeyLayout::Shape::SmallSquircle), // D3, log
KeyLayout(656, 1375, KeyLayout::Shape::SmallSquircle), // D4, i
KeyLayout(810, 1375, KeyLayout::Shape::SmallSquircle), // D5, comma
KeyLayout(963, 1375, KeyLayout::Shape::SmallSquircle), // D6, power
{X(210), Y(2040)}, // I1, 0
{X(395), Y(2040)}, // I2, .
{X(580), Y(2040)}, // I3, x10
{X(765), Y(2040)}, // I4, Ans
{X(950), Y(2040)}, // I5, EXE
KeyLayout(198, 1498, KeyLayout::Shape::SmallSquircle), // E1, sin
KeyLayout(352, 1498, KeyLayout::Shape::SmallSquircle), // E2, cos
KeyLayout(506, 1498, KeyLayout::Shape::SmallSquircle), // E3, tan
KeyLayout(656, 1498, KeyLayout::Shape::SmallSquircle), // E4, pi
KeyLayout(810, 1498, KeyLayout::Shape::SmallSquircle), // E5, sqrt
KeyLayout(963, 1498, KeyLayout::Shape::SmallSquircle), // E6, square
KeyLayout(210, 1629, KeyLayout::Shape::LargeSquircle), // F1, 7
KeyLayout(395, 1629, KeyLayout::Shape::LargeSquircle), // F2, 8
KeyLayout(580, 1629, KeyLayout::Shape::LargeSquircle), // F3, 9
KeyLayout(765, 1629, KeyLayout::Shape::LargeSquircle), // F4, (
KeyLayout(950, 1629, KeyLayout::Shape::LargeSquircle), // F5, )
KeyLayout(210, 1766, KeyLayout::Shape::LargeSquircle), // G1, 4
KeyLayout(395, 1766, KeyLayout::Shape::LargeSquircle), // G2, 5
KeyLayout(580, 1766, KeyLayout::Shape::LargeSquircle), // G3, 6
KeyLayout(765, 1766, KeyLayout::Shape::LargeSquircle), // G4, *
KeyLayout(950, 1766, KeyLayout::Shape::LargeSquircle), // G5, /
KeyLayout(210, 1902, KeyLayout::Shape::LargeSquircle), // H1, 1
KeyLayout(395, 1902, KeyLayout::Shape::LargeSquircle), // H2, 2
KeyLayout(580, 1902, KeyLayout::Shape::LargeSquircle), // H3, 3
KeyLayout(765, 1902, KeyLayout::Shape::LargeSquircle), // H4, +
KeyLayout(950, 1902, KeyLayout::Shape::LargeSquircle), // H5, -
KeyLayout(210, 2040, KeyLayout::Shape::LargeSquircle), // I1, 0
KeyLayout(395, 2040, KeyLayout::Shape::LargeSquircle), // I2, .
KeyLayout(580, 2040, KeyLayout::Shape::LargeSquircle), // I3, x10
KeyLayout(765, 2040, KeyLayout::Shape::LargeSquircle), // I4, Ans
KeyLayout(950, 2040, KeyLayout::Shape::LargeSquircle), // I5, EXE
};
static void getKeyCenter(int validKeyIndex, SDL_Point * point) {
assert(validKeyIndex >= 0);
assert(validKeyIndex < Keyboard::NumberOfValidKeys);
makeAbsolute(&sKeyCenters[validKeyIndex], point);
assert(validKeyIndex >= 0 && validKeyIndex < Keyboard::NumberOfValidKeys);
makeAbsolute(sKeyLayouts[validKeyIndex].center(), point);
}
Keyboard::Key keyAt(SDL_Point * p) {
int minSquaredDistance = -1;
Keyboard::Key nearestKey = Keyboard::Key::None;
static void getKeyRectangle(int validKeyIndex, SDL_Texture * texture, SDL_Rect * rect) {
assert(validKeyIndex >= 0 && validKeyIndex < Keyboard::NumberOfValidKeys);
SDL_FPoint point = sKeyLayouts[validKeyIndex].center();
int w, h;
SDL_QueryTexture(texture, NULL, NULL, &w, &h);
SDL_FRect fRect;
fRect.w = X(w);
fRect.h = Y(h);
fRect.x = point.x - fRect.w/2.0f;
fRect.y = point.y - fRect.h/2.0f;
makeAbsolute(fRect, rect);
}
static SDL_Texture * sBackgroundTexture = nullptr;
static SDL_Texture * sKeyLayoutTextures[KeyLayout::NumberOfShapes];
void init(SDL_Renderer * renderer) {
sBackgroundTexture = IonSimulatorLoadImage(renderer, "background.jpg");
for (size_t i = 0; i < KeyLayout::NumberOfShapes; i++) {
sKeyLayoutTextures[i] = IonSimulatorLoadImage(renderer, KeyLayout::assetName[i]);
}
}
static int sHighlightedKeyIndex = -1;
Keyboard::Key getHighlightedKey() {
Keyboard::Key k = Keyboard::Key::None;
if (sHighlightedKeyIndex >= 0) {
k = Keyboard::ValidKeys[sHighlightedKeyIndex];
}
return k;
}
void highlightKeyAt(SDL_Point * p) {
int newHighlightedKeyIndex = -1;
int minSquaredDistance = INT_MAX;
/* The closenessThreshold is apportioned to the size of the frame. As the
* width and the height have a constant ratio, we can compute the
* closenessThreshold from the frame width exclusively. */
int closenessThreshold = sFrame.w/6;
int squaredClosenessThreshold = closenessThreshold*closenessThreshold;
for (int i=0; i<Keyboard::NumberOfValidKeys; i++) {
SDL_Point keyCenter;
getKeyCenter(i, &keyCenter);
int dx = keyCenter.x - p->x;
int dy = keyCenter.y - p->y;
int squaredDistance = dx*dx + dy*dy;
if (squaredDistance < minSquaredDistance || minSquaredDistance < 0) {
if (squaredDistance < squaredClosenessThreshold && squaredDistance < minSquaredDistance) {
minSquaredDistance = squaredDistance;
nearestKey = Keyboard::ValidKeys[i];
newHighlightedKeyIndex = i;
}
}
return nearestKey;
if (newHighlightedKeyIndex != sHighlightedKeyIndex) {
sHighlightedKeyIndex = newHighlightedKeyIndex;
Main::setNeedsRefresh();
}
}
void unhighlightKey() {
sHighlightedKeyIndex = -1;
Main::setNeedsRefresh();
}
void drawHighlightedKey(SDL_Renderer * renderer) {
if (sHighlightedKeyIndex < 0) {
return;
}
int shape = static_cast<int>(sKeyLayouts[sHighlightedKeyIndex].shape());
SDL_Texture * keyTexture = sKeyLayoutTextures[shape];
SDL_Rect rect;
getKeyRectangle(sHighlightedKeyIndex, keyTexture, &rect);
SDL_RenderCopy(renderer, keyTexture, nullptr, &rect);
}
void draw(SDL_Renderer * renderer) {
SDL_Rect backgroundRect;
getBackgroundRect(&backgroundRect);
SDL_RenderCopy(renderer, sBackgroundTexture, nullptr, &backgroundRect);
drawHighlightedKey(renderer);
}
void quit() {
SDL_DestroyTexture(sBackgroundTexture);
sBackgroundTexture = nullptr;
}
#endif
}
}
}

View File

@@ -8,12 +8,22 @@ namespace Ion {
namespace Simulator {
namespace Layout {
#if !EPSILON_SDL_SCREEN_ONLY
void recompute(int width, int height);
void getScreenRect(SDL_Rect * rect);
void getBackgroundRect(SDL_Rect * rect);
Ion::Keyboard::Key keyAt(SDL_Point * p);
Ion::Keyboard::Key getHighlightedKey();
void highlightKeyAt(SDL_Point * p);
void unhighlightKey();
void init(SDL_Renderer * renderer);
void draw(SDL_Renderer * renderer);
void quit();
#endif
}
}

View File

@@ -1,7 +1,9 @@
#include "main.h"
#include "display.h"
#include "platform.h"
#include "haptics.h"
#include "layout.h"
#include "platform.h"
#include "telemetry.h"
#include "random.h"
#include <assert.h>
@@ -93,6 +95,13 @@ int main(int argc, char * argv[]) {
if (!argument_volatile) {
savePython();
}
Ion::Simulator::Haptics::init();
ion_main(arguments.size(), &arguments[0]);
// Shutdown
Ion::Simulator::Haptics::shutdown();
Ion::Simulator::Main::quit();
#endif
Ion::Simulator::Main::quit();
@@ -205,7 +214,6 @@ namespace Main {
static SDL_Window * sWindow = nullptr;
static SDL_Renderer * sRenderer = nullptr;
static SDL_Texture * sBackgroundTexture = nullptr;
static bool sNeedsRefresh = false;
static SDL_Rect sScreenRect;
@@ -257,12 +265,9 @@ void init() {
Display::init(sRenderer);
// No need to load background in web simulator.
#ifndef __EMSCRIPTEN__
if (!argument_screen_only) {
sBackgroundTexture = IonSimulatorLoadImage(sRenderer, "background.jpg");
}
#endif
#if !EPSILON_SDL_SCREEN_ONLY
Layout::init(sRenderer);
#endif
relayout();
}
@@ -273,6 +278,7 @@ void relayout() {
SDL_GetWindowSize(sWindow, &windowWidth, &windowHeight);
SDL_RenderSetLogicalSize(sRenderer, windowWidth, windowHeight);
#if !EPSILON_SDL_SCREEN_ONLY
if (argument_screen_only) {
// Keep original aspect ration in screen_only mode.
float scale = (float)(Ion::Display::Width) / (float)(Ion::Display::Height);
@@ -289,6 +295,12 @@ void relayout() {
} else {
Layout::recompute(windowWidth, windowHeight);
}
#else
sScreenRect.x = 0;
sScreenRect.y = 0;
sScreenRect.w = windowWidth;
sScreenRect.h = windowHeight;
#endif
setNeedsRefresh();
}
@@ -302,27 +314,33 @@ void refresh() {
return;
}
#if EPSILON_SDL_SCREEN_ONLY
Display::draw(sRenderer, &sScreenRect);
#else
if (argument_screen_only) {
Display::draw(sRenderer, &sScreenRect);
} else {
SDL_Rect screenRect;
Layout::getScreenRect(&screenRect);
SDL_Rect backgroundRect;
Layout::getBackgroundRect(&backgroundRect);
SDL_SetRenderDrawColor(sRenderer, 194, 194, 194, 255);
SDL_RenderClear(sRenderer);
SDL_RenderCopy(sRenderer, sBackgroundTexture, nullptr, &backgroundRect);
// Can change sNeedsRefresh state if a key is highlighted and needs to be reset
Layout::draw(sRenderer);
Display::draw(sRenderer, &screenRect);
}
#endif
SDL_RenderPresent(sRenderer);
sNeedsRefresh = false;
IonSimulatorCallbackDidRefresh();
}
void quit() {
#if !EPSILON_SDL_SCREEN_ONLY
Layout::quit();
#endif
Display::quit();
SDL_DestroyWindow(sWindow);
SDL_Quit();
}

View File

@@ -20,6 +20,8 @@ ion_src += $(addprefix ion/src/simulator/web/, \
ion_src += $(addprefix ion/src/simulator/shared/, \
dummy/language.cpp \
dummy/haptics_enabled.cpp \
haptics.cpp \
)
ion_src += ion/src/shared/collect_registers.cpp

View File

@@ -109,17 +109,17 @@ a.action svg {
KEYBOARD .nav span {
position: absolute; }
KEYBOARD .nav .left {
top: 36%;
top: 37%;
left: 2%;
width: 15%;
height: 28%; }
KEYBOARD .nav .right {
top: 36%;
top: 37%;
left: 17%;
width: 15%;
height: 28%; }
KEYBOARD .nav .top {
top: 7%;
top: 9%;
left: 12%;
width: 10%;
height: 40%; }
@@ -131,23 +131,25 @@ a.action svg {
KEYBOARD .nav .home {
top: 15%;
left: 41%;
width: 16%;
height: 33%; }
width: 16.5%;
height: 31%; }
KEYBOARD .nav .power {
top: 54%;
left: 42%;
width: 16%;
height: 33%; }
top: 55.4%;
left: 41%;
width: 16.5%;
height: 31%; }
KEYBOARD .nav .ok {
top: 31%;
left: 67%;
width: 13%;
height: 38%; }
width: 13.5%;
height: 38%;
border-radius: 48%; }
KEYBOARD .nav .back {
top: 31%;
left: 84%;
width: 13%;
height: 38%; }
left: 83.3%;
width: 13.5%;
height: 38%;
border-radius: 48%; }
KEYBOARD .functions {
position: absolute;
top: 26.75%;

View File

@@ -6,6 +6,8 @@ ion_src += $(addprefix ion/src/simulator/windows/, \
ion_src += $(addprefix ion/src/simulator/shared/, \
dummy/callback.cpp \
dummy/haptics_enabled.cpp \
haptics.cpp \
)
ion_src += ion/src/shared/collect_registers.cpp
@@ -15,4 +17,22 @@ ion_src += ion/src/simulator/shared/dummy/telemetry_init.cpp
ion_src += ion/src/shared/telemetry_console.cpp
endif
# RC file dependencies
$(call object_for,ion/src/simulator/windows/resources.rc): WRFLAGS += -I $(BUILD_DIR)
# The header of images is refered to as <ion/src/simulator/windows/resources.h> so make sure it's findable this way
SFLAGS += -I$(BUILD_DIR)
LDFLAGS += -lgdiplus
$(eval $(call rule_for, \
RESGEN, \
ion/src/simulator/windows/resources_gen.rc ion/src/simulator/windows/images.h, \
$(ion_simulator_assets_paths), \
$$(PYTHON) ion/src/simulator/windows/resgen.py $(ion_simulator_assets) -o $$@, \
global \
))
$(call object_for,ion/src/simulator/windows/images.cpp): $(BUILD_DIR)/ion/src/simulator/windows/images.h
$(call object_for,ion/src/simulator/windows/resources.rc): $(BUILD_DIR)/ion/src/simulator/windows/resources_gen.rc

View File

@@ -1,9 +1,11 @@
#include "../shared/platform.h"
#include <ion/src/simulator/windows/images.h>
#include <SDL.h>
#include <windows.h>
#include <olectl.h>
#include <gdiplus.h>
#include <assert.h>
/* Loading images using GDI+
* On Windows, we decompress JPEG images using GDI+ which is widely available.
@@ -40,7 +42,14 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
LPSTREAM stream;
const char * resname = MAKEINTRESOURCE(300);
int resourceID = -1;
for (size_t i = 0; i < sizeof(resourcesIdentifiers)/sizeof(resourcesIdentifiers[0]); i++) {
if (strcmp(identifier, resourcesIdentifiers[i].identifier()) == 0) {
resourceID = resourcesIdentifiers[i].id();
}
}
assert(resourceID >= 0);
const char * resname = MAKEINTRESOURCE(resourceID);
CreateStreamOnResource(resname, &stream);
Gdiplus::Bitmap * image = Gdiplus::Bitmap::FromStream(stream);
@@ -52,6 +61,8 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
Gdiplus::BitmapData * bitmapData = new Gdiplus::BitmapData;
image->LockBits(&rc, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, bitmapData);
size_t bytesPerPixel = 4;
SDL_Texture * texture = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_ARGB8888,
@@ -64,8 +75,10 @@ SDL_Texture * IonSimulatorLoadImage(SDL_Renderer * renderer, const char * identi
texture,
NULL,
bitmapData->Scan0,
4 * width
);
bytesPerPixel * width
);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
image->UnlockBits(bitmapData);
delete bitmapData;

View File

@@ -0,0 +1,57 @@
# This script generates two files:
# - A .rc: the list of the resources to be included in resources.rc
# - A C++ header: the mapping of the resource names and identifiers
import sys
import re
import argparse
import io
parser = argparse.ArgumentParser(description="Process some windows resources.")
parser.add_argument('assets', metavar='asset', type=str, nargs='+', help='The list of assets')
parser.add_argument('-o', required=True, help='The file to generate')
args = parser.parse_args()
def print_declaration(f, asset, identifier):
f.write(str(identifier) + ' RCDATA ' + '"../assets/' + asset + '"\n')
def print_mapping(f, asset, identifier):
f.write('ResourceID("' + asset + '", ' + str(identifier) + '),\n')
def print_mapping_header(f):
f.write("#ifndef ION_SIMULATOR_WINDOWS_IMAGES_H\n")
f.write("#define ION_SIMULATOR_WINDOWS_IMAGES_H\n\n")
f.write("// This file is auto-generated by resgen.py\n\n")
f.write("\nclass ResourceID {\n")
f.write("public:\n")
f.write(" constexpr ResourceID(const char * identifier, int id) : m_identifier(identifier), m_id(id) {}\n")
f.write(" const char * identifier() const { return m_identifier; }\n")
f.write(" int id() const { return m_id; }\n")
f.write("private:\n")
f.write(" const char * m_identifier;\n")
f.write(" int m_id;\n")
f.write("};\n\n")
f.write("static constexpr ResourceID resourcesIdentifiers[] = {\n")
def print_mapping_footer(f):
f.write("};\n\n")
f.write("#endif\n")
def print(files, path, print_header, print_footer, process_asset):
f = open(path, "w")
print_header(f)
identifier = 1000
for asset in files:
process_asset(f, asset, identifier)
identifier += 1
print_footer(f)
f.close()
if (args.o.endswith(".rc")):
print(args.assets, args.o, lambda f: None, lambda f: None, print_declaration)
if (args.o.endswith(".h")):
print(args.assets, args.o, print_mapping_header, print_mapping_footer, print_mapping)

View File

@@ -1,4 +1,4 @@
300 RCDATA "../assets/background.jpg"
#include <ion/src/simulator/windows/resources_gen.rc>
1 VERSIONINFO
FILEVERSION 1,0,0,0

View File

@@ -4,10 +4,15 @@ KDColor KDColor::blend(KDColor first, KDColor second, uint8_t alpha) {
/* This function is a hot path since it's being called for every single pixel
* whenever we want to display a string. In this context, we're quite often
* calling it with a value of either 0 or 0xFF, which can be very trivially
* dealt with. So let's make a special case for them. */
* dealt with. Similarly, blending the same two colors yields a trivial
* result and can be bypassed. Let's make a special case for them. */
if (alpha == 0) {
return second;
} else if (alpha == 0xFF) {
}
if (alpha == 0xFF) {
return first;
}
if (first == second) {
return first;
}
@@ -15,14 +20,9 @@ KDColor KDColor::blend(KDColor first, KDColor second, uint8_t alpha) {
// First is RRRRR GGGGGG BBBBB
// Second is same
uint8_t oneMinusAlpha = 0xFF-alpha;
uint16_t oneMinusAlpha = 0x100-alpha;
uint16_t red = first.red()*alpha + second.red()*oneMinusAlpha;
uint16_t green = first.green()*alpha + second.green()*oneMinusAlpha;
uint16_t blue = first.blue()*alpha + second.blue()*oneMinusAlpha;
return RGB888(red>>8, green>>8, blue>>8);
// Blend White + black, ask for white
// white.red() = 0x1F << 3 = 0xF8
// white.red() * 0xFF = 0xF708, we wanted 0xF800
}

View File

@@ -17,10 +17,25 @@ QUIZ_CASE(kandinsky_color_rgb) {
quiz_assert(KDColor::RGB24(0x123456) == 0x11AA);
}
void assert_colors_blend_to(KDColor c1, KDColor c2, uint8_t alpha, KDColor res) {
quiz_assert(KDColor::blend(c1, c2, alpha) == res );
}
QUIZ_CASE(kandinsky_color_blend) {
KDColor midGray = KDColor::RGB24(0x7F7F7F);
KDColor res = KDColor::blend(KDColorWhite, KDColorBlack, 0xFF);
quiz_assert(res == KDColorWhite);
quiz_assert(KDColor::blend(KDColorWhite, KDColorBlack, 0) == KDColorBlack);
quiz_assert(KDColor::blend(KDColorWhite, KDColorBlack, 0x7F) == midGray);
KDColor midTurquoise = KDColor::RGB24(0x007F7F);
assert_colors_blend_to(KDColorWhite, KDColorBlack, 0x00, KDColorBlack);
assert_colors_blend_to(KDColorWhite, KDColorBlack, 0xFF, KDColorWhite);
assert_colors_blend_to(KDColorWhite, KDColorBlack, 0x7F, midGray);
assert_colors_blend_to(KDColorGreen, KDColorBlue, 0x00, KDColorBlue);
assert_colors_blend_to(KDColorGreen, KDColorBlue, 0xFF, KDColorGreen);
assert_colors_blend_to(KDColorGreen, KDColorBlue, 0x7F, midTurquoise);
// Assert that blending two identical colors does not produce strange colors.
for (uint16_t col = 0; col < 0xFFFF; col++) {
KDColor color = KDColor::RGB16(col);
assert_colors_blend_to(color, color, col>>8, color);
}
}

View File

@@ -12,12 +12,15 @@ bool micropython_port_vm_hook_loop() {
/* Doing too many things here slows down Python execution quite a lot. So we
* only do things once in a while and return as soon as possible otherwise. */
static int c = 0;
c = (c + 1) % 20000;
if (c != 0) {
static uint64_t t = Ion::Timing::millis();
static constexpr uint64_t delay = 100;
uint64_t t2 = Ion::Timing::millis();
if (t2 - t < delay) {
return false;
}
t = t2;
micropython_port_vm_hook_refresh_print();
// Check if the user asked for an interruption from the keyboard

View File

@@ -63,16 +63,6 @@ mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) {
KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : KDColorWhite;
MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox();
KDIonContext::sharedContext()->drawString(text, point, KDFont::LargeFont, textColor, backgroundColor);
/* Before and after execution of "modkandinsky_draw_string",
* "micropython_port_vm_hook_loop" is called by "mp_execute_bytecode" and will
* call "micropython_port_interrupt_if_needed" every 20000 calls.
* However, "drawString" function might take some time to execute leading to
* drastically decrease the frequency of calls to
* "micropython_port_vm_hook_loop" and thus to
* "micropython_port_interrupt_if_needed". So we add an extra
* check for user interruption here. This way the user can still interrupt an
* infinite loop calling 'drawString' for instance. */
micropython_port_interrupt_if_needed();
return mp_const_none;
}
@@ -93,8 +83,6 @@ mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) {
KDColor color = MicroPython::Color::Parse(args[4]);
MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox();
KDIonContext::sharedContext()->fillRect(rect, color);
// Cf comment on modkandinsky_draw_string
micropython_port_interrupt_if_needed();
return mp_const_none;
}

View File

@@ -244,7 +244,8 @@ KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){
NamedColor("orange", Palette::Orange),
NamedColor("purple", Palette::Purple),
NamedColor("grey", Palette::GreyDark),
NamedColor("cyan", Palette::Cyan)
NamedColor("cyan", Palette::Cyan),
NamedColor("magenta", Palette::Magenta)
};
for (NamedColor p : pairs) {
if (strcmp(p.name(), color) == 0) {