From 2b9242fac2ca0877b45f771427040eef04ff260e Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 25 Feb 2016 23:10:58 +0100 Subject: [PATCH] [Horus] GUI continued --- .../opentx/simulator/opentxsimulator.cpp | 4 +- radio/sdcard/horus/THEMES/Darkblue/thumb.bmp | Bin 0 -> 6462 bytes .../horus/THEMES/Darkblue/topmenu_opentx.bmp | Bin 0 -> 3858 bytes .../aboutbg.bmp} | Bin .../{default_bg.bmp => Default/mainbg.bmp} | Bin radio/sdcard/horus/THEMES/Default/sleep.bmp | Bin 0 -> 90138 bytes radio/sdcard/horus/THEMES/Default/thumb.bmp | Bin 0 -> 6462 bytes radio/sdcard/horus/WIDGETS/Counter/main.lua | 19 + radio/src/CMakeLists.txt | 11 +- radio/src/Makefile | 2 +- radio/src/bitmaps/horus/CMakeLists.txt | 6 +- radio/src/bitmaps/horus/bmp_background.png | Bin 59319 -> 0 bytes radio/src/bitmaps/horus/bmp_sleep.png | Bin 15868 -> 0 bytes .../src/bitmaps/horus/bmp_topmenu_opentx.png | Bin 1142 -> 0 bytes .../src/bitmaps/horus/mask_about_headico.png | Bin 0 -> 664 bytes radio/src/bmp.cpp | 516 -------------- radio/src/gui/gui_helpers.h | 4 +- radio/src/gui/horus/bitmapbuffer.cpp | 640 ++++++++++++++++++ radio/src/gui/horus/bitmapbuffer.h | 242 +++++++ radio/src/gui/horus/bitmaps.cpp | 39 +- radio/src/gui/horus/bitmaps.h | 12 - radio/src/gui/horus/colors.h | 116 ++++ radio/src/gui/horus/lcd.cpp | 453 +------------ radio/src/gui/horus/lcd.h | 196 ++---- radio/src/gui/horus/menu_general_calib.cpp | 6 +- .../src/gui/horus/menu_general_sdmanager.cpp | 10 +- radio/src/gui/horus/menu_general_setup.cpp | 2 +- radio/src/gui/horus/menu_general_trainer.cpp | 4 +- radio/src/gui/horus/menu_model_curves.cpp | 6 +- .../gui/horus/menu_model_custom_functions.cpp | 2 +- .../src/gui/horus/menu_model_flightmodes.cpp | 6 +- radio/src/gui/horus/menu_model_gvars.cpp | 6 +- radio/src/gui/horus/menu_model_inputs.cpp | 4 +- radio/src/gui/horus/menu_model_limits.cpp | 10 +- radio/src/gui/horus/menu_model_mixes.cpp | 4 +- radio/src/gui/horus/menu_model_select.cpp | 7 +- radio/src/gui/horus/menu_model_setup.cpp | 10 +- radio/src/gui/horus/menu_model_telemetry.cpp | 18 +- radio/src/gui/horus/menus.h | 7 +- radio/src/gui/horus/popups.cpp | 17 +- radio/src/gui/horus/screens_setup.cpp | 137 ++-- radio/src/gui/horus/splash.cpp | 10 +- radio/src/gui/horus/theme.cpp | 48 +- radio/src/gui/horus/theme.h | 35 +- radio/src/gui/horus/themes/bmp_darkblue.png | Bin 1184 -> 0 bytes radio/src/gui/horus/themes/bmp_default.png | Bin 3409 -> 0 bytes radio/src/gui/horus/themes/darkblue.cpp | 15 +- radio/src/gui/horus/themes/default.cpp | 31 +- radio/src/gui/horus/view_about.cpp | 3 +- radio/src/gui/horus/view_main.cpp | 28 +- radio/src/gui/horus/view_statistics.cpp | 4 +- radio/src/gui/horus/widget.h | 1 + radio/src/gui/horus/widgets.cpp | 41 +- radio/src/gui/horus/widgets/gauge.cpp | 4 +- radio/src/gui/horus/widgets/modelbmp.cpp | 85 +-- radio/src/gui/taranis/bmp.cpp | 252 +++++++ radio/src/gui/taranis/gui.h | 1 - radio/src/gui/taranis/lcd.h | 2 +- .../gui/taranis/menu_general_sdmanager.cpp | 2 +- radio/src/lua/api_lcd.cpp | 4 +- radio/src/lua/interface.cpp | 32 +- radio/src/opentx.cpp | 4 +- radio/src/opentx.h | 8 +- radio/src/sdcard.h | 13 +- radio/src/syscalls.c | 6 +- radio/src/targets/horus/board_horus.h | 4 +- radio/src/targets/horus/lcd_driver.cpp | 51 +- radio/src/tests/lcd.cpp | 16 +- radio/src/translations/en.h.txt | 7 + radio/util/img2lbm.py | 21 +- 70 files changed, 1770 insertions(+), 1474 deletions(-) create mode 100644 radio/sdcard/horus/THEMES/Darkblue/thumb.bmp create mode 100644 radio/sdcard/horus/THEMES/Darkblue/topmenu_opentx.bmp rename radio/sdcard/horus/THEMES/{default_aboutbg.bmp => Default/aboutbg.bmp} (100%) rename radio/sdcard/horus/THEMES/{default_bg.bmp => Default/mainbg.bmp} (100%) create mode 100644 radio/sdcard/horus/THEMES/Default/sleep.bmp create mode 100644 radio/sdcard/horus/THEMES/Default/thumb.bmp create mode 100644 radio/sdcard/horus/WIDGETS/Counter/main.lua delete mode 100644 radio/src/bitmaps/horus/bmp_background.png delete mode 100644 radio/src/bitmaps/horus/bmp_sleep.png delete mode 100644 radio/src/bitmaps/horus/bmp_topmenu_opentx.png create mode 100644 radio/src/bitmaps/horus/mask_about_headico.png delete mode 100644 radio/src/bmp.cpp create mode 100644 radio/src/gui/horus/bitmapbuffer.cpp create mode 100644 radio/src/gui/horus/bitmapbuffer.h create mode 100644 radio/src/gui/horus/colors.h delete mode 100644 radio/src/gui/horus/themes/bmp_darkblue.png delete mode 100644 radio/src/gui/horus/themes/bmp_default.png create mode 100644 radio/src/gui/taranis/bmp.cpp diff --git a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp index 0d1141b5a..f1cf8fbfe 100644 --- a/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp +++ b/companion/src/firmwares/opentx/simulator/opentxsimulator.cpp @@ -134,7 +134,7 @@ namespace NAMESPACE { #include "radio/src/trainer_input.cpp" #if defined(PCBHORUS) -#include "radio/src/bmp.cpp" +#include "radio/src/gui/horus/bitmapbuffer.cpp" #include "radio/src/gui/horus/bitmaps.cpp" #include "radio/src/gui/horus/curves.cpp" #include "radio/src/gui/horus/fonts.cpp" @@ -224,7 +224,7 @@ namespace NAMESPACE { #include "radio/src/gui/Flamenco/splash.cpp" #include "radio/src/targets/flamenco/tw8823_driver.cpp" #elif defined(PCBTARANIS) -#include "radio/src/bmp.cpp" +#include "radio/src/gui/taranis/bmp.cpp" #include "radio/src/gui/taranis/fonts.cpp" #include "radio/src/gui/taranis/widgets.cpp" #include "radio/src/gui/taranis/navigation.cpp" diff --git a/radio/sdcard/horus/THEMES/Darkblue/thumb.bmp b/radio/sdcard/horus/THEMES/Darkblue/thumb.bmp new file mode 100644 index 0000000000000000000000000000000000000000..af5de373ece9d8df5ca166b3b338c55f4bb7df85 GIT binary patch literal 6462 zcmeHLX~OGu+uTyQ~g0l~;3Y7iC0T{h!N5^-ONf>O7*)x@P*72LJ?+JDeK z`G0TcTtvl6+k8lidF1i+z@0f~X3i|<%&oB+L>&LwrT;4gEg_Teh|A(0)A%`s;}QgZ z(9ggJ9C!7{)ik!v1PK3jjJU;!?a4Zvc`9-2F1r)H$#SfY)MCqN#7PLAlc{o?PFLXA zrMp_gH(7}dmjzxo&70EW^hZMwotH=aXC8VUV%-Cm9&4jUUiPtDACm8cWdF9+4Oep_ zyhC|7b(iAART6|#S21>+l_)Eaz+RPu^Qph`usNshm-wYG8}FMUu-N>B$3%S`8Y=ae zZhwIbkDllfqCdW(GrsqYk8n1v$H`=IklcVCTZ0-@DaGh9X>l|etXqJG&E2pk!#k>k z+meFq?lc^|)8Npl3c~R_71sMzs8D91w?U4R33U)Xz<)4Si1n^SESaOQY>DRKX?%uG zeJW5Px@?>I8=lzl|HZ1xwTPAh?3K~LeE-lEkDnc4S|gITgV&RET{Qdc3fOmh zWc=Qdg3Ecq>#kU1;HrCyt|kLs*DA1dhg>F~@$C-e!8fjf+nj<;dlJ0E`Pd(l1;INk z!^gKd=&4G>q$vZwJ9yZfUD^v~&+-kw+}8W<-%(f(A)Bqc>BvQ&kz|`Qus0a2+f7~9 z+G5EsTCyb>Z2tqYnBQ-L>9=AfP<-5ce*1f1{+Ewz-N-xF{(IRh2FRC}`*%1|%*WWl zr~+Hv8Q7uxU9}0W?D}%OuJ`0?&s{P_Qanh>uax&Aa-7dplP#$@rCP-5kdJIyZFzQ& zY{|v`upGw|T3pN+{kBkDW43rl?x8J|SH}}Y54K#MA748k{%L)GzEF%k_$91r!Z+ua zet(P|QSP5p9dkCPVd1SBvmLpZwJWh?{XVSrKUCh#H$`KvOMwNu9CL4!*mu{U%OFNU zwgg6PK6FK;m@&nMy)CPB4;iZ#ps}(X6{=jwv$8PlsKHP}I!0|pa9U&-Z_!}U68?T! zp;|g!9}Rbh7Gq{9hOKJqacbbPrQ*|6G4=-}I2x1Fxqmlnfj)ObN|2VO+uY1H=l=!u`|AZ$)WzG;9m7FdxKJcj8W~nm^Jb> z`OKxpZeJGP^RK-UP|q9{_Fy&HNB!h>cRJYnqEB;1w3Z3+s*pox(Gzs2BjC^rFjN&A zMuyvGU4;@f8faHLsI+?&}%fPt*C&hwhk@L&1fyB$)Q42w&PaCuyq zTb@IXIKyv8W+voe1R2?*qJ0;OM5rU5RO@sUUnS6~)llZ;@%|B$4-$kz%9}(a5G16( VTA>JAs7N=>E&Y`F`DnA+{scFbG$jB4 literal 0 HcmV?d00001 diff --git a/radio/sdcard/horus/THEMES/Darkblue/topmenu_opentx.bmp b/radio/sdcard/horus/THEMES/Darkblue/topmenu_opentx.bmp new file mode 100644 index 0000000000000000000000000000000000000000..cbc7f814949b3b93d41dedebfe17ad08921c4378 GIT binary patch literal 3858 zcmds&O-Q9>6vy9tr%chglOk$Hb<@U$F>WG3L6mV34W*E12u-A-BASbgiX0bZi<(Le zt1;1yAcBgxR}j&?ipVTQ6jy$KBBH1BKiBj6yw6-^?zP$U;Qu}!=RD{4oaf{9{>Mpo z94U|a_YvzY)|)J!6LfBI{d2%^p56|LyIRO`+&}032cNX4;dyJn<;c+xF{F%)46;j2 zO^s_;Kj&@s?`CxwDh_^M@ zPwr#=#`+>+C-7W5cdW;VC+&MYX0GuM?pHnQ7ZDqbk64JMv9Xp9S;~Kf+kYE&`2E6q znWa7#b;NAU`<10^)W5T^&#;xVOdHmLeXzLbN5u|uEHihszhb4aa0g8Gm)sA-jhc;E z->^(RcpWU0`xo~EtpA#Qi(}e*Z*tMjznXiayphWT7T%7{ME@t2k99@PH)bR553F$P z+t+h%%*MOHnc8gJgZExAyP4zL_f6M<@%ZccQN1zjb^0}2<$wAD*!Yg%d!h`~$n&u^ z_7C@8XI*jMqRRVyivjj`tQ1y{W&4`@2D`4Vj_T{{MXjx^_PI7mZ7 zL(iqLu`#;)&Rud6dF~gi&kbGV;^FEZyc@>fIQOt~bngGS##`&|?xwc3HcC%V_uQG< z+FEL8XrQ>bIL|otDkdg|IyyS2v$NAxS67$&7I8Px&&dn6l&Jh56 zE-t3NzCP)1Zf?4QN4vYbaxc)HnVF%SoE)*^p3Ke7$sJf+TqFqj>6<7kE2D*l Q1sWe87k{v4XJ;|zUtS99mjD0& literal 0 HcmV?d00001 diff --git a/radio/sdcard/horus/THEMES/default_aboutbg.bmp b/radio/sdcard/horus/THEMES/Default/aboutbg.bmp similarity index 100% rename from radio/sdcard/horus/THEMES/default_aboutbg.bmp rename to radio/sdcard/horus/THEMES/Default/aboutbg.bmp diff --git a/radio/sdcard/horus/THEMES/default_bg.bmp b/radio/sdcard/horus/THEMES/Default/mainbg.bmp similarity index 100% rename from radio/sdcard/horus/THEMES/default_bg.bmp rename to radio/sdcard/horus/THEMES/Default/mainbg.bmp diff --git a/radio/sdcard/horus/THEMES/Default/sleep.bmp b/radio/sdcard/horus/THEMES/Default/sleep.bmp new file mode 100644 index 0000000000000000000000000000000000000000..279a9b85554c4d61e48274d34c8374357618b405 GIT binary patch literal 90138 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 aYYnbKT;pHC8lcKX{GN8)amUqJCjLJfREjnL literal 0 HcmV?d00001 diff --git a/radio/sdcard/horus/THEMES/Default/thumb.bmp b/radio/sdcard/horus/THEMES/Default/thumb.bmp new file mode 100644 index 0000000000000000000000000000000000000000..37de4998e3193133e915a18b70f105e27197d5e3 GIT binary patch literal 6462 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 bFPjp-w6^J6*RbhZi5u)2-@Jxx&*%R?ODUFA literal 0 HcmV?d00001 diff --git a/radio/sdcard/horus/WIDGETS/Counter/main.lua b/radio/sdcard/horus/WIDGETS/Counter/main.lua new file mode 100644 index 000000000..86ff9bd4b --- /dev/null +++ b/radio/sdcard/horus/WIDGETS/Counter/main.lua @@ -0,0 +1,19 @@ +local options = { + { "Source", SOURCE, 1 }, + { "Min", VALUE, -1024 }, + { "Max", VALUE, 1024 }, + { "Color", COLOR, RED } +} + +local function create(zone, options) + local pie = { zone=zone, options=options, counter=0 } + print(options.Source) + return pie +end + +function refresh(pie) + pie.counter = pie.counter + 1 + lcd.drawNumber(pie.zone.x, pie.zone.y, pie.counter, LEFT + DBLSIZE + TEXT_COLOR); +end + +return { name="Counter", options=options, create=create, refresh=refresh } diff --git a/radio/src/CMakeLists.txt b/radio/src/CMakeLists.txt index 2653aa4bb..09a64d50e 100644 --- a/radio/src/CMakeLists.txt +++ b/radio/src/CMakeLists.txt @@ -70,6 +70,7 @@ if(PCB STREQUAL HORUS) file(GLOB WIDGETS_SRC RELATIVE ${RADIO_SRC_DIRECTORY}/gui/horus ${RADIO_SRC_DIRECTORY}/gui/horus/widgets/*.cpp) set(GUI_SRC ${GUI_SRC} + bitmapbuffer.cpp curves.cpp bitmaps.cpp menu_general_hardware.cpp @@ -87,7 +88,7 @@ if(PCB STREQUAL HORUS) ${LAYOUTS_SRC} ${WIDGETS_SRC} ) - set(SRC ${SRC} bmp.cpp targets/taranis/rtc_driver.cpp) + set(SRC ${SRC} targets/taranis/rtc_driver.cpp) set(TARGET_SRC ${TARGET_SRC} board_horus.cpp) set(FIRMWARE_TARGET_SRC ${FIRMWARE_TARGET_SRC} @@ -146,8 +147,7 @@ elseif(PCB STREQUAL TARANIS) add_definitions(-DPCBTARANIS) add_definitions(-DAUDIO -DVOICE -DRTCLOCK) add_definitions(-DDBLKEYS -DVIRTUALINPUTS -DLUAINPUTS -DXCURVES -DVARIO) - set(SRC ${SRC} bmp.cpp) - set(GUI_SRC ${GUI_SRC} menu_model_inputs.cpp menu_model_mixes.cpp menu_general_diagkeys.cpp menu_general_diaganas.cpp menu_general_hardware.cpp view_channels.cpp view_telemetry.cpp view_text.cpp view_about.cpp) + set(GUI_SRC ${GUI_SRC} bmp.cpp menu_model_inputs.cpp menu_model_mixes.cpp menu_general_diagkeys.cpp menu_general_diaganas.cpp menu_general_hardware.cpp view_channels.cpp view_telemetry.cpp view_text.cpp view_about.cpp) set(TARGET_SRC ${TARGET_SRC} board_taranis.cpp rtc_driver.cpp) set(FIRMWARE_SRC ${FIRMWARE_SRC} loadboot.cpp) set(FIRMWARE_TARGET_SRC @@ -767,6 +767,7 @@ foreach(FILE ${PULSES_SRC}) endforeach() add_definitions(-DCORRECT_NEGATIVE_SHIFTS) +add_definitions(-Wall -Wno-strict-aliasing -Wformat -Wreturn-type -Wunused -Wuninitialized -Wunknown-pragmas -Wno-switch -Wtype-limits) if(NOT WIN32) add_subdirectory(targets/simu) @@ -775,8 +776,6 @@ endif() set(SRC ${SRC} ${FIRMWARE_SRC}) -set(WARNING_FLAGS "-Wall -Wno-strict-aliasing -Wformat -Wreturn-type -Wunused -Wuninitialized -Wunknown-pragmas -Wno-switch -Wtype-limits") - # trick to remove the -rdynamic and --out-implib issues set(CMAKE_EXECUTABLE_SUFFIX ".elf") set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") @@ -798,7 +797,7 @@ if(ARCH STREQUAL ARM) set(COMMON_FLAGS "-mcpu=${MCU} -mthumb -fomit-frame-pointer -fverbose-asm -Wa,-ahlms=firmware.lst -O${OPT} -gdwarf-2 -DHSE_VALUE=${HSE_VALUE} -fno-exceptions -fdata-sections -ffunction-sections ${WARNING_FLAGS}") set(CMAKE_C_FLAGS "${COMMON_FLAGS} -Wimplicit") set(CMAKE_CXX_FLAGS "${COMMON_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "-mcpu=${MCU} -mthumb -nostartfiles -lm -T${RADIO_SRC_DIRECTORY}/${LINKER_SCRIPT} -Wl,-Map=firmware.map,--cref,--no-warn-mismatch,--gc-sections") + set(CMAKE_EXE_LINKER_FLAGS "-mcpu=${MCU} -mthumb -lm -T${RADIO_SRC_DIRECTORY}/${LINKER_SCRIPT} -Wl,-Map=firmware.map,--cref,--no-warn-mismatch,--gc-sections") if(PCB STREQUAL TARANIS) add_subdirectory(targets/${TARGET_DIR}/bootloader) diff --git a/radio/src/Makefile b/radio/src/Makefile index 85d70de56..13d356d8d 100644 --- a/radio/src/Makefile +++ b/radio/src/Makefile @@ -979,7 +979,7 @@ ifeq ($(PCB), TARANIS) PULSESSRC = pulses/pulses_arm.cpp pulses/ppm_arm.cpp pulses/pxx_arm.cpp pulses/crossfire.cpp CPPSRC += tasks_arm.cpp audio_arm.cpp sbus.cpp telemetry/telemetry.cpp CPPSRC += targets/taranis/pulses_driver.cpp targets/taranis/keys_driver.cpp targets/taranis/adc_driver.cpp targets/taranis/trainer_driver.cpp targets/taranis/audio_driver.cpp targets/taranis/serial2_driver.cpp targets/taranis/telemetry_driver.cpp - CPPSRC += bmp.cpp gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp + CPPSRC += gui/$(GUIDIRECTORY)/bmp.cpp gui/$(GUIDIRECTORY)/view_channels.cpp gui/$(GUIDIRECTORY)/view_about.cpp gui/$(GUIDIRECTORY)/view_text.cpp debug.cpp EXTRABOARDSRC += loadboot.cpp ifeq ($(PCBREV), REV9E) CPPSRC += targets/taranis/top_lcd_driver.cpp diff --git a/radio/src/bitmaps/horus/CMakeLists.txt b/radio/src/bitmaps/horus/CMakeLists.txt index 081ae6d38..4b32dcc23 100644 --- a/radio/src/bitmaps/horus/CMakeLists.txt +++ b/radio/src/bitmaps/horus/CMakeLists.txt @@ -8,9 +8,9 @@ add_custom_target(ttf_horus_fonts DEPENDS ttf_horus_tinsize ttf_horus_smlsize tt add_bitmaps_target(horus_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/bmp_*.png" 480 5/6/5) add_bitmaps_target(horus_calibration_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/calibration/bmp_*.png" 480 5/6/5) -add_bitmaps_target(horus_button_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/button/alpha_*.png" 480 5/6/5/8) -add_bitmaps_target(horus_alpha_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/alpha_*.png" 480 5/6/5/8) -add_bitmaps_target(horus_alpha_calibration_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/calibration/alpha_*.png" 480 5/6/5/8) +add_bitmaps_target(horus_button_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/button/alpha_*.png" 480 4/4/4/4) +add_bitmaps_target(horus_alpha_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/alpha_*.png" 480 4/4/4/4) +add_bitmaps_target(horus_alpha_calibration_bitmaps "${RADIO_SRC_DIRECTORY}/bitmaps/horus/calibration/alpha_*.png" 480 4/4/4/4) add_bitmaps_target(horus_masks ${RADIO_SRC_DIRECTORY}/bitmaps/horus/mask_*.png 480 8bits) add_bitmaps_target(horus_slider_masks "${RADIO_SRC_DIRECTORY}/bitmaps/horus/slider/*.png" 480 8bits) add_bitmaps_target(horus_layouts_masks "${RADIO_SRC_DIRECTORY}/gui/horus/layouts/*.png" 480 8bits) diff --git a/radio/src/bitmaps/horus/bmp_background.png b/radio/src/bitmaps/horus/bmp_background.png deleted file mode 100644 index 02fa6695d60aea67d8f79a89cb12ae23fcdb8536..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59319 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 diff --git a/radio/src/bitmaps/horus/bmp_sleep.png b/radio/src/bitmaps/horus/bmp_sleep.png deleted file mode 100644 index 70e163272d0e325e9c29b836861fd2bb000008d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15868 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

Q@JywLm0&M5||C7`2?YR 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 IM6N<$f}|Q8BLDyZ diff --git a/radio/src/bitmaps/horus/mask_about_headico.png b/radio/src/bitmaps/horus/mask_about_headico.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ebe7ecabe7d3beabed69325668009853d409d9 GIT binary patch literal 664 zcmV;J0%!e+P)|l%|?-8Y{o`Ic<$mh?!^yt%*?58ojLWKPjk+hPGdqu1dUCW zWf+gg@bmKnG);rmYW-Y<8d|MZ!)P>$Hk%Cr0096U4hJTaN$hkwZ)?AT*XuQAGMO9p zDlE&QBuVPa(X$A zgUjVYQUU+~`~4m!lgZr}5zO;EG5V^O2wtsLU&D{JL@-U$U&D{JL~t}3{ThC%B{|iS zB%#meBRA~E)rvgNYljgLbGe*qxKt|rI>*qh7B9TBr5VXOqR_m+N z>~uPDFc^@-k|ZI+FvQ^h@pwEqo6Xe2=kpm;sgx#ENh}t_>-DM*E*6Wrpi1iX`e(S; z>!I0f))fwi!|&nEW`lmeUsqJgd_KQ9PnQJCvasE5ABS~m?RNXtzk^4k5p=szfe&{KqwRf0DwP1W2voPWqb+%0000 LCD_W) { - return NULL; - } - - FRESULT result = f_open(&bmpFile, filename, FA_OPEN_EXISTING | FA_READ); - if (result != FR_OK) { - return NULL; - } - - if (f_size(&bmpFile) < 14) { - f_close(&bmpFile); - return NULL; - } - - result = f_read(&bmpFile, buf, 14, &read); - if (result != FR_OK || read != 14) { - f_close(&bmpFile); - return NULL; - } - - if (buf[0] != 'B' || buf[1] != 'M') { - f_close(&bmpFile); - return NULL; - } - - uint32_t fsize = *((uint32_t *)&buf[2]); - uint32_t hsize = *((uint32_t *)&buf[10]); /* header size */ - - uint32_t len = limit((uint32_t)4, (uint32_t)(hsize-14), (uint32_t)32); - result = f_read(&bmpFile, buf, len, &read); - if (result != FR_OK || read != len) { - f_close(&bmpFile); - return NULL; - } - - uint32_t ihsize = *((uint32_t *)&buf[0]); /* more header size */ - - /* invalid header size */ - if (ihsize + 14 > hsize) { - f_close(&bmpFile); - return NULL; - } - - /* sometimes file size is set to some headers size, set a real size in that case */ - if (fsize == 14 || fsize == ihsize + 14) - fsize = f_size(&bmpFile) - 2; - - /* declared file size less than header size */ - if (fsize <= hsize) { - f_close(&bmpFile); - return NULL; - } - - uint32_t w, h; - - switch (ihsize){ - case 40: // windib - case 56: // windib v3 - case 64: // OS/2 v2 - case 108: // windib v4 - case 124: // windib v5 - w = *((uint32_t *)&buf[4]); - h = *((uint32_t *)&buf[8]); - buf += 12; - break; - case 12: // OS/2 v1 - w = *((uint16_t *)&buf[4]); - h = *((uint16_t *)&buf[6]); - buf += 8; - break; - default: - f_close(&bmpFile); - return NULL; - } - - if (*((uint16_t *)&buf[0]) != 1) { /* planes */ - f_close(&bmpFile); - return NULL; - } - - if (w > width || h > height) { - f_close(&bmpFile); - return NULL; - } - - uint16_t depth = *((uint16_t *)&buf[2]); - - buf = &bmpBuf[0]; - - if (depth == 4) { - if (f_lseek(&bmpFile, hsize-64) != FR_OK || f_read(&bmpFile, buf, 64, &read) != FR_OK || read != 64) { - f_close(&bmpFile); - return NULL; - } - for (uint8_t i=0; i<16; i++) { - palette[i] = buf[4*i] >> 4; - } - } - else { - if (f_lseek(&bmpFile, hsize) != FR_OK) { - f_close(&bmpFile); - return NULL; - } - } - - uint8_t * dest = bmp; - - *dest++ = w; - *dest++ = h; - - memset(dest, 0, BITMAP_BUFFER_SIZE(w, h) - 2); - - uint32_t rowSize; - - switch (depth) { - case 1: - rowSize = ((w+31)/32)*4; - for (uint32_t i=0; i=0; i--) { - result = f_read(&bmpFile, buf, rowSize, &read); - if (result != FR_OK || read != rowSize) { - f_close(&bmpFile); - return NULL; - } - uint8_t * dst = dest + (i/2)*w; - for (uint32_t j=0; j> ((j & 1) ? 0 : 4)) & 0x0F; - uint8_t val = palette[index] << ((i & 1) ? 4 : 0); - *dst++ |= val ^ ((i & 1) ? 0xF0 : 0x0F); - } - } - break; - - default: - f_close(&bmpFile); - return NULL; - } - - f_close(&bmpFile); - return bmp; -} -#elif defined(PCBHORUS) -uint8_t * bmpLoad(const char * filename) -{ - FIL bmpFile; - UINT read; - uint8_t palette[16]; - uint8_t bmpBuf[LCD_W]; /* maximum with LCD_W */ - uint8_t * buf = &bmpBuf[0]; - - FRESULT result = f_open(&bmpFile, filename, FA_OPEN_EXISTING | FA_READ); - if (result != FR_OK) { - return NULL; - } - - if (f_size(&bmpFile) < 14) { - f_close(&bmpFile); - return NULL; - } - - result = f_read(&bmpFile, buf, 14, &read); - if (result != FR_OK || read != 14) { - f_close(&bmpFile); - return NULL; - } - - if (buf[0] != 'B' || buf[1] != 'M') { - f_close(&bmpFile); - return NULL; - } - - uint32_t fsize = *((uint32_t *)&buf[2]); - uint32_t hsize = *((uint32_t *)&buf[10]); /* header size */ - - uint32_t len = limit((uint32_t)4, (uint32_t)(hsize-14), (uint32_t)32); - result = f_read(&bmpFile, buf, len, &read); - if (result != FR_OK || read != len) { - f_close(&bmpFile); - return NULL; - } - - uint32_t ihsize = *((uint32_t *)&buf[0]); /* more header size */ - - /* invalid header size */ - if (ihsize + 14 > hsize) { - f_close(&bmpFile); - return NULL; - } - - /* sometimes file size is set to some headers size, set a real size in that case */ - if (fsize == 14 || fsize == ihsize + 14) - fsize = f_size(&bmpFile) - 2; - - /* declared file size less than header size */ - if (fsize <= hsize) { - f_close(&bmpFile); - return NULL; - } - - uint32_t w, h; - - switch (ihsize){ - case 40: // windib - case 56: // windib v3 - case 64: // OS/2 v2 - case 108: // windib v4 - case 124: // windib v5 - w = *((uint32_t *)&buf[4]); - h = *((uint32_t *)&buf[8]); - buf += 12; - break; - case 12: // OS/2 v1 - w = *((uint16_t *)&buf[4]); - h = *((uint16_t *)&buf[6]); - buf += 8; - break; - default: - f_close(&bmpFile); - return NULL; - } - - if (*((uint16_t *)&buf[0]) != 1) { /* planes */ - f_close(&bmpFile); - return NULL; - } - - uint16_t depth = *((uint16_t *)&buf[2]); - - buf = &bmpBuf[0]; - - if (depth == 4) { - if (f_lseek(&bmpFile, hsize-64) != FR_OK || f_read(&bmpFile, buf, 64, &read) != FR_OK || read != 64) { - f_close(&bmpFile); - return NULL; - } - for (uint8_t i=0; i<16; i++) { - palette[i] = buf[4*i]; - } - } - else { - if (f_lseek(&bmpFile, hsize) != FR_OK) { - f_close(&bmpFile); - return NULL; - } - } - - uint8_t * bmp = (uint8_t *)malloc(BITMAP_BUFFER_SIZE(w, h)); - uint16_t * dest = (uint16_t *)bmp; - if (bmp == NULL) { - f_close(&bmpFile); - return NULL; - } - - *dest++ = w; - *dest++ = h; - memset(dest, 0, BITMAP_BUFFER_SIZE(w, h) - 4); - - uint32_t rowSize; - - switch (depth) { - case 32: - for (int i=h-1; i>=0; i--) { - uint8_t * dst = ((uint8_t *)dest) + i*w*2; - for (unsigned int j=0; j>24) & 0xff, (pixel>>16) & 0xff, (pixel>>8) & 0xff); - dst += 2; - } - } - break; - - case 1: - break; - - case 4: - rowSize = ((4*w+31)/32)*4; - for (int32_t i=h-1; i>=0; i--) { - result = f_read(&bmpFile, buf, rowSize, &read); - if (result != FR_OK || read != rowSize) { - f_close(&bmpFile); - free(bmp); - return NULL; - } - uint8_t * dst = ((uint8_t *)dest) + i*w*2; - for (uint32_t j=0; j> ((j & 1) ? 0 : 4)) & 0x0F; - uint8_t val = palette[index]; - *((uint16_t *)dst) = RGB(val, val, val); - dst += 2; - // *dst++ = 0x0F; - } - } - break; - - default: - f_close(&bmpFile); - free(bmp); - return NULL; - } - - f_close(&bmpFile); - return bmp; -} -#endif - -const uint8_t bmpHeader[] = { - 0x42, 0x4d, 0xF8, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x28, 0x00, - 0x00, 0x00, 212, 0x00, 0x00, 0x00, 64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0xbc, 0x38, 0x00, 0x00, 0xbc, 0x38, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xee, 0xee, 0xee, 0x00, 0xdd, 0xdd, - 0xdd, 0x00, 0xcc, 0xcc, 0xcc, 0x00, 0xbb, 0xbb, 0xbb, 0x00, 0xaa, 0xaa, 0xaa, 0x00, 0x99, 0x99, - 0x99, 0x00, 0x88, 0x88, 0x88, 0x00, 0x77, 0x77, 0x77, 0x00, 0x66, 0x66, 0x66, 0x00, 0x55, 0x55, - 0x55, 0x00, 0x44, 0x44, 0x44, 0x00, 0x33, 0x33, 0x33, 0x00, 0x22, 0x22, 0x22, 0x00, 0x11, 0x11, - 0x11, 0x00, 0x00, 0x00, 0x00, 0x00 -}; - -inline display_t getPixel(unsigned int x, unsigned int y) -{ - if (x>=LCD_W || y>=LCD_H) - return 0; - display_t * p = &displayBuf[y / 2 * LCD_W + x]; - return (y & 1) ? (*p >> 4) : (*p & 0x0F); -} - -const char *writeScreenshot() -{ - FIL bmpFile; - UINT written; - char filename[42]; // /SCREENSHOTS/screen-2013-01-01-123540.bmp - - // check and create folder here - strcpy_P(filename, SCREENSHOTS_PATH); - const char * error = sdCheckAndCreateDirectory(filename); - if (error) { - return error; - } - - char * tmp = strAppend(&filename[sizeof(SCREENSHOTS_PATH)-1], "/screen"); - tmp = strAppendDate(tmp, true); - strcpy(tmp, BITMAPS_EXT); - - FRESULT result = f_open(&bmpFile, filename, FA_CREATE_ALWAYS | FA_WRITE); - if (result != FR_OK) { - return SDCARD_ERROR(result); - } - - result = f_write(&bmpFile, bmpHeader, sizeof(bmpHeader), &written); - if (result != FR_OK || written != sizeof(bmpHeader)) { - f_close(&bmpFile); - return SDCARD_ERROR(result); - } - - for (int y=LCD_H-1; y>=0; y-=1) { - for (int x=0; x<8*((LCD_W+7)/8); x+=2) { - uint8_t byte = getPixel(x+1, y) + (getPixel(x, y) << 4); - f_write(&bmpFile, &byte, 1, &written); - if (result != FR_OK || written != 1) { - f_close(&bmpFile); - return SDCARD_ERROR(result); - } - } - } - - f_close(&bmpFile); - - return NULL; -} - -#if defined(PCBHORUS) - - -#define STB_IMAGE_IMPLEMENTATION -#define STBI_ONLY_PNG -#define STBI_ONLY_JPEG -#define STBI_ONLY_BMP -#define STBI_NO_STDIO -#include "thirdparty/Stb/stb_image.h" - - -// fill 'data' with 'size' bytes. return number of bytes actually read -int stbc_read(void *user, char *data, int size) -{ - FIL * fp = (FIL *)user; - UINT br = 0; - FRESULT res = f_read(fp, data, size, &br); - if (res == FR_OK) { - return (int)br; - } - return 0; -} - -// skip the next 'n' bytes, or 'unget' the last -n bytes if negative -void stbc_skip(void *user, int n) -{ - FIL * fp = (FIL *)user; - f_lseek(fp, f_tell(fp) + n); -} - -// returns nonzero if we are at end of file/data -int stbc_eof(void *user) -{ - FIL * fp = (FIL *)user; - return f_eof(fp); -} - -// callbacks for stb-image -const stbi_io_callbacks stbCallbacks = { - stbc_read, - stbc_skip, - stbc_eof -}; - - -const char * imgLoad(uint8_t * bmp, const char * filename, uint16_t width, uint16_t height) -{ - FIL imgFile; - - // if (width > LCD_W) { - // return STR_INCOMPATIBLE; - // } - - FRESULT result = f_open(&imgFile, filename, FA_OPEN_EXISTING | FA_READ); - if (result != FR_OK) { - return SDCARD_ERROR(result); - } - - // if (f_size(&bmpFile) < 14) { - // f_close(&bmpFile); - // return STR_INCOMPATIBLE; - // } - - int x,y,n; - unsigned char *data = stbi_load_from_callbacks(&stbCallbacks, &imgFile, &x, &y, &n, 3); - f_close(&imgFile); - - if (!data) { - return "stb error"; - } - - //convert to 565 fromat - // todo use dma2d for conversion from 888 to 565 - unsigned char *p = data; - uint16_t * dest = (uint16_t *)bmp; - - *dest++ = min(width, x); - *dest++ = min(height, y); - - for(int row = 0; row < min(height, y); ++row) { - unsigned char *l = p; - for(int col = 0; col < min(width, x); ++col) { - *dest = RGB(l[0], l[1], l[2]); - ++dest; - l += 3; - } - p += 3 * x; - } - stbi_image_free(data); - return 0; -} - -#endif // if defined(PCBHORUS) diff --git a/radio/src/gui/gui_helpers.h b/radio/src/gui/gui_helpers.h index 0ce944586..9c1238055 100644 --- a/radio/src/gui/gui_helpers.h +++ b/radio/src/gui/gui_helpers.h @@ -2,7 +2,7 @@ * Copyright (C) OpenTX * * Based on code named - * th9x - http://code.google.com/p/th9x + * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * @@ -23,7 +23,7 @@ typedef bool (*IsValueAvailable)(int); -int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable); +int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable=NULL); int getFirstAvailable(int min, int max, IsValueAvailable isValueAvailable); #if defined(VIRTUALINPUTS) diff --git a/radio/src/gui/horus/bitmapbuffer.cpp b/radio/src/gui/horus/bitmapbuffer.cpp new file mode 100644 index 000000000..7a29728a9 --- /dev/null +++ b/radio/src/gui/horus/bitmapbuffer.cpp @@ -0,0 +1,640 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include "opentx.h" + +void BitmapBuffer::drawAlphaPixel(display_t * p, uint8_t opacity, uint16_t color) +{ + if (opacity == OPACITY_MAX) { + drawPixel(p, color); + } + else if (opacity != 0) { + uint8_t bgWeight = OPACITY_MAX - opacity; + COLOR_SPLIT(color, red, green, blue); + COLOR_SPLIT(*p, bgRed, bgGreen, bgBlue); + uint16_t r = (bgRed * bgWeight + red * opacity) / OPACITY_MAX; + uint16_t g = (bgGreen * bgWeight + green * opacity) / OPACITY_MAX; + uint16_t b = (bgBlue * bgWeight + blue * opacity) / OPACITY_MAX; + drawPixel(p, COLOR_JOIN(r, g, b)); + } +} + +void BitmapBuffer::drawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att) +{ + if (y >= height) return; + if (x+w > width) { w = width - x; } + + display_t * p = getPixelPtr(x, y); + display_t color = lcdColorTable[COLOR_IDX(att)]; + uint8_t opacity = 0x0F - (att >> 24); + + if (pat == SOLID) { + while (w--) { + drawAlphaPixel(p, opacity, color); + p++; + } + } + else { + while (w--) { + if (pat & 1) { + drawAlphaPixel(p, opacity, color); + pat = (pat >> 1) | 0x80; + } + else { + pat = pat >> 1; + } + p++; + } + } +} + +void BitmapBuffer::drawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att) +{ + if (x >= width) return; + if (y >= height) return; + if (h<0) { y+=h; h=-h; } + if (y<0) { h+=y; y=0; if (h<=0) return; } + if (y+h > height) { h = height - y; } + + display_t color = lcdColorTable[COLOR_IDX(att)]; + uint8_t opacity = 0x0F - (att >> 24); + + if (pat == SOLID) { + while (h--) { + drawAlphaPixel(x, y, opacity, color); + y++; + } + } + else { + if (pat==DOTTED && !(y%2)) { + pat = ~pat; + } + while (h--) { + if (pat & 1) { + drawAlphaPixel(x, y, opacity, color); + pat = (pat >> 1) | 0x80; + } + else { + pat = pat >> 1; + } + y++; + } + } +} + +void BitmapBuffer::drawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att) +{ + for (coord_t i=y; i= 360 || endAngle <= 0) + return false; + + if (startAngle == 0) { + slopes[1] = 100000; + slopes[2] = -100000; + } + else { + float angle1 = float(startAngle) * PI / 180; + if (startAngle >= 180) { + slopes[1] = -100000; + slopes[2] = cos(angle1) * 100 / sin(angle1); + } + else { + slopes[1] = cos(angle1) * 100 / sin(angle1); + slopes[2] = -100000; + } + } + + if (endAngle == 360) { + slopes[0] = -100000; + slopes[3] = 100000; + } + else { + float angle2 = float(endAngle) * PI / 180; + if (endAngle >= 180) { + slopes[0] = -100000; + slopes[3] = -cos(angle2) * 100 / sin(angle2); + } + else { + slopes[0] = cos(angle2) * 100 / sin(angle2); + slopes[3] = -100000; + } + } + + return true; +} + +void BitmapBuffer::drawPie(int x0, int y0, int radius, int startAngle, int endAngle) +{ + int slopes[4]; + if (!evalSlopes(slopes, startAngle, endAngle)) + return; + + for (int y=0; y<=radius; y++) { + for (int x=0; x<=radius; x++) { + if (x*x+y*y <= radius*radius) { + int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); + if (slope >= slopes[0] && slope < slopes[1]) { + drawPixel(x0+x, y0-y, WHITE); + } + if (-slope >= slopes[0] && -slope < slopes[1]) { + drawPixel(x0+x, y0+y, WHITE); + } + if (slope >= slopes[2] && slope < slopes[3]) { + drawPixel(x0-x, y0-y, WHITE); + } + if (-slope >= slopes[2] && -slope < slopes[3]) { + drawPixel(x0-x, y0+y, WHITE); + } + } + } + } +} + +void BitmapBuffer::drawBitmapPattern(coord_t x, coord_t y, const uint8_t * bmp, LcdFlags flags, coord_t offset, coord_t width) +{ + coord_t w = *((uint16_t *)bmp); + coord_t height = *(((uint16_t *)bmp)+1); + + if (!width || width > w) { + width = w; + } + + if (x+width > this->width) { + width = this->width-x; + } + + display_t color = lcdColorTable[COLOR_IDX(flags)]; + + for (coord_t row=0; row 0) drawBitmapPattern(x, y, font, flags, offset, width); + lcdNextPos = x + width; +} + +void BitmapBuffer::drawSizedText(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) +{ + int width = getTextWidth(s, len, flags); + int height = getFontHeight(flags); + int fontindex = FONTSIZE(flags) >> 8; + const pm_uchar * font = fontsTable[fontindex]; + const uint16_t * fontspecs = fontspecsTable[fontindex]; + + if (flags & RIGHT) + x -= width; + else if (flags & CENTERED) + x -= width/2; + + if ((flags&INVERS) && ((~flags & BLINK) || BLINK_ON_PHASE)) { + flags = TEXT_INVERTED_COLOR | (flags & 0x0ffff); + if (FONTSIZE(flags) == TINSIZE) + drawSolidFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); + else if (FONTSIZE(flags) == SMLSIZE) + drawSolidFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + else + drawSolidFilledRect(x-INVERT_HORZ_MARGIN, y/*-INVERT_VERT_MARGIN*/, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); + } + + char str[256]; + if (flags & ZCHAR) + strcat_zchar(str, s, len); + else + strAppend(str, s, len); + + const coord_t orig_x = x; + bool setx = false; + while (len--) { + unsigned char c; + if (flags & ZCHAR) + c = idx2char(*s); + else + c = pgm_read_byte(s); + if (setx) { + x = c; + setx = false; + } + else if (!c) { + break; + } + else if (c >= 0x20) { + drawFontPattern(x, y, font, fontspecs, getMappedChar(c), flags); + x = lcdNextPos; + } + else if (c == 0x1F) { // X-coord prefix + setx = true; + } + else if (c == 0x1E) { + x = orig_x; + y += height; + } + else if (c == 1) { + x += 1; + } + else { + x += 2*(c-1); + } + s++; + } + lcdNextPos = x; +} + +void BitmapBuffer::drawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle) +{ + const uint16_t * q = img; + coord_t width = *q++; + coord_t height = *q++; + + int slopes[4]; + if (!evalSlopes(slopes, startAngle, endAngle)) + return; + + int w2 = width/2; + int h2 = height/2; + + for (int y=h2-1; y>=0; y--) { + for (int x=w2-1; x>=0; x--) { + int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); + if (slope >= slopes[0] && slope < slopes[1]) { + *getPixelPtr(x0+w2+x, y0+h2-y) = q[(h2-y)*width + w2+x]; + } + if (-slope >= slopes[0] && -slope < slopes[1]) { + *getPixelPtr(x0+w2+x, y0+h2+y) = q[(h2+y)*width + w2+x]; + } + if (slope >= slopes[2] && slope < slopes[3]) { + *getPixelPtr(x0+w2-x, y0+h2-y) = q[(h2-y)*width + w2-x]; + } + if (-slope >= slopes[2] && -slope < slopes[3]) { + *getPixelPtr(x0+w2-x, y0+h2+y) = q[(h2+y)*width + w2-x]; + } + } + } +} + +void BitmapBuffer::drawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFlags flags, int startAngle, int endAngle) +{ + coord_t width = *((uint16_t *)img); + coord_t height = *(((uint16_t *)img)+1); + const uint8_t * q = img+4; + + int slopes[4]; + if (!evalSlopes(slopes, startAngle, endAngle)) + return; + + display_t color = lcdColorTable[COLOR_IDX(flags)]; + + int w2 = width/2; + int h2 = height/2; + + for (int y=h2-1; y>=0; y--) { + for (int x=w2-1; x>=0; x--) { + int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); + if (slope >= slopes[0] && slope < slopes[1]) { + drawAlphaPixel(x0+w2+x, y0+h2-y, q[(h2-y)*width + w2+x], color); + } + if (-slope >= slopes[0] && -slope < slopes[1]) { + drawAlphaPixel(x0+w2+x, y0+h2+y, q[(h2+y)*width + w2+x], color); + } + if (slope >= slopes[2] && slope < slopes[3]) { + drawAlphaPixel(x0+w2-x, y0+h2-y, q[(h2-y)*width + w2-x], color); + } + if (-slope >= slopes[2] && -slope < slopes[3]) { + drawAlphaPixel(x0+w2-x, y0+h2+y, q[(h2+y)*width + w2-x], color); + } + } + } +} + +BitmapBuffer * BitmapBuffer::load(const char * filename) +{ + FIL bmpFile; + UINT read; + uint8_t palette[16]; + uint8_t bmpBuf[LCD_W]; /* maximum with LCD_W */ + uint8_t * buf = &bmpBuf[0]; + + FRESULT result = f_open(&bmpFile, filename, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) { + return NULL; + } + + if (f_size(&bmpFile) < 14) { + f_close(&bmpFile); + return NULL; + } + + result = f_read(&bmpFile, buf, 14, &read); + if (result != FR_OK || read != 14) { + f_close(&bmpFile); + return NULL; + } + + if (buf[0] != 'B' || buf[1] != 'M') { + f_close(&bmpFile); + return NULL; + } + + uint32_t fsize = *((uint32_t *)&buf[2]); + uint32_t hsize = *((uint32_t *)&buf[10]); /* header size */ + + uint32_t len = limit((uint32_t)4, (uint32_t)(hsize-14), (uint32_t)32); + result = f_read(&bmpFile, buf, len, &read); + if (result != FR_OK || read != len) { + f_close(&bmpFile); + return NULL; + } + + uint32_t ihsize = *((uint32_t *)&buf[0]); /* more header size */ + + /* invalid header size */ + if (ihsize + 14 > hsize) { + f_close(&bmpFile); + return NULL; + } + + /* sometimes file size is set to some headers size, set a real size in that case */ + if (fsize == 14 || fsize == ihsize + 14) + fsize = f_size(&bmpFile) - 2; + + /* declared file size less than header size */ + if (fsize <= hsize) { + f_close(&bmpFile); + return NULL; + } + + uint32_t w, h; + + switch (ihsize){ + case 40: // windib + case 56: // windib v3 + case 64: // OS/2 v2 + case 108: // windib v4 + case 124: // windib v5 + w = *((uint32_t *)&buf[4]); + h = *((uint32_t *)&buf[8]); + buf += 12; + break; + case 12: // OS/2 v1 + w = *((uint16_t *)&buf[4]); + h = *((uint16_t *)&buf[6]); + buf += 8; + break; + default: + f_close(&bmpFile); + return NULL; + } + + if (*((uint16_t *)&buf[0]) != 1) { /* planes */ + f_close(&bmpFile); + return NULL; + } + + uint16_t depth = *((uint16_t *)&buf[2]); + + buf = &bmpBuf[0]; + + if (depth == 4) { + if (f_lseek(&bmpFile, hsize-64) != FR_OK || f_read(&bmpFile, buf, 64, &read) != FR_OK || read != 64) { + f_close(&bmpFile); + return NULL; + } + for (uint8_t i=0; i<16; i++) { + palette[i] = buf[4*i]; + } + } + else { + if (f_lseek(&bmpFile, hsize) != FR_OK) { + f_close(&bmpFile); + return NULL; + } + } + + BitmapBuffer * bmp = new BitmapBuffer(w, h); + if (bmp == NULL) { + f_close(&bmpFile); + return NULL; + } + + uint16_t * dest = bmp->data; + uint32_t rowSize; + + switch (depth) { + case 32: + for (int i=h-1; i>=0; i--) { + uint8_t * dst = ((uint8_t *)dest) + i*w*2; + for (unsigned int j=0; j>24) & 0xff, (pixel>>16) & 0xff, (pixel>>8) & 0xff); + dst += 2; + } + } + break; + + case 1: + break; + + case 4: + rowSize = ((4*w+31)/32)*4; + for (int32_t i=h-1; i>=0; i--) { + result = f_read(&bmpFile, buf, rowSize, &read); + if (result != FR_OK || read != rowSize) { + f_close(&bmpFile); + free(bmp); + return NULL; + } + uint8_t * dst = ((uint8_t *)dest) + i*w*2; + for (uint32_t j=0; j> ((j & 1) ? 0 : 4)) & 0x0F; + uint8_t val = palette[index]; + *((uint16_t *)dst) = RGB(val, val, val); + dst += 2; + // *dst++ = 0x0F; + } + } + break; + + default: + f_close(&bmpFile); + free(bmp); + return NULL; + } + + f_close(&bmpFile); + return bmp; +} + + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#define STBI_ONLY_JPEG +#define STBI_ONLY_BMP +#define STBI_NO_STDIO +#include "thirdparty/Stb/stb_image.h" + + +// fill 'data' with 'size' bytes. return number of bytes actually read +int stbc_read(void *user, char *data, int size) +{ + FIL * fp = (FIL *)user; + UINT br = 0; + FRESULT res = f_read(fp, data, size, &br); + if (res == FR_OK) { + return (int)br; + } + return 0; +} + +// skip the next 'n' bytes, or 'unget' the last -n bytes if negative +void stbc_skip(void *user, int n) +{ + FIL * fp = (FIL *)user; + f_lseek(fp, f_tell(fp) + n); +} + +// returns nonzero if we are at end of file/data +int stbc_eof(void *user) +{ + FIL * fp = (FIL *)user; + return f_eof(fp); +} + +// callbacks for stb-image +const stbi_io_callbacks stbCallbacks = { + stbc_read, + stbc_skip, + stbc_eof +}; + +const char * imgLoad(uint8_t * bmp, const char * filename, uint16_t width, uint16_t height) +{ + FIL imgFile; + + FRESULT result = f_open(&imgFile, filename, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + + int x,y,n; + unsigned char *data = stbi_load_from_callbacks(&stbCallbacks, &imgFile, &x, &y, &n, 3); + f_close(&imgFile); + + if (!data) { + return "stb error"; + } + + //convert to 565 fromat + // todo use dma2d for conversion from 888 to 565 + unsigned char *p = data; + uint16_t * dest = (uint16_t *)bmp; + + *dest++ = min(width, x); + *dest++ = min(height, y); + + for(int row = 0; row < min(height, y); ++row) { + unsigned char *l = p; + for(int col = 0; col < min(width, x); ++col) { + *dest = RGB(l[0], l[1], l[2]); + ++dest; + l += 3; + } + p += 3 * x; + } + stbi_image_free(data); + return 0; +} + +float getBitmapScale(const BitmapBuffer * bitmap, int width, int height) +{ + float widthScale = float(width) / bitmap->getWidth(); + float heightScale = float(height) / bitmap->getHeight(); + return min(widthScale, heightScale); +} diff --git a/radio/src/gui/horus/bitmapbuffer.h b/radio/src/gui/horus/bitmapbuffer.h new file mode 100644 index 000000000..a70d8eff3 --- /dev/null +++ b/radio/src/gui/horus/bitmapbuffer.h @@ -0,0 +1,242 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _BITMAP_BUFFER_H_ +#define _BITMAP_BUFFER_H_ + +#include +#include "colors.h" + +// TODO should go to lcd.h again +typedef int coord_t; +typedef uint32_t LcdFlags; +typedef uint16_t display_t; + +inline int getBitmapScaledSize(int size, float scale) +{ + if (scale == 0.0) + return size; + else + return (0.5 + size) * scale; +} + +template +class BitmapBufferBase +{ + public: + BitmapBufferBase(int width, int height, T * data): + width(width), + height(height), + data(data) + { + } + + int getWidth() const + { + return width; + } + + int getHeight() const + { + return height; + } + + protected: + int width; + int height; + + public: // TODO protected + T * data; +}; + +typedef BitmapBufferBase Bitmap; + +class BitmapBuffer: public BitmapBufferBase +{ + public: + + BitmapBuffer(int width, int height): + BitmapBufferBase(width, height, NULL) + { + data = (uint16_t *)malloc(width*height*sizeof(uint16_t)); + } + + BitmapBuffer(int width, int height, uint16_t * data): + BitmapBufferBase(width, height, data) + { + } + + ~BitmapBuffer() + { + free(data); + } + + inline void clear() + { + drawSolidFilledRect(0, 0, width, height, 0); + } + + inline void drawPixel(display_t * p, display_t value) + { + *p = value; + } + + inline display_t * getPixelPtr(coord_t x, coord_t y) + { + return &data[y*width + x]; + } + + inline void drawPixel(coord_t x, coord_t y, display_t value) + { + display_t * p = getPixelPtr(x, y); + drawPixel(p, value); + } + + void drawAlphaPixel(display_t * p, uint8_t opacity, uint16_t color); + + inline void drawAlphaPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color) + { + display_t * p = getPixelPtr(x, y); + drawAlphaPixel(p, opacity, color); + } + + void drawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att); + + void drawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att); + + inline void drawSolidFilledRect(coord_t x, coord_t y, coord_t w, coord_t h, LcdFlags flags) + { + DMAFillRect(data, width, x, y, w, h, lcdColorTable[COLOR_IDX(flags)]); + } + + void drawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att); + + void invertRect(coord_t x, coord_t y, coord_t w, coord_t h, LcdFlags att); + + void drawCircle(int x0, int y0, int radius); + + void drawPie(int x0, int y0, int radius, int startAngle, int endAngle); + + void drawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle); + + void drawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFlags flags, int startAngle, int endAngle); + + static BitmapBuffer * load(const char * filename); + + void drawBitmapPattern(coord_t x, coord_t y, const uint8_t * bmp, LcdFlags flags, coord_t offset=0, coord_t width=0); + + void drawFontPattern(coord_t x, coord_t y, const uint8_t * font, const uint16_t * spec, int index, LcdFlags flags); + + void drawSizedText(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags); + + template + void drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx=0, coord_t srcy=0, coord_t w=0, coord_t h=0, float scale=0) + { + int srcw = bmp->getWidth(); + int srch = bmp->getHeight(); + + if (w == 0) + w = srcw; + if (h == 0) + h = srch; + if (srcx+w > srcw) + w = srcw - srcx; + if (srcy+h > srch) + h = srch - srcy; + + if (scale == 0) { + if (x + w > width) { + w = width - x; + } + if (y + h > height) { + h = height - y; + } + if (srcw == w) { + DMACopyBitmap(data, width, x, y, &bmp->data[srcw*srcy], srcw, h); + } + else { + for (int i=0; idata[srcw*(srcy+i)+srcx], w, 1); + } + } + } + else { + int scaledw = w * scale; + int scaledh = h * scale; + + if (x + scaledw > width) + scaledw = width - x; + if (y + scaledh > height) + scaledh = height - y; + + for (int i = 0; i < scaledh; i++) { + uint16_t * p = &data[(y + i) * width + x]; + const uint16_t * qstart = &bmp->data[(srcy + int(i / scale)) * bmp->getWidth() + srcx]; + for (int j = 0; j < scaledw; j++) { + const uint16_t * q = qstart + int(j / scale); + *p = *q; + p++; + } + } + } + } + + template + void drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h) + { + coord_t bitmapWidth = bitmap->getWidth(); + + float scale = float(h) / bitmap->getHeight(); + int width = getBitmapScaledSize(bitmapWidth, scale); + if (width > w) { + int ww = (0.5 + w) / scale; + drawBitmap(x, y, bitmap, (bitmapWidth - ww)/2, 0, ww, 0, scale); + } + else { + drawBitmap(x+(w-width)/2, y, bitmap, 0, 0, 0, 0, scale); + } + } + + template + void drawAlphaBitmap(coord_t x, coord_t y, const T * bmp) + { + int width = bmp->getWidth(); + int height = bmp->getHeight(); + + if (width == 0 || height == 0) { + return; + } + + for (coord_t line=0; linedata) + line*width*2; + for (coord_t col=0; col> 4, ((q[1] & 0x0f) << 12) + ((q[0] & 0xf0) << 3) + ((q[0] & 0x0f) << 1)); + p++; q+=2; + } + } + } +}; + +float getBitmapScale(const BitmapBuffer * bitmap, int width, int height); + +extern BitmapBuffer * lcd; + +#endif // _BITMAP_BUFFER_H_ diff --git a/radio/src/gui/horus/bitmaps.cpp b/radio/src/gui/horus/bitmaps.cpp index ff628a8d8..9a96bfb65 100644 --- a/radio/src/gui/horus/bitmaps.cpp +++ b/radio/src/gui/horus/bitmaps.cpp @@ -18,7 +18,7 @@ * GNU General Public License for more details. */ -#include "../../opentx.h" +#include "opentx.h" /* * Header bitmaps @@ -268,30 +268,10 @@ const uint8_t LBM_STAR1[] = { #include "mask_library_star_1.lbm" }; -/* - * Calibration screen - */ - -const uint8_t LBM_HORUS[] = { -#include "alpha_horus.lbm" -}; - -const uint8_t LBM_STICK_BACKGROUND[] = { -#include "alpha_stick_background.lbm" -}; - -const uint8_t LBM_STICK_POINTER[] = { -#include "alpha_stick_pointer.lbm" -}; - /* * Other */ -const uint8_t LBM_ASTERISK[] = { -#include "alpha_asterisk.lbm" -}; - const uint8_t LBM_POINT[] = { #include "mask_point.lbm" }; @@ -308,14 +288,6 @@ const uint8_t LBM_CURVE_COORD_SHADOW[] = { #include "mask_coord_shadow.lbm" }; -const uint8_t LBM_SHUTDOWN[] = { -#include "alpha_shutdown.lbm" -}; - -const uint8_t LBM_SLEEP[] __ALIGNED = { -#include "bmp_sleep.lbm" -}; - const uint8_t LBM_SHUTDOWN_CIRCLE[] = { #include "mask_shutdown_circle.lbm" }; @@ -348,14 +320,6 @@ const uint8_t LBM_CARROUSSEL_RIGHT[] = { #include "mask_carroussel_right.lbm" }; -const uint8_t LBM_BUTTON_ON[] = { -#include "alpha_button_on.lbm" -}; - -const uint8_t LBM_BUTTON_OFF[] = { -#include "alpha_button_off.lbm" -}; - const uint8_t LBM_SWIPE_CIRCLE[] = { #include "mask_swipe_circle.lbm" }; @@ -367,4 +331,3 @@ const uint8_t LBM_SWIPE_LEFT[] = { const uint8_t LBM_SWIPE_RIGHT[] = { #include "mask_swipe_right.lbm" }; - diff --git a/radio/src/gui/horus/bitmaps.h b/radio/src/gui/horus/bitmaps.h index bcb4d9f25..e85aea784 100644 --- a/radio/src/gui/horus/bitmaps.h +++ b/radio/src/gui/horus/bitmaps.h @@ -65,20 +65,12 @@ extern const uint8_t LBM_SCORE1[]; extern const uint8_t LBM_STAR0[]; extern const uint8_t LBM_STAR1[]; -// Calibration screen -extern const uint8_t LBM_HORUS[]; -extern const uint8_t LBM_STICK_BACKGROUND[]; -extern const uint8_t LBM_STICK_POINTER[]; - // Other icons extern const uint8_t LBM_SPLASH[]; -extern const uint8_t LBM_ASTERISK[]; extern const uint8_t LBM_POINT[]; extern const uint8_t LBM_CURVE_POINT[]; extern const uint8_t LBM_CURVE_POINT_CENTER[]; extern const uint8_t LBM_CURVE_COORD_SHADOW[]; -extern const uint8_t LBM_SHUTDOWN[]; -extern const uint8_t LBM_SLEEP[]; extern const uint8_t LBM_SHUTDOWN_CIRCLE[]; // Slider bitmaps @@ -92,10 +84,6 @@ extern const uint8_t LBM_SLIDER_POINT_IN[]; extern const uint8_t LBM_CARROUSSEL_LEFT[]; extern const uint8_t LBM_CARROUSSEL_RIGHT[]; -// Button bitmaps -extern const uint8_t LBM_BUTTON_ON[]; -extern const uint8_t LBM_BUTTON_OFF[]; - extern const uint8_t LBM_SWIPE_CIRCLE[]; extern const uint8_t LBM_SWIPE_LEFT[]; extern const uint8_t LBM_SWIPE_RIGHT[]; diff --git a/radio/src/gui/horus/colors.h b/radio/src/gui/horus/colors.h new file mode 100644 index 000000000..c64f89188 --- /dev/null +++ b/radio/src/gui/horus/colors.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _COLORS_H_ +#define _COLORS_H_ + +// remove windows default definitions +#undef OPAQUE +#undef RGB + +#define TO5BITS(x) ((x) >> 3) +#define TO6BITS(x) ((x) >> 2) +#define RGB(r, g, b) ((TO5BITS(r) << 11) + (TO6BITS(g) << 5) + (TO5BITS(b) << 0)) +#define WHITE RGB(0xFF, 0xFF, 0xFF) +#define BLACK RGB(0, 0, 0) +#define YELLOW RGB(0xF0, 0xD0, 0x10) +#define BLUE RGB(0x30, 0xA0, 0xE0) +#define GREY RGB(96, 96, 96) +#define DARKGREY RGB(64, 64, 64) +#define LIGHTGREY RGB(180, 180, 180) +#define RED RGB(229, 32, 30) +#define DARKRED RGB(160, 0, 6) + +#define OPACITY_MAX 0x0F +#define OPACITY(x) ((x)<<24) + +enum LcdColorIndex +{ + TEXT_COLOR_INDEX, + TEXT_BGCOLOR_INDEX, + TEXT_INVERTED_COLOR_INDEX, + TEXT_INVERTED_BGCOLOR_INDEX, + LINE_COLOR_INDEX, + SCROLLBOX_COLOR_INDEX, + MENU_TITLE_BGCOLOR_INDEX, + MENU_TITLE_COLOR_INDEX, + MENU_TITLE_DISABLE_COLOR_INDEX, + HEADER_COLOR_INDEX, + ALARM_COLOR_INDEX, + WARNING_COLOR_INDEX, + TEXT_DISABLE_COLOR_INDEX, + CURVE_AXIS_COLOR_INDEX, + CURVE_COLOR_INDEX, + CURVE_CURSOR_COLOR_INDEX, + HEADER_BGCOLOR_INDEX, + HEADER_ICON_BGCOLOR_INDEX, + HEADER_CURRENT_BGCOLOR_INDEX, + TITLE_BGCOLOR_INDEX, + TRIM_BGCOLOR_INDEX, + TRIM_SHADOW_COLOR_INDEX, + MAINVIEW_PANES_COLOR_INDEX, + MAINVIEW_GRAPHICS_COLOR_INDEX, + OVERLAY_COLOR_INDEX, + CUSTOM_COLOR_INDEX, + LCD_COLOR_COUNT +}; + +extern uint16_t lcdColorTable[LCD_COLOR_COUNT]; + +#define COLOR(index) ((index) << 16) +#define COLOR_IDX(att) uint8_t((att) >> 16) + +#define TEXT_COLOR COLOR(TEXT_COLOR_INDEX) +#define TEXT_BGCOLOR COLOR(TEXT_BGCOLOR_INDEX) +#define TEXT_INVERTED_COLOR COLOR(TEXT_INVERTED_COLOR_INDEX) +#define TEXT_INVERTED_BGCOLOR COLOR(TEXT_INVERTED_BGCOLOR_INDEX) +#define LINE_COLOR COLOR(LINE_COLOR_INDEX) +#define SCROLLBOX_COLOR COLOR(SCROLLBOX_COLOR_INDEX) +#define HEADER_SEPARATOR_COLOR COLOR(HEADER_SEPARATOR_COLOR_INDEX) +#define MENU_TITLE_BGCOLOR COLOR(MENU_TITLE_BGCOLOR_INDEX) +#define MENU_TITLE_COLOR COLOR(MENU_TITLE_COLOR_INDEX) +#define MENU_TITLE_DISABLE_COLOR COLOR(MENU_TITLE_DISABLE_COLOR_INDEX) +#define HEADER_COLOR COLOR(HEADER_COLOR_INDEX) +#define ALARM_COLOR COLOR(ALARM_COLOR_INDEX) +#define WARNING_COLOR COLOR(WARNING_COLOR_INDEX) +#define TEXT_DISABLE_COLOR COLOR(TEXT_DISABLE_COLOR_INDEX) +#define CURVE_AXIS_COLOR COLOR(CURVE_AXIS_COLOR_INDEX) +#define CURVE_COLOR COLOR(CURVE_COLOR_INDEX) +#define CURVE_CURSOR_COLOR COLOR(CURVE_CURSOR_COLOR_INDEX) +#define TITLE_BGCOLOR COLOR(TITLE_BGCOLOR_INDEX) +#define TRIM_BGCOLOR COLOR(TRIM_BGCOLOR_INDEX) +#define TRIM_SHADOW_COLOR COLOR(TRIM_SHADOW_COLOR_INDEX) +#define HEADER_BGCOLOR COLOR(HEADER_BGCOLOR_INDEX) +#define HEADER_ICON_BGCOLOR COLOR(HEADER_ICON_BGCOLOR_INDEX) +#define HEADER_CURRENT_BGCOLOR COLOR(HEADER_CURRENT_BGCOLOR_INDEX) +#define MAINVIEW_PANES_COLOR COLOR(MAINVIEW_PANES_COLOR_INDEX) +#define MAINVIEW_GRAPHICS_COLOR COLOR(MAINVIEW_GRAPHICS_COLOR_INDEX) +#define OVERLAY_COLOR COLOR(OVERLAY_COLOR_INDEX) +#define CUSTOM_COLOR COLOR(CUSTOM_COLOR_INDEX) + +#define COLOR_SPLIT(color, r, g, b) \ + uint16_t r = ((color) & 0xF800) >> 11; \ + uint16_t g = ((color) & 0x07E0) >> 5; \ + uint16_t b = ((color) & 0x001F) + +#define COLOR_JOIN(r, g, b) \ + (((r) << 11) + ((g) << 5) + (b)) + +#endif // _COLORS_H_ diff --git a/radio/src/gui/horus/lcd.cpp b/radio/src/gui/horus/lcd.cpp index 63dadf370..52ac733b9 100644 --- a/radio/src/gui/horus/lcd.cpp +++ b/radio/src/gui/horus/lcd.cpp @@ -26,6 +26,7 @@ display_t displayBuf[DISPLAY_BUFFER_SIZE]; #endif + uint16_t lcdColorTable[LCD_COLOR_COUNT]; coord_t lcdNextPos; @@ -52,31 +53,6 @@ int getCharWidth(char c, const uint16_t * spec) return getFontPatternWidth(spec, getMappedChar(c)); } -void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags flags, coord_t offset, coord_t width) -{ - coord_t w = *((uint16_t *)img); - coord_t height = *(((uint16_t *)img)+1); - - if (!width || width > w) { - width = w; - } - - if (x+width > LCD_W) { - width = LCD_W-x; - } - - display_t color = lcdColorTable[COLOR_IDX(flags)]; - - for (coord_t row=0; row> 8]; } -float getBitmapScale(const uint8_t * bmp, int dstWidth, int dstHeight) -{ - int bmpWidth = getBitmapWidth(bmp); - int bmpHeight = getBitmapHeight(bmp); - - if (bmpWidth == 0 || bmpHeight == 0) - return 0; - - float widthScale = float(dstWidth) / bmpWidth; - float heightScale = float(dstHeight) / bmpHeight; - - return min(widthScale, heightScale); -} - int getTextWidth(const char * s, int len, LcdFlags flags) { const uint16_t * specs = fontspecsTable[FONTSIZE(flags) >> 8]; @@ -142,79 +104,12 @@ int getTextWidth(const char * s, int len, LcdFlags flags) return result; } -void lcdDrawSizedText(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags flags) -{ - int width = getTextWidth(s, len, flags); - int height = getFontHeight(flags); - int fontindex = FONTSIZE(flags) >> 8; - const pm_uchar * font = fontsTable[fontindex]; - const uint16_t * fontspecs = fontspecsTable[fontindex]; - - if ((flags&INVERS) && ((~flags & BLINK) || BLINK_ON_PHASE)) { - flags = TEXT_INVERTED_COLOR | (flags & 0x0ffff); - if (FONTSIZE(flags) == TINSIZE) - lcdDrawSolidFilledRect(x-INVERT_HORZ_MARGIN+2, y-INVERT_VERT_MARGIN+2, width+2*INVERT_HORZ_MARGIN-5, INVERT_LINE_HEIGHT-7, TEXT_INVERTED_BGCOLOR); - else if (FONTSIZE(flags) == SMLSIZE) - lcdDrawSolidFilledRect(x-INVERT_HORZ_MARGIN+1, y-INVERT_VERT_MARGIN, width+2*INVERT_HORZ_MARGIN-2, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); - else - lcdDrawSolidFilledRect(x-INVERT_HORZ_MARGIN, y/*-INVERT_VERT_MARGIN*/, width+2*INVERT_HORZ_MARGIN, INVERT_LINE_HEIGHT, TEXT_INVERTED_BGCOLOR); - } - - char str[256]; - if (flags & ZCHAR) - strcat_zchar(str, s, len); - else - strAppend(str, s, len); - - const coord_t orig_x = x; - bool setx = false; - while (len--) { - unsigned char c; - if (flags & ZCHAR) - c = idx2char(*s); - else - c = pgm_read_byte(s); - if (setx) { - x = c; - setx = false; - } - else if (!c) { - break; - } - else if (c >= 0x20) { - lcdPutFontPattern(x, y, font, fontspecs, getMappedChar(c), flags); - x = lcdNextPos; - } - else if (c == 0x1F) { // X-coord prefix - setx = true; - } - else if (c == 0x1E) { - x = orig_x; - y += height; - } - else if (c == 1) { - x += 1; - } - else { - x += 2*(c-1); - } - s++; - } - lcdNextPos = x; -} - void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags flags) { lcdDrawSizedText(x, y, s, 255, flags); } -void lcd_putsCenter(coord_t y, const pm_char * s, LcdFlags attr) -{ - int x = (LCD_W - getTextWidth(s)) / 2; - lcdDrawText(x, y, s, attr); -} - -void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s,uint8_t idx, LcdFlags flags) +void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s, uint8_t idx, LcdFlags flags) { uint8_t length; length = pgm_read_byte(s++); @@ -230,10 +125,6 @@ void lcdDrawHexNumber(coord_t x, coord_t y, uint32_t val, LcdFlags flags) val >>= 4; } s[4] = '\0'; - if (!(flags & LEFT)) - x -= getTextWidth(s); - else - flags -= LEFT; lcdDrawText(x, y, s, flags); } @@ -261,8 +152,7 @@ void lcdDrawNumber(coord_t x, coord_t y, int32_t val, LcdFlags flags, uint8_t le } } while (val!=0 || mode>0 || (mode==MODE(LEADING0) && idx= LCD_H) return; - if (x+w > LCD_W) { w = LCD_W - x; } - - display_t * p = PIXEL_PTR(x, y); - display_t color = lcdColorTable[COLOR_IDX(att)]; - uint8_t opacity = 0x0F - (att >> 24); - - if (pat == SOLID) { - while (w--) { - lcdDrawAlphaPixel(p, opacity, color); - p++; - } - } - else { - while (w--) { - if (pat & 1) { - lcdDrawAlphaPixel(p, opacity, color); - pat = (pat >> 1) | 0x80; - } - else { - pat = pat >> 1; - } - p++; - } - } -} - -void lcdDrawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att) -{ - if (x >= LCD_W) return; - if (y >= LCD_H) return; - if (h<0) { y+=h; h=-h; } - if (y<0) { h+=y; y=0; if (h<=0) return; } - if (y+h > LCD_H) { h = LCD_H - y; } - - display_t color = lcdColorTable[COLOR_IDX(att)]; - uint8_t opacity = 0x0F - (att >> 24); - - if (pat == SOLID) { - while (h--) { - lcdDrawAlphaPixel(x, y, opacity, color); - y++; - } - } - else { - if (pat==DOTTED && !(y%2)) { - pat = ~pat; - } - while (h--) { - if (pat & 1) { - lcdDrawAlphaPixel(x, y, opacity, color); - pat = (pat >> 1) | 0x80; - } - else { - pat = pat >> 1; - } - y++; - } - } -} - -#if defined(SIMU) -inline void lcdDrawBitmapDMA(coord_t x, coord_t y, coord_t width, coord_t height, const uint8_t * img) -{ - lcdDrawBitmap(x, y, img-4, 0, 0, 1.0); -} -#endif - -#if !defined(BOOT) -void lcdDrawAlphaBitmap(coord_t x, coord_t y, const uint8_t * bmp) -{ - int width = getBitmapWidth(bmp); - int height = getBitmapHeight(bmp); - - if (width == 0 || height == 0) { - return; - } - - for (coord_t line=0; line>4, *((uint16_t *)q)); - p++; q+=3; - } - } -} - -void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * bmp, coord_t offset, coord_t height, float scale) -{ - int width = getBitmapWidth(bmp); - int h = getBitmapHeight(bmp); - - if (!height || height > h) { - height = h; - } - - if (x+height > LCD_W) { - height = LCD_W-x; - } - - if (width == 0 || height == 0) { - return; - } - - if (scale == 0) { - lcdDrawBitmapDMA(x, y, width, height, bmp + 4 + offset * width * 2); - } - else { - int dstwidth = width * scale; - int dstheight = height * scale; - for (coord_t i=0; i= 360 || endAngle <= 0) - return false; - - if (startAngle == 0) { - slopes[1] = 100000; - slopes[2] = -100000; - } - else { - float angle1 = float(startAngle) * PI / 180; - if (startAngle >= 180) { - slopes[1] = -100000; - slopes[2] = cos(angle1) * 100 / sin(angle1); - } - else { - slopes[1] = cos(angle1) * 100 / sin(angle1); - slopes[2] = -100000; - } - } - - if (endAngle == 360) { - slopes[0] = -100000; - slopes[3] = 100000; - } - else { - float angle2 = float(endAngle) * PI / 180; - if (endAngle >= 180) { - slopes[0] = -100000; - slopes[3] = -cos(angle2) * 100 / sin(angle2); - } - else { - slopes[0] = cos(angle2) * 100 / sin(angle2); - slopes[3] = -100000; - } - } - - return true; -} - -void lcdDrawPie(int x0, int y0, int radius, int startAngle, int endAngle) -{ - int slopes[4]; - if (!evalSlopes(slopes, startAngle, endAngle)) - return; - - for (int y=0; y<=radius; y++) { - for (int x=0; x<=radius; x++) { - if (x*x+y*y <= radius*radius) { - int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); - if (slope >= slopes[0] && slope < slopes[1]) { - lcdDrawPixel(x0+x, y0-y, WHITE); - } - if (-slope >= slopes[0] && -slope < slopes[1]) { - lcdDrawPixel(x0+x, y0+y, WHITE); - } - if (slope >= slopes[2] && slope < slopes[3]) { - lcdDrawPixel(x0-x, y0-y, WHITE); - } - if (-slope >= slopes[2] && -slope < slopes[3]) { - lcdDrawPixel(x0-x, y0+y, WHITE); - } - } - } - } -} - -void lcdDrawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle) -{ - const uint16_t * q = img; - coord_t width = *q++; - coord_t height = *q++; - - int slopes[4]; - if (!evalSlopes(slopes, startAngle, endAngle)) - return; - - int w2 = width/2; - int h2 = height/2; - - for (int y=h2-1; y>=0; y--) { - for (int x=w2-1; x>=0; x--) { - int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); - if (slope >= slopes[0] && slope < slopes[1]) { - displayBuf[(y0+h2-y)*LCD_W + x0+w2+x] = q[(h2-y)*width + w2+x]; - } - if (-slope >= slopes[0] && -slope < slopes[1]) { - displayBuf[(y0+h2+y)*LCD_W + x0+w2+x] = q[(h2+y)*width + w2+x]; - } - if (slope >= slopes[2] && slope < slopes[3]) { - displayBuf[(y0+h2-y)*LCD_W + x0+w2-x] = q[(h2-y)*width + w2-x]; - } - if (-slope >= slopes[2] && -slope < slopes[3]) { - displayBuf[(y0+h2+y)*LCD_W + x0+w2-x] = q[(h2+y)*width + w2-x]; - } - } - } -} - -void lcdDrawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFlags flags, int startAngle, int endAngle) -{ - coord_t width = *((uint16_t *)img); - coord_t height = *(((uint16_t *)img)+1); - const uint8_t * q = img+4; - - int slopes[4]; - if (!evalSlopes(slopes, startAngle, endAngle)) - return; - - display_t color = lcdColorTable[COLOR_IDX(flags)]; - - int w2 = width/2; - int h2 = height/2; - - for (int y=h2-1; y>=0; y--) { - for (int x=w2-1; x>=0; x--) { - int slope = (x==0 ? (y<0 ? -99000 : 99000) : y*100/x); - if (slope >= slopes[0] && slope < slopes[1]) { - lcdDrawAlphaPixel(x0+w2+x, y0+h2-y, q[(h2-y)*width + w2+x], color); - } - if (-slope >= slopes[0] && -slope < slopes[1]) { - lcdDrawAlphaPixel(x0+w2+x, y0+h2+y, q[(h2+y)*width + w2+x], color); - } - if (slope >= slopes[2] && slope < slopes[3]) { - lcdDrawAlphaPixel(x0+w2-x, y0+h2-y, q[(h2-y)*width + w2-x], color); - } - if (-slope >= slopes[2] && -slope < slopes[3]) { - lcdDrawAlphaPixel(x0+w2-x, y0+h2+y, q[(h2+y)*width + w2-x], color); - } - } + for (int i=0; i> 4) +#define ZCHAR 0x10 + /* rect, square flags */ #define ROUND 0x04 @@ -69,109 +70,14 @@ #define TIMEHOUR 0x2000 #define STREXPANDED 0x4000 -// remove windows default definitions -#undef OPAQUE -#undef RGB - -#define TO5BITS(x) ((x) >> 3) -#define TO6BITS(x) ((x) >> 2) -#define RGB(r, g, b) ((TO5BITS(r) << 11) + (TO6BITS(g) << 5) + (TO5BITS(b) << 0)) -#define WHITE RGB(0xFF, 0xFF, 0xFF) -#define BLACK RGB(0, 0, 0) -#define YELLOW RGB(0xF0, 0xD0, 0x10) -#define BLUE RGB(0x30, 0xA0, 0xE0) -#define GREY RGB(96, 96, 96) -#define DARKGREY RGB(64, 64, 64) -#define LIGHTGREY RGB(180, 180, 180) -#define RED RGB(229, 32, 30) -#define DARKRED RGB(160, 0, 6) - -#define LcdFlags uint32_t - -#define OPACITY_MAX 0x0F -#define OPACITY(x) ((x)<<24) - -enum LcdColorIndex -{ - TEXT_COLOR_INDEX, - TEXT_BGCOLOR_INDEX, - TEXT_INVERTED_COLOR_INDEX, - TEXT_INVERTED_BGCOLOR_INDEX, - LINE_COLOR_INDEX, - SCROLLBOX_COLOR_INDEX, - MENU_TITLE_BGCOLOR_INDEX, - MENU_TITLE_COLOR_INDEX, - MENU_TITLE_DISABLE_COLOR_INDEX, - HEADER_COLOR_INDEX, - ALARM_COLOR_INDEX, - WARNING_COLOR_INDEX, - TEXT_DISABLE_COLOR_INDEX, - CURVE_AXIS_COLOR_INDEX, - CURVE_COLOR_INDEX, - CURVE_CURSOR_COLOR_INDEX, - HEADER_BGCOLOR_INDEX, - HEADER_ICON_BGCOLOR_INDEX, - HEADER_CURRENT_BGCOLOR_INDEX, - TITLE_BGCOLOR_INDEX, - TRIM_BGCOLOR_INDEX, - TRIM_SHADOW_COLOR_INDEX, - MAINVIEW_PANES_COLOR_INDEX, - MAINVIEW_GRAPHICS_COLOR_INDEX, - OVERLAY_COLOR_INDEX, - CUSTOM_COLOR_INDEX, - LCD_COLOR_COUNT -}; - -extern uint16_t lcdColorTable[LCD_COLOR_COUNT]; - -#define COLOR(index) ((index) << 16) -#define COLOR_IDX(att) uint8_t((att) >> 16) - -#define TEXT_COLOR COLOR(TEXT_COLOR_INDEX) -#define TEXT_BGCOLOR COLOR(TEXT_BGCOLOR_INDEX) -#define TEXT_INVERTED_COLOR COLOR(TEXT_INVERTED_COLOR_INDEX) -#define TEXT_INVERTED_BGCOLOR COLOR(TEXT_INVERTED_BGCOLOR_INDEX) -#define LINE_COLOR COLOR(LINE_COLOR_INDEX) -#define SCROLLBOX_COLOR COLOR(SCROLLBOX_COLOR_INDEX) -#define HEADER_SEPARATOR_COLOR COLOR(HEADER_SEPARATOR_COLOR_INDEX) -#define MENU_TITLE_BGCOLOR COLOR(MENU_TITLE_BGCOLOR_INDEX) -#define MENU_TITLE_COLOR COLOR(MENU_TITLE_COLOR_INDEX) -#define MENU_TITLE_DISABLE_COLOR COLOR(MENU_TITLE_DISABLE_COLOR_INDEX) -#define HEADER_COLOR COLOR(HEADER_COLOR_INDEX) -#define ALARM_COLOR COLOR(ALARM_COLOR_INDEX) -#define WARNING_COLOR COLOR(WARNING_COLOR_INDEX) -#define TEXT_DISABLE_COLOR COLOR(TEXT_DISABLE_COLOR_INDEX) -#define CURVE_AXIS_COLOR COLOR(CURVE_AXIS_COLOR_INDEX) -#define CURVE_COLOR COLOR(CURVE_COLOR_INDEX) -#define CURVE_CURSOR_COLOR COLOR(CURVE_CURSOR_COLOR_INDEX) -#define TITLE_BGCOLOR COLOR(TITLE_BGCOLOR_INDEX) -#define TRIM_BGCOLOR COLOR(TRIM_BGCOLOR_INDEX) -#define TRIM_SHADOW_COLOR COLOR(TRIM_SHADOW_COLOR_INDEX) -#define HEADER_BGCOLOR COLOR(HEADER_BGCOLOR_INDEX) -#define HEADER_ICON_BGCOLOR COLOR(HEADER_ICON_BGCOLOR_INDEX) -#define HEADER_CURRENT_BGCOLOR COLOR(HEADER_CURRENT_BGCOLOR_INDEX) -#define MAINVIEW_PANES_COLOR COLOR(MAINVIEW_PANES_COLOR_INDEX) -#define MAINVIEW_GRAPHICS_COLOR COLOR(MAINVIEW_GRAPHICS_COLOR_INDEX) -#define OVERLAY_COLOR COLOR(OVERLAY_COLOR_INDEX) -#define CUSTOM_COLOR COLOR(CUSTOM_COLOR_INDEX) - -#define COLOR_SPLIT(color, r, g, b) \ - uint16_t r = ((color) & 0xF800) >> 11; \ - uint16_t g = ((color) & 0x07E0) >> 5; \ - uint16_t b = ((color) & 0x001F) - -#define COLOR_JOIN(r, g, b) \ - (((r) << 11) + ((g) << 5) + (b)) - -#define display_t uint16_t +#include "colors.h" #define DISPLAY_PIXELS_COUNT (LCD_W*LCD_H) #define DISPLAY_BUFFER_SIZE (sizeof(display_t)*DISPLAY_PIXELS_COUNT) #if defined(SIMU) extern display_t displayBuf[DISPLAY_BUFFER_SIZE]; #else -extern uint8_t * CurrentFrameBuffer; -#define displayBuf ((uint16_t *)CurrentFrameBuffer) +#define displayBuf lcd->data #endif #define lcdRefreshWait() @@ -184,9 +90,17 @@ extern coord_t lcdNextPos; void lcdDrawChar(coord_t x, coord_t y, const unsigned char c, LcdFlags attr=0); void lcdDrawText(coord_t x, coord_t y, const pm_char * s, LcdFlags attr=0); void lcdDrawTextAtIndex(coord_t x, coord_t y, const pm_char * s, uint8_t idx, LcdFlags attr=0); -void lcdDrawSizedText(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags attr=0); -void lcdDrawSizedText(coord_t x, coord_t y, const pm_char * s, unsigned char len); -void lcd_putsCenter(coord_t y, const pm_char * s, LcdFlags attr=0); + +inline void lcdClear() +{ + lcd->clear(); +} + +inline void lcdDrawSizedText(coord_t x, coord_t y, const pm_char * s, uint8_t len, LcdFlags attr=0) +{ + lcd->drawSizedText(x, y, s, len, attr); +} + void lcdDrawHexNumber(coord_t x, coord_t y, uint32_t val, LcdFlags mode=0); void lcdDrawNumber(coord_t x, coord_t y, int32_t val, LcdFlags flags=0, uint8_t len=0, const char * prefix=NULL, const char * suffix=NULL); @@ -220,21 +134,18 @@ void putsTimer(coord_t x, coord_t y, putstime_t tme, LcdFlags att=0); #define PIXEL_PTR(x, y) &displayBuf[(y)*LCD_W + (x)] -inline void lcdDrawPixel(display_t * p, display_t value) -{ - *p = value; -} - -inline void lcdDrawPixel(coord_t x, coord_t y, display_t value) -{ - display_t * p = PIXEL_PTR(x, y); - lcdDrawPixel(p, value); -} - void lcdDrawAlphaPixel(display_t * p, uint8_t opacity, uint16_t color); void lcdDrawPoint(coord_t x, coord_t y, LcdFlags att=0); -void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0); -void lcdDrawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att=0); +inline void lcdDrawHorizontalLine(coord_t x, coord_t y, coord_t w, uint8_t pat, LcdFlags att=0) +{ + lcd->drawHorizontalLine(x, y, w, pat, att); +} + +inline void lcdDrawVerticalLine(coord_t x, coord_t y, coord_t h, uint8_t pat, LcdFlags att=0) +{ + lcd->drawVerticalLine(x, y, h, pat, att); +} + void lcdDrawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, uint8_t pat=SOLID, LcdFlags att=0); inline void lcdDrawAlphaPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t color) @@ -246,7 +157,7 @@ inline void lcdDrawAlphaPixel(coord_t x, coord_t y, uint8_t opacity, uint16_t co #if !defined(SIMU) inline void lcdDrawSolidFilledRect(coord_t x, coord_t y, coord_t w, coord_t h, LcdFlags flags) { - lcdDrawSolidFilledRectDMA(x, y, w, h, lcdColorTable[COLOR_IDX(flags)]); + DMAFillRect(lcd->data, LCD_W, x, y, w, h, lcdColorTable[COLOR_IDX(flags)]); } #else void lcdDrawSolidFilledRect(coord_t x, coord_t y, coord_t w, coord_t h, LcdFlags flags); @@ -257,13 +168,6 @@ inline void lcdSetColor(uint16_t color) lcdColorTable[CUSTOM_COLOR_INDEX] = color; } -inline void lcdClear() -{ - lcdDrawSolidFilledRect(0, 0, LCD_W, LCD_H, 0); -} - -void lcdInvertRect(coord_t x, coord_t y, coord_t w, coord_t h, LcdFlags flags); - inline void lcdDrawSolidHorizontalLine(coord_t x, coord_t y, coord_t w, LcdFlags att) { lcdDrawSolidFilledRect(x, y, w, 1, att); @@ -283,13 +187,21 @@ inline void lcdDrawSolidRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t lcdDrawSolidFilledRect(x, y+h-thickness, w, thickness, att); } -void lcdDrawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att=0); +inline void lcdDrawFilledRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t pat, LcdFlags att=0) +{ + lcd->drawFilledRect(x, y, w, h, pat, att); +} + void lcdDrawBlackOverlay(); void lcdDrawRect(coord_t x, coord_t y, coord_t w, coord_t h, uint8_t thickness=1, uint8_t pat=SOLID, LcdFlags att=0); void lcdDrawCircle(int x0, int y0, int radius); void lcdDrawPie(int x0, int y0, int radius, int angle1=0, int angle2=360); void lcdDrawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle); -void lcdDrawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdFlags flags=0, int startAngle=0, int endAngle=360); + +inline void lcdDrawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * bmp, LcdFlags flags=0, int startAngle=0, int endAngle=360) +{ + lcd->drawBitmapPatternPie(x0, y0, bmp, flags, startAngle, endAngle); +} inline void lcdDrawSquare(coord_t x, coord_t y, coord_t w, LcdFlags att=0) { @@ -306,27 +218,19 @@ inline int getBitmapHeight(const uint8_t * bmp) return *(((const uint16_t *)bmp)+1); } -inline int getBitmapScaledSize(int size, float scale) -{ - if (scale == 0.0) - return size; - else - return size * scale; -} - -float getBitmapScale(const uint8_t * bmp, int dstWidth, int dstHeight); +char getMappedChar(unsigned char c); +int getFontHeight(LcdFlags flags); int getTextWidth(const pm_char *s, int len=0, LcdFlags flags=0); -void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * img, coord_t offset=0, coord_t height=0, float scale=0); -void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags flags=0, coord_t offset=0, coord_t width=0); -void lcdDrawAlphaBitmap(coord_t x, coord_t y, const uint8_t * bmp); + +inline void lcdDrawBitmapPattern(coord_t x, coord_t y, const uint8_t * img, LcdFlags flags=0, coord_t offset=0, coord_t width=0) +{ + lcd->drawBitmapPattern(x, y, img, flags, offset, width); +} #define lcdSetRefVolt(...) void lcdSetContrast(); #define lcdOff(...) -uint8_t * bmpLoad(const char * filename); -const char * imgLoad(uint8_t * dest, const char * filename, uint16_t width, uint16_t height); - #if defined(BOOT) #define BLINK_ON_PHASE (0) #else diff --git a/radio/src/gui/horus/menu_general_calib.cpp b/radio/src/gui/horus/menu_general_calib.cpp index 404dd5ae6..3523b1b9b 100644 --- a/radio/src/gui/horus/menu_general_calib.cpp +++ b/radio/src/gui/horus/menu_general_calib.cpp @@ -18,7 +18,7 @@ * GNU General Public License for more details. */ -#include "../../opentx.h" +#include "opentx.h" #define XPOT_DELTA 10 #define XPOT_DELAY 10 /* cycles */ @@ -55,6 +55,8 @@ void drawPots() OPTION_SLIDER_SQUARE_BUTTON); } +#include "alpha_horus.lbm" + bool menuCommonCalib(evt_t event) { drawScreenTemplate(NULL, LBM_CALIBRATION_ICON, OPTION_MENU_NO_FOOTER); @@ -182,7 +184,7 @@ bool menuCommonCalib(evt_t event) break; } - lcdDrawAlphaBitmap((LCD_W-206)/2, LCD_H-220, LBM_HORUS); + lcd->drawAlphaBitmap((LCD_W-206)/2, LCD_H-220, &ALPHA_HORUS); drawSticks(); drawPots(); diff --git a/radio/src/gui/horus/menu_general_sdmanager.cpp b/radio/src/gui/horus/menu_general_sdmanager.cpp index d619a6682..d07b872f0 100644 --- a/radio/src/gui/horus/menu_general_sdmanager.cpp +++ b/radio/src/gui/horus/menu_general_sdmanager.cpp @@ -27,7 +27,7 @@ #define IS_FILE(fname) ((bool)(NODE_TYPE(fname))) int currentBitmapIndex = 0; -uint8_t * currentBitmap = NULL; +BitmapBuffer * currentBitmap = NULL; bool menuGeneralSdManagerInfo(evt_t event) { @@ -365,12 +365,12 @@ bool menuGeneralSdManager(evt_t _event) if (ext && (!strcasecmp(ext, BITMAPS_EXT) || !strcasecmp(ext, PNG_EXT) || !strcasecmp(ext, JPG_EXT))) { if (currentBitmapIndex != menuVerticalPosition) { currentBitmapIndex = menuVerticalPosition; - free(currentBitmap); - currentBitmap = bmpLoad(reusableBuffer.sdmanager.lines[index]); + delete currentBitmap; + currentBitmap = BitmapBuffer::load(reusableBuffer.sdmanager.lines[index]); + // TODO scale in case of a too large bitmap } if (currentBitmap) { - // TODO scale in case of a too large bitmap - lcdDrawBitmap(LCD_W / 2, LCD_H / 2, currentBitmap); + lcd->drawBitmap(LCD_W / 2, LCD_H / 2, currentBitmap); } } diff --git a/radio/src/gui/horus/menu_general_setup.cpp b/radio/src/gui/horus/menu_general_setup.cpp index 1c690e33b..a9b804f04 100644 --- a/radio/src/gui/horus/menu_general_setup.cpp +++ b/radio/src/gui/horus/menu_general_setup.cpp @@ -18,7 +18,7 @@ * GNU General Public License for more details. */ -#include "../../opentx.h" +#include "opentx.h" #define RADIO_SETUP_2ND_COLUMN 220 #define YEAR_SEPARATOR_OFFSET 42 diff --git a/radio/src/gui/horus/menu_general_trainer.cpp b/radio/src/gui/horus/menu_general_trainer.cpp index 946db75d7..737602fc0 100644 --- a/radio/src/gui/horus/menu_general_trainer.cpp +++ b/radio/src/gui/horus/menu_general_trainer.cpp @@ -2,7 +2,7 @@ * Copyright (C) OpenTX * * Based on code named - * th9x - http://code.google.com/p/th9x + * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * @@ -33,7 +33,7 @@ bool menuGeneralTrainer(evt_t event) MENU(STR_MENUTRAINER, LBM_RADIO_ICONS, menuTabGeneral, e_Trainer, (slave ? 0 : 6), { 2, 2, 2, 2, 0/*, 0*/ }); if (slave) { - lcd_putsCenter(5*FH, STR_SLAVE, TEXT_COLOR); + // TODO lcd_putsCenter(5*FH, STR_SLAVE, TEXT_COLOR); return true; } diff --git a/radio/src/gui/horus/menu_model_curves.cpp b/radio/src/gui/horus/menu_model_curves.cpp index 7dd682e3e..49017544c 100644 --- a/radio/src/gui/horus/menu_model_curves.cpp +++ b/radio/src/gui/horus/menu_model_curves.cpp @@ -238,9 +238,9 @@ bool menuModelCurveOne(evt_t event) if (crv.type==CURVE_TYPE_CUSTOM && i>0 && i<5+crv.points-1) x = points[5+crv.points+i-1]; if (i>=pointsOfs && ifadeIn = checkIncDec(event, p->fadeIn, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); - lcdDrawNumber(FLIGHT_MODES_FADEIN_COLUMN, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1); + lcdDrawNumber(FLIGHT_MODES_FADEIN_COLUMN, y, (10/DELAY_STEP)*p->fadeIn, attr|PREC1|RIGHT); break; case ITEM_FLIGHT_MODES_FADE_OUT: if (active) p->fadeOut = checkIncDec(event, p->fadeOut, 0, DELAY_MAX, EE_MODEL|NO_INCDEC_MARKS); - lcdDrawNumber(FLIGHT_MODES_FADEOUT_COLUMN, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1); + lcdDrawNumber(FLIGHT_MODES_FADEOUT_COLUMN, y, (10/DELAY_STEP)*p->fadeOut, attr|PREC1|RIGHT); break; } diff --git a/radio/src/gui/horus/menu_model_gvars.cpp b/radio/src/gui/horus/menu_model_gvars.cpp index 17011a646..c24756e66 100644 --- a/radio/src/gui/horus/menu_model_gvars.cpp +++ b/radio/src/gui/horus/menu_model_gvars.cpp @@ -2,7 +2,7 @@ * Copyright (C) OpenTX * * Based on code named - * th9x - http://code.google.com/p/th9x + * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * @@ -76,9 +76,9 @@ bool menuModelGVars(evt_t event) } else { if (abs(v) >= 1000) - lcdDrawNumber(x, y+1, v, TINSIZE|attr); + lcdDrawNumber(x, y+1, v, TINSIZE|attr|RIGHT); else - lcdDrawNumber(x, y, v, attr); + lcdDrawNumber(x, y, v, attr|RIGHT); vmin = -GVAR_MAX; vmax = GVAR_MAX; } if (attr) { diff --git a/radio/src/gui/horus/menu_model_inputs.cpp b/radio/src/gui/horus/menu_model_inputs.cpp index 5fa6f188a..069cec51b 100644 --- a/radio/src/gui/horus/menu_model_inputs.cpp +++ b/radio/src/gui/horus/menu_model_inputs.cpp @@ -295,7 +295,7 @@ bool menuModelExpoOne(evt_t event) #define _STR_MAX(x) PSTR("/" #x) #define STR_MAX(x) _STR_MAX(x) -#define EXPO_LINE_WEIGHT_POS 92 +#define EXPO_LINE_WEIGHT_POS 110 #define EXPO_LINE_SRC_POS 115 #define EXPO_LINE_CURVE_POS 162 #define EXPO_LINE_SWITCH_POS 210 @@ -527,7 +527,7 @@ bool menuModelExposAll(evt_t event) if (cur-menuVerticalOffset >= 0 && cur-menuVerticalOffset < NUM_BODY_LINES) { LcdFlags attr = ((s_copyMode || sub != cur) ? 0 : INVERS); - GVAR_MENU_ITEM(EXPO_LINE_WEIGHT_POS, y, ed->weight, MIN_EXPO_WEIGHT, 100, attr | (isExpoActive(i) ? BOLD : 0), 0, 0); + GVAR_MENU_ITEM(EXPO_LINE_WEIGHT_POS, y, ed->weight, MIN_EXPO_WEIGHT, 100, RIGHT | attr | (isExpoActive(i) ? BOLD : 0), 0, 0); displayExpoLine(y, ed); if (s_copyMode) { diff --git a/radio/src/gui/horus/menu_model_limits.cpp b/radio/src/gui/horus/menu_model_limits.cpp index 2d885732e..799b7b20b 100644 --- a/radio/src/gui/horus/menu_model_limits.cpp +++ b/radio/src/gui/horus/menu_model_limits.cpp @@ -169,9 +169,9 @@ bool menuModelLimits(evt_t event) } #if defined(PPM_UNIT_US) - lcdDrawNumber(LIMITS_OFFSET_POS, y, ((int32_t)ld->offset*128) / 25, attr|PREC1); + lcdDrawNumber(LIMITS_OFFSET_POS, y, ((int32_t)ld->offset*128) / 25, attr|PREC1|RIGHT); #else - lcdDrawNumber(LIMITS_OFFSET_POS, y, ld->offset, attr|PREC1); + lcdDrawNumber(LIMITS_OFFSET_POS, y, ld->offset, attr|PREC1|RIGHT); #endif if (active) { ld->offset = checkIncDec(event, ld->offset, -1000, 1000, EE_MODEL, NULL, stops1000); @@ -187,7 +187,7 @@ bool menuModelLimits(evt_t event) ld->min = GVAR_MENU_ITEM(LIMITS_MIN_POS, y, ld->min, -LIMIT_EXT_MAX, LIMIT_EXT_MAX, attr|PREC1, 0, event); break; } - lcdDrawNumber(LIMITS_MIN_POS, y, MIN_MAX_DISPLAY(ld->min-LIMITS_MIN_MAX_OFFSET), attr|PREC1); + lcdDrawNumber(LIMITS_MIN_POS, y, MIN_MAX_DISPLAY(ld->min-LIMITS_MIN_MAX_OFFSET), attr|PREC1|RIGHT); if (active) ld->min = LIMITS_MIN_MAX_OFFSET + checkIncDec(event, ld->min-LIMITS_MIN_MAX_OFFSET, -limit, 0, EE_MODEL, NULL, stops1000); break; @@ -196,7 +196,7 @@ bool menuModelLimits(evt_t event) ld->max = GVAR_MENU_ITEM(LIMITS_MAX_POS, y, ld->max, -LIMIT_EXT_MAX, LIMIT_EXT_MAX, attr|PREC1, 0, event); break; } - lcdDrawNumber(LIMITS_MAX_POS, y, MIN_MAX_DISPLAY(ld->max+LIMITS_MIN_MAX_OFFSET), attr|PREC1); + lcdDrawNumber(LIMITS_MAX_POS, y, MIN_MAX_DISPLAY(ld->max+LIMITS_MIN_MAX_OFFSET), attr|PREC1|RIGHT); if (active) ld->max = -LIMITS_MIN_MAX_OFFSET + checkIncDec(event, ld->max+LIMITS_MIN_MAX_OFFSET, 0, +limit, EE_MODEL, NULL, stops1000); break; @@ -231,7 +231,7 @@ bool menuModelLimits(evt_t event) #if defined(PPM_CENTER_ADJUSTABLE) case ITEM_LIMITS_PPM_CENTER: - lcdDrawNumber(LIMITS_PPM_CENTER_POS, y, PPM_CENTER+ld->ppmCenter, attr); + lcdDrawNumber(LIMITS_PPM_CENTER_POS, y, PPM_CENTER+ld->ppmCenter, attr|RIGHT); if (active) { CHECK_INCDEC_MODELVAR(event, ld->ppmCenter, -PPM_CENTER_MAX, +PPM_CENTER_MAX); } diff --git a/radio/src/gui/horus/menu_model_mixes.cpp b/radio/src/gui/horus/menu_model_mixes.cpp index 22713ac09..28dd2e1b5 100644 --- a/radio/src/gui/horus/menu_model_mixes.cpp +++ b/radio/src/gui/horus/menu_model_mixes.cpp @@ -270,7 +270,7 @@ bool menuModelMixOne(evt_t event) #define _STR_MAX(x) PSTR("/" #x) #define STR_MAX(x) _STR_MAX(x) -#define MIX_LINE_WEIGHT_POS 92 +#define MIX_LINE_WEIGHT_POS 110 #define MIX_LINE_SRC_POS 115 #define MIX_LINE_CURVE_POS 162 #define MIX_LINE_SWITCH_POS 210 @@ -502,7 +502,7 @@ bool menuModelMixAll(evt_t event) putsMixerSource(MIX_LINE_SRC_POS, y, md->srcRaw); - gvarWeightItem(MIX_LINE_WEIGHT_POS, y, md, attr | (isMixActive(i) ? BOLD : 0), event); + gvarWeightItem(MIX_LINE_WEIGHT_POS, y, md, RIGHT | attr | (isMixActive(i) ? BOLD : 0), event); displayMixLine(y, md); diff --git a/radio/src/gui/horus/menu_model_select.cpp b/radio/src/gui/horus/menu_model_select.cpp index 21f8ed660..f58eead3d 100644 --- a/radio/src/gui/horus/menu_model_select.cpp +++ b/radio/src/gui/horus/menu_model_select.cpp @@ -61,10 +61,11 @@ void drawModel(coord_t x, coord_t y, const char * name, bool selected) for (int i=0; i<4; i++) { lcdDrawBitmapPattern(x+104+i*11, y+25, LBM_SCORE0, TITLE_BGCOLOR); } - uint8_t * bitmap = bmpLoad(header.bitmap); + GET_FILENAME(filename, BITMAPS_PATH, header.bitmap, BITMAPS_EXT); + const BitmapBuffer * bitmap = BitmapBuffer::load(filename); if (bitmap) { - lcdDrawBitmap(x+5, y+24, bitmap, 0, 0, getBitmapScale(bitmap, 64, 32)); - free(bitmap); + lcd->drawScaledBitmap(bitmap, x+5, y+24, 64, 32); + delete bitmap; } else { lcdDrawBitmapPattern(x+5, y+23, LBM_LIBRARY_SLOT, TEXT_COLOR); diff --git a/radio/src/gui/horus/menu_model_setup.cpp b/radio/src/gui/horus/menu_model_setup.cpp index 916df74af..c6b9a773c 100644 --- a/radio/src/gui/horus/menu_model_setup.cpp +++ b/radio/src/gui/horus/menu_model_setup.cpp @@ -612,7 +612,7 @@ bool menuModelSetup(evt_t event) if (IS_MODULE_PPM(moduleIdx)) { lcdDrawText(MENUS_MARGIN_LEFT, y, STR_PPMFRAME); lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, (int16_t)moduleData.ppmFrameLength*5 + 225, (menuHorizontalPosition<=0 ? attr : 0) | PREC1|LEFT, 0, NULL, STR_MS); - lcdDrawNumber(MODEL_SETUP_2ND_COLUMN+90, y, (moduleData.ppmDelay*50)+300, (CURSOR_ON_LINE() || menuHorizontalPosition==1) ? attr : 0, 0, NULL, "us"); + lcdDrawNumber(MODEL_SETUP_2ND_COLUMN+90, y, (moduleData.ppmDelay*50)+300, (CURSOR_ON_LINE() || menuHorizontalPosition==1) ? attr|RIGHT : RIGHT, 0, NULL, "us"); lcdDrawText(MODEL_SETUP_2ND_COLUMN+120, y, moduleData.ppmPulsePol ? "+" : "-", (CURSOR_ON_LINE() || menuHorizontalPosition==2) ? attr : 0); if (attr && s_editMode>0) { @@ -641,7 +641,7 @@ bool menuModelSetup(evt_t event) lcdDrawText(MENUS_MARGIN_LEFT, y, STR_RXNUM); } if (IS_MODULE_XJT(moduleIdx) || IS_MODULE_DSM2(moduleIdx)) { - if (xOffsetBind) lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0|LEFT, 2); + if (xOffsetBind) lcdDrawNumber(MODEL_SETUP_2ND_COLUMN, y, g_model.header.modelId[moduleIdx], (l_posHorz==0 ? attr : 0) | LEADING0 | LEFT, 2); if (attr && l_posHorz==0 && s_editMode>0) { CHECK_INCDEC_MODELVAR_ZERO(event, g_model.header.modelId[moduleIdx], IS_MODULE_DSM2(moduleIdx) ? 20 : 63); } @@ -770,13 +770,13 @@ bool menuModelFailsafe(evt_t event) } #if defined(PPM_UNIT_US) uint8_t wbar = (longNames ? SLIDER_W-10 : SLIDER_W); - lcdDrawNumber(x+COL_W-4-wbar-ofs, y, PPM_CH_CENTER(ch)+val/2, flags); + lcdDrawNumber(x+COL_W-4-wbar-ofs, y, PPM_CH_CENTER(ch)+val/2, flags|RIGHT); #elif defined(PPM_UNIT_PERCENT_PREC1) uint8_t wbar = (longNames ? SLIDER_W-16 : SLIDER_W-6); - lcdDrawNumber(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val), PREC1|flags); + lcdDrawNumber(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val), PREC1|flags|RIGHT); #else uint8_t wbar = (longNames ? SLIDER_W-10 : SLIDER_W); - lcdDrawNumber(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val)/10, flags); + lcdDrawNumber(x+COL_W-4-wbar-ofs, y, calcRESXto1000(val)/10, flags|RIGHT); #endif // Gauge diff --git a/radio/src/gui/horus/menu_model_telemetry.cpp b/radio/src/gui/horus/menu_model_telemetry.cpp index 216f332e3..b17c5b6bd 100644 --- a/radio/src/gui/horus/menu_model_telemetry.cpp +++ b/radio/src/gui/horus/menu_model_telemetry.cpp @@ -2,7 +2,7 @@ * Copyright (C) OpenTX * * Based on code named - * th9x - http://code.google.com/p/th9x + * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * @@ -334,7 +334,7 @@ bool menuModelSensor(evt_t event) lcdDrawText(MENUS_MARGIN_LEFT, y, STR_AUTOOFFSET); sensor->autoOffset = editCheckBox(sensor->autoOffset, SENSOR_2ND_COLUMN, y, attr, event); break; - + case SENSOR_FIELD_ONLYPOSITIVE: lcdDrawText(MENUS_MARGIN_LEFT, y, STR_ONLYPOSITIVE); sensor->onlyPositive = editCheckBox(sensor->onlyPositive, SENSOR_2ND_COLUMN, y, attr, event); @@ -344,7 +344,7 @@ bool menuModelSensor(evt_t event) lcdDrawText(MENUS_MARGIN_LEFT, y, STR_FILTER); sensor->filter = editCheckBox(sensor->filter, SENSOR_2ND_COLUMN, y, attr, event); break; - + case SENSOR_FIELD_PERSISTENT: lcdDrawText(MENUS_MARGIN_LEFT, y, NO_INDENT(STR_PERSISTENT)); sensor->persistent = editCheckBox(sensor->persistent, SENSOR_2ND_COLUMN, y, attr, event); @@ -381,7 +381,7 @@ void onSensorMenu(const char *result) } else if (result == STR_COPY) { int newIndex = availableTelemetryIndex(); - + if (newIndex >= 0) { TelemetrySensor & sourceSensor = g_model.telemetrySensors[index]; TelemetrySensor & newSensor = g_model.telemetrySensors[newIndex]; @@ -390,7 +390,7 @@ void onSensorMenu(const char *result) TelemetryItem & newItem = telemetryItems[newIndex]; newItem = sourceItem; storageDirty(EE_MODEL); - } + } else { POPUP_WARNING(STR_TELEMETRYFULL); } @@ -406,7 +406,7 @@ bool menuModelTelemetry(evt_t event) delTelemetryIndex(i); } } - + MENU(STR_MENUTELEMETRY, LBM_MODEL_ICONS, menuTabModel, e_Telemetry, ITEM_TELEMETRY_MAX, { TELEMETRY_TYPE_ROWS RSSI_ROWS SENSORS_ROWS VARIO_ROWS }); for (int i=0; idrawMessageBox("", "", "", MESSAGEBOX_TYPE_WARNING); + // lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + // lcdDrawSolidRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, 2, ALARM_COLOR); // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_WARNING); } void displayMessageBox() { - lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); - lcdDrawSolidRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, 2, WARNING_COLOR); + // theme->drawMessageBox("", "", "", MESSAGEBOX_TYPE_INFO); + // lcdDrawSolidFilledRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, TEXT_BGCOLOR); + // lcdDrawSolidRect(POPUP_X, POPUP_Y, POPUP_W, POPUP_H, 2, WARNING_COLOR); // lcdDrawBitmap(POPUP_X+15, POPUP_Y+20, LBM_MESSAGE); } @@ -75,11 +77,12 @@ void displayPopup(const char * title) void displayWarning(evt_t event) { warningResult = false; + if (warningType == WARNING_TYPE_INPUT) - displayMessageBox(); + theme->drawMessageBox(warningText, "", "", MESSAGEBOX_TYPE_INFO); else - displayWarningBox(); - lcdDrawSizedText(WARNING_LINE_X, WARNING_LINE_Y, warningText, WARNING_LINE_LEN, DBLSIZE | (warningType == WARNING_TYPE_INPUT ? WARNING_COLOR : ALARM_COLOR)); + theme->drawMessageBox(warningText, "", "", MESSAGEBOX_TYPE_WARNING); + if (warningInfoText) { lcdDrawSizedText(WARNING_LINE_X, WARNING_INFOLINE_Y, warningInfoText, warningInfoLength, WARNING_INFO_FLAGS); } diff --git a/radio/src/gui/horus/screens_setup.cpp b/radio/src/gui/horus/screens_setup.cpp index c0d8dab15..8e73b808e 100644 --- a/radio/src/gui/horus/screens_setup.cpp +++ b/radio/src/gui/horus/screens_setup.cpp @@ -57,6 +57,16 @@ void onZoneOptionFileSelectionMenu(const char * result) } } +int getZoneOptionColumns(const ZoneOption * option) +{ + if (option->type == ZoneOption::Color) { + return 2; + } + else { + return 0; + } +} + void editZoneOption(coord_t y, const ZoneOption * option, ZoneOptionValue * value, LcdFlags attr, uint32_t i_flags, evt_t event) { lcdDrawText(MENUS_MARGIN_LEFT, y, option->name); @@ -115,11 +125,29 @@ void editZoneOption(coord_t y, const ZoneOption * option, ZoneOptionValue * valu } } else if (option->type == ZoneOption::Color) { + COLOR_SPLIT(value->unsignedValue, r, g, b); + lcdSetColor(value->unsignedValue); - lcdDrawSolidRect(SCREENS_SETUP_2ND_COLUMN, y, 40, 15, 1, attr ? TEXT_INVERTED_BGCOLOR : TEXT_COLOR); - lcdDrawSolidFilledRect(SCREENS_SETUP_2ND_COLUMN + 1, y + 1, 38, 13, CUSTOM_COLOR); - if (attr) { - value->unsignedValue = checkIncDec(event, value->unsignedValue, i_flags, 65535, 0); + lcdDrawSolidFilledRect(SCREENS_SETUP_2ND_COLUMN-1, y-1, 42, 17, TEXT_COLOR); + lcdDrawSolidFilledRect(SCREENS_SETUP_2ND_COLUMN, y, 40, 15, CUSTOM_COLOR); + + lcdDrawText(SCREENS_SETUP_2ND_COLUMN + 50, y, "R:", TEXT_COLOR); + lcdDrawNumber(SCREENS_SETUP_2ND_COLUMN + 70, y, r << 3, LEFT|TEXT_COLOR|((attr && menuHorizontalPosition == 0) ? attr : 0)); + if (attr && menuHorizontalPosition == 0) { + r = checkIncDec(event, r, 0, (1<<5)-1, i_flags); + } + lcdDrawText(SCREENS_SETUP_2ND_COLUMN + 110, y, "G:", TEXT_COLOR); + lcdDrawNumber(SCREENS_SETUP_2ND_COLUMN + 130, y, g << 2, LEFT|TEXT_COLOR|((attr && menuHorizontalPosition == 1) ? attr : 0)); + if (attr && menuHorizontalPosition == 1) { + g = checkIncDec(event, g, 0, (1<<6)-1, i_flags); + } + lcdDrawText(SCREENS_SETUP_2ND_COLUMN + 170, y, "B:", TEXT_COLOR); + lcdDrawNumber(SCREENS_SETUP_2ND_COLUMN + 190, y, b << 3, LEFT|TEXT_COLOR|((attr && menuHorizontalPosition == 2) ? attr : 0)); + if (attr && menuHorizontalPosition == 2) { + b = checkIncDec(event, b, 0, (1<<5)-1, i_flags); + } + if (attr && checkIncDec_Ret) { + value->unsignedValue = COLOR_JOIN(r, g, b); } } } @@ -155,11 +183,6 @@ bool menuWidgetSettings(evt_t event) return menuSettings("Widget settings", currentWidget, EE_MODEL, event); } -bool menuThemeSettings(evt_t event) -{ - return menuSettings("Theme settings", theme, EE_GENERAL, event); -} - bool menuWidgetChoice(evt_t event) { static Widget * previousWidget; @@ -328,7 +351,7 @@ T * editThemeChoice(coord_t x, coord_t y, T * array[], uint8_t count, T * curren } if (attr) { if (menuHorizontalPosition < 0) { - lcdDrawSolidFilledRect(x-3, y-2, min(4, count)*56+1, 2*FH-5, TEXT_INVERTED_BGCOLOR); + lcdDrawSolidFilledRect(x-3, y-1, min(4, count)*56+1, 2*FH-5, TEXT_INVERTED_BGCOLOR); } else { if (needsOffsetCheck) { @@ -345,15 +368,15 @@ T * editThemeChoice(coord_t x, coord_t y, T * array[], uint8_t count, T * curren unsigned int last = min(menuHorizontalOffset + 4, count); for (unsigned int i=menuHorizontalOffset, pos=x; idrawThumb(pos, y, current == element ? ((attr && menuHorizontalPosition < 0) ? TEXT_INVERTED_COLOR : TEXT_INVERTED_BGCOLOR) : LINE_COLOR); + element->drawThumb(pos, y+1, current == element ? ((attr && menuHorizontalPosition < 0) ? TEXT_INVERTED_COLOR : TEXT_INVERTED_BGCOLOR) : LINE_COLOR); } if (count > 4) { - lcdDrawBitmapPattern(x - 12, y, LBM_CARROUSSEL_LEFT, menuHorizontalOffset > 0 ? LINE_COLOR : CURVE_AXIS_COLOR); - lcdDrawBitmapPattern(x + 4 * 56, y, LBM_CARROUSSEL_RIGHT, last < countRegisteredLayouts ? LINE_COLOR : CURVE_AXIS_COLOR); + lcdDrawBitmapPattern(x - 12, y+1, LBM_CARROUSSEL_LEFT, menuHorizontalOffset > 0 ? LINE_COLOR : CURVE_AXIS_COLOR); + lcdDrawBitmapPattern(x + 4 * 56, y+1, LBM_CARROUSSEL_RIGHT, last < countRegisteredLayouts ? LINE_COLOR : CURVE_AXIS_COLOR); } if (attr && menuHorizontalPosition >= 0) { - lcdDrawSolidRect(x + (menuHorizontalPosition - menuHorizontalOffset) * 56 - 3, y - 2, 57, 35, 1, TEXT_INVERTED_BGCOLOR); - if (menuHorizontalPosition != currentIndex && event == EVT_KEY_BREAK(KEY_ENTER)) { + lcdDrawSolidRect(x + (menuHorizontalPosition - menuHorizontalOffset) * 56 - 3, y - 1, 57, 35, 1, TEXT_INVERTED_BGCOLOR); + if (event == EVT_KEY_BREAK(KEY_ENTER)) { s_editMode = 0; return array[menuHorizontalPosition]; } @@ -361,19 +384,39 @@ T * editThemeChoice(coord_t x, coord_t y, T * array[], uint8_t count, T * curren return NULL; } +int getOptionsCount(const ZoneOption * options) +{ + if (options == NULL) { + return 0; + } + else { + int count = 0; + for (const ZoneOption * option = options; option->name; option++) { + count++; + } + return count; + } +} + enum menuScreensThemeItems { ITEM_SCREEN_SETUP_THEME, - ITEM_SCREEN_SETUP_THEME_SETTINGS = ITEM_SCREEN_SETUP_THEME+2, - ITEM_SCREEN_SETUP_TOPBAR, - ITEM_SCREEN_SETUP_MAX + ITEM_SCREEN_SETUP_THEME_OPTION1 = ITEM_SCREEN_SETUP_THEME+2 }; bool menuScreensTheme(evt_t event) { bool needsOffsetCheck = (menuVerticalPosition != 0 || menuHorizontalPosition < 0); + const ZoneOption * options = theme->getOptions(); + int optionsCount = getOptionsCount(options); + linesCount = ITEM_SCREEN_SETUP_THEME_OPTION1 + optionsCount + 1; menuPageCount = updateMainviewsMenu(); - MENU_WITH_OPTIONS("User interface", LBM_SCREENS_SETUP_ICONS, menuTabScreensSetup, menuPageCount, 0, ITEM_SCREEN_SETUP_MAX, { uint8_t(NAVIGATION_LINE_BY_LINE|uint8_t(countRegisteredThemes-1)), ORPHAN_ROW, 0, 0, 0, 0 }); + uint8_t mstate_tab[2 + MAX_THEME_OPTIONS + 1] = { uint8_t(NAVIGATION_LINE_BY_LINE|uint8_t(countRegisteredThemes-1)), ORPHAN_ROW }; + for (int i=0; i(SCREENS_SETUP_2ND_COLUMN, y, registeredThemes, countRegisteredThemes, theme, needsOffsetCheck, attr, event); + Theme * new_theme = editThemeChoice(SCREENS_SETUP_2ND_COLUMN, y, registeredThemes, countRegisteredThemes, theme, needsOffsetCheck, attr, event); if (new_theme) { new_theme->init(); loadTheme(new_theme); @@ -393,31 +436,35 @@ bool menuScreensTheme(evt_t event) break; } - case ITEM_SCREEN_SETUP_THEME_SETTINGS: - drawButton(SCREENS_SETUP_2ND_COLUMN, y, "Theme settings", attr); - if (attr && event == EVT_KEY_BREAK(KEY_ENTER) && theme->getOptions()) { - s_editMode = 0; - pushMenu(menuThemeSettings); - } + case ITEM_SCREEN_SETUP_THEME+1: break; - case ITEM_SCREEN_SETUP_TOPBAR: - lcdDrawText(MENUS_MARGIN_LEFT, y, "Top bar"); - drawButton(SCREENS_SETUP_2ND_COLUMN, y, "Setup", attr); - if (attr && event == EVT_KEY_BREAK(KEY_ENTER)) { - currentScreen = customScreens[0]; - currentContainer = topbar; - pushMenu(menuWidgetsSetup); + default: + { + uint8_t index = k - ITEM_SCREEN_SETUP_THEME_OPTION1; + if (index < optionsCount) { + const ZoneOption * option = &options[index]; + ZoneOptionValue * value = theme->getOptionValue(index); + editZoneOption(y, option, value, attr, EE_GENERAL, event); + } + else if (index == optionsCount) { + lcdDrawText(MENUS_MARGIN_LEFT, y, "Top bar"); + drawButton(SCREENS_SETUP_2ND_COLUMN, y, "Setup", attr); + if (attr && event == EVT_KEY_BREAK(KEY_ENTER)) { + currentScreen = customScreens[0]; + currentContainer = topbar; + pushMenu(menuWidgetsSetup); + } } break; - + } } } return true; } -enum menuScreenSetup { +enum MenuScreenSetupItems { ITEM_SCREEN_SETUP_LAYOUT, ITEM_SCREEN_SETUP_WIDGETS_SETUP = ITEM_SCREEN_SETUP_LAYOUT+2, ITEM_SCREEN_SETUP_LAYOUT_OPTION1, @@ -433,16 +480,20 @@ bool menuScreenSetup(int index, evt_t event) currentContainer = currentScreen; bool needsOffsetCheck = (menuVerticalPosition != 0 || menuHorizontalPosition < 0); - linesCount = ITEM_SCREEN_SETUP_LAYOUT_OPTION1; const ZoneOption * options = currentScreen->getFactory()->getOptions(); - for (const ZoneOption * option = options; option->name; option++) { - linesCount++; - } + int optionsCount = getOptionsCount(options); + linesCount = ITEM_SCREEN_SETUP_LAYOUT_OPTION1 + optionsCount; char title[] = "Main view X"; title[sizeof(title)-2] = '1' + index; menuPageCount = updateMainviewsMenu(); - MENU_WITH_OPTIONS(title, LBM_SCREENS_SETUP_ICONS, menuTabScreensSetup, menuPageCount, index+1, linesCount, { uint8_t(NAVIGATION_LINE_BY_LINE|uint8_t(countRegisteredLayouts-1)), ORPHAN_ROW, 0, 0, 0, 0 }); + + uint8_t mstate_tab[2 + MAX_LAYOUT_OPTIONS] = { uint8_t(NAVIGATION_LINE_BY_LINE|uint8_t(countRegisteredLayouts-1)), ORPHAN_ROW }; + for (int i=0; igetOptionValue(index); editZoneOption(y, option, value, attr, EE_MODEL, event); } break; + } } } diff --git a/radio/src/gui/horus/splash.cpp b/radio/src/gui/horus/splash.cpp index 94164d7b4..1bebd12fd 100644 --- a/radio/src/gui/horus/splash.cpp +++ b/radio/src/gui/horus/splash.cpp @@ -18,18 +18,14 @@ * GNU General Public License for more details. */ -#include "../../opentx.h" - -const uint8_t LBM_SPLASH[] __ALIGNED = { +#include "opentx.h" #include "bmp_splash.lbm" -}; #if defined(SPLASH) void drawSplash() { - lcdClear(); - - lcdDrawBitmap((LCD_W-256)/2, (LCD_H-256)/2, LBM_SPLASH); + lcd->clear(); + lcd->drawBitmap((LCD_W-BMP_SPLASH.getWidth())/2, (LCD_H-BMP_SPLASH.getHeight())/2, &BMP_SPLASH); #if MENUS_LOCK == 1 if (readonly == false) { diff --git a/radio/src/gui/horus/theme.cpp b/radio/src/gui/horus/theme.cpp index b503c7226..c9b926343 100644 --- a/radio/src/gui/horus/theme.cpp +++ b/radio/src/gui/horus/theme.cpp @@ -37,9 +37,29 @@ ZoneOptionValue * Theme::getOptionValue(unsigned int index) const return &g_eeGeneral.themeData.options[index]; } -void Theme::drawThumb(uint16_t x, uint16_t y, uint32_t flags) const +const char * Theme::getFilePath(const char * filename) const { - lcdDrawBitmap(x, y, bitmap); + static char path[_MAX_LFN+1] = THEMES_PATH "/"; + strcpy(path + sizeof(THEMES_PATH), getName()); + int len = sizeof(THEMES_PATH) + strlen(path + sizeof(THEMES_PATH)); + path[len] = '/'; + strcpy(path+len+1, filename); + return path; +} + +void Theme::drawThumb(uint16_t x, uint16_t y, uint32_t flags) +{ + #define THUMB_WIDTH 51 + #define THUMB_HEIGHT 31 + if (!thumb) { + thumb = BitmapBuffer::load(getFilePath("thumb.bmp")); + } + if (thumb) { + lcd->drawBitmap(x, y, thumb); + } + if (flags == LINE_COLOR) { + lcdDrawFilledRect(x, y, THUMB_WIDTH, THUMB_HEIGHT, SOLID, OVERLAY_COLOR | OPACITY(10)); + } } void Theme::drawBackground() const @@ -47,6 +67,13 @@ void Theme::drawBackground() const lcdDrawSolidFilledRect(0, 0, LCD_W, LCD_H, TEXT_BGCOLOR); } +void Theme::drawAboutBackground() const +{ + drawBackground(); +} + +#include "alpha_asterisk.lbm" + void Theme::drawMessageBox(const char * title, const char * text, const char * action, uint32_t flags) const { //if (flags & MESSAGEBOX_TYPE_ALERT) { @@ -55,7 +82,10 @@ void Theme::drawMessageBox(const char * title, const char * text, const char * a //} if ((flags & MESSAGEBOX_TYPE_ALERT) || (flags & MESSAGEBOX_TYPE_WARNING)) { - lcdDrawAlphaBitmap(POPUP_X-80, POPUP_Y+12, LBM_ASTERISK); + lcd->drawAlphaBitmap(POPUP_X-80, POPUP_Y+12, &ALPHA_ASTERISK); + } + else { + lcd->drawAlphaBitmap(POPUP_X-80, POPUP_Y+12, &ALPHA_ASTERISK); } #if defined(TRANSLATIONS_FR) || defined(TRANSLATIONS_IT) || defined(TRANSLATIONS_CZ) @@ -79,9 +109,9 @@ void Theme::drawMessageBox(const char * title, const char * text, const char * a } } -const Theme * registeredThemes[MAX_REGISTERED_THEMES]; // TODO dynamic +Theme * registeredThemes[MAX_REGISTERED_THEMES]; // TODO dynamic unsigned int countRegisteredThemes = 0; -void registerTheme(const Theme * theme) +void registerTheme(Theme * theme) { if (countRegisteredThemes < MAX_REGISTERED_THEMES) { TRACE("register theme %s", theme->getName()); @@ -89,10 +119,10 @@ void registerTheme(const Theme * theme) } } -const Theme * getTheme(const char * name) +Theme * getTheme(const char * name) { for (unsigned int i=0; igetName())) { return theme; } @@ -100,7 +130,7 @@ const Theme * getTheme(const char * name) return NULL; } -void loadTheme(const Theme * new_theme) +void loadTheme(Theme * new_theme) { TRACE("load theme %s", new_theme->getName()); theme = new_theme; @@ -112,7 +142,7 @@ void loadTheme() char name[sizeof(g_eeGeneral.themeName)+1]; memset(name, 0, sizeof(name)); strncpy(name, g_eeGeneral.themeName, sizeof(g_eeGeneral.themeName)); - const Theme * new_theme = getTheme(name); + Theme * new_theme = getTheme(name); if (new_theme) { loadTheme(new_theme); } diff --git a/radio/src/gui/horus/theme.h b/radio/src/gui/horus/theme.h index cc50a4ad1..f80ea8960 100644 --- a/radio/src/gui/horus/theme.h +++ b/radio/src/gui/horus/theme.h @@ -21,10 +21,12 @@ #ifndef _THEME_H_ #define _THEME_H_ +#include "bitmapbuffer.h" + #define MAX_THEME_OPTIONS 5 class Theme; -void registerTheme(const Theme * theme); +void registerTheme(Theme * theme); #define MESSAGEBOX_TYPE_INFO 0 #define MESSAGEBOX_TYPE_QUESTION 1 @@ -38,10 +40,10 @@ class Theme ZoneOptionValue options[MAX_THEME_OPTIONS]; }; - Theme(const char * name, const uint8_t * bitmap, const ZoneOption * options=NULL): + Theme(const char * name, const ZoneOption * options=NULL): name(name), - bitmap(bitmap), - options(options) + options(options), + thumb(NULL) { registerTheme(this); } @@ -51,6 +53,10 @@ class Theme return name; } + const char * getFilePath(const char * filename) const; + + void drawThumb(uint16_t x, uint16_t y, uint32_t flags); + inline const ZoneOption * getOptions() const { return options; @@ -60,31 +66,36 @@ class Theme ZoneOptionValue * getOptionValue(unsigned int index) const; - virtual void drawThumb(uint16_t x, uint16_t y, uint32_t flags) const; - virtual void load() const = 0; virtual void drawBackground() const; + virtual void drawAboutBackground() const; + virtual void drawTopbarBackground(const uint8_t * icon) const = 0; virtual void drawMessageBox(const char * title, const char * text, const char * action, uint32_t flags) const; protected: const char * name; - const uint8_t * bitmap; const ZoneOption * options; + BitmapBuffer * thumb; }; -extern const Theme * theme; +extern Theme * theme; + +inline const char * getThemePath(const char * filename) +{ + return theme->getFilePath(filename); +} #define MAX_REGISTERED_THEMES 10 extern unsigned int countRegisteredThemes; -void registerTheme(const Theme * theme); -extern const Theme * registeredThemes[MAX_REGISTERED_THEMES]; // TODO dynamic +void registerTheme(Theme * theme); +extern Theme * registeredThemes[MAX_REGISTERED_THEMES]; // TODO dynamic -const Theme * getTheme(const char * name); -void loadTheme(const Theme * theme); +Theme * getTheme(const char * name); +void loadTheme(Theme * theme); void loadTheme(); #endif // _THEME_H_ diff --git a/radio/src/gui/horus/themes/bmp_darkblue.png b/radio/src/gui/horus/themes/bmp_darkblue.png deleted file mode 100644 index 108b4c007e8c8ddef327b2771e1437001a928627..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1184 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 diff --git a/radio/src/gui/horus/themes/darkblue.cpp b/radio/src/gui/horus/themes/darkblue.cpp index 6c4e8e200..c47ff7bb6 100644 --- a/radio/src/gui/horus/themes/darkblue.cpp +++ b/radio/src/gui/horus/themes/darkblue.cpp @@ -20,19 +20,11 @@ #include "opentx.h" -const uint8_t LBM_TOPMENU_BMP_OPENTX[] __ALIGNED = { -#include "bmp_topmenu_opentx.lbm" -}; - -const uint8_t LBM_THEME_DARKBLUE[] __ALIGNED = { -#include "bmp_darkblue.lbm" -}; - class DarkblueTheme: public Theme { public: DarkblueTheme(): - Theme("Darkblue", LBM_THEME_DARKBLUE) + Theme("Darkblue") { } @@ -74,11 +66,12 @@ class DarkblueTheme: public Theme lcdDrawBitmapPattern(5, 7, icon, MENU_TITLE_COLOR); } else { - lcdDrawBitmap(5, 7, LBM_TOPMENU_BMP_OPENTX); + static BitmapBuffer * thumb = BitmapBuffer::load(getFilePath("topmenu_opentx.bmp")); + lcd->drawBitmap(5, 7, thumb); } drawTopbarDatetime(); } }; -DarkblueTheme darkblueTheme; +const DarkblueTheme darkblueTheme; diff --git a/radio/src/gui/horus/themes/default.cpp b/radio/src/gui/horus/themes/default.cpp index 3918e8840..eab0edd47 100644 --- a/radio/src/gui/horus/themes/default.cpp +++ b/radio/src/gui/horus/themes/default.cpp @@ -24,17 +24,7 @@ const uint8_t LBM_TOPMENU_MASK_OPENTX[] = { #include "mask_topmenu_opentx.lbm" }; -const uint8_t LBM_MAINVIEW_BACKGROUND[] __ALIGNED = { -#include "bmp_background.lbm" -}; - -const uint8_t LBM_THEME_DEFAULT[] __ALIGNED = { -#include "bmp_default.lbm" -}; - const ZoneOption OPTIONS_THEME_DEFAULT[] = { - { "Default background", ZoneOption::Bool, { .boolValue = 1 } }, - { "Background file", ZoneOption::File, { .stringValue = "\0\0\0\0\0\0\0" } }, { "Background color", ZoneOption::Color, { .unsignedValue = WHITE } }, { NULL, ZoneOption::Bool } }; @@ -43,7 +33,7 @@ class DefaultTheme: public Theme { public: DefaultTheme(): - Theme("Default", LBM_THEME_DEFAULT, OPTIONS_THEME_DEFAULT) + Theme("Default", OPTIONS_THEME_DEFAULT) { } @@ -78,15 +68,24 @@ class DefaultTheme: public Theme virtual void drawBackground() const { - if (g_eeGeneral.themeData.options[0].boolValue) { - lcdDrawBitmap(0, 0, LBM_MAINVIEW_BACKGROUND); + static BitmapBuffer * backgroundBitmap = BitmapBuffer::load(getThemePath("mainbg.bmp")); + if (backgroundBitmap) { + lcd->drawBitmap(0, 0, backgroundBitmap); } else { - lcdSetColor(g_eeGeneral.themeData.options[2].unsignedValue); + lcdSetColor(g_eeGeneral.themeData.options[0].unsignedValue); lcdDrawSolidFilledRect(0, 0, LCD_W, LCD_H, CUSTOM_COLOR); } } + virtual void drawAboutBackground() const + { + static BitmapBuffer * backgroundBitmap = BitmapBuffer::load(getThemePath("aboutbg.bmp")); + if (backgroundBitmap) { + lcd->drawBitmap(0, 0, backgroundBitmap); + } + } + virtual void drawTopbarBackground(const uint8_t * icon) const { lcdDrawSolidFilledRect(0, 0, LCD_W, MENU_HEADER_HEIGHT, HEADER_BGCOLOR); @@ -103,5 +102,5 @@ class DefaultTheme: public Theme } }; -const DefaultTheme defaultTheme; -const Theme * theme = &defaultTheme; +DefaultTheme defaultTheme; +Theme * theme = &defaultTheme; diff --git a/radio/src/gui/horus/view_about.cpp b/radio/src/gui/horus/view_about.cpp index ae6e86a1d..1fc350b89 100644 --- a/radio/src/gui/horus/view_about.cpp +++ b/radio/src/gui/horus/view_about.cpp @@ -62,7 +62,8 @@ bool menuAboutView(evt_t event) break; } - drawScreenTemplate("About", NULL); + theme->drawAboutBackground(); + theme->drawTopbarBackground(NULL); uint8_t screenDuration = 150; diff --git a/radio/src/gui/horus/view_main.cpp b/radio/src/gui/horus/view_main.cpp index 6dcf2346d..7ed03682c 100644 --- a/radio/src/gui/horus/view_main.cpp +++ b/radio/src/gui/horus/view_main.cpp @@ -18,7 +18,7 @@ * GNU General Public License for more details. */ -#include "../../opentx.h" +#include "opentx.h" #define TRIM_LH_X 10 #define TRIM_LV_X 24 @@ -80,14 +80,6 @@ void drawTrims(uint8_t flightMode) } } -bool isViewAvailable(int index) -{ - if (index <= VIEW_CHANNELS) - return true; - else - return TELEMETRY_SCREEN_TYPE(index-VIEW_TELEM1) != TELEMETRY_SCREEN_TYPE_NONE; -} - void onMainViewMenu(const char *result) { if (result == STR_MODEL_SELECT) { @@ -129,6 +121,16 @@ void onMainViewMenu(const char *result) } } +int getMainViewsCount() +{ + for (int index=1; indexrefresh(); + if (customScreens[g_eeGeneral.view]) { + customScreens[g_eeGeneral.view]->refresh(); } return true; diff --git a/radio/src/gui/horus/view_statistics.cpp b/radio/src/gui/horus/view_statistics.cpp index a3b475e90..32fad0900 100644 --- a/radio/src/gui/horus/view_statistics.cpp +++ b/radio/src/gui/horus/view_statistics.cpp @@ -2,7 +2,7 @@ * Copyright (C) OpenTX * * Based on code named - * th9x - http://code.google.com/p/th9x + * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * @@ -140,7 +140,7 @@ bool menuStatisticsDebug(evt_t event) lcdDrawText(MENU_DEBUG_COL1_OFS+120, MENU_DEBUG_Y_RTOS+1, "[Audio]", HEADER_COLOR|SMLSIZE); lcdDrawNumber(MENU_DEBUG_COL1_OFS+150, MENU_DEBUG_Y_RTOS, audioStack.available(), LEFT); - lcd_putsCenter(7*FH+1, STR_MENUTORESET); + // TODO lcd_putsCenter(7*FH+1, STR_MENUTORESET); // lcdInvertLastLine(); return true; diff --git a/radio/src/gui/horus/widget.h b/radio/src/gui/horus/widget.h index da75647ce..509743581 100644 --- a/radio/src/gui/horus/widget.h +++ b/radio/src/gui/horus/widget.h @@ -22,6 +22,7 @@ #define _WIDGET_H_ #include +#include #define MAX_WIDGET_OPTIONS 5 diff --git a/radio/src/gui/horus/widgets.cpp b/radio/src/gui/horus/widgets.cpp index 94e2c0d3a..813cb78d2 100644 --- a/radio/src/gui/horus/widgets.cpp +++ b/radio/src/gui/horus/widgets.cpp @@ -31,7 +31,7 @@ const char * const STR_MONTHS[] = { "Jan", "Fev", "Mar", "Apr", "May", "Jun", "J #define DATETIME_SEPARATOR_X 425 #define DATETIME_LINE1 9 #define DATETIME_LINE2 23 -#define DATETIME_LEFT(s) (LCD_W+DATETIME_SEPARATOR_X+8-getTextWidth(s, SMLSIZE))/2 +#define DATETIME_MIDDLE (LCD_W+DATETIME_SEPARATOR_X+8)/2 void drawTopbarDatetime() { @@ -41,19 +41,24 @@ void drawTopbarDatetime() gettime(&t); char str[10]; sprintf(str, "%d %s", t.tm_mday, STR_MONTHS[t.tm_mon]); - lcdDrawText(DATETIME_LEFT(str), DATETIME_LINE1, str, SMLSIZE|TEXT_INVERTED_COLOR); + lcdDrawText(DATETIME_MIDDLE, DATETIME_LINE1, str, SMLSIZE|TEXT_INVERTED_COLOR|CENTERED); getTimerString(str, getValue(MIXSRC_TX_TIME)); - lcdDrawText(DATETIME_LEFT(str), DATETIME_LINE2, str, SMLSIZE|TEXT_INVERTED_COLOR); + lcdDrawText(DATETIME_MIDDLE, DATETIME_LINE2, str, SMLSIZE|TEXT_INVERTED_COLOR|CENTERED); } +#include "alpha_stick_background.lbm" +#include "alpha_stick_pointer.lbm" #define STICK_PANEL_WIDTH 68 void drawStick(coord_t x, coord_t y, int16_t xval, int16_t yval) { - lcdDrawAlphaBitmap(x, y, LBM_STICK_BACKGROUND); - lcdDrawAlphaBitmap(x + 2 + STICK_PANEL_WIDTH/2 + STICK_PANEL_WIDTH/2 * xval/RESX, y + 2 + STICK_PANEL_WIDTH/2 - STICK_PANEL_WIDTH/2 * yval/RESX, LBM_STICK_POINTER); + lcd->drawAlphaBitmap(x, y, &ALPHA_STICK_BACKGROUND); + lcd->drawAlphaBitmap(x + 2 + STICK_PANEL_WIDTH/2 + STICK_PANEL_WIDTH/2 * xval/RESX, y + 2 + STICK_PANEL_WIDTH/2 - STICK_PANEL_WIDTH/2 * yval/RESX, &ALPHA_STICK_POINTER); } +#include "alpha_button_on.lbm" +#include "alpha_button_off.lbm" + void drawButton(coord_t x, coord_t y, const char * label, LcdFlags attr) { int width = getTextWidth(label, 0, attr); @@ -70,9 +75,9 @@ void drawButton(coord_t x, coord_t y, const char * label, LcdFlags attr) lcdDrawText(x+padding+8, y, label, TEXT_COLOR); } if (attr & BUTTON_OFF) - lcdDrawAlphaBitmap(x-6, y+3, LBM_BUTTON_OFF); + lcd->drawAlphaBitmap(x-6, y+3, &ALPHA_BUTTON_OFF); else if (attr & BUTTON_ON) - lcdDrawAlphaBitmap(x-6, y+3, LBM_BUTTON_ON); + lcd->drawAlphaBitmap(x-6, y+3, &ALPHA_BUTTON_ON); } void drawCheckBox(coord_t x, coord_t y, uint8_t value, LcdFlags attr) @@ -383,11 +388,6 @@ int16_t editGVarFieldValue(coord_t x, coord_t y, int16_t value, int16_t min, int } if (GV_IS_GV_VALUE(value, min, max)) { - if (attr & LEFT) - attr -= LEFT; /* because of ZCHAR */ - else - x -= 20; - attr &= ~PREC1; int8_t idx = (int16_t) GV_INDEX_CALC_DELTA(value, delta); @@ -421,25 +421,26 @@ int16_t editGVarFieldValue(coord_t x, coord_t y, int16_t value, int16_t min, int } #endif -#define SLEEP_BITMAP_WIDTH 150 -#define SLEEP_BITMAP_HEIGHT 150 void drawSleepBitmap() { - lcdClear(); - lcdDrawBitmap((LCD_W-SLEEP_BITMAP_WIDTH)/2, (LCD_H-SLEEP_BITMAP_HEIGHT)/2, LBM_SLEEP); + lcd->clear(); + const BitmapBuffer * bitmap = BitmapBuffer::load(getThemePath("sleep.bmp")); + if (bitmap) { + lcd->drawBitmap((LCD_W-bitmap->getWidth())/2, (LCD_H-bitmap->getHeight())/2, bitmap); + } lcdRefresh(); } -#define SHUTDOWN_BITMAP_WIDTH 110 -#define SHUTDOWN_BITMAP_HEIGHT 110 +#include "alpha_shutdown.lbm" + #define SHUTDOWN_CIRCLE_DIAMETER 150 void drawShutdownBitmap(uint32_t index) { static uint32_t last_index = 0xffffffff; if (index < last_index) { - lcdDrawBlackOverlay(); - lcdDrawAlphaBitmap((LCD_W-SHUTDOWN_BITMAP_WIDTH)/2, (LCD_H-SHUTDOWN_BITMAP_HEIGHT)/2, LBM_SHUTDOWN); + theme->drawBackground(); + lcd->drawAlphaBitmap((LCD_W-ALPHA_SHUTDOWN.getWidth())/2, (LCD_H-ALPHA_SHUTDOWN.getHeight())/2, &ALPHA_SHUTDOWN); lcdStoreBackupBuffer(); } else { diff --git a/radio/src/gui/horus/widgets/gauge.cpp b/radio/src/gui/horus/widgets/gauge.cpp index d33896c20..000243c4f 100644 --- a/radio/src/gui/horus/widgets/gauge.cpp +++ b/radio/src/gui/horus/widgets/gauge.cpp @@ -64,8 +64,8 @@ void GaugeWidget::refresh() // Gauge lcdSetColor(color); lcdDrawSolidFilledRect(zone.x, zone.y + 16, zone.w, 16, TEXT_INVERTED_COLOR); - lcdDrawNumber((percent >= 100 ? 20 : (percent >= 10 ? 10 : 0)) + zone.x+zone.w/2, zone.y + 17, percent, SMLSIZE | CUSTOM_COLOR, 0, NULL, "%"); - lcdInvertRect(zone.x + w, zone.y + 16, zone.w - w, 16, CUSTOM_COLOR); + lcdDrawNumber(zone.x+zone.w/2, zone.y + 17, percent, SMLSIZE | CUSTOM_COLOR | CENTERED, 0, NULL, "%"); + lcd->invertRect(zone.x + w, zone.y + 16, zone.w - w, 16, CUSTOM_COLOR); } BaseWidgetFactory gaugeWidget("Gauge", GaugeWidget::options); diff --git a/radio/src/gui/horus/widgets/modelbmp.cpp b/radio/src/gui/horus/widgets/modelbmp.cpp index cc8226058..02e2dc199 100644 --- a/radio/src/gui/horus/widgets/modelbmp.cpp +++ b/radio/src/gui/horus/widgets/modelbmp.cpp @@ -25,57 +25,60 @@ class ModelBitmapWidget: public Widget public: ModelBitmapWidget(const WidgetFactory * factory, const Zone & zone, Widget::PersistentData * persistentData): Widget(factory, zone, persistentData), - bitmap(NULL) + buffer(NULL) { - memset(bitmapFilename, 0, sizeof(bitmapFilename)); - } - - void loadBitmap() - { - char filename[] = BITMAPS_PATH "/xxxxxxxxxx.bmp"; - strncpy(filename+sizeof(BITMAPS_PATH), g_model.header.bitmap, sizeof(g_model.header.bitmap)); - strcat(filename+sizeof(BITMAPS_PATH), BITMAPS_EXT); - bitmap = bmpLoad(filename); - memcpy(bitmapFilename, g_model.header.bitmap, sizeof(g_model.header.bitmap)); - // TODO rescale the bitmap here instead of every refresh! + memset(bitmapFilename, 255, sizeof(bitmapFilename)); } virtual ~ModelBitmapWidget() { - free(bitmap); + delete buffer; } - virtual void refresh(); +#define DRAW_SCALED_BITMAP_FIT_WIDTH 1 +#define DRAW_SCALED_BITMAP_FIT_HEIGHT 2 + + void refreshBuffer() + { + delete buffer; + buffer = new BitmapBuffer(zone.w, zone.h); + if (buffer) { + buffer->drawBitmap(0, 0, lcd, zone.x, zone.y, zone.w, zone.h); + GET_FILENAME(filename, BITMAPS_PATH, g_model.header.bitmap, BITMAPS_EXT); + BitmapBuffer * bitmap = BitmapBuffer::load(filename); + if (zone.h >= 96 && zone.w >= 120) { + buffer->drawFilledRect(0, 0, zone.w, zone.h, SOLID, MAINVIEW_PANES_COLOR | OPACITY(5)); + buffer->drawBitmapPattern(6, 4, LBM_MODEL_ICON, MAINVIEW_GRAPHICS_COLOR); + buffer->drawSizedText(45, 10, g_model.header.name, LEN_MODEL_NAME, ZCHAR | SMLSIZE); + buffer->drawSolidFilledRect(39, 27, zone.w - 48, 2, MAINVIEW_GRAPHICS_COLOR); + if (bitmap) { + buffer->drawScaledBitmap(bitmap, 0, 38, zone.w, zone.h - 38); + } + } + else { + if (bitmap) { + buffer->drawScaledBitmap(bitmap, 0, 0, zone.w, zone.h); + } + } + delete bitmap; + } + } + + virtual void refresh() + { + if (memcmp(bitmapFilename, g_model.header.bitmap, sizeof(g_model.header.bitmap)) != 0) { + refreshBuffer(); + memcpy(bitmapFilename, g_model.header.bitmap, sizeof(g_model.header.bitmap)); + } + + if (buffer) { + lcd->drawBitmap(zone.x, zone.y, buffer); + } + } protected: char bitmapFilename[sizeof(g_model.header.bitmap)]; - uint8_t * bitmap; + BitmapBuffer * buffer; }; -void ModelBitmapWidget::refresh() -{ - if (memcmp(bitmapFilename, g_model.header.bitmap, sizeof(g_model.header.bitmap)) != 0) { - loadBitmap(); - } - - if (zone.h >= 96) { - lcdDrawFilledRect(zone.x, zone.y, zone.w, zone.h, SOLID, MAINVIEW_PANES_COLOR | OPACITY(5)); - lcdDrawBitmapPattern(zone.x + 6, zone.y + 4, LBM_MODEL_ICON, MAINVIEW_GRAPHICS_COLOR); - lcdDrawSizedText(zone.x + 45, zone.y + 10, g_model.header.name, LEN_MODEL_NAME, ZCHAR | SMLSIZE); - lcdDrawSolidFilledRect(zone.x + 39, zone.y + 27, zone.w - 48, 2, MAINVIEW_GRAPHICS_COLOR); - if (bitmap) { - float scale = getBitmapScale(bitmap, zone.w, zone.h - 25); - int width = getBitmapScaledSize(getBitmapWidth(bitmap), scale); - int height = getBitmapScaledSize(getBitmapHeight(bitmap), scale); - lcdDrawBitmap(zone.x + (zone.w - width) / 2, zone.y + zone.h - height / 2 - height / 2, bitmap, 0, 0, scale); - } - } - else if (bitmap) { - float scale = getBitmapScale(bitmap, 1000, zone.h); - int width = getBitmapScaledSize(getBitmapWidth(bitmap), scale); - int height = getBitmapScaledSize(getBitmapHeight(bitmap), scale); - lcdDrawBitmap(zone.x + (zone.w - width) / 2, zone.y + (zone.h - height) / 2, bitmap, 0, 0, scale); - } -} - BaseWidgetFactory modelBitmapWidget("ModelBmp", NULL); diff --git a/radio/src/gui/taranis/bmp.cpp b/radio/src/gui/taranis/bmp.cpp new file mode 100644 index 000000000..96e4d5ff0 --- /dev/null +++ b/radio/src/gui/taranis/bmp.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (C) OpenTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "opentx.h" + +uint8_t * lcdLoadBitmap(uint8_t * bmp, const char * filename, uint16_t width, uint16_t height) +{ + FIL bmpFile; + UINT read; + uint8_t palette[16]; + uint8_t bmpBuf[LCD_W]; /* maximum with LCD_W */ + uint8_t * buf = &bmpBuf[0]; + + if (width > LCD_W) { + return NULL; + } + + FRESULT result = f_open(&bmpFile, filename, FA_OPEN_EXISTING | FA_READ); + if (result != FR_OK) { + return NULL; + } + + if (f_size(&bmpFile) < 14) { + f_close(&bmpFile); + return NULL; + } + + result = f_read(&bmpFile, buf, 14, &read); + if (result != FR_OK || read != 14) { + f_close(&bmpFile); + return NULL; + } + + if (buf[0] != 'B' || buf[1] != 'M') { + f_close(&bmpFile); + return NULL; + } + + uint32_t fsize = *((uint32_t *)&buf[2]); + uint32_t hsize = *((uint32_t *)&buf[10]); /* header size */ + + uint32_t len = limit((uint32_t)4, (uint32_t)(hsize-14), (uint32_t)32); + result = f_read(&bmpFile, buf, len, &read); + if (result != FR_OK || read != len) { + f_close(&bmpFile); + return NULL; + } + + uint32_t ihsize = *((uint32_t *)&buf[0]); /* more header size */ + + /* invalid header size */ + if (ihsize + 14 > hsize) { + f_close(&bmpFile); + return NULL; + } + + /* sometimes file size is set to some headers size, set a real size in that case */ + if (fsize == 14 || fsize == ihsize + 14) + fsize = f_size(&bmpFile) - 2; + + /* declared file size less than header size */ + if (fsize <= hsize) { + f_close(&bmpFile); + return NULL; + } + + uint32_t w, h; + + switch (ihsize){ + case 40: // windib + case 56: // windib v3 + case 64: // OS/2 v2 + case 108: // windib v4 + case 124: // windib v5 + w = *((uint32_t *)&buf[4]); + h = *((uint32_t *)&buf[8]); + buf += 12; + break; + case 12: // OS/2 v1 + w = *((uint16_t *)&buf[4]); + h = *((uint16_t *)&buf[6]); + buf += 8; + break; + default: + f_close(&bmpFile); + return NULL; + } + + if (*((uint16_t *)&buf[0]) != 1) { /* planes */ + f_close(&bmpFile); + return NULL; + } + + if (w > width || h > height) { + f_close(&bmpFile); + return NULL; + } + + uint16_t depth = *((uint16_t *)&buf[2]); + + buf = &bmpBuf[0]; + + if (depth == 4) { + if (f_lseek(&bmpFile, hsize-64) != FR_OK || f_read(&bmpFile, buf, 64, &read) != FR_OK || read != 64) { + f_close(&bmpFile); + return NULL; + } + for (uint8_t i=0; i<16; i++) { + palette[i] = buf[4*i] >> 4; + } + } + else { + if (f_lseek(&bmpFile, hsize) != FR_OK) { + f_close(&bmpFile); + return NULL; + } + } + + uint8_t * dest = bmp; + + *dest++ = w; + *dest++ = h; + + memset(dest, 0, BITMAP_BUFFER_SIZE(w, h) - 2); + + uint32_t rowSize; + + switch (depth) { + case 1: + rowSize = ((w+31)/32)*4; + for (uint32_t i=0; i=0; i--) { + result = f_read(&bmpFile, buf, rowSize, &read); + if (result != FR_OK || read != rowSize) { + f_close(&bmpFile); + return NULL; + } + uint8_t * dst = dest + (i/2)*w; + for (uint32_t j=0; j> ((j & 1) ? 0 : 4)) & 0x0F; + uint8_t val = palette[index] << ((i & 1) ? 4 : 0); + *dst++ |= val ^ ((i & 1) ? 0xF0 : 0x0F); + } + } + break; + + default: + f_close(&bmpFile); + return NULL; + } + + f_close(&bmpFile); + return bmp; +} + +const uint8_t bmpHeader[] = { + 0x42, 0x4d, 0xF8, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 212, 0x00, 0x00, 0x00, 64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0xbc, 0x38, 0x00, 0x00, 0xbc, 0x38, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xee, 0xee, 0xee, 0x00, 0xdd, 0xdd, + 0xdd, 0x00, 0xcc, 0xcc, 0xcc, 0x00, 0xbb, 0xbb, 0xbb, 0x00, 0xaa, 0xaa, 0xaa, 0x00, 0x99, 0x99, + 0x99, 0x00, 0x88, 0x88, 0x88, 0x00, 0x77, 0x77, 0x77, 0x00, 0x66, 0x66, 0x66, 0x00, 0x55, 0x55, + 0x55, 0x00, 0x44, 0x44, 0x44, 0x00, 0x33, 0x33, 0x33, 0x00, 0x22, 0x22, 0x22, 0x00, 0x11, 0x11, + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +inline display_t getPixel(unsigned int x, unsigned int y) +{ + if (x>=LCD_W || y>=LCD_H) + return 0; + display_t * p = &displayBuf[y / 2 * LCD_W + x]; + return (y & 1) ? (*p >> 4) : (*p & 0x0F); +} + +const char * writeScreenshot() +{ + FIL bmpFile; + UINT written; + char filename[42]; // /SCREENSHOTS/screen-2013-01-01-123540.bmp + + // check and create folder here + strcpy_P(filename, SCREENSHOTS_PATH); + const char * error = sdCheckAndCreateDirectory(filename); + if (error) { + return error; + } + + char * tmp = strAppend(&filename[sizeof(SCREENSHOTS_PATH)-1], "/screen"); + tmp = strAppendDate(tmp, true); + strcpy(tmp, BITMAPS_EXT); + + FRESULT result = f_open(&bmpFile, filename, FA_CREATE_ALWAYS | FA_WRITE); + if (result != FR_OK) { + return SDCARD_ERROR(result); + } + + result = f_write(&bmpFile, bmpHeader, sizeof(bmpHeader), &written); + if (result != FR_OK || written != sizeof(bmpHeader)) { + f_close(&bmpFile); + return SDCARD_ERROR(result); + } + + for (int y=LCD_H-1; y>=0; y-=1) { + for (int x=0; x<8*((LCD_W+7)/8); x+=2) { + uint8_t byte = getPixel(x+1, y) + (getPixel(x, y) << 4); + f_write(&bmpFile, &byte, 1, &written); + if (result != FR_OK || written != 1) { + f_close(&bmpFile); + return SDCARD_ERROR(result); + } + } + } + + f_close(&bmpFile); + + return NULL; +} diff --git a/radio/src/gui/taranis/gui.h b/radio/src/gui/taranis/gui.h index 159742fa9..f7a540617 100644 --- a/radio/src/gui/taranis/gui.h +++ b/radio/src/gui/taranis/gui.h @@ -54,7 +54,6 @@ struct MenuItem { const MenuHandlerFunc action; }; -int circularIncDec(int current, int inc, int min, int max, IsValueAvailable isValueAvailable=NULL); void drawSplash(); void drawScreenIndex(uint8_t index, uint8_t count, uint8_t attr); void drawVerticalScrollbar(coord_t x, coord_t y, coord_t h, uint16_t offset, uint16_t count, uint8_t visible); diff --git a/radio/src/gui/taranis/lcd.h b/radio/src/gui/taranis/lcd.h index 4cea7e3f7..a751dc026 100644 --- a/radio/src/gui/taranis/lcd.h +++ b/radio/src/gui/taranis/lcd.h @@ -220,7 +220,7 @@ void lcdSetContrast(); void lcdRefresh(); #endif -uint8_t * bmpLoad(uint8_t * dest, const char * filename, uint16_t width, uint16_t height); +uint8_t * lcdLoadBitmap(uint8_t * dest, const char * filename, uint16_t width, uint16_t height); const char * writeScreenshot(); #if defined(BOOT) diff --git a/radio/src/gui/taranis/menu_general_sdmanager.cpp b/radio/src/gui/taranis/menu_general_sdmanager.cpp index 1660b4394..ebc551804 100644 --- a/radio/src/gui/taranis/menu_general_sdmanager.cpp +++ b/radio/src/gui/taranis/menu_general_sdmanager.cpp @@ -458,7 +458,7 @@ void menuGeneralSdManager(evt_t _event) char * ext = getFileExtension(reusableBuffer.sdmanager.lines[index], SD_SCREEN_FILE_LENGTH+1); if (ext && !strcasecmp(ext, BITMAPS_EXT)) { if (lastPos != menuVerticalPosition) { - if (!bmpLoad(modelBitmap, reusableBuffer.sdmanager.lines[index], MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT)) { + if (!lcdLoadBitmap(modelBitmap, reusableBuffer.sdmanager.lines[index], MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT)) { memcpy(modelBitmap, logo_taranis, MODEL_BITMAP_SIZE); } } diff --git a/radio/src/lua/api_lcd.cpp b/radio/src/lua/api_lcd.cpp index 7c6a03b9e..3de14fca6 100644 --- a/radio/src/lua/api_lcd.cpp +++ b/radio/src/lua/api_lcd.cpp @@ -355,11 +355,11 @@ static int luaLcdDrawPixmap(lua_State *L) #if defined(PCBTARANIS) uint8_t bitmap[BITMAP_BUFFER_SIZE(LCD_W/2, LCD_H)]; // width max is LCD_W/2 pixels for saving stack and avoid a malloc here - if (bmpLoad(bitmap, filename, LCD_W/2, LCD_H)) { + if (lcdLoadBitmap(bitmap, filename, LCD_W/2, LCD_H)) { lcdDrawBitmap(x, y, bitmap); } #else - uint8_t * bitmap = bmpLoad(filename); + uint8_t * bitmap = lcdLoadBitmap(filename); if (bitmap) { lcdDrawBitmap(x, y, bitmap); free(bitmap); diff --git a/radio/src/lua/interface.cpp b/radio/src/lua/interface.cpp index a026ee881..58e58dcec 100644 --- a/radio/src/lua/interface.cpp +++ b/radio/src/lua/interface.cpp @@ -781,7 +781,7 @@ ZoneOption * createOptionsArray(int reference) if (reference == 0) { return NULL; } - + int count = 0; lua_rawgeti(L, LUA_REGISTRYINDEX, reference); for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { @@ -829,8 +829,8 @@ class LuaTheme: public Theme friend void luaLoadThemeCallback(); public: - LuaTheme(const char * name, const uint8_t * bitmap, int options): - Theme(name, bitmap, createOptionsArray(options)), + LuaTheme(const char * name, int options): + Theme(name, createOptionsArray(options)), loadFunction(0), drawBackgroundFunction(0), drawTopbarBackgroundFunction(0), @@ -870,7 +870,7 @@ class LuaTheme: public Theme void luaLoadThemeCallback() { - const char * name=NULL, * bitmap=NULL; + const char * name=NULL; int themeOptions=0, loadFunction=0, drawBackgroundFunction=0, drawTopbarBackgroundFunction=0; luaL_checktype(L, -1, LUA_TTABLE); @@ -880,9 +880,6 @@ void luaLoadThemeCallback() if (!strcmp(key, "name")) { name = luaL_checkstring(L, -1); } - else if (!strcmp(key, "bitmap")) { - bitmap = luaL_checkstring(L, -1); - } else if (!strcmp(key, "options")) { themeOptions = luaL_ref(L, LUA_REGISTRYINDEX); lua_pushnil(L); @@ -901,12 +898,8 @@ void luaLoadThemeCallback() } } - if (name && bitmap) { - char path[LUA_FULLPATH_MAXLEN+1]; - strcpy(path, THEMES_PATH "/"); - strcpy(path+sizeof(THEMES_PATH), bitmap); - uint8_t * bitmap = bmpLoad(path/*, 51, 31*/); // TODO rescale - LuaTheme * theme = new LuaTheme(name, bitmap, themeOptions); + if (name) { + LuaTheme * theme = new LuaTheme(name, themeOptions); theme->loadFunction = loadFunction; theme->drawBackgroundFunction = drawBackgroundFunction; theme->drawTopbarBackgroundFunction = drawTopbarBackgroundFunction; @@ -1070,21 +1063,24 @@ void luaLoadFiles(const char * directory, void (*callback)()) fno.lfsize = sizeof(lfn); strcpy(path, directory); - int pathlen = strlen(path); FRESULT res = f_opendir(&dir, path); /* Open the directory */ if (res == FR_OK) { + int pathlen = strlen(path); path[pathlen++] = '/'; for (;;) { res = f_readdir(&dir, &fno); /* Read a directory item */ if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */ fn = * fno.lfname ? fno.lfname : fno.fname; uint8_t len = strlen(fn); - // Eliminates directories / non scripts files - if (len < 5 || strcasecmp(fn+len-4, SCRIPTS_EXT) || (fno.fattrib & AM_DIR)) continue; - strcpy(&path[pathlen], fn); - luaLoadFile(path, callback); + if (len > 0 && fn[0]!='.' && (fno.fattrib & AM_DIR)) { + strcpy(&path[pathlen], fn); + strcat(&path[pathlen], "/main.lua"); + if (isFileAvailable(path)) { + luaLoadFile(path, callback); + } + } } } else { diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 0e24d59dc..c95c002b6 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -38,7 +38,7 @@ bool loadModelBitmap(char * name, uint8_t * bitmap) char lfn[] = BITMAPS_PATH "/xxxxxxxxxx.bmp"; strncpy(lfn+sizeof(BITMAPS_PATH), name, len); strcpy(lfn+sizeof(BITMAPS_PATH)+len, BITMAPS_EXT); - if (bmpLoad(bitmap, lfn, MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT)) { + if (lcdLoadBitmap(bitmap, lfn, MODEL_BITMAP_WIDTH, MODEL_BITMAP_HEIGHT)) { return true; } } @@ -2606,7 +2606,7 @@ int main(void) // lcdSetRefVolt(25); #endif -#if defined(PCBTARANIS) +#if defined(PCBTARANIS) || defined(PCBHORUS) drawSplash(); #endif diff --git a/radio/src/opentx.h b/radio/src/opentx.h index ec78f0d87..667e417f4 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -203,7 +203,11 @@ #define ROTARY_ENCODER_NAVIGATION #endif -#define __ALIGNED __attribute__((aligned(32))) +#if defined(SIMU) + #define __ALIGNED +#else + #define __ALIGNED __attribute__((aligned(32))) +#endif #if defined(SIMU) #define __DMA @@ -213,7 +217,7 @@ #define __DMA __ALIGNED #endif -#if defined(PCBHORUS) +#if defined(PCBHORUS) && !defined(SIMU) #define __SDRAM __attribute__((section(".sdram"), aligned(32))) #else #define __SDRAM __DMA diff --git a/radio/src/sdcard.h b/radio/src/sdcard.h index 9e817bd21..a19e29491 100644 --- a/radio/src/sdcard.h +++ b/radio/src/sdcard.h @@ -36,9 +36,9 @@ #define EEPROMS_PATH ROOT_PATH "EEPROMS" #define SCRIPTS_PATH ROOT_PATH "SCRIPTS" #define WIZARD_PATH SCRIPTS_PATH "/WIZARD" -#define THEMES_PATH SCRIPTS_PATH "/THEMES" -#define LAYOUTS_PATH SCRIPTS_PATH "/LAYOUTS" -#define WIDGETS_PATH SCRIPTS_PATH "/WIDGETS" +#define THEMES_PATH ROOT_PATH "THEMES" +#define LAYOUTS_PATH ROOT_PATH "LAYOUTS" +#define WIDGETS_PATH ROOT_PATH "WIDGETS" #define WIZARD_NAME "wizard.lua" #define TEMPLATES_PATH SCRIPTS_PATH "/TEMPLATES" #define SCRIPTS_MIXES_PATH SCRIPTS_PATH "/MIXES" @@ -57,6 +57,13 @@ #define EEPROM_EXT ".bin" #define SPORT_FIRMWARE_EXT ".frk" +#define GET_FILENAME(filename, path, var, ext) \ + char filename[sizeof(path) + sizeof(var) + sizeof(ext)]; \ + memcpy(filename, path, sizeof(path) - 1); \ + filename[sizeof(path) - 1] = '/'; \ + memcpy(&filename[sizeof(path)], var, sizeof(var)); \ + strcat(&filename[sizeof(path)], ext) + extern FATFS g_FATFS_Obj; extern FIL g_oLogFile; diff --git a/radio/src/syscalls.c b/radio/src/syscalls.c index 08b9d0a4e..eb5e995c8 100644 --- a/radio/src/syscalls.c +++ b/radio/src/syscalls.c @@ -147,7 +147,7 @@ extern int _getpid ( void ) return -1 ; } -void _init (void) -{ +//void _init (void) +//{ -} +//} diff --git a/radio/src/targets/horus/board_horus.h b/radio/src/targets/horus/board_horus.h index d47fa33fb..227f6194d 100644 --- a/radio/src/targets/horus/board_horus.h +++ b/radio/src/targets/horus/board_horus.h @@ -259,8 +259,8 @@ void ledBlue(void); // LCD driver void lcdInit(void); void lcdRefresh(void); -void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); -void lcdDrawBitmapDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t * bitmap); +void DMAFillRect(uint16_t * dest, uint16_t destw, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); +void DMACopyBitmap(uint16_t * dest, uint16_t destw, uint16_t x, uint16_t y, const uint16_t * src, uint16_t srcw, uint16_t h); void lcdStoreBackupBuffer(void); int lcdRestoreBackupBuffer(void); diff --git a/radio/src/targets/horus/lcd_driver.cpp b/radio/src/targets/horus/lcd_driver.cpp index 38280a756..9da6039f4 100644 --- a/radio/src/targets/horus/lcd_driver.cpp +++ b/radio/src/targets/horus/lcd_driver.cpp @@ -2,7 +2,7 @@ * Copyright (C) OpenTX * * Based on code named - * th9x - http://code.google.com/p/th9x + * th9x - http://code.google.com/p/th9x * er9x - http://code.google.com/p/er9x * gruvin9x - http://code.google.com/p/gruvin9x * @@ -41,7 +41,6 @@ uint8_t LCD_FIRST_FRAME_BUFFER[DISPLAY_BUFFER_SIZE] __SDRAM; uint8_t LCD_SECOND_FRAME_BUFFER[DISPLAY_BUFFER_SIZE] __SDRAM; uint8_t LCD_BACKUP_FRAME_BUFFER[DISPLAY_BUFFER_SIZE] __SDRAM; -uint8_t * CurrentFrameBuffer = LCD_FIRST_FRAME_BUFFER; uint32_t CurrentLayer = LCD_FIRST_LAYER; #define NRST_LOW() do { LCD_GPIO_NRST->BSRRH = LCD_GPIO_PIN_NRST; } while(0) @@ -381,6 +380,10 @@ void LCD_Init(void) LCD_ControlLight(100); } +BitmapBuffer lcdBuffer1(LCD_W, LCD_H, (uint16_t *)LCD_FIRST_FRAME_BUFFER); +BitmapBuffer lcdBuffer2(LCD_W, LCD_H, (uint16_t *)LCD_SECOND_FRAME_BUFFER); +BitmapBuffer * lcd = &lcdBuffer1; + /** * @brief Sets the LCD Layer. * @param Layerx: specifies the Layer foreground or background. @@ -389,11 +392,11 @@ void LCD_Init(void) void LCD_SetLayer(uint32_t Layerx) { if (Layerx == LCD_FIRST_LAYER) { - CurrentFrameBuffer = LCD_FIRST_FRAME_BUFFER; + lcd = &lcdBuffer1; CurrentLayer = LCD_FIRST_LAYER; } else { - CurrentFrameBuffer = LCD_SECOND_FRAME_BUFFER; + lcd = &lcdBuffer2; CurrentLayer = LCD_SECOND_LAYER; } } @@ -428,31 +431,26 @@ void lcdInit(void) LCD_SetLayer(LCD_FIRST_LAYER); // lcdClear(); LCD_SetTransparency(0); - + /* Set Foreground layer */ LCD_SetLayer(LCD_SECOND_LAYER); - lcdClear(); + lcd->clear(); LCD_SetTransparency(255); } -void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) +void DMAFillRect(uint16_t * dest, uint16_t destw, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { - uint32_t addr = (uint32_t)CurrentFrameBuffer + 2*(LCD_W*y + x); - uint8_t red = (0xF800 & color) >> 11; - uint8_t blue = 0x001F & color; - uint8_t green = (0x07E0 & color) >> 5; - DMA2D_DeInit(); DMA2D_InitTypeDef DMA2D_InitStruct; DMA2D_InitStruct.DMA2D_Mode = DMA2D_R2M; DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; - DMA2D_InitStruct.DMA2D_OutputGreen = green; - DMA2D_InitStruct.DMA2D_OutputBlue = blue; - DMA2D_InitStruct.DMA2D_OutputRed = red; + DMA2D_InitStruct.DMA2D_OutputGreen = (0x07E0 & color) >> 5; + DMA2D_InitStruct.DMA2D_OutputBlue = 0x001F & color; + DMA2D_InitStruct.DMA2D_OutputRed = (0xF800 & color) >> 11; DMA2D_InitStruct.DMA2D_OutputAlpha = 0x0F; - DMA2D_InitStruct.DMA2D_OutputMemoryAdd = addr; - DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_W - w); + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest) + 2*(destw*y + x); + DMA2D_InitStruct.DMA2D_OutputOffset = (destw - w); DMA2D_InitStruct.DMA2D_NumberOfLine = h; DMA2D_InitStruct.DMA2D_PixelPerLine = w; DMA2D_Init(&DMA2D_InitStruct); @@ -464,31 +462,26 @@ void lcdDrawSolidFilledRectDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, u while (DMA2D_GetFlagStatus(DMA2D_FLAG_TC) == RESET); } -void lcdDrawBitmapDMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t * bitmap) +void DMACopyBitmap(uint16_t * dest, uint16_t destw, uint16_t x, uint16_t y, const uint16_t * src, uint16_t srcw, uint16_t h) { - if ((uint32_t(bitmap) & 0x03) != 0) - return; - - uint32_t addr = (uint32_t)CurrentFrameBuffer + 2*(LCD_W*y + x); - DMA2D_DeInit(); DMA2D_InitTypeDef DMA2D_InitStruct; DMA2D_InitStruct.DMA2D_Mode = DMA2D_M2M; DMA2D_InitStruct.DMA2D_CMode = DMA2D_RGB565; - DMA2D_InitStruct.DMA2D_OutputMemoryAdd = addr; + DMA2D_InitStruct.DMA2D_OutputMemoryAdd = CONVERT_PTR_UINT(dest) + 2*(destw*y + x); DMA2D_InitStruct.DMA2D_OutputGreen = 0; DMA2D_InitStruct.DMA2D_OutputBlue = 0; DMA2D_InitStruct.DMA2D_OutputRed = 0; DMA2D_InitStruct.DMA2D_OutputAlpha = 0; - DMA2D_InitStruct.DMA2D_OutputOffset = (LCD_W - w); + DMA2D_InitStruct.DMA2D_OutputOffset = (destw - srcw); DMA2D_InitStruct.DMA2D_NumberOfLine = h; - DMA2D_InitStruct.DMA2D_PixelPerLine = w; + DMA2D_InitStruct.DMA2D_PixelPerLine = srcw; DMA2D_Init(&DMA2D_InitStruct); DMA2D_FG_InitTypeDef DMA2D_FG_InitStruct; DMA2D_FG_StructInit(&DMA2D_FG_InitStruct); - DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(bitmap); + DMA2D_FG_InitStruct.DMA2D_FGMA = CONVERT_PTR_UINT(src); DMA2D_FG_InitStruct.DMA2D_FGO = 0; DMA2D_FG_InitStruct.DMA2D_FGCM = CM_RGB565; DMA2D_FG_InitStruct.DMA2D_FGPFC_ALPHA_MODE = NO_MODIF_ALPHA_VALUE; @@ -537,12 +530,12 @@ void DMAcopy(void * src, void * dest, int len) void lcdStoreBackupBuffer() { - DMAcopy(CurrentFrameBuffer, LCD_BACKUP_FRAME_BUFFER, DISPLAY_BUFFER_SIZE); + DMAcopy(lcd->data, LCD_BACKUP_FRAME_BUFFER, DISPLAY_BUFFER_SIZE); } int lcdRestoreBackupBuffer() { - DMAcopy(LCD_BACKUP_FRAME_BUFFER, CurrentFrameBuffer, DISPLAY_BUFFER_SIZE); + DMAcopy(LCD_BACKUP_FRAME_BUFFER, lcd->data, DISPLAY_BUFFER_SIZE); return 1; } diff --git a/radio/src/tests/lcd.cpp b/radio/src/tests/lcd.cpp index 703dfea57..b5ebb93c7 100644 --- a/radio/src/tests/lcd.cpp +++ b/radio/src/tests/lcd.cpp @@ -296,7 +296,7 @@ TEST(Lcd, BMPWrapping) { lcdClear(); uint8_t bitmap[2+40*40/2]; - bmpLoad(bitmap, TESTS_PATH "/tests/plane.bmp", 40, 40); + lcdLoadBitmap(bitmap, TESTS_PATH "/tests/plane.bmp", 40, 40); lcdDrawBitmap(200, 0, bitmap); lcdDrawBitmap(200, 60, bitmap); lcdDrawBitmap(240, 60, bitmap); // x too big @@ -365,31 +365,31 @@ TEST(Lcd, lcdDrawBitmapLoadAndDisplay) // Test proper BMP files, they should display correctly { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(7, 32)); - EXPECT_TRUE(bmpLoad(bitmap.buffer(), TESTS_PATH "/tests/4b_7x32.bmp", 7, 32) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/tests/4b_7x32.bmp", 7, 32) != NULL); bitmap.leakCheck(); lcdDrawBitmap(10, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(6, 32)); - EXPECT_TRUE(bmpLoad(bitmap.buffer(), TESTS_PATH "/tests/1b_6x32.bmp", 6, 32) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/tests/1b_6x32.bmp", 6, 32) != NULL); bitmap.leakCheck(); lcdDrawBitmap(20, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(31, 31)); - EXPECT_TRUE(bmpLoad(bitmap.buffer(), TESTS_PATH "/tests/4b_31x31.bmp", 31, 31) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/tests/4b_31x31.bmp", 31, 31) != NULL); bitmap.leakCheck(); lcdDrawBitmap(30, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(39, 32)); - EXPECT_TRUE(bmpLoad(bitmap.buffer(), TESTS_PATH "/tests/1b_39x32.bmp", 39, 32) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/tests/1b_39x32.bmp", 39, 32) != NULL); bitmap.leakCheck(); lcdDrawBitmap(70, 2, bitmap.buffer()); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(20, 20)); - EXPECT_TRUE(bmpLoad(bitmap.buffer(), TESTS_PATH "/tests/4b_20x20.bmp", 20, 20) != NULL); + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/tests/4b_20x20.bmp", 20, 20) != NULL); bitmap.leakCheck(); lcdDrawBitmap(120, 2, bitmap.buffer()); } @@ -398,12 +398,12 @@ TEST(Lcd, lcdDrawBitmapLoadAndDisplay) // Test various bad BMP files, they should not display { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(LCD_W+1, 32)); - EXPECT_TRUE(bmpLoad(bitmap.buffer(), "", LCD_W+1, 32) == NULL) << "to wide"; + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), "", LCD_W+1, 32) == NULL) << "to wide"; bitmap.leakCheck(); } { TestBuffer<1000> bitmap(BITMAP_BUFFER_SIZE(10, 10)); - EXPECT_TRUE(bmpLoad(bitmap.buffer(), TESTS_PATH "/tests/1b_39x32.bmp", 10, 10) == NULL) << "to small buffer"; + EXPECT_TRUE(lcdLoadBitmap(bitmap.buffer(), TESTS_PATH "/tests/1b_39x32.bmp", 10, 10) == NULL) << "to small buffer"; bitmap.leakCheck(); } } diff --git a/radio/src/translations/en.h.txt b/radio/src/translations/en.h.txt index d069b009e..e08298acb 100644 --- a/radio/src/translations/en.h.txt +++ b/radio/src/translations/en.h.txt @@ -873,10 +873,17 @@ #define TR_DELAY "Delay" #define TR_SD_CARD "SD CARD" #define TR_SDHC_CARD "SD-HC CARD" +#if defined(COLORLCD) +#define TR_NO_SOUNDS_ON_SD "No Sounds" +#define TR_NO_MODELS_ON_SD "No Models" +#define TR_NO_BITMAPS_ON_SD "No Bitmaps" +#define TR_NO_SCRIPTS_ON_SD "No Scripts" +#else #define TR_NO_SOUNDS_ON_SD "No Sounds" BREAKSPACE "on SD" #define TR_NO_MODELS_ON_SD "No Models" BREAKSPACE "on SD" #define TR_NO_BITMAPS_ON_SD "No Bitmaps" BREAKSPACE "on SD" #define TR_NO_SCRIPTS_ON_SD "No Scripts" BREAKSPACE "on SD" +#endif #define TR_SCRIPT_SYNTAX_ERROR "Script syntax error" #define TR_SCRIPT_PANIC "Script panic" #define TR_SCRIPT_KILLED "Script killed" diff --git a/radio/util/img2lbm.py b/radio/util/img2lbm.py index dd88a8eca..b23b15a6e 100755 --- a/radio/util/img2lbm.py +++ b/radio/util/img2lbm.py @@ -41,29 +41,32 @@ with open(sys.argv[2], "w") as f: f.write("0x%02x," % value) f.write("\n") elif what == "4/4/4/4": - colors = [] - f.write("%d,%d,\n" % (width, height)) + constant = sys.argv[2].upper()[:-4] + values = [] for y in range(height): for x in range(width): pixel = image.pixel(x, y) - f.write("0x%1x%1x%1x%1x," % (Qt.qAlpha(pixel) // 16, Qt.qRed(pixel) // 16, Qt.qGreen(pixel) // 16, Qt.qBlue(pixel) // 16)) - f.write("\n") + val = ((Qt.qAlpha(pixel) // 16) << 12) + ((Qt.qRed(pixel) // 16) << 8) + ((Qt.qGreen(pixel) // 16) << 4) + ((Qt.qBlue(pixel) // 16) << 0) + values.append(str(val)) + f.write("const uint16_t __%s[] __ALIGNED = { %s };\n" % (constant, ",".join(values))) + f.write("const Bitmap %s(%d, %d, __%s);\n" % (constant, width, height, constant)) elif what == "5/6/5": - colors = [] - writeSize(f, width, height) + constant = sys.argv[2].upper()[:-4] + values = [] for y in range(height): for x in range(width): pixel = image.pixel(x, y) val = ((Qt.qRed(pixel) >> 3) << 11) + ((Qt.qGreen(pixel) >> 2) << 5) + ((Qt.qBlue(pixel) >> 3) << 0) - f.write("%d,%d," % (val % 256, val // 256)) - f.write("\n") + values.append(str(val)) + f.write("const uint16_t __%s[] __ALIGNED = { %s };\n" % (constant, ",".join(values))) + f.write("const Bitmap %s(%d, %d, __%s);\n" % (constant, width, height, constant)) elif what == "5/6/5/8": colors = [] writeSize(f, width, height) for y in range(height): for x in range(width): pixel = image.pixel(x, y) - val = ((Qt.qRed(pixel) >> 3) << 11) + ((Qt.qGreen(pixel) >> 2) << 5) + ((Qt.qBlue(pixel) >> 3) << 0) + val = ((Qt.qRed(pixel) >> 4) << 12) + ((Qt.qGreen(pixel) >> 4) << 7) + ((Qt.qBlue(pixel) >> 4) << 1) f.write("%d,%d,%d," % (val % 256, val // 256, Qt.qAlpha(pixel))) f.write("\n") elif what == "4bits":