mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
Merge branch 'omega-hotfix' into omega-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = "4π·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"
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
2
docs/build/index.md
vendored
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ bool isLockActive();
|
||||
void setLongRepetition(bool longRepetition);
|
||||
bool isLongRepetition();
|
||||
void updateModifiersFromEvent(Event e);
|
||||
void didPressNewKey();
|
||||
|
||||
// Plain
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 \
|
||||
)
|
||||
|
||||
10
ion/src/device/shared/events.cpp
Normal file
10
ion/src/device/shared/events.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <ion/events.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Events {
|
||||
|
||||
void didPressNewKey() {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
20
ion/src/simulator/android/src/cpp/haptics_enabled.cpp
Normal file
20
ion/src/simulator/android/src/cpp/haptics_enabled.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
BIN
ion/src/simulator/assets/horizontal_arrow.png
Normal file
BIN
ion/src/simulator/assets/horizontal_arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
ion/src/simulator/assets/large_squircle.png
Normal file
BIN
ion/src/simulator/assets/large_squircle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
ion/src/simulator/assets/round.png
Normal file
BIN
ion/src/simulator/assets/round.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
ion/src/simulator/assets/small_squircle.png
Normal file
BIN
ion/src/simulator/assets/small_squircle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
ion/src/simulator/assets/vertical_arrow.png
Normal file
BIN
ion/src/simulator/assets/vertical_arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
@@ -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;
|
||||
|
||||
70
ion/src/simulator/linux/incbin.py
Normal file
70
ion/src/simulator/linux/incbin.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
15
ion/src/simulator/shared/dummy/haptics_enabled.cpp
Normal file
15
ion/src/simulator/shared/dummy/haptics_enabled.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
13
ion/src/simulator/shared/events.cpp
Normal file
13
ion/src/simulator/shared/events.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include <ion/events.h>
|
||||
#include "haptics.h"
|
||||
#include <SDL.h>
|
||||
|
||||
namespace Ion {
|
||||
namespace Events {
|
||||
|
||||
void didPressNewKey() {
|
||||
Simulator::Haptics::rumble();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
35
ion/src/simulator/shared/haptics.cpp
Normal file
35
ion/src/simulator/shared/haptics.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
17
ion/src/simulator/shared/haptics.h
Normal file
17
ion/src/simulator/shared/haptics.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
57
ion/src/simulator/windows/resgen.py
Normal file
57
ion/src/simulator/windows/resgen.py
Normal 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)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
300 RCDATA "../assets/background.jpg"
|
||||
#include <ion/src/simulator/windows/resources_gen.rc>
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 1,0,0,0
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user