[education/blinken] /: Add android support for blinken
Albert Astals Cid
null at kde.org
Thu Oct 31 08:18:08 GMT 2024
Git commit b7794593d70b0a8ea90e8c793baa297901440060 by Albert Astals Cid, on behalf of hanyang zhang.
Committed on 31/10/2024 at 08:17.
Pushed by aacid into branch 'master'.
Add android support for blinken
use QML to draw ui, and origin blinken logic file
M +2 -0 .gitlab-ci.yml
M +11 -1 .kde-ci.yml
M +45 -13 CMakeLists.txt
M +2 -1 doc/CMakeLists.txt
M +3 -0 doc/index.docbook
M +9 -2 fonts/CMakeLists.txt
M +9 -10 icons/CMakeLists.txt
M +44 -1 images/CMakeLists.txt
A +168 -0 images/color/blue.svg
A +140 -0 images/color/blue_highlight.svg
A +145 -0 images/color/green.svg
A +164 -0 images/color/green_highlight.svg
A +188 -0 images/color/red.svg
A +188 -0 images/color/red_highlight.svg
A +165 -0 images/color/yellow.svg
A +164 -0 images/color/yellow_highlight.svg
A +100 -0 images/numbers/0.svg
A +89 -0 images/numbers/1.svg
A +100 -0 images/numbers/2.svg
A +100 -0 images/numbers/3.svg
A +84 -0 images/numbers/4.svg
A +100 -0 images/numbers/5.svg
A +104 -0 images/numbers/6.svg
A +76 -0 images/numbers/7.svg
A +108 -0 images/numbers/8.svg
A +104 -0 images/numbers/9.svg
A +45 -0 images/numbers/blackBlock.svg
A +124 -0 images/numbers/blank.svg
A +124 -0 images/numbers/full.svg
A +45 -0 images/numbers/redBlock.svg
A +149 -0 images/ui/background.svg
A +10 -0 images/ui/downArrow.svg
A +59 -0 images/ui/exit.svg
A +62 -0 images/ui/exit_highlight.svg
A +83 -0 images/ui/highScore.svg
A +83 -0 images/ui/highScore_highlight.svg
A +59 -0 images/ui/menu.svg
A +185 -0 images/ui/menu_list.svg
A +10 -0 images/ui/rightArrow.svg
M +16 -1 sounds/CMakeLists.txt
M +99 -39 src/CMakeLists.txt
A +44 -0 src/android/AndroidManifest.xml
A +- -- src/android/res/drawable-hdpi/icon.png
A +- -- src/android/res/drawable-ldpi/icon.png
A +- -- src/android/res/drawable-mdpi/icon.png
A +- -- src/android/res/drawable-xhdpi/icon.png
A +- -- src/android/res/drawable-xxhdpi/icon.png
A +- -- src/android/res/drawable-xxxhdpi/icon.png
A +8 -0 src/android/res/values/styles.xml
M +65 -64 src/blinken.cpp
M +4 -4 src/blinken.h
M +6 -6 src/blinken.kcfg
M +41 -41 src/blinkengame.cpp
M +50 -32 src/blinkengame.h
M +9 -9 src/button.cpp
M +2 -2 src/button.h
A +103 -0 src/highScoreManager.cpp [License: GPL(v2.0+)]
A +38 -0 src/highScoreManager.h [License: GPL(v2.0+)]
M +4 -92 src/highscoredialog.cpp
M +1 -21 src/highscoredialog.h
M +40 -8 src/main.cpp
A +143 -0 src/maskedmousearea.cpp [License: BSD]
A +109 -0 src/maskedmousearea.h [License: BSD]
M +3 -1 src/settings.kcfgc
M +97 -11 src/soundsplayer.cpp
M +19 -3 src/soundsplayer.h
A +323 -0 src/ui/Blinken.qml [License: GPL(v2.0+)]
A +131 -0 src/ui/BlinkenMenu.qml [License: GPL(v2.0+)]
A +37 -0 src/ui/ExitButton.qml [License: GPL(v2.0+)]
A +43 -0 src/ui/GameButton.qml [License: GPL(v2.0+)]
A +118 -0 src/ui/GameButtons.qml [License: GPL(v2.0+)]
A +218 -0 src/ui/GameOptions.qml [License: GPL(v2.0+)]
A +35 -0 src/ui/HighScoreButton.qml [License: GPL(v2.0+)]
A +95 -0 src/ui/HighScoreLists.qml [License: GPL(v2.0+)]
A +42 -0 src/ui/Numbers.qml [License: GPL(v2.0+)]
A +114 -0 src/ui/ScoreAndCounter.qml [License: GPL(v2.0+)]
A +72 -0 src/ui/ScoreList.qml [License: GPL(v2.0+)]
https://invent.kde.org/education/blinken/-/commit/b7794593d70b0a8ea90e8c793baa297901440060
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e665009..5607a73 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,3 +9,5 @@ include:
- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/flatpak.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml
+ - /gitlab-templates/android-qt6.yml
+ - /gitlab-templates/craft-android-qt6-apks.yml
diff --git a/.kde-ci.yml b/.kde-ci.yml
index 0fc85bd..4e48645 100644
--- a/.kde-ci.yml
+++ b/.kde-ci.yml
@@ -6,8 +6,18 @@ Dependencies:
'require':
'frameworks/extra-cmake-modules': '@latest-kf6'
'frameworks/ki18n': '@latest-kf6'
- 'frameworks/kxmlgui': '@latest-kf6'
'frameworks/kguiaddons': '@latest-kf6'
+
+- 'on': ['Android/Qt6']
+ 'require':
+ 'frameworks/kconfig': '@latest-kf6'
+ 'frameworks/kconfigwidgets': '@latest-kf6'
+ 'frameworks/kirigami': '@latest-kf6'
+ 'libraries/kirigami-addons': '@latest-kf6'
+
+- 'on': ['Linux/Qt6', 'FreeBSD/Qt6', 'Windows/Qt6']
+ 'require':
+ 'frameworks/kxmlgui': '@latest-kf6'
'frameworks/kdoctools': '@latest-kf6'
'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 63250a3..7706104 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,6 +8,14 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
project(blinken VERSION ${RELEASE_SERVICE_VERSION})
+option(QML_VERSION_FOR_DESKTOP "enable QML version for desktop" OFF)
+
+if(ANDROID OR QML_VERSION_FOR_DESKTOP)
+ set(QML_VERSION ON)
+else()
+ set(QML_VERSION OFF)
+endif()
+
# minimal Qt requirement
set(QT_MIN_VERSION "6.5.0")
set(KF_MIN_VERSION "6.0.0")
@@ -28,33 +36,57 @@ include(ECMSetupVersion)
include(FeatureSummary)
include(ECMDeprecationSettings)
-find_package (Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED Core Widgets Svg)
-
-find_package (KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
- CoreAddons I18n XmlGui GuiAddons DocTools DBusAddons Crash
-)
+if(ANDROID)
+ include(ECMAddAndroidApk)
+endif()
-find_package (Phonon4Qt6 REQUIRED)
+if(QML_VERSION)
+ add_compile_definitions(QML_VERSION)
+ include(ECMQmlModule)
+
+ find_package (Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS
+ Core Quick Gui Qml Multimedia
+ )
+ find_package (KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
+ CoreAddons I18n GuiAddons Kirigami
+ )
+ find_package(KF6Config ${KF_MIN_VERSION} REQUIRED)
+ find_package(KF6ConfigWidgets ${KF_MIN_VERSION} REQUIRED)
+ find_package(KF6KirigamiAddons 1.0 REQUIRED)
+
+else()
+
+ find_package (Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED
+ Core Widgets Svg Qml
+ )
+ find_package (KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
+ CoreAddons I18n XmlGui GuiAddons DocTools DBusAddons Crash
+ )
+ find_package (Phonon4Qt6 REQUIRED)
+
+ add_subdirectory( doc )
+endif()
# global include directories
include_directories (${CMAKE_CURRENT_BINARY_DIR})
ecm_set_disabled_deprecation_versions(QT 6.8.0 KF 6.5.0)
-add_subdirectory( doc )
-add_subdirectory( src )
-add_subdirectory( images )
-add_subdirectory( icons )
-add_subdirectory( sounds )
-add_subdirectory( fonts )
+
+add_subdirectory( src )
+add_subdirectory( images )
+add_subdirectory( sounds )
+add_subdirectory( icons )
+add_subdirectory( fonts )
########### install files ###############
ki18n_install(po)
+
if (KF6DocTools_FOUND)
kdoctools_install(po)
endif()
+
install( FILES README.packagers DESTINATION ${KDE_INSTALL_DATADIR}/blinken/ )
install( FILES org.kde.blinken.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} )
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
-
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
index 3ed0857..14af1dd 100644
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -1,4 +1,5 @@
########### install files ###############
#
-
+if(NOT ANDROID)
kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR blinken)
+endif()
diff --git a/doc/index.docbook b/doc/index.docbook
index 05710e2..8087008 100644
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@ -309,6 +309,9 @@ Contributors:
<para>"Steve" font: Steve Jordi
<email>steve at sjordi.com</email>
</para>
+<para>Android port: zhang hanyang
+<email>hanyangzhang at qq.com</email>
+</para>
</listitem>
</itemizedlist>
diff --git a/fonts/CMakeLists.txt b/fonts/CMakeLists.txt
index ba3edad..f683b94 100644
--- a/fonts/CMakeLists.txt
+++ b/fonts/CMakeLists.txt
@@ -1,2 +1,9 @@
-install( FILES steve.ttf DESTINATION ${KDE_INSTALL_DATADIR}/blinken/fonts )
-
+if(QML_VERSION)
+ qt_add_resources(blinken "fonts"
+ PREFIX "/"
+ FILES
+ steve.ttf
+ )
+else()
+ install( FILES steve.ttf DESTINATION ${KDE_INSTALL_DATADIR}/blinken/fonts )
+endif()
diff --git a/icons/CMakeLists.txt b/icons/CMakeLists.txt
index edc7a8f..f2e92e2 100644
--- a/icons/CMakeLists.txt
+++ b/icons/CMakeLists.txt
@@ -1,11 +1,10 @@
-ecm_install_icons( ICONS
- 128-apps-blinken.png
- 16-apps-blinken.png
- 22-apps-blinken.png
- 32-apps-blinken.png
- 48-apps-blinken.png
- 64-apps-blinken.png
- sc-apps-blinken.svgz
- DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor
+ecm_install_icons(ICONS
+ 128-apps-blinken.png
+ 16-apps-blinken.png
+ 22-apps-blinken.png
+ 32-apps-blinken.png
+ 48-apps-blinken.png
+ 64-apps-blinken.png
+ sc-apps-blinken.svgz
+ DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor
)
-
diff --git a/images/CMakeLists.txt b/images/CMakeLists.txt
index ea2325c..b74cc8c 100644
--- a/images/CMakeLists.txt
+++ b/images/CMakeLists.txt
@@ -1,2 +1,45 @@
-install( FILES blinken.svg DESTINATION ${KDE_INSTALL_DATADIR}/blinken/images )
+if(QML_VERSION)
+ set(IMAGES
+ ui/background.svg
+ ui/exit_highlight.svg
+ ui/exit.svg
+ ui/highScore_highlight.svg
+ ui/highScore.svg
+ ui/menu_list.svg
+ ui/menu.svg
+ ui/rightArrow.svg
+ ui/downArrow.svg
+ numbers/0.svg
+ numbers/1.svg
+ numbers/2.svg
+ numbers/3.svg
+ numbers/4.svg
+ numbers/5.svg
+ numbers/6.svg
+ numbers/7.svg
+ numbers/8.svg
+ numbers/9.svg
+ numbers/blackBlock.svg
+ numbers/redBlock.svg
+ numbers/blank.svg
+ numbers/full.svg
+
+ color/blue_highlight.svg
+ color/blue.svg
+ color/green_highlight.svg
+ color/green.svg
+ color/red_highlight.svg
+ color/red.svg
+ color/yellow_highlight.svg
+ color/yellow.svg
+ )
+
+ qt_add_resources(blinken "images"
+ PREFIX "/"
+ FILES
+ ${IMAGES}
+ )
+else()
+ install( FILES blinken.svg DESTINATION ${KDE_INSTALL_DATADIR}/blinken/images )
+endif()
diff --git a/images/color/blue.svg b/images/color/blue.svg
new file mode 100644
index 0000000..92b9e08
--- /dev/null
+++ b/images/color/blue.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 374.25 250"
+ xml:space="preserve"
+ sodipodi:docname="blue.svg"
+ width="374.25"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs11"><radialGradient
+ inkscape:collect="always"
+ xlink:href="#path28332_00000125561446015486516160000013323403246331414440_"
+ id="radialGradient1"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,-0.6673,-104.4855,1225.2798)"
+ cx="476.89981"
+ cy="607.03003"
+ r="186.75" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#path28314_00000060023190442832720600000004055112189393602459_"
+ id="radialGradient2"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,-0.6673,-104.4162,1225.3611)"
+ cx="609.42731"
+ cy="459.7796"
+ r="186.75" /><radialGradient
+ inkscape:collect="always"
+ xlink:href="#path27394_00000092452723319382642570000013502502858929694891_"
+ id="radialGradient3"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,-0.6743,-803.0044,1910.8429)"
+ cx="1479.2612"
+ cy="1691.2972"
+ r="381.78589" /></defs><sodipodi:namedview
+ id="namedview11"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#0453A2;}
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#path28332_00000091704882182657372600000015056983348821855904_);}
+ .st2{fill-rule:evenodd;clip-rule:evenodd;fill:url(#path28314_00000165933956259879055950000003512389886847918734_);}
+
+ .st3{fill-rule:evenodd;clip-rule:evenodd;fill:url(#path27394_00000020386733222620888540000007405472570404088469_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;}
+ .st4{opacity:0.0493;fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;enable-background:new ;}
+</style>
+<g
+ id="blue_normal"
+ transform="matrix(1.002437,0,0,0.99931661,-300.59003,-772.82554)">
+ <path
+ id="path27386"
+ class="st0"
+ d="m 300.78,774.59 c 2.03,67.31 43.48,128.35 109.94,173.02 67.03,45.06 159.39,73.27 261.56,74.2 V 973.18 C 563.93,970.46 476.43,882.95 473.72,774.59 Z" />
+
+ <radialGradient
+ id="path28332_00000125561446015486516160000013323403246331414440_"
+ cx="476.89981"
+ cy="607.03003"
+ r="186.75"
+ gradientTransform="matrix(1,0,0,-0.6673,-104.4855,1225.2798)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#5F8CC3;stop-opacity:0.9609"
+ id="stop1" />
+ <stop
+ offset="1"
+ style="stop-color:#004182;stop-opacity:0"
+ id="stop2" />
+ </radialGradient>
+
+ <path
+ id="path28332"
+ style="clip-rule:evenodd;fill:url(#radialGradient1);fill-rule:evenodd"
+ d="m 300.91,774.32 c 2.03,67.31 43.48,128.36 109.94,173.03 67.03,45.06 159.39,73.26 261.56,74.19 V 972.92 C 564.06,970.2 476.56,882.69 473.85,774.33 H 300.91 Z" />
+
+ <radialGradient
+ id="path28314_00000060023190442832720600000004055112189393602459_"
+ cx="609.42731"
+ cy="459.7796"
+ r="186.75"
+ gradientTransform="matrix(1,0,0,-0.6673,-104.4162,1225.3611)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#004182"
+ id="stop3" />
+ <stop
+ offset="1"
+ style="stop-color:#004182;stop-opacity:0"
+ id="stop4" />
+ </radialGradient>
+
+ <path
+ id="path28314"
+ style="clip-rule:evenodd;fill:url(#radialGradient2);fill-rule:evenodd"
+ d="m 300.98,774.4 c 2.03,67.31 43.48,128.36 109.94,173.03 67.03,45.06 159.39,73.26 261.56,74.19 V 973 C 564.13,970.28 476.63,882.77 473.92,774.41 H 300.98 Z" />
+
+ <radialGradient
+ id="path27394_00000092452723319382642570000013502502858929694891_"
+ cx="1479.2612"
+ cy="1691.2972"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-803.0044,1910.8429)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop5" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#D2E5F6"
+ id="stop6" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#004182;stop-opacity:0"
+ id="stop7" />
+ <stop
+ offset="0.5"
+ style="stop-color:#0453A2;stop-opacity:0.3455"
+ id="stop8" />
+ <stop
+ offset="0.782"
+ style="stop-color:#2E6CB5;stop-opacity:0.5455"
+ id="stop9" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#004182;stop-opacity:0.4818"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop11" />
+ </radialGradient>
+
+ <path
+ id="path27394"
+ style="clip-rule:evenodd;fill:url(#radialGradient3);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331"
+ d="m 300.77,774.39 c 2.03,67.31 43.48,128.36 109.94,173.03 67.03,45.06 159.39,73.26 261.56,74.19 V 972.99 C 563.92,970.27 476.42,882.76 473.71,774.4 H 300.77 Z" />
+ <path
+ id="path48021"
+ class="st4"
+ d="m 358.2,774.4 c 36.58,44.37 87.28,80.31 146.84,103.44 C 486.03,847.75 474.65,812.36 473.7,774.4 Z" />
+</g>
+</svg>
diff --git a/images/color/blue_highlight.svg b/images/color/blue_highlight.svg
new file mode 100644
index 0000000..be1ce86
--- /dev/null
+++ b/images/color/blue_highlight.svg
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 374.39001 250"
+ xml:space="preserve"
+ sodipodi:docname="blue_highlight.svg"
+ width="374.39001"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs11" /><sodipodi:namedview
+ id="namedview11"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#2C62AE;}
+
+ .st1{opacity:0.5845;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49064_00000098217621103341813630000007278829100273501864_);enable-background:new ;}
+
+ .st2{opacity:0.5;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49066_00000115500165207928487450000000296612667821403266_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new ;}
+
+ .st3{opacity:0.1761;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49068_00000046319246402669846160000015837282477196382394_);enable-background:new ;}
+</style>
+<g
+ id="blue_highlight"
+ transform="matrix(1.0020002,0,0,1.0031611,-363.47443,-1310.5561)">
+ <path
+ id="path49060"
+ class="st0"
+ d="m 363.75,1307.05 c 2.03,67.31 43.48,128.35 109.94,173.02 67.03,45.06 159.39,73.27 261.56,74.2 v -48.63 C 626.9,1502.92 539.4,1415.41 536.69,1307.05 Z" />
+
+ <radialGradient
+ id="path49064_00000056415399061775253280000000693924165062618256_"
+ cx="705.51837"
+ cy="189.58881"
+ r="186.75"
+ gradientTransform="matrix(1,0,0,-0.6673,-138.1061,1577.5394)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#004182"
+ id="stop1" />
+ <stop
+ offset="1"
+ style="stop-color:#004182;stop-opacity:0"
+ id="stop2" />
+ </radialGradient>
+
+ <path
+ id="path49064"
+ style="clip-rule:evenodd;opacity:0.5845;fill:url(#path49064_00000056415399061775253280000000693924165062618256_);fill-rule:evenodd;enable-background:new"
+ d="m 363.38,1306.86 c 2.03,67.31 43.48,128.36 109.94,173.03 67.03,45.06 159.39,73.26 261.56,74.19 v -48.62 C 626.53,1502.74 539.03,1415.23 536.32,1306.87 H 363.38 Z" />
+
+ <radialGradient
+ id="path49066_00000127744913500451459760000002731960833579333554_"
+ cx="1575.3523"
+ cy="1429.4684"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-836.1231,2267.3208)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop3" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#D2E5F6"
+ id="stop4" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#004182;stop-opacity:0"
+ id="stop5" />
+ <stop
+ offset="0.5"
+ style="stop-color:#0453A2;stop-opacity:0.3455"
+ id="stop6" />
+ <stop
+ offset="0.782"
+ style="stop-color:#2E6CB5;stop-opacity:0.5455"
+ id="stop7" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#004182;stop-opacity:0.4818"
+ id="stop8" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop9" />
+ </radialGradient>
+
+ <path
+ id="path49066"
+ style="clip-rule:evenodd;opacity:0.5;fill:url(#path49066_00000127744913500451459760000002731960833579333554_);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new"
+ d="m 363.75,1307.42 c 2.03,67.31 43.48,128.36 109.94,173.03 67.03,45.06 159.39,73.26 261.56,74.19 v -48.62 C 626.9,1503.3 539.4,1415.79 536.69,1307.43 H 363.75 Z" />
+
+ <radialGradient
+ id="path49068_00000054258629456709708870000006354480002332805544_"
+ cx="824.01111"
+ cy="783.3656"
+ r="379.28589"
+ gradientTransform="matrix(1,-0.00226167,-0.00301218,-1.3318,-134.1685,2142.1609)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop11" />
+ </radialGradient>
+
+ <path
+ id="path49068"
+ style="clip-rule:evenodd;opacity:0.1761;fill:url(#path49068_00000054258629456709708870000006354480002332805544_);fill-rule:evenodd;enable-background:new"
+ d="m 421.17,1306.87 c 36.58,44.37 87.28,80.31 146.84,103.44 -19.01,-30.09 -30.39,-65.48 -31.34,-103.44 z" />
+</g>
+</svg>
diff --git a/images/color/green.svg b/images/color/green.svg
new file mode 100644
index 0000000..c939632
--- /dev/null
+++ b/images/color/green.svg
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 374.25 250"
+ xml:space="preserve"
+ sodipodi:docname="green.svg"
+ width="374.25"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs11" /><sodipodi:namedview
+ id="namedview11"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#11843B;}
+
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#path27392_00000124867452088018918330000003393700036090762903_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;}
+
+ .st2{opacity:0.5246;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path34613_00000078738337522360425190000001563085163484979900_);enable-background:new ;}
+
+ .st3{opacity:0.5035;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path33718_00000044863439465072258580000012064167419240576138_);enable-background:new ;}
+ .st4{opacity:0.0246;fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;enable-background:new ;}
+</style>
+<g
+ id="green_normal"
+ transform="matrix(1.0020068,0,0,1.0031495,-308.76536,-759.91368)">
+ <path
+ id="path27384"
+ class="st0"
+ d="M 507.59,758.58 C 504.87,866.94 417.4,954.45 309.06,957.17 v 48.63 c 102.17,-0.93 194.53,-29.14 261.56,-74.2 66.46,-44.68 107.9,-105.71 109.94,-173.02 z" />
+
+ <radialGradient
+ id="path27392_00000092433464702926929160000008609019140570630580_"
+ cx="1109.6371"
+ cy="1714.4271"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-806.9696,1910.636)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop1" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#D4EADA"
+ id="stop2" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#11843B;stop-opacity:0"
+ id="stop3" />
+ <stop
+ offset="0.5"
+ style="stop-color:#1DA550;stop-opacity:0.3451"
+ id="stop4" />
+ <stop
+ offset="0.782"
+ style="stop-color:#46B46B;stop-opacity:0.5451"
+ id="stop5" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#11843B;stop-opacity:0.4727"
+ id="stop6" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop7" />
+ </radialGradient>
+
+ <path
+ id="path27392"
+ style="clip-rule:evenodd;fill:url(#path27392_00000092433464702926929160000008609019140570630580_);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331"
+ d="M 507.59,758.58 C 504.87,866.94 417.4,954.46 309.06,957.17 v 48.62 c 102.17,-0.93 194.53,-29.13 261.56,-74.19 66.46,-44.68 107.9,-105.72 109.94,-173.03 H 507.59 Z" />
+
+ <radialGradient
+ id="path34613_00000071560101637334869520000014430612806751397306_"
+ cx="699.02087"
+ cy="634.14789"
+ r="185.75"
+ gradientTransform="matrix(0.9939,-0.1716,-0.0766,-0.4438,-129.6354,1180.7937)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#095C31"
+ id="stop8" />
+ <stop
+ offset="1"
+ style="stop-color:#095C31;stop-opacity:0"
+ id="stop9" />
+ </radialGradient>
+
+ <path
+ id="path34613"
+ style="clip-rule:evenodd;opacity:0.5246;fill:url(#path34613_00000071560101637334869520000014430612806751397306_);fill-rule:evenodd;enable-background:new"
+ d="M 507.59,758.58 C 504.87,866.94 417.4,954.46 309.06,957.17 v 48.62 c 102.17,-0.93 194.53,-29.13 261.56,-74.19 66.46,-44.68 107.9,-105.72 109.94,-173.03 H 507.59 Z" />
+
+ <radialGradient
+ id="path33718_00000052075961771307649480000014218016242397967769_"
+ cx="897.86963"
+ cy="417.7084"
+ r="185.75"
+ gradientTransform="matrix(0.7679,-0.1027,-0.0491,-0.367,-249.5514,1195.1643)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#095C31"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#095C31;stop-opacity:0"
+ id="stop11" />
+ </radialGradient>
+
+ <path
+ id="path33718"
+ style="clip-rule:evenodd;opacity:0.5035;fill:url(#path33718_00000052075961771307649480000014218016242397967769_);fill-rule:evenodd;enable-background:new"
+ d="M 507.59,758.58 C 504.87,866.94 417.4,954.46 309.06,957.17 v 48.62 c 102.17,-0.93 194.53,-29.13 261.56,-74.19 66.46,-44.68 107.9,-105.72 109.94,-173.03 H 507.59 Z" />
+ <path
+ id="path48055"
+ class="st4"
+ d="m 507.58,758.59 c -0.75,29.81 -7.91,58.05 -20.16,83.38 40.73,-21.91 75.78,-50.3 103,-83.38 z" />
+</g>
+</svg>
diff --git a/images/color/green_highlight.svg b/images/color/green_highlight.svg
new file mode 100644
index 0000000..5f6c355
--- /dev/null
+++ b/images/color/green_highlight.svg
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 374.25 250"
+ xml:space="preserve"
+ sodipodi:docname="green_highlight.svg"
+ width="374.25"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs13" /><sodipodi:namedview
+ id="namedview13"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#6AB82D;}
+
+ .st1{opacity:0.5;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49074_00000098198372180388715100000000457878385575343777_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new ;}
+
+ .st2{opacity:0.5246;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49076_00000097502808370843341640000017387398230225104549_);enable-background:new ;}
+
+ .st3{opacity:0.5035;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49078_00000057845661951333102590000011645076166096498875_);enable-background:new ;}
+
+ .st4{opacity:0.1761;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49080_00000067958334669141494030000003199325611023958696_);enable-background:new ;}
+</style>
+<g
+ id="green_highlight"
+ transform="matrix(1.0019982,0,0,1.0031389,-357.40011,-1299.9594)">
+ <path
+ id="path49072"
+ class="st0"
+ d="m 556.13,1296.35 c -2.72,108.36 -90.19,195.87 -198.53,198.59 v 48.63 c 102.17,-0.93 194.53,-29.14 261.56,-74.2 66.46,-44.68 107.9,-105.71 109.94,-173.02 z" />
+
+ <radialGradient
+ id="path49074_00000061442800080391003520000014261525934452600236_"
+ cx="1186.5231"
+ cy="1444.7301"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-835.3149,2267.1147)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop1" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#D4EADA"
+ id="stop2" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#11843B;stop-opacity:0"
+ id="stop3" />
+ <stop
+ offset="0.5"
+ style="stop-color:#1DA550;stop-opacity:0.3451"
+ id="stop4" />
+ <stop
+ offset="0.782"
+ style="stop-color:#46B46B;stop-opacity:0.5451"
+ id="stop5" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#11843B;stop-opacity:0.4727"
+ id="stop6" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop7" />
+ </radialGradient>
+
+ <path
+ id="path49074"
+ style="clip-rule:evenodd;opacity:0.5;fill:url(#path49074_00000061442800080391003520000014261525934452600236_);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new"
+ d="m 556.13,1296.92 c -2.72,108.36 -90.19,195.88 -198.53,198.59 v 48.62 c 102.17,-0.93 194.53,-29.13 261.56,-74.19 66.46,-44.68 107.9,-105.72 109.94,-173.03 H 556.13 Z" />
+
+ <radialGradient
+ id="path49076_00000099625125967241823390000000860723222905392771_"
+ cx="684.05322"
+ cy="-33.007"
+ r="185.75"
+ gradientTransform="matrix(0.9939,-0.1716,-0.0766,-0.4438,-117.3536,1420.475)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#095C31"
+ id="stop8" />
+ <stop
+ offset="1"
+ style="stop-color:#095C31;stop-opacity:0"
+ id="stop9" />
+ </radialGradient>
+
+ <path
+ id="path49076"
+ style="clip-rule:evenodd;opacity:0.5246;fill:url(#path49076_00000099625125967241823390000000860723222905392771_);fill-rule:evenodd;enable-background:new"
+ d="m 556.13,1296.92 c -2.72,108.36 -90.19,195.88 -198.53,198.59 v 48.62 c 102.17,-0.93 194.53,-29.13 261.56,-74.19 66.46,-44.68 107.9,-105.72 109.94,-173.03 H 556.13 Z" />
+
+ <radialGradient
+ id="path49078_00000079487091142516674360000016431458055873060514_"
+ cx="896.26221"
+ cy="-511.24689"
+ r="185.75"
+ gradientTransform="matrix(0.7679,-0.1027,-0.0491,-0.367,-245.3972,1391.8029)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#095C31"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#095C31;stop-opacity:0"
+ id="stop11" />
+ </radialGradient>
+
+ <path
+ id="path49078"
+ style="clip-rule:evenodd;opacity:0.5035;fill:url(#path49078_00000079487091142516674360000016431458055873060514_);fill-rule:evenodd;enable-background:new"
+ d="m 556.13,1296.35 c -2.72,108.36 -90.19,195.88 -198.53,198.59 v 48.62 c 102.17,-0.93 194.53,-29.13 261.56,-74.19 66.46,-44.68 107.9,-105.72 109.94,-173.03 H 556.13 Z" />
+
+ <radialGradient
+ id="path49080_00000166663580299678216930000005496189042077002885_"
+ cx="435.20721"
+ cy="791.75293"
+ r="379.28589"
+ gradientTransform="matrix(1,-0.00226167,-0.00301218,-1.3318,-134.1685,2142.1609)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop12" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop13" />
+ </radialGradient>
+
+ <path
+ id="path49080"
+ style="clip-rule:evenodd;opacity:0.1761;fill:url(#path49080_00000166663580299678216930000005496189042077002885_);fill-rule:evenodd;enable-background:new"
+ d="m 556.12,1296.36 c -0.75,29.81 -7.91,58.05 -20.16,83.38 40.73,-21.91 75.78,-50.3 103,-83.38 z" />
+</g>
+</svg>
diff --git a/images/color/red.svg b/images/color/red.svg
new file mode 100644
index 0000000..91ee446
--- /dev/null
+++ b/images/color/red.svg
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 347.25 250"
+ xml:space="preserve"
+ sodipodi:docname="red.svg"
+ width="347.25"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs15" /><sodipodi:namedview
+ id="namedview15"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#B41D23;}
+
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#path27390_00000001642420658109818190000009090763747277451910_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;}
+
+ .st2{opacity:0.5352;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path40850_00000018925456596390428630000007889162573793817486_);enable-background:new ;}
+
+ .st3{opacity:0.5352;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path41745_00000029009028376141733640000007089464836489597070_);enable-background:new ;}
+
+ .st4{opacity:0.1761;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path48052_00000026869060482806714020000006704680069684470917_);enable-background:new ;}
+
+ .st5{opacity:0.1972;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path48078_00000138555999722409553480000007210567485955704966_);enable-background:new ;}
+</style>
+<g
+ id="red_normal"
+ transform="matrix(0.92971834,0,0,0.99869999,-291.34624,-748.87085)">
+ <path
+ id="path27382"
+ class="st0"
+ d="m 314.33,750.83 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 685.83 C 683.81,930.73 642.36,869.73 575.89,825.05 508.86,779.99 416.5,751.75 314.33,750.83 Z" />
+
+ <radialGradient
+ id="path27390_00000137113957019434214490000004953697350998814607_"
+ cx="1114.9054"
+ cy="1341.5286"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-806.9696,1908.9574)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop1" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#F9D1D2"
+ id="stop2" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#831A1F;stop-opacity:0"
+ id="stop3" />
+ <stop
+ offset="0.5"
+ style="stop-color:#A31F24;stop-opacity:0.3451"
+ id="stop4" />
+ <stop
+ offset="0.782"
+ style="stop-color:#BD2E31;stop-opacity:0.5451"
+ id="stop5" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#841B1F;stop-opacity:0.4824"
+ id="stop6" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop7" />
+ </radialGradient>
+
+ <path
+ id="path27390"
+ style="clip-rule:evenodd;fill:url(#path27390_00000137113957019434214490000004953697350998814607_);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331"
+ d="m 314.33,750.82 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 685.83 C 683.81,930.72 642.36,869.72 575.89,825.04 508.86,779.99 416.5,751.75 314.33,750.82 Z" />
+
+ <radialGradient
+ id="path40850_00000053507904088158860120000009903788965790649002_"
+ cx="666.93323"
+ cy="336.80301"
+ r="185.75"
+ gradientTransform="matrix(1,0,0,-0.6655,-108.5895,1137.4078)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#821A1F"
+ id="stop8" />
+ <stop
+ offset="1"
+ style="stop-color:#821A1F;stop-opacity:0"
+ id="stop9" />
+ </radialGradient>
+
+ <path
+ id="path40850"
+ style="clip-rule:evenodd;opacity:0.5352;fill:url(#path40850_00000053507904088158860120000009903788965790649002_);fill-rule:evenodd;enable-background:new"
+ d="m 314.33,750.82 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 685.83 C 683.81,930.72 642.36,869.72 575.89,825.04 508.86,779.99 416.5,751.75 314.33,750.82 Z" />
+
+ <radialGradient
+ id="path41745_00000105395135201026400770000004294659394890368675_"
+ cx="1471.5319"
+ cy="515.90259"
+ r="185.75"
+ gradientTransform="matrix(0.5746,-0.00460262,-0.00331358,-0.4137,-406.6095,1072.9193)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#821A1F"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#821A1F;stop-opacity:0"
+ id="stop11" />
+ </radialGradient>
+
+ <path
+ id="path41745"
+ style="clip-rule:evenodd;opacity:0.5352;fill:url(#path41745_00000105395135201026400770000004294659394890368675_);fill-rule:evenodd;enable-background:new"
+ d="m 314.33,750.82 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 685.83 C 683.81,930.72 642.36,869.72 575.89,825.04 508.86,779.99 416.5,751.75 314.33,750.82 Z" />
+
+ <radialGradient
+ id="path48052_00000165937496115156358410000014152596680545459391_"
+ cx="364.24109"
+ cy="477.77121"
+ r="379.28589"
+ gradientTransform="matrix(1,-0.00226167,-0.00301218,-1.3318,-107.4204,1435.8314)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop12" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop13" />
+ </radialGradient>
+
+ <path
+ id="path48052"
+ style="clip-rule:evenodd;opacity:0.1761;fill:url(#path48052_00000165937496115156358410000014152596680545459391_);fill-rule:evenodd;enable-background:new"
+ d="m 314.32,750.83 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 h 90.97 C 626.61,967.74 643,933.86 651.41,897.67 632.73,870.56 607.06,846 575.88,825.05 508.85,779.99 416.49,751.76 314.32,750.83 Z" />
+
+ <radialGradient
+ id="path48078_00000011753432873456411560000001982600393105500593_"
+ cx="486.67181"
+ cy="453.44189"
+ r="185.75"
+ gradientTransform="matrix(1,0,0,-0.6655,-108.5895,1137.4092)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop14" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop15" />
+ </radialGradient>
+
+ <path
+ id="path48078"
+ style="clip-rule:evenodd;opacity:0.1972;fill:url(#path48078_00000011753432873456411560000001982600393105500593_);fill-rule:evenodd;enable-background:new"
+ d="m 314.32,750.83 v 12.03 c 6.33,-0.21 12.69,-0.31 19.09,-0.31 190.55,0 317.63,102.5 331.22,235.5 h 21.19 C 683.8,930.73 642.35,869.73 575.88,825.05 508.85,779.99 416.49,751.76 314.32,750.83 Z" />
+</g>
+</svg>
diff --git a/images/color/red_highlight.svg b/images/color/red_highlight.svg
new file mode 100644
index 0000000..43504e3
--- /dev/null
+++ b/images/color/red_highlight.svg
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 347.25 250"
+ xml:space="preserve"
+ sodipodi:docname="red_highlight.svg"
+ width="347.25"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs15" /><sodipodi:namedview
+ id="namedview15"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E7211A;}
+
+ .st1{opacity:0.5;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49086_00000007400659765346506100000009882442648106408633_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new ;}
+
+ .st2{opacity:0.5352;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49088_00000154402871267858631560000011123965789897339313_);enable-background:new ;}
+
+ .st3{opacity:0.2394;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49090_00000059995565381919725410000002489727941950454416_);enable-background:new ;}
+
+ .st4{opacity:0.1761;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49092_00000085250855370913651110000004514189976051801730_);enable-background:new ;}
+
+ .st5{opacity:0.1972;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49094_00000029727046107930941000000014102528012560749698_);enable-background:new ;}
+</style>
+<g
+ id="red_highlight"
+ transform="matrix(0.92971832,0,0,0.99996388,-324.86218,-602.33824)">
+ <path
+ id="path49084"
+ class="st0"
+ d="m 350.42,603.37 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 721.92 C 719.9,783.27 678.45,722.27 611.98,677.59 544.96,632.53 452.59,604.29 350.42,603.37 Z" />
+
+ <radialGradient
+ id="path49086_00000088123563754489200550000014464248633402128799_"
+ cx="1179.3438"
+ cy="381.48999"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-835.3151,1114.1246)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop1" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#F9D1D2"
+ id="stop2" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#831A1F;stop-opacity:0"
+ id="stop3" />
+ <stop
+ offset="0.5"
+ style="stop-color:#A31F24;stop-opacity:0.3451"
+ id="stop4" />
+ <stop
+ offset="0.782"
+ style="stop-color:#BD2E31;stop-opacity:0.5451"
+ id="stop5" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#841B1F;stop-opacity:0.4824"
+ id="stop6" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop7" />
+ </radialGradient>
+
+ <path
+ id="path49086"
+ style="clip-rule:evenodd;opacity:0.5;fill:url(#path49086_00000088123563754489200550000014464248633402128799_);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new"
+ d="m 350.42,603.36 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 721.92 C 719.9,783.26 678.45,722.26 611.98,677.58 544.95,632.53 452.59,604.29 350.42,603.36 Z" />
+
+ <radialGradient
+ id="path49088_00000009579967759151284600000014993876448802444697_"
+ cx="731.37152"
+ cy="-633.9104"
+ r="185.75"
+ gradientTransform="matrix(1,0,0,-0.6655,-136.935,343.4033)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#821A1F"
+ id="stop8" />
+ <stop
+ offset="1"
+ style="stop-color:#821A1F;stop-opacity:0"
+ id="stop9" />
+ </radialGradient>
+
+ <path
+ id="path49088"
+ style="clip-rule:evenodd;opacity:0.5352;fill:url(#path49088_00000009579967759151284600000014993876448802444697_);fill-rule:evenodd;enable-background:new"
+ d="m 350.42,602.79 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 721.92 C 719.9,782.69 678.45,721.69 611.98,677.01 544.95,631.96 452.59,603.72 350.42,602.79 Z" />
+
+ <radialGradient
+ id="path49090_00000105422793748167587710000008537505370322311835_"
+ cx="1552.7554"
+ cy="-950.00238"
+ r="185.75"
+ gradientTransform="matrix(0.5746,-0.00460262,-0.00331358,-0.4137,-422.046,318.8405)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#821A1F"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#821A1F;stop-opacity:0"
+ id="stop11" />
+ </radialGradient>
+
+ <path
+ id="path49090"
+ style="clip-rule:evenodd;opacity:0.2394;fill:url(#path49090_00000105422793748167587710000008537505370322311835_);fill-rule:evenodd;enable-background:new"
+ d="m 350.42,602.79 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 H 721.92 C 719.9,782.69 678.45,721.69 611.98,677.01 544.95,631.96 452.59,603.72 350.42,602.79 Z" />
+
+ <radialGradient
+ id="path49092_00000126319401202548710930000000408607587414488252_"
+ cx="427.45599"
+ cy="-86.358398"
+ r="379.28589"
+ gradientTransform="matrix(1,-0.00226167,-0.00301218,-1.3318,-136.2418,537.1832)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop12" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop13" />
+ </radialGradient>
+
+ <path
+ id="path49092"
+ style="clip-rule:evenodd;opacity:0.1761;fill:url(#path49092_00000126319401202548710930000000408607587414488252_);fill-rule:evenodd;enable-background:new"
+ d="m 350.41,603.37 v 48.66 c 108.36,2.72 195.83,90.2 198.53,198.56 h 90.97 C 662.7,820.28 679.09,786.4 687.5,750.21 668.82,723.1 643.15,698.54 611.97,677.59 544.94,632.53 452.58,604.3 350.41,603.37 Z" />
+
+ <radialGradient
+ id="path49094_00000155847793702457443990000014782902820458712458_"
+ cx="338.34149"
+ cy="-567.78027"
+ r="379.28589"
+ gradientTransform="matrix(1,0.00594694,0.00301218,-0.5065,-137.6906,351.3566)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop14" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop15" />
+ </radialGradient>
+
+ <path
+ id="path49094"
+ style="clip-rule:evenodd;opacity:0.1972;fill:url(#path49094_00000155847793702457443990000014782902820458712458_);fill-rule:evenodd;enable-background:new"
+ d="m 350.41,603.37 v 12.03 c 6.33,-0.21 12.69,-0.31 19.09,-0.31 190.55,0 317.63,102.5 331.22,235.5 h 21.19 C 719.89,783.27 678.44,722.27 611.97,677.59 544.94,632.53 452.58,604.3 350.41,603.37 Z" />
+</g>
+</svg>
diff --git a/images/color/yellow.svg b/images/color/yellow.svg
new file mode 100644
index 0000000..e5d7aa6
--- /dev/null
+++ b/images/color/yellow.svg
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 347.25 250"
+ xml:space="preserve"
+ sodipodi:docname="yellow.svg"
+ width="347.25"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs13" /><sodipodi:namedview
+ id="namedview13"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E9BD1B;}
+
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#path27388_00000007390922995376802790000014288275829239385521_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;}
+
+ .st2{opacity:0.4754;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path41749_00000121257938634359593800000012126117125210795192_);enable-background:new ;}
+
+ .st3{opacity:0.1021;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path47998_00000117666679933676951420000008818754639384450459_);enable-background:new ;}
+
+ .st4{opacity:0.257;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path48007_00000118393246674199960730000008031841035827927466_);enable-background:new ;}
+</style>
+<g
+ id="yellow_normal"
+ transform="matrix(0.92971834,0,0,0.99867763,-303.19045,-755.22)">
+ <path
+ id="path27380"
+ class="st0"
+ d="m 698.62,757.22 c -102.17,0.93 -194.53,29.16 -261.56,74.22 -66.47,44.68 -107.92,105.68 -109.94,173 h 172.94 c 2.7,-108.36 90.21,-195.85 198.56,-198.56 z" />
+
+ <radialGradient
+ id="path27388_00000028319886440632200980000010314327820123833228_"
+ cx="1504.7948"
+ cy="1329.6599"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-802.1964,1907.3481)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop1" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#FBF9D3"
+ id="stop2" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#867D2A;stop-opacity:0"
+ id="stop3" />
+ <stop
+ offset="0.5"
+ style="stop-color:#A89E1C;stop-opacity:0.3451"
+ id="stop4" />
+ <stop
+ offset="0.782"
+ style="stop-color:#C0B734;stop-opacity:0.5451"
+ id="stop5" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#86802B;stop-opacity:0.4824"
+ id="stop6" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop7" />
+ </radialGradient>
+
+ <path
+ id="path27388"
+ style="clip-rule:evenodd;fill:url(#path27388_00000028319886440632200980000010314327820123833228_);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331"
+ d="m 698.61,757.22 c -102.17,0.93 -194.53,29.16 -261.56,74.22 -66.47,44.68 -107.92,105.68 -109.94,173 h 172.94 c 2.7,-108.36 90.21,-195.85 198.56,-198.56 z" />
+
+ <radialGradient
+ id="path41749_00000145770143306937822660000003621301476068107694_"
+ cx="1115.5029"
+ cy="446.57941"
+ r="185.7502"
+ gradientTransform="matrix(0.7416,0,0,-0.4871,-331.5229,1098.3711)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#9A7A26"
+ id="stop8" />
+ <stop
+ offset="1"
+ style="stop-color:#9A7A26;stop-opacity:0"
+ id="stop9" />
+ </radialGradient>
+
+ <path
+ id="path41749"
+ style="clip-rule:evenodd;opacity:0.4754;fill:url(#path41749_00000145770143306937822660000003621301476068107694_);fill-rule:evenodd;enable-background:new"
+ d="m 698.61,757.22 c -102.17,0.93 -194.53,29.16 -261.56,74.22 -66.47,44.68 -107.92,105.68 -109.94,173 h 172.94 c 2.7,-108.36 90.21,-195.85 198.56,-198.56 z" />
+
+ <linearGradient
+ id="path47998_00000090994483967329618330000008837402008167169467_"
+ gradientUnits="userSpaceOnUse"
+ x1="612.53223"
+ y1="428.63049"
+ x2="653.7757"
+ y2="152.2146"
+ gradientTransform="matrix(1,0,0,-1,-103.8162,1206.0718)">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop11" />
+ </linearGradient>
+
+ <path
+ id="path47998"
+ style="clip-rule:evenodd;opacity:0.1021;fill:url(#path47998_00000090994483967329618330000008837402008167169467_);fill-rule:evenodd;enable-background:new"
+ d="m 698.61,757.22 c -102.17,0.92 -194.53,28.96 -261.56,73.7 -45.54,30.4 -79.3,68.4 -96.72,110.82 8.89,21.53 20.7,41.97 35.09,60.98 h 124.62 c 2.7,-107.61 90.21,-194.49 198.56,-197.19 v -48.31 z" />
+
+ <radialGradient
+ id="path48007_00000088851671310769822710000017053448804128889783_"
+ cx="-1134.6531"
+ cy="1600.9248"
+ r="170.04691"
+ gradientTransform="matrix(-0.2203,1.7096,1.3676,0.1762,-1910.7996,2330.0566)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop12" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop13" />
+ </radialGradient>
+
+ <path
+ id="path48007"
+ style="clip-rule:evenodd;opacity:0.257;fill:url(#path48007_00000088851671310769822710000017053448804128889783_);fill-rule:evenodd;enable-background:new"
+ d="m 698.61,757.22 c -34.61,0.31 -68.09,3.76 -99.84,9.94 -140.62,32.31 -230.34,129.3 -240.25,237.28 h 2.28 C 371.11,874.58 517.16,779.07 698.61,769.69 Z" />
+</g>
+</svg>
diff --git a/images/color/yellow_highlight.svg b/images/color/yellow_highlight.svg
new file mode 100644
index 0000000..5e63b0a
--- /dev/null
+++ b/images/color/yellow_highlight.svg
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 374.25 250"
+ xml:space="preserve"
+ sodipodi:docname="yellow_highlight.svg"
+ width="374.25"
+ height="250"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs13" /><sodipodi:namedview
+ id="namedview13"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#EFEA3C;}
+
+ .st1{opacity:0.5;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49050_00000020390923318355770340000002833857294862892682_);stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new ;}
+
+ .st2{opacity:0.1866;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49052_00000137839318039913439300000010321817807017620115_);enable-background:new ;}
+
+ .st3{opacity:0.1761;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49054_00000075879811142468609280000004259611817696555674_);enable-background:new ;}
+
+ .st4{opacity:0.1972;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path49056_00000103248099191378228130000014001420832639859134_);enable-background:new ;}
+</style>
+<g
+ id="yellow_highlight"
+ transform="matrix(1.0024858,0,0,1.0003079,-352.20334,-1286.586)">
+ <path
+ id="path49048"
+ class="st0"
+ d="m 723.83,1287.76 c -102.17,0.93 -194.53,29.16 -261.56,74.22 -66.47,44.68 -107.92,105.68 -109.94,173 h 172.94 c 2.7,-108.36 90.21,-195.85 198.56,-198.56 z" />
+
+ <radialGradient
+ id="path49050_00000057148608972308723630000017212283753461938588_"
+ cx="1563.701"
+ cy="1078.0957"
+ r="381.78589"
+ gradientTransform="matrix(1,0,0,-0.6743,-835.8862,2267.6858)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#323232"
+ id="stop1" />
+ <stop
+ offset="0.0769"
+ style="stop-color:#FBF9D3"
+ id="stop2" />
+ <stop
+ offset="0.2629"
+ style="stop-color:#867D2A;stop-opacity:0"
+ id="stop3" />
+ <stop
+ offset="0.5"
+ style="stop-color:#A89E1C;stop-opacity:0.3451"
+ id="stop4" />
+ <stop
+ offset="0.782"
+ style="stop-color:#C0B734;stop-opacity:0.5451"
+ id="stop5" />
+ <stop
+ offset="0.9006"
+ style="stop-color:#86802B;stop-opacity:0.4824"
+ id="stop6" />
+ <stop
+ offset="1"
+ style="stop-color:#393A3A"
+ id="stop7" />
+ </radialGradient>
+
+ <path
+ id="path49050"
+ style="clip-rule:evenodd;opacity:0.5;fill:url(#path49050_00000057148608972308723630000017212283753461938588_);fill-rule:evenodd;stroke:#040000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;enable-background:new"
+ d="m 723.83,1287.19 c -102.17,0.93 -194.53,29.16 -261.56,74.22 -66.47,44.68 -107.92,105.68 -109.94,173 h 172.94 c 2.7,-108.36 90.21,-195.85 198.56,-198.56 z" />
+
+ <radialGradient
+ id="path49052_00000055697574590528709220000012556362571328177082_"
+ cx="1183.3948"
+ cy="-106.3397"
+ r="185.7502"
+ gradientTransform="matrix(0.7416,0,0,-0.4871,-356.6549,1359.5702)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#9A7A26"
+ id="stop8" />
+ <stop
+ offset="1"
+ style="stop-color:#9A7A26;stop-opacity:0"
+ id="stop9" />
+ </radialGradient>
+
+ <path
+ id="path49052"
+ style="clip-rule:evenodd;opacity:0.1866;fill:url(#path49052_00000055697574590528709220000012556362571328177082_);fill-rule:evenodd;enable-background:new"
+ d="m 723.83,1287.76 c -102.17,0.93 -194.53,29.16 -261.56,74.22 -66.47,44.68 -107.92,105.68 -109.94,173 h 172.94 c 2.7,-108.36 90.21,-195.85 198.56,-198.56 z" />
+
+ <radialGradient
+ id="path49054_00000043449575703102263510000017721467983082905518_"
+ cx="811.82397"
+ cy="605.48419"
+ r="379.28589"
+ gradientTransform="matrix(1,-0.00226167,-0.00301218,-1.3318,-134.7397,2143.3032)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop10" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop11" />
+ </radialGradient>
+
+ <path
+ id="path49054"
+ style="clip-rule:evenodd;opacity:0.1761;fill:url(#path49054_00000043449575703102263510000017721467983082905518_);fill-rule:evenodd;enable-background:new"
+ d="m 723.82,1287.2 c -102.17,0.93 -194.53,29.16 -261.56,74.22 -45.54,30.61 -79.3,68.88 -96.72,111.59 8.89,21.68 20.7,42.26 35.09,61.41 h 124.63 c 2.7,-108.36 90.21,-195.85 198.56,-198.56 z" />
+
+ <radialGradient
+ id="path49056_00000024724300295543902210000014960781455240754344_"
+ cx="722.65881"
+ cy="136.13049"
+ r="379.28589"
+ gradientTransform="matrix(1,0.00594694,0.00301218,-0.5065,-140.3351,1390.573)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0"
+ style="stop-color:#FFFFFF"
+ id="stop12" />
+ <stop
+ offset="1"
+ style="stop-color:#FFFFFF;stop-opacity:0"
+ id="stop13" />
+ </radialGradient>
+
+ <path
+ id="path49056"
+ style="clip-rule:evenodd;opacity:0.1972;fill:url(#path49056_00000024724300295543902210000014960781455240754344_);fill-rule:evenodd;enable-background:new"
+ d="m 723.82,1288.34 c -34.61,0.31 -68.09,3.76 -99.84,9.94 -140.62,32.31 -230.34,129.3 -240.25,237.28 h 2.28 c 10.31,-129.86 156.36,-225.37 337.81,-234.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/0.svg b/images/numbers/0.svg
new file mode 100644
index 0000000..b3080ec
--- /dev/null
+++ b/images/numbers/0.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="0.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="zero"
+ transform="matrix(1.8600531,0,0,1.8288394,-27.045173,-32.57163)">
+ <path
+ id="path3957"
+ class="st0"
+ d="m 23.33,25.31 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3965"
+ class="st0"
+ d="m 32.12,25.31 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3969"
+ class="st0"
+ d="m 40.91,42.88 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3971"
+ class="st0"
+ d="m 40.91,34.1 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3973"
+ class="st0"
+ d="m 40.91,25.31 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path3975"
+ class="st0"
+ d="m 40.91,51.67 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3977"
+ class="st0"
+ d="m 14.54,42.88 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3979"
+ class="st0"
+ d="m 14.54,34.1 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3981"
+ class="st0"
+ d="m 14.54,25.31 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path3983"
+ class="st0"
+ d="m 14.54,51.67 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3985"
+ class="st0"
+ d="m 23.33,60.46 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3987"
+ class="st0"
+ d="m 32.12,60.46 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3989"
+ class="st0"
+ d="m 40.91,60.46 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path3991"
+ class="st0"
+ d="m 14.54,56.71 v -3.75 h 7.5 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/1.svg b/images/numbers/1.svg
new file mode 100644
index 0000000..97cef4e
--- /dev/null
+++ b/images/numbers/1.svg
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="1.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+ .st1{fill:#E7211A;fill-opacity:0;}
+</style>
+<g
+ id="one"
+ transform="matrix(1.8545776,0,0,1.8275539,-37.555196,-31.068416)">
+ <g
+ id="g3091"
+ transform="translate(-16.7872)">
+ <path
+ id="path2994"
+ class="st0"
+ d="m 45.93,42.11 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2996"
+ class="st0"
+ d="m 45.93,33.32 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2998"
+ class="st0"
+ d="m 45.93,24.54 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3000"
+ class="st0"
+ d="m 45.93,50.9 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2978"
+ class="st0"
+ d="m 37.14,24.54 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path3045"
+ class="st0"
+ d="m 45.93,59.68 v -7.5 h 7.5 v 7.5 z" />
+ </g>
+ <path
+ id="path3060"
+ class="st1"
+ d="M 29.04,24.5 V 17 h 7.5 v 7.5 z" />
+ <path
+ id="path3062"
+ class="st1"
+ d="M 20.25,24.5 V 17 h 7.5 v 7.5 z" />
+ <path
+ id="path3058"
+ class="st1"
+ d="m 37.93,24.54 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2170"
+ class="st1"
+ d="m 46.72,24.54 v -7.5 h 7.5 v 7.5 z" />
+</g>
+</svg>
diff --git a/images/numbers/2.svg b/images/numbers/2.svg
new file mode 100644
index 0000000..fee8bfa
--- /dev/null
+++ b/images/numbers/2.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="2.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="two"
+ transform="matrix(1.8606025,0,0,1.8288394,-30.699941,-27.816647)">
+ <path
+ id="path4051"
+ class="st0"
+ d="m 25.29,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4055"
+ class="st0"
+ d="m 25.29,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4059"
+ class="st0"
+ d="m 34.07,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4063"
+ class="st0"
+ d="m 34.07,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4067"
+ class="st0"
+ d="m 42.86,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4069"
+ class="st0"
+ d="M 42.86,31.5 V 24 h 7.5 v 7.5 z" />
+ <path
+ id="path4071"
+ class="st0"
+ d="m 42.86,22.71 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path4075"
+ class="st0"
+ d="m 16.5,40.29 v -7.5 H 24 v 7.5 z" />
+ <path
+ id="path4079"
+ class="st0"
+ d="m 16.5,22.71 v -3.75 l 3.75,-3.75 H 24 v 7.5 z" />
+ <path
+ id="path4081"
+ class="st0"
+ d="m 16.5,49.07 v -7.5 H 24 v 7.5 z" />
+ <path
+ id="path4083"
+ class="st0"
+ d="m 25.29,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4085"
+ class="st0"
+ d="m 34.07,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4087"
+ class="st0"
+ d="m 42.86,57.86 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path4089"
+ class="st0"
+ d="M 16.5,54.11 V 50.36 H 24 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/3.svg b/images/numbers/3.svg
new file mode 100644
index 0000000..8e6da7f
--- /dev/null
+++ b/images/numbers/3.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="3.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="three"
+ transform="matrix(1.8606025,0,0,1.8288394,-25.992618,-27.816647)">
+ <path
+ id="path4129"
+ class="st0"
+ d="m 22.76,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4133"
+ class="st0"
+ d="m 22.76,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4137"
+ class="st0"
+ d="m 31.55,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4141"
+ class="st0"
+ d="m 31.55,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4145"
+ class="st0"
+ d="m 40.33,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4147"
+ class="st0"
+ d="M 40.33,31.5 V 24 h 7.5 v 7.5 z" />
+ <path
+ id="path4149"
+ class="st0"
+ d="m 40.33,22.71 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path4151"
+ class="st0"
+ d="m 40.33,49.07 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4153"
+ class="st0"
+ d="m 13.97,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4157"
+ class="st0"
+ d="m 13.97,22.71 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path4161"
+ class="st0"
+ d="m 22.76,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4163"
+ class="st0"
+ d="m 31.55,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4165"
+ class="st0"
+ d="m 40.33,57.86 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path4167"
+ class="st0"
+ d="m 13.97,54.11 v -3.75 h 7.5 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/4.svg b/images/numbers/4.svg
new file mode 100644
index 0000000..240edfa
--- /dev/null
+++ b/images/numbers/4.svg
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="4.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="four"
+ transform="matrix(1.8606025,0,0,1.8288394,-30.699941,-27.194842)">
+ <path
+ id="path4213"
+ class="st0"
+ d="m 25.29,39.95 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4221"
+ class="st0"
+ d="m 34.07,39.95 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4223"
+ class="st0"
+ d="m 42.86,39.95 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4225"
+ class="st0"
+ d="m 42.86,31.16 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4227"
+ class="st0"
+ d="m 42.86,22.37 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path4229"
+ class="st0"
+ d="m 42.86,48.73 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4231"
+ class="st0"
+ d="m 16.5,39.95 v -7.5 H 24 v 7.5 z" />
+ <path
+ id="path4233"
+ class="st0"
+ d="m 16.5,31.16 v -7.5 H 24 v 7.5 z" />
+ <path
+ id="path4235"
+ class="st0"
+ d="m 16.5,22.37 v -3.75 l 3.75,-3.75 H 24 v 7.5 z" />
+ <path
+ id="path4243"
+ class="st0"
+ d="m 42.86,57.52 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/5.svg b/images/numbers/5.svg
new file mode 100644
index 0000000..82b3bb5
--- /dev/null
+++ b/images/numbers/5.svg
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="5.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="five"
+ transform="matrix(1.8606025,0,0,1.8288394,-30.699941,-27.011958)">
+ <path
+ id="path4287"
+ class="st0"
+ d="m 25.29,39.85 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4291"
+ class="st0"
+ d="m 25.29,22.27 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4295"
+ class="st0"
+ d="m 34.07,39.85 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4299"
+ class="st0"
+ d="m 34.07,22.27 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4303"
+ class="st0"
+ d="m 42.86,39.85 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4307"
+ class="st0"
+ d="m 42.86,22.27 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path4309"
+ class="st0"
+ d="m 42.86,48.63 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4311"
+ class="st0"
+ d="m 16.5,39.85 v -7.5 H 24 v 7.5 z" />
+ <path
+ id="path4313"
+ class="st0"
+ d="m 16.5,31.06 v -7.5 H 24 v 7.5 z" />
+ <path
+ id="path4315"
+ class="st0"
+ d="m 16.5,22.27 v -3.75 l 3.75,-3.75 H 24 v 7.5 z" />
+ <path
+ id="path4319"
+ class="st0"
+ d="m 25.29,57.42 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4321"
+ class="st0"
+ d="m 34.07,57.42 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4323"
+ class="st0"
+ d="m 42.86,57.42 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path4325"
+ class="st0"
+ d="M 16.5,53.67 V 49.92 H 24 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/6.svg b/images/numbers/6.svg
new file mode 100644
index 0000000..c40a7d0
--- /dev/null
+++ b/images/numbers/6.svg
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="6.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="six"
+ transform="matrix(1.8606025,0,0,1.8288394,-31.890727,-27.816647)">
+ <path
+ id="path4365"
+ class="st0"
+ d="m 25.93,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4369"
+ class="st0"
+ d="m 25.93,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4373"
+ class="st0"
+ d="m 34.72,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4377"
+ class="st0"
+ d="m 34.72,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4382"
+ class="st0"
+ d="m 43.5,40.29 v -7.5 H 51 v 7.5 z" />
+ <path
+ id="path4386"
+ class="st0"
+ d="m 43.5,22.71 v -7.5 h 3.75 L 51,18.96 v 3.75 z" />
+ <path
+ id="path4388"
+ class="st0"
+ d="m 43.5,49.07 v -7.5 H 51 v 7.5 z" />
+ <path
+ id="path4390"
+ class="st0"
+ d="m 17.14,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4392"
+ class="st0"
+ d="M 17.14,31.5 V 24 h 7.5 v 7.5 z" />
+ <path
+ id="path4394"
+ class="st0"
+ d="m 17.14,22.71 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path4396"
+ class="st0"
+ d="m 17.14,49.07 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4398"
+ class="st0"
+ d="m 25.93,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4400"
+ class="st0"
+ d="m 34.72,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4402"
+ class="st0"
+ d="m 43.5,57.86 v -7.5 H 51 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path4404"
+ class="st0"
+ d="m 17.14,54.11 v -3.75 h 7.5 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/7.svg b/images/numbers/7.svg
new file mode 100644
index 0000000..8cbba40
--- /dev/null
+++ b/images/numbers/7.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="7.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="seven"
+ transform="matrix(1.8600531,0,0,1.8288394,-27.045173,-29.1517)">
+ <path
+ id="path4449"
+ class="st0"
+ d="m 23.33,23.44 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4457"
+ class="st0"
+ d="m 32.12,23.44 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4461"
+ class="st0"
+ d="m 40.91,41.02 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4463"
+ class="st0"
+ d="m 40.91,32.23 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4465"
+ class="st0"
+ d="m 40.91,23.44 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path4467"
+ class="st0"
+ d="m 40.91,49.81 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4473"
+ class="st0"
+ d="m 14.54,23.44 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path4481"
+ class="st0"
+ d="m 40.91,58.59 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/8.svg b/images/numbers/8.svg
new file mode 100644
index 0000000..0db9fed
--- /dev/null
+++ b/images/numbers/8.svg
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="8.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="eight"
+ transform="matrix(1.8606025,0,0,1.8288394,-32.709392,-27.816647)">
+ <path
+ id="path4517"
+ class="st0"
+ d="m 26.37,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4521"
+ class="st0"
+ d="m 26.37,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4525"
+ class="st0"
+ d="m 35.16,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4529"
+ class="st0"
+ d="m 35.16,22.71 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4533"
+ class="st0"
+ d="m 43.94,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4535"
+ class="st0"
+ d="M 43.94,31.5 V 24 h 7.5 v 7.5 z" />
+ <path
+ id="path4537"
+ class="st0"
+ d="m 43.94,22.71 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path4539"
+ class="st0"
+ d="m 43.94,49.07 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4541"
+ class="st0"
+ d="m 17.58,40.29 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4543"
+ class="st0"
+ d="M 17.58,31.5 V 24 h 7.5 v 7.5 z" />
+ <path
+ id="path4545"
+ class="st0"
+ d="m 17.58,22.71 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path4547"
+ class="st0"
+ d="m 17.58,49.07 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4549"
+ class="st0"
+ d="m 26.37,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4551"
+ class="st0"
+ d="m 35.16,57.86 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4553"
+ class="st0"
+ d="m 43.94,57.86 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path4555"
+ class="st0"
+ d="m 17.58,54.11 v -3.75 h 7.5 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/9.svg b/images/numbers/9.svg
new file mode 100644
index 0000000..d39f80a
--- /dev/null
+++ b/images/numbers/9.svg
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="9.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="nine"
+ transform="matrix(1.8606025,0,0,1.8288394,-30.346426,-30.322157)">
+ <path
+ id="path4597"
+ class="st0"
+ d="m 25.1,41.65 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4601"
+ class="st0"
+ d="m 25.1,24.08 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4605"
+ class="st0"
+ d="m 33.89,41.65 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4610"
+ class="st0"
+ d="m 33.89,24.08 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4614"
+ class="st0"
+ d="m 42.67,41.65 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4616"
+ class="st0"
+ d="m 42.67,32.87 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4618"
+ class="st0"
+ d="m 42.67,24.08 v -7.5 h 3.75 l 3.75,3.75 v 3.75 z" />
+ <path
+ id="path4620"
+ class="st0"
+ d="m 42.67,50.44 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4622"
+ class="st0"
+ d="m 16.31,41.65 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4624"
+ class="st0"
+ d="m 16.31,32.87 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4626"
+ class="st0"
+ d="m 16.31,24.08 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path4630"
+ class="st0"
+ d="m 25.1,59.23 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4632"
+ class="st0"
+ d="m 33.89,59.23 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4634"
+ class="st0"
+ d="m 42.67,59.23 v -7.5 h 7.5 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path4636"
+ class="st0"
+ d="m 16.31,55.48 v -3.75 h 7.5 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/blackBlock.svg b/images/numbers/blackBlock.svg
new file mode 100644
index 0000000..0d4b3d9
--- /dev/null
+++ b/images/numbers/blackBlock.svg
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 80 59.999998"
+ xml:space="preserve"
+ sodipodi:docname="my_blackBlock.svg"
+ width="80"
+ height="60"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.102171"
+ inkscape:cx="31.478383"
+ inkscape:cy="39.150002"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#373737;}
+</style>
+<path
+ id="led_on"
+ class="st0"
+ d="M 0,60 V 0 h 80 v 60 z"
+ style="stroke-width:5.56038" />
+</svg>
diff --git a/images/numbers/blank.svg b/images/numbers/blank.svg
new file mode 100644
index 0000000..f2f6d4a
--- /dev/null
+++ b/images/numbers/blank.svg
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="blank.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#373737;}
+</style>
+<g
+ id="all_off"
+ transform="matrix(1.8606025,0,0,1.8288394,-31.890727,-31.126846)">
+ <path
+ id="path2984"
+ class="st0"
+ d="m 25.93,42.09 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2986"
+ class="st0"
+ d="m 25.93,33.3 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2988"
+ class="st0"
+ d="m 25.93,24.52 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2990"
+ class="st0"
+ d="m 25.93,50.88 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4005"
+ class="st0"
+ d="m 34.72,42.09 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4007"
+ class="st0"
+ d="m 34.72,33.3 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4009"
+ class="st0"
+ d="m 34.72,24.52 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4011"
+ class="st0"
+ d="m 34.72,50.88 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3004"
+ class="st0"
+ d="m 43.5,42.09 v -7.5 H 51 v 7.5 z" />
+ <path
+ id="path3006"
+ class="st0"
+ d="M 43.5,33.3 V 25.8 H 51 v 7.5 z" />
+ <path
+ id="path3008"
+ class="st0"
+ d="m 43.5,24.52 v -7.5 h 3.75 L 51,20.77 v 3.75 z" />
+ <path
+ id="path3010"
+ class="st0"
+ d="m 43.5,50.88 v -7.5 H 51 v 7.5 z" />
+ <path
+ id="path2974"
+ class="st0"
+ d="m 17.14,42.09 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path2976"
+ class="st0"
+ d="m 17.14,33.3 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4019"
+ class="st0"
+ d="m 17.14,24.52 v -3.75 l 3.75,-3.75 h 3.75 v 7.5 z" />
+ <path
+ id="path2980"
+ class="st0"
+ d="m 17.14,50.88 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3043"
+ class="st0"
+ d="m 25.93,59.67 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path4023"
+ class="st0"
+ d="m 34.72,59.67 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path3047"
+ class="st0"
+ d="m 43.5,59.67 v -7.5 H 51 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path3049"
+ class="st0"
+ d="m 17.14,55.92 v -3.75 h 7.5 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/full.svg b/images/numbers/full.svg
new file mode 100644
index 0000000..7829e00
--- /dev/null
+++ b/images/numbers/full.svg
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 62.999999 78.000003"
+ xml:space="preserve"
+ sodipodi:docname="full.svg"
+ width="63"
+ height="78"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.106044"
+ inkscape:cx="31.46632"
+ inkscape:cy="39.134998"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#E7211A;}
+</style>
+<g
+ id="all_on"
+ transform="matrix(1.8606025,0,0,1.8288394,-28.169522,-30.17585)">
+ <path
+ id="path6515"
+ class="st0"
+ d="m 23.93,41.57 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6517"
+ class="st0"
+ d="m 23.93,32.79 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6519"
+ class="st0"
+ d="m 23.93,24 v -7.5 h 7.5 V 24 Z" />
+ <path
+ id="path6521"
+ class="st0"
+ d="m 23.93,50.36 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6523"
+ class="st0"
+ d="m 32.72,41.57 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6525"
+ class="st0"
+ d="m 32.72,32.79 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6527"
+ class="st0"
+ d="m 32.72,24 v -7.5 h 7.5 V 24 Z" />
+ <path
+ id="path6529"
+ class="st0"
+ d="m 32.72,50.36 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6531"
+ class="st0"
+ d="m 41.5,41.57 v -7.5 H 49 v 7.5 z" />
+ <path
+ id="path6533"
+ class="st0"
+ d="m 41.5,32.79 v -7.5 H 49 v 7.5 z" />
+ <path
+ id="path6535"
+ class="st0"
+ d="m 41.5,24 v -7.5 h 3.75 L 49,20.25 V 24 Z" />
+ <path
+ id="path6537"
+ class="st0"
+ d="m 41.5,50.36 v -7.5 H 49 v 7.5 z" />
+ <path
+ id="path6539"
+ class="st0"
+ d="m 15.14,41.57 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6541"
+ class="st0"
+ d="m 15.14,32.79 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6543"
+ class="st0"
+ d="m 15.14,24 v -3.75 l 3.75,-3.75 h 3.75 V 24 Z" />
+ <path
+ id="path6545"
+ class="st0"
+ d="m 15.14,50.36 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6547"
+ class="st0"
+ d="m 23.93,59.15 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6549"
+ class="st0"
+ d="m 32.72,59.15 v -7.5 h 7.5 v 7.5 z" />
+ <path
+ id="path6551"
+ class="st0"
+ d="m 41.5,59.15 v -7.5 H 49 v 3.75 l -3.75,3.75 z" />
+ <path
+ id="path6553"
+ class="st0"
+ d="m 15.14,55.4 v -3.75 h 7.5 v 7.5 h -3.75 z" />
+</g>
+</svg>
diff --git a/images/numbers/redBlock.svg b/images/numbers/redBlock.svg
new file mode 100644
index 0000000..5e66cf7
--- /dev/null
+++ b/images/numbers/redBlock.svg
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 80 59.999998"
+ xml:space="preserve"
+ sodipodi:docname="my_redBlock.svg"
+ width="80"
+ height="60"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="10.102171"
+ inkscape:cx="31.478383"
+ inkscape:cy="39.150002"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#FF0000;}
+</style>
+<path
+ id="led_off"
+ class="st0"
+ d="M 0,60 V 0 h 80 v 60 z"
+ style="stroke-width:5.56038" />
+</svg>
diff --git a/images/ui/background.svg b/images/ui/background.svg
new file mode 100644
index 0000000..a08e908
--- /dev/null
+++ b/images/ui/background.svg
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1"
+ id="svg1" inkscape:output_extension="org.inkscape.output.svg.inkscape" sodipodi:docbase="/home/kdesvn/kdeedu/blinken/images" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 812.5 694.5"
+ style="enable-background:new 0 0 812.5 694.5;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#rect1709_00000064334702873236885670000004241528755142738327_);}
+ .st1{fill:none;stroke:#383838;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.1886;}
+ .st2{fill-rule:evenodd;clip-rule:evenodd;fill-opacity:0.502;}
+ .st3{fill-rule:evenodd;clip-rule:evenodd;fill:#F5F5F5;}
+ .st4{fill-rule:evenodd;clip-rule:evenodd;fill:#2B71B2;}
+ .st5{fill-rule:evenodd;clip-rule:evenodd;fill:#C30303;}
+ .st6{fill:none;stroke:#CCCCCC;stroke-width:3.125;stroke-linecap:round;stroke-linejoin:round;}
+
+ .st7{opacity:0.7746;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path1727_00000155124340099948612670000014066227957154663350_);enable-background:new ;}
+ .st8{fill-rule:evenodd;clip-rule:evenodd;fill:#7F7F7F;}
+
+ .st9{fill-rule:evenodd;clip-rule:evenodd;fill:url(#path5399_00000114045329996181949210000008322080954425342640_);stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;}
+ .st10{fill-rule:evenodd;clip-rule:evenodd;stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
+
+ .st11{opacity:0.1972;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path48097_00000182516336237710819470000015777076019582871464_);enable-background:new ;}
+
+ .st12{opacity:0.3838;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path48099_00000101825511418332618270000003415598852090494867_);enable-background:new ;}
+ .st13{fill:url(#logo_00000181797632872633696110000012755291667149670561_);}
+</style>
+<sodipodi:namedview bordercolor="#666666" borderopacity="1.0" gridtolerance="10.0" guidetolerance="10.0" height="694.5px" id="base" inkscape:current-layer="svg1" inkscape:cx="770.60061" inkscape:cy="500.01047" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-height="712" inkscape:window-width="1024" inkscape:window-x="0" inkscape:window-y="236" inkscape:zoom="0.2962773" objecttolerance="10.0" pagecolor="#ff61ff" width="812.5px">
+ </sodipodi:namedview>
+<g id="layer1">
+</g>
+<g id="blinkenBase" inkscape:label="#g3578">
+
+ <radialGradient id="rect1709_00000018956041742066813540000017622318366510112674_" cx="-275.8941" cy="1163.557" r="406.25" gradientTransform="matrix(-1.169554e-02 1.4328 1.7777 1.451054e-02 -1857.3894 506.3183)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#BF0303"/>
+ <stop offset="1" style="stop-color:#8C0000"/>
+ </radialGradient>
+
+ <path id="rect1709" sodipodi:nodetypes="ccccc" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#rect1709_00000018956041742066813540000017622318366510112674_);" d="
+ M0,0v694.5h812.5V0H0z"/>
+ <path id="path13986" class="st1" d="M787.4,278.8c0,140.7-169.9,254.9-379.3,254.9S28.8,419.5,28.8,278.8S198.8,23.8,408.1,23.8
+ S787.4,138,787.4,278.8z"/>
+ <g id="g4811" transform="translate(0,2)">
+ <path id="path8373" class="st2" d="M530.4,358.6L0,386.5v306.4h549.8L530.4,358.6z"/>
+ <path id="rect1087" class="st3" d="M522.9,353.6L0,381.1v311.8h542.7L522.9,353.6z"/>
+ <g id="g3543" transform="translate(0,32)">
+ <path id="path1711" class="st4" d="M526,374.6L0,405.2v1.2l526.1-30.6L526,374.6z M527.9,408.2L0,438.9v1.2l528-30.7L527.9,408.2
+ z M529.9,441.7L0,472.5v1.3l530-30.8L529.9,441.7z M531.8,475.2L0,506.2v1.2l531.9-30.9L531.8,475.2z M533.8,508.8L0,539.8v1.2
+ L533.9,510L533.8,508.8z M535.8,542.3L0,573.5v1.2l535.8-31.2L535.8,542.3z M537.7,575.9L0,607.2v1.2l537.8-31.3L537.7,575.9z
+ M539.7,609.4L0,640.8v1.2l539.7-31.4L539.7,609.4z M541.6,643l-309.2,18h21.3l287.9-16.8L541.6,643z"/>
+ <path id="path1710" class="st5" d="M14.9,346.1l-1.2,0.1L32,660.9h1.2L14.9,346.1z"/>
+ </g>
+ <path id="rect2359" class="st6" d="M542.7,692.9l-19.8-339.4L0,381.1"/>
+ </g>
+
+ <radialGradient id="path1727_00000074401396513925399320000013085088147146445490_" cx="328.1548" cy="303.5868" r="233.3452" gradientTransform="matrix(1.6254 0 0 -1.0926 -138.7977 625.4459)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#000000"/>
+ <stop offset="0.9545" style="stop-color:#000000"/>
+ <stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
+ </radialGradient>
+
+ <path id="path1727" style="opacity:0.7746;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path1727_00000074401396513925399320000013085088147146445490_);enable-background:new ;" d="
+ M773.9,293.8c0,140.7-169.9,254.9-379.3,254.9S15.3,434.5,15.3,293.8S185.2,38.8,394.6,38.8S773.9,153,773.9,293.8z"/>
+ <path id="path1758" class="st8" d="M787.4,278.8c0,140.7-169.9,254.9-379.3,254.9S28.8,419.5,28.8,278.8S198.8,23.8,408.1,23.8
+ S787.4,138,787.4,278.8z"/>
+
+ <radialGradient id="path5399_00000028297224035011499380000005891762418215496840_" cx="402.1226" cy="322.9481" r="381.7858" gradientTransform="matrix(1 0 0 -0.6743 6 496.527)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#323232"/>
+ <stop offset="7.692308e-02" style="stop-color:#DFE1E1"/>
+ <stop offset="0.2629" style="stop-color:#B6B1B1"/>
+ <stop offset="0.5" style="stop-color:#8D8282"/>
+ <stop offset="0.782" style="stop-color:#FFFFFF"/>
+ <stop offset="0.9006" style="stop-color:#DFD9DF"/>
+ <stop offset="1" style="stop-color:#3A3A3A"/>
+ </radialGradient>
+
+ <path id="path5399" style="fill-rule:evenodd;clip-rule:evenodd;fill:url(#path5399_00000028297224035011499380000005891762418215496840_);stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.331;" d="
+ M787.4,278.8c0,140.7-169.9,254.9-379.3,254.9S28.8,419.5,28.8,278.8S198.8,23.8,408.1,23.8S787.4,138,787.4,278.8z"/>
+ <path id="path6043" class="st10" d="M598.4,278.8c0,105-85.3,190.3-190.3,190.3s-190.3-85.3-190.3-190.3S303.1,88.5,408.1,88.5
+ S598.4,173.7,598.4,278.8z"/>
+ <g id="g48095" transform="translate(705.567,-1080.3135)">
+
+ <radialGradient id="path48097_00000006706562918716039730000010635923123959617412_" cx="-1145.7865" cy="558.0082" r="379.2859" gradientTransform="matrix(1 5.946939e-03 3.012183e-03 -0.5065 702.7335 1432.4973)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
+ </radialGradient>
+
+ <path id="path48097" sodipodi:nodetypes="ccccscsc" style="opacity:0.1972;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path48097_00000006706562918716039730000010635923123959617412_);enable-background:new ;" d="
+ M-296.2,1104c-209.4,0-345,127.9-345,268.6c1.6,13.1,5.3,18.4,2.6-5.3c0-144.4,166.1-250.2,367.9-250.2
+ c201.8,0,332.5,115,332.5,259.4c0,46.7-17.2,90.5-47.2,128.5C57.7,1463.7,83,1413.3,83,1359C83,1218.2-86.9,1104-296.2,1104z"/>
+
+ <radialGradient id="path48099_00000121990157961290319960000000628075532797803941_" cx="-1054.8149" cy="770.1763" r="379.2859" gradientTransform="matrix(1 -2.261666e-03 -3.012183e-03 -1.3318 708.3231 2176.6987)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
+ </radialGradient>
+
+ <path id="path48099" style="opacity:0.3838;fill-rule:evenodd;clip-rule:evenodd;fill:url(#path48099_00000121990157961290319960000000628075532797803941_);enable-background:new ;" d="
+ M-296.2,1104c-172.2,0-317.8,77.3-364,183.1c47.1,120.8,185.3,208.3,348.3,208.3c178.8,0,327.8-105.3,359.4-244.2
+ C-13,1164.3-144.2,1104-296.2,1104z"/>
+ </g>
+
+ <linearGradient id="logo_00000051365927271445851430000016307207654751142796_" gradientUnits="userSpaceOnUse" x1="242.0625" y1="416.4688" x2="562.0625" y2="416.4688" gradientTransform="matrix(1 0 0 -1 6 694.4375)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5" style="stop-color:#BBBBBB"/>
+ <stop offset="0.75" style="stop-color:#747474"/>
+ <stop offset="1" style="stop-color:#FFFFFF"/>
+ </linearGradient>
+ <path id="logo" style="fill:url(#logo_00000051365927271445851430000016307207654751142796_);" d="M257.2,246.1
+ c-2.5,0-4.7,0.9-6.5,2.7c-1.7,1.7-2.6,3.9-2.6,6.4v29.2c0,2.5,0.9,4.7,2.6,6.5c1.8,1.7,4,2.6,6.5,2.6h29.2c2.5,0,4.7-0.9,6.4-2.6
+ c1.8-1.8,2.7-3.9,2.7-6.5v-10.9c0-1.3-0.2-2.5-0.7-3.7c0.5-1.1,0.7-2.3,0.7-3.6v-10.9c0-2.5-0.9-4.7-2.7-6.4
+ c-1.7-1.8-3.9-2.7-6.4-2.7H257.2z M308.3,246.1c-2.5,0-4.7,0.9-6.5,2.7c-1.7,1.7-2.6,3.9-2.6,6.4v29.2c0,2.5,0.9,4.7,2.6,6.5
+ c1.8,1.7,4,2.6,6.5,2.6h29.1c2.5,0,4.7-0.9,6.4-2.6c1.8-1.8,2.7-3.9,2.7-6.5c0-2.5-0.9-4.7-2.7-6.4c-1.7-1.8-3.9-2.7-6.4-2.7h-20
+ v-20c0-2.5-0.9-4.7-2.7-6.4C313,247,310.8,246.1,308.3,246.1z M359.4,246.1c-2.5,0-4.7,0.9-6.5,2.7c-1.7,1.7-2.6,3.9-2.6,6.4
+ c0,3.1,1.2,5.5,3.6,7.3c-2.4,1.8-3.6,4.3-3.6,7.3v14.6c0,2.5,0.8,4.7,2.6,6.5c1.8,1.7,4,2.6,6.5,2.6s4.7-0.9,6.4-2.6
+ c1.8-1.8,2.7-3.9,2.7-6.5v-14.6c0-3-1.3-5.4-3.7-7.3c2.4-1.8,3.7-4.2,3.7-7.3c0-2.5-0.9-4.7-2.7-6.4
+ C364.1,247,361.9,246.1,359.4,246.1z M381.3,246.1c-2.5,0-4.7,0.9-6.5,2.7c-1.7,1.7-2.6,3.9-2.6,6.4v29.2c0,2.5,0.9,4.7,2.6,6.5
+ c1.8,1.7,3.9,2.6,6.5,2.6c2.5,0,4.7-0.9,6.5-2.6c1.8-1.8,2.7-3.9,2.7-6.5v-20h10.9v20c0,2.5,0.9,4.7,2.6,6.5c1.8,1.7,4,2.6,6.5,2.6
+ c1,0,1.9-0.2,2.8-0.4l-3.8,4.9l4.6,4.4l6.8-4.7c1.7,1,5,2.3,6.9,2.8l2.1,9.4h5.6l1.7-9.2c1.9-0.5,6-1.7,7.7-2.6l7.2,5.1l4.8-5.2
+ l-0.7-1.6l-3.6,1.5l-4-5.4c-3.3,4.3-10.1,7.1-16.1,7.1c-10,0-20.4-9.9-20.4-19.5c0-4.9,2.6-10.5,6.3-14.3h1.2v-1.1
+ c1.1-1,2.3-1.8,3.5-2.4v-1.4c-0.9,0.3-0.4,0.2-1.2,0.6l-2.3-1.5v-4.7c0-2.5-0.9-4.7-2.7-6.4c-1.7-1.8-3.8-2.7-6.4-2.7H381.3z
+ M478.7,246.1c-2.5,0-4.7,0.9-6.5,2.7c-1.7,1.7-2.6,3.9-2.6,6.4c0,3,1.3,5.4,3.7,7.3c-2.4,1.8-3.7,4.2-3.7,7.3v14.6
+ c0,2.5,0.9,4.7,2.6,6.5c1.8,1.7,4,2.6,6.5,2.6h29.1c2.5,0,4.7-0.9,6.4-2.6c1.8-1.8,2.7-3.9,2.7-6.5c0-3.1-1.2-5.5-3.6-7.3
+ c2.4-1.8,3.6-4.3,3.6-7.3c0-3.1-1.2-5.5-3.6-7.3c2.4-1.8,3.6-4.3,3.6-7.3c0-2.5-0.9-4.7-2.7-6.4c-1.7-1.8-3.9-2.7-6.4-2.7H478.7z
+ M529.8,246.1c-2.5,0-4.7,0.9-6.5,2.7c-1.7,1.7-2.6,3.9-2.6,6.4v29.2c0,2.5,0.8,4.7,2.6,6.5c1.8,1.7,4,2.6,6.5,2.6s4.7-0.9,6.4-2.6
+ c1.8-1.8,2.7-3.9,2.7-6.5v-20h10.9v20c0,2.5,0.9,4.7,2.6,6.5c1.8,1.7,3.9,2.6,6.5,2.6c2.5,0,4.7-0.9,6.4-2.6
+ c1.8-1.8,2.7-3.9,2.7-6.5v-29.2c0-2.5-0.9-4.7-2.7-6.4c-1.7-1.8-3.9-2.7-6.4-2.7H529.8z M453.2,248l-13.6,18.5l0.5-18.3l-11.1,1.2
+ v41.5l11.1-0.8l0.1-17.6l13.6,19.2L465,288l-13.9-18.9v-0.2l13.4-17.3L453.2,248z M256.6,249.8c0.2,0,0.4,0,0.6,0h29.2
+ c1.5,0,2.8,0.5,3.9,1.6c1.1,1.1,1.6,2.3,1.6,3.8v10.9c0,1.5-0.5,2.7-1.4,3.6c0.9,1,1.4,2.2,1.4,3.7v10.9c0,1.5-0.5,2.8-1.6,3.8
+ c-1.1,1.1-2.4,1.6-3.9,1.6h-29.2c-1.5,0-2.8-0.6-3.9-1.6s-1.6-2.3-1.6-3.8v-29.2c0-1.5,0.5-2.8,1.6-3.8
+ C254.2,250.5,255.4,249.9,256.6,249.8z M307.8,249.8c0.2,0,0.4,0,0.6,0c1.5,0,2.8,0.5,3.8,1.6c1.1,1.1,1.6,2.3,1.6,3.8v23.7h23.7
+ c1.5,0,2.8,0.6,3.9,1.7c1.1,1.1,1.6,2.3,1.6,3.8c0,1.5-0.5,2.8-1.6,3.8c-1.1,1.1-2.4,1.6-3.9,1.6h-29.1c-1.5,0-2.8-0.6-3.9-1.6
+ c-1.1-1.1-1.6-2.3-1.6-3.8v-29.2c0-1.5,0.5-2.8,1.6-3.8C305.4,250.5,306.5,249.9,307.8,249.8z M358.9,249.8c0.2,0,0.3,0,0.5,0
+ c1.5,0,2.8,0.5,3.8,1.6c1.1,1.1,1.6,2.3,1.6,3.8c0,1.5-0.5,2.8-1.6,3.9c-1.1,1.1-2.3,1.6-3.8,1.6c-1.5,0-2.8-0.5-3.9-1.6
+ c-1.1-1.1-1.6-2.4-1.6-3.9c0-1.5,0.5-2.8,1.6-3.8C356.5,250.5,357.6,249.9,358.9,249.8z M380.8,249.8c0.2,0,0.3,0,0.5,0h29.2
+ c1.5,0,2.8,0.5,3.8,1.6c1.1,1.1,1.6,2.3,1.6,3.8v2.3l-1.3-0.9l-4.8,4.1l5.6,8.2c-0.9,1.4-2.5,4-3,5.6l-7.4,1.8v-15.7h-18.2v23.7
+ c0,1.5-0.5,2.8-1.6,3.8c-1.1,1.1-2.4,1.6-3.9,1.6s-2.8-0.6-3.8-1.6s-1.6-2.3-1.6-3.8v-29.2c0-1.5,0.5-2.8,1.6-3.8
+ C378.4,250.5,379.5,249.9,380.8,249.8z M478.2,249.8c0.2,0,0.4,0,0.6,0h29.1c1.5,0,2.8,0.5,3.9,1.6c1.1,1.1,1.6,2.3,1.6,3.8
+ c0,1.5-0.5,2.8-1.6,3.9c-1.1,1.1-2.4,1.6-3.9,1.6h-29.1c-1.5,0-2.8-0.5-3.9-1.6c-1.1-1.1-1.6-2.4-1.6-3.9c0-1.5,0.5-2.8,1.6-3.8
+ C475.8,250.5,476.9,249.9,478.2,249.8z M529.2,249.8c0.2,0,0.4,0,0.6,0h29.1c1.5,0,2.8,0.5,3.9,1.6c1.1,1.1,1.6,2.3,1.6,3.8v29.2
+ c0,1.5-0.5,2.8-1.6,3.8c-1.1,1.1-2.4,1.6-3.9,1.6c-1.5,0-2.8-0.6-3.8-1.6c-1.1-1.1-1.6-2.3-1.6-3.8v-23.7h-18.2v23.7
+ c0,1.5-0.5,2.8-1.6,3.8c-1.1,1.1-2.3,1.6-3.8,1.6s-2.8-0.6-3.9-1.6c-1.1-1.1-1.6-2.3-1.6-3.8v-29.2c0-1.5,0.5-2.8,1.6-3.8
+ C526.9,250.5,528,249.9,529.2,249.8z M262.7,260.7v3.7h18.2v-3.7H262.7z M359.4,264.4c1.5,0,2.8,0.5,3.8,1.6
+ c1.1,1.1,1.6,2.3,1.6,3.8v14.6c0,1.5-0.5,2.8-1.6,3.8c-1.1,1.1-2.3,1.6-3.8,1.6s-2.8-0.6-3.9-1.6c-1.1-1.1-1.6-2.3-1.6-3.8v-14.6
+ c0-1.5,0.5-2.8,1.6-3.8C356.6,264.9,357.9,264.4,359.4,264.4z M478.7,264.4h29.1c1.5,0,2.8,0.5,3.9,1.6c1.1,1.1,1.6,2.3,1.6,3.8
+ c0,1.5-0.5,2.8-1.6,3.9c-1.1,1.1-2.4,1.6-3.9,1.6h-23.7v3.6h23.7c1.5,0,2.8,0.6,3.9,1.7c1.1,1.1,1.6,2.3,1.6,3.8
+ c0,1.5-0.5,2.8-1.6,3.8c-1.1,1.1-2.4,1.6-3.9,1.6h-29.1c-1.5,0-2.8-0.6-3.9-1.6s-1.6-2.3-1.6-3.8v-14.6c0-1.5,0.5-2.8,1.6-3.8
+ C475.9,264.9,477.2,264.4,478.7,264.4z M262.7,275.3v3.6h18.2v-3.6H262.7z M405,283l6.9,2.2c0.3,0.9,1,2.3,1.7,3.6
+ c-0.9,0.7-1.9,1.1-3.1,1.1c-1.5,0-2.8-0.6-3.9-1.6c-1.1-1.1-1.6-2.3-1.6-3.8V283z M414.3,289.9c0.2,0.3,0.4,0.8,0.6,1.1l-0.6,0.8
+ V289.9z"/>
+</g>
+</svg>
diff --git a/images/ui/downArrow.svg b/images/ui/downArrow.svg
new file mode 100644
index 0000000..16ff09e
--- /dev/null
+++ b/images/ui/downArrow.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" id="layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 792 612" style="enable-background:new 0 0 792 612;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#B73822;}
+</style>
+<path id="arrow_down" class="st0" d="M386.71,291.69c-1.37,0.14-2.56,1.02-3.08,2.29s-0.32,2.73,0.55,3.8l16.26,20.02
+ c0.71,0.87,1.78,1.38,2.91,1.38c1.13,0,2.2-0.51,2.91-1.38l16.26-20.02c0.9-1.12,1.08-2.66,0.46-3.96c-0.62-1.3-1.93-2.13-3.37-2.14
+ h-32.52C386.96,291.69,386.83,291.69,386.71,291.69z"/>
+</svg>
diff --git a/images/ui/exit.svg b/images/ui/exit.svg
new file mode 100644
index 0000000..2f63e44
--- /dev/null
+++ b/images/ui/exit.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 600 600"
+ xml:space="preserve"
+ sodipodi:docname="exit.svg"
+ width="600"
+ height="600"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13654"
+ inkscape:cy="306"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1"
+ inkscape:clip-to-page="false" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#972C23;}
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#B73822;}
+ .st2{fill:none;stroke:#B73822;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<g
+ id="quit_normal"
+ transform="matrix(6.6666654,0,0,6.6666667,-1539.4663,-1804.0667)">
+ <path
+ id="rect5194"
+ class="st0"
+ d="m 240.59,270.61 h 70.66 c 5.34,0 9.67,4.33 9.67,9.67 v 70.66 c 0,5.34 -4.33,9.67 -9.67,9.67 h -70.66 c -5.34,0 -9.67,-4.33 -9.67,-9.67 v -70.66 c -0.01,-5.34 4.32,-9.67 9.67,-9.67 z" />
+ <path
+ id="path696"
+ class="st1"
+ d="m 286.6,288.78 v 11.15 c 8.64,4.57 13.55,14.18 11.82,24.07 -1.92,11.06 -11.49,19 -22.72,18.9 -11.23,-0.1 -20.65,-8.27 -22.38,-19.37 -1.52,-9.8 3.39,-19.25 11.91,-23.73 v -10.98 c -11.28,3.87 -19.39,13.4 -21.7,24.58 -0.78,3.77 -0.87,7.71 -0.25,11.7 2.48,15.94 16.2,27.87 32.34,28.01 16.14,0.14 30.08,-11.52 32.84,-27.42 2.77,-15.9 -6.46,-31.56 -21.7,-36.87 -0.05,-0.02 -0.11,-0.03 -0.16,-0.04 z" />
+ <path
+ id="path701"
+ class="st2"
+ d="M 275.92,281.87 V 316.8" />
+</g>
+</svg>
diff --git a/images/ui/exit_highlight.svg b/images/ui/exit_highlight.svg
new file mode 100644
index 0000000..a405f34
--- /dev/null
+++ b/images/ui/exit_highlight.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 600 600"
+ xml:space="preserve"
+ sodipodi:docname="my_exit_highlight.svg"
+ width="600"
+ height="600"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13654"
+ inkscape:cy="306"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#B73822;}
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#972C23;}
+ .st2{fill:none;stroke:#972C23;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<g
+ id="quit_hover"
+ transform="matrix(6.6666667,0,0,6.6666667,-922.26667,-1002.9333)">
+ <path
+ id="rect1564"
+ class="st0"
+ d="m 148.01,150.44 h 70.66 c 5.34,0 9.67,4.33 9.67,9.67 v 70.66 c 0,5.34 -4.33,9.67 -9.67,9.67 h -70.66 c -5.34,0 -9.67,-4.33 -9.67,-9.67 v -70.66 c 0,-5.34 4.33,-9.67 9.67,-9.67 z" />
+ <g
+ id="g1362"
+ transform="translate(2.70918,-121.0568)">
+ <path
+ id="path1567"
+ class="st1"
+ d="m 191.31,289.66 v 11.15 c 8.64,4.57 13.55,14.18 11.82,24.07 -1.92,11.06 -11.49,19 -22.72,18.9 -11.23,-0.1 -20.65,-8.27 -22.38,-19.37 -1.52,-9.8 3.39,-19.25 11.91,-23.73 V 289.7 c -11.28,3.87 -19.39,13.4 -21.7,24.58 -0.78,3.77 -0.87,7.71 -0.25,11.7 2.48,15.94 16.2,27.87 32.34,28.01 16.14,0.14 30.08,-11.52 32.84,-27.42 2.77,-15.9 -6.46,-31.56 -21.7,-36.87 -0.04,-0.02 -0.1,-0.02 -0.16,-0.04 z" />
+ <path
+ id="path1569"
+ class="st2"
+ d="m 180.63,282.75 v 34.93" />
+ </g>
+</g>
+</svg>
diff --git a/images/ui/highScore.svg b/images/ui/highScore.svg
new file mode 100644
index 0000000..3d6886e
--- /dev/null
+++ b/images/ui/highScore.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 600 600"
+ xml:space="preserve"
+ sodipodi:docname="highScore.svg"
+ width="600"
+ height="600"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13654"
+ inkscape:cy="306"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#972C23;}
+
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#B73822;stroke:#B73822;stroke-width:3.125;stroke-linecap:round;stroke-linejoin:round;}
+ .st2{fill:none;stroke:#B73822;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<g
+ id="highscore_normal"
+ transform="matrix(6.6666667,0,0,6.6666667,4027.7333,-1136.2667)">
+ <path
+ id="rect5192"
+ class="st0"
+ d="m -594.49,170.44 h 70.66 c 5.34,0 9.67,4.33 9.67,9.67 v 70.66 c 0,5.34 -4.33,9.67 -9.67,9.67 h -70.66 c -5.34,0 -9.67,-4.33 -9.67,-9.67 v -70.66 c 0,-5.34 4.33,-9.67 9.67,-9.67 z" />
+ <g
+ id="g2366"
+ transform="translate(-140.8411,-59.62938)">
+ <path
+ id="path2345"
+ class="st1"
+ d="m -452.03,239.13 h 12.52 c 1.23,0 2.22,0.99 2.22,2.21 v 12.45 c 0,1.22 -0.99,2.21 -2.22,2.21 h -12.52 c -1.23,0 -2.22,-0.99 -2.22,-2.21 v -12.45 c 0,-1.22 0.99,-2.21 2.22,-2.21 z" />
+ <path
+ id="path2349"
+ class="st1"
+ d="m -452.03,266.64 h 12.52 c 1.23,0 2.22,0.99 2.22,2.21 v 12.45 c 0,1.22 -0.99,2.21 -2.22,2.21 h -12.52 c -1.23,0 -2.22,-0.99 -2.22,-2.21 v -12.45 c 0,-1.23 0.99,-2.21 2.22,-2.21 z" />
+ <g
+ id="g2361"
+ transform="translate(-1.25)">
+ <path
+ id="path2343"
+ class="st2"
+ d="m -422.59,247.57 h 36.36" />
+ <path
+ id="path2347"
+ class="st2"
+ d="m -422.59,275.07 h 29.82" />
+ <path
+ id="path2351"
+ class="st2"
+ d="m -422.59,302.57 h 39.27" />
+ </g>
+ <path
+ id="path2353"
+ class="st1"
+ d="m -452.03,294.14 h 12.52 c 1.23,0 2.22,0.99 2.22,2.21 v 12.45 c 0,1.22 -0.99,2.21 -2.22,2.21 h -12.52 c -1.23,0 -2.22,-0.99 -2.22,-2.21 v -12.45 c 0,-1.23 0.99,-2.21 2.22,-2.21 z" />
+ </g>
+</g>
+</svg>
diff --git a/images/ui/highScore_highlight.svg b/images/ui/highScore_highlight.svg
new file mode 100644
index 0000000..2fb817f
--- /dev/null
+++ b/images/ui/highScore_highlight.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 600 600"
+ xml:space="preserve"
+ sodipodi:docname="highScore_highlight.svg"
+ width="600"
+ height="600"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13654"
+ inkscape:cy="306"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#B73822;}
+
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#972C23;stroke:#972C23;stroke-width:3.125;stroke-linecap:round;stroke-linejoin:round;}
+ .st2{fill:none;stroke:#972C23;stroke-width:7.5;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<g
+ id="highscore_hover"
+ transform="matrix(6.6666667,0,0,6.6666667,3761.0667,-336.26667)">
+ <path
+ id="rect2331"
+ class="st0"
+ d="m -554.49,50.44 h 70.66 c 5.34,0 9.67,4.33 9.67,9.67 v 70.66 c 0,5.34 -4.33,9.67 -9.67,9.67 h -70.66 c -5.34,0 -9.67,-4.33 -9.67,-9.67 V 60.11 c 0,-5.34 4.33,-9.67 9.67,-9.67 z" />
+ <g
+ id="g4377"
+ transform="translate(-0.841086,-179.6294)">
+ <path
+ id="path4379"
+ class="st1"
+ d="m -552.03,239.13 h 12.52 c 1.23,0 2.22,0.99 2.22,2.21 v 12.45 c 0,1.22 -0.99,2.21 -2.22,2.21 h -12.52 c -1.23,0 -2.22,-0.99 -2.22,-2.21 v -12.45 c 0,-1.22 0.99,-2.21 2.22,-2.21 z" />
+ <path
+ id="path4381"
+ class="st1"
+ d="m -552.03,266.64 h 12.52 c 1.23,0 2.22,0.99 2.22,2.21 v 12.45 c 0,1.22 -0.99,2.21 -2.22,2.21 h -12.52 c -1.23,0 -2.22,-0.99 -2.22,-2.21 v -12.45 c 0,-1.23 0.99,-2.21 2.22,-2.21 z" />
+ <g
+ id="g4383"
+ transform="translate(-1.25)">
+ <path
+ id="path4385"
+ class="st2"
+ d="m -522.59,247.57 h 36.36" />
+ <path
+ id="path4387"
+ class="st2"
+ d="m -522.59,275.07 h 29.82" />
+ <path
+ id="path4389"
+ class="st2"
+ d="m -522.59,302.57 h 39.27" />
+ </g>
+ <path
+ id="path4391"
+ class="st1"
+ d="m -552.03,294.14 h 12.52 c 1.23,0 2.22,0.99 2.22,2.21 v 12.45 c 0,1.22 -0.99,2.21 -2.22,2.21 h -12.52 c -1.23,0 -2.22,-0.99 -2.22,-2.21 v -12.45 c 0,-1.23 0.99,-2.21 2.22,-2.21 z" />
+ </g>
+</g>
+</svg>
diff --git a/images/ui/menu.svg b/images/ui/menu.svg
new file mode 100644
index 0000000..29d2479
--- /dev/null
+++ b/images/ui/menu.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 600 600"
+ xml:space="preserve"
+ sodipodi:docname="menu.svg"
+ width="600"
+ height="600"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#972C23;}
+
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#B73822;stroke:#B73822;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
+ .st2{fill-rule:evenodd;clip-rule:evenodd;fill:#972C23;stroke:#972C23;stroke-width:1.7442;stroke-linejoin:round;}
+</style>
+<g
+ id="help_normal"
+ transform="matrix(2.6666667,0,0,2.6666667,-273.57334,-1303.1734)">
+ <path
+ id="path4462"
+ class="st0"
+ d="m 248.01,624.44 h 70.66 c 5.36,0 9.67,4.31 9.67,9.67 v 70.66 c 0,5.36 -4.31,9.67 -9.67,9.67 h -70.66 c -5.36,0 -9.67,-4.31 -9.67,-9.67 v -70.66 c 0,-5.36 4.32,-9.67 9.67,-9.67 z" />
+ <path
+ id="path839"
+ class="st1"
+ d="m 283.34,634.44 c -19.32,0 -35,15.68 -35,35 0,19.32 15.68,35 35,35 19.32,0 35,-15.68 35,-35 0,-19.32 -15.68,-35 -35,-35 z" />
+ <path
+ id="path2224"
+ class="st2"
+ d="m 282.67,642.43 c 2.34,0 4.56,0.36 6.58,1.05 2.03,0.69 3.81,1.68 5.37,3.02 1.56,1.34 2.78,2.95 3.65,4.86 0.89,1.89 1.3,4.04 1.3,6.37 0,4.18 -1.03,7.41 -3.14,9.73 -2.09,2.29 -4.96,3.81 -8.64,4.57 v 1.34 c 0,1.07 -0.25,2.03 -0.71,2.85 -0.45,0.8 -1.11,1.42 -1.93,1.89 -0.8,0.45 -1.7,0.67 -2.73,0.67 -1.02,0 -1.94,-0.23 -2.77,-0.67 -0.8,-0.47 -1.42,-1.09 -1.89,-1.89 -0.45,-0.82 -0.67,-1.78 -0.67,-2.85 v -5.49 c 0,-1.14 0.2,-2.04 0.67,-2.64 0.47,-0.6 1.05,-0.99 1.72,-1.22 0.67,-0.22 1.68,-0.47 3.02,-0.71 3.47,-0.65 5.2,-2.39 5.2,-5.24 0,-1.16 -0.49,-2.19 -1.43,-3.1 -0.91,-0.93 -2.11,-1.38 -3.61,-1.38 -1.38,0 -2.47,0.26 -3.27,0.8 -0.78,0.53 -1.53,1.32 -2.31,2.35 -0.76,1 -1.51,1.77 -2.22,2.31 -0.69,0.51 -1.65,0.75 -2.85,0.75 -1.36,0 -2.5,-0.53 -3.48,-1.55 -0.96,-1.05 -1.43,-2.25 -1.43,-3.65 0,-2.45 0.79,-4.61 2.39,-6.46 1.6,-1.85 3.59,-3.26 6,-4.23 2.42,-1 4.8,-1.48 7.18,-1.48 z m -0.33,41.99 c 1.05,0 2.02,0.28 2.93,0.84 0.91,0.56 1.6,1.29 2.14,2.22 0.56,0.91 0.84,1.91 0.84,2.98 0,1.09 -0.28,2.13 -0.84,3.06 -0.53,0.91 -1.25,1.6 -2.18,2.14 -0.91,0.53 -1.87,0.8 -2.89,0.8 -1.02,0 -2,-0.26 -2.93,-0.8 -0.91,-0.53 -1.67,-1.23 -2.22,-2.14 -0.53,-0.93 -0.8,-1.97 -0.8,-3.06 0,-1.07 0.26,-2.06 0.8,-2.98 0.56,-0.93 1.31,-1.67 2.22,-2.22 0.9,-0.56 1.88,-0.84 2.93,-0.84 z" />
+</g>
+</svg>
diff --git a/images/ui/menu_list.svg b/images/ui/menu_list.svg
new file mode 100644
index 0000000..b027061
--- /dev/null
+++ b/images/ui/menu_list.svg
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ id="layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 600 600"
+ xml:space="preserve"
+ sodipodi:docname="menu_list.svg"
+ width="600"
+ height="600"
+ inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs1" /><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:zoom="1.2924837"
+ inkscape:cx="396.13652"
+ inkscape:cy="305.99999"
+ inkscape:window-width="1920"
+ inkscape:window-height="1001"
+ inkscape:window-x="1911"
+ inkscape:window-y="-9"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer_1" />
+<style
+ type="text/css"
+ id="style1">
+ .st0{fill:#B73822;}
+
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#972C23;stroke:#972C23;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:5.531;}
+
+ .st2{fill-rule:evenodd;clip-rule:evenodd;fill:#B73822;stroke:#B73822;stroke-width:1.7442;stroke-linejoin:round;stroke-miterlimit:5.531;}
+ .st3{fill-rule:evenodd;clip-rule:evenodd;fill:#972C23;}
+ .st4{fill-rule:evenodd;clip-rule:evenodd;fill:#B73822;}
+ .st5{fill:none;stroke:#972C23;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+ .st6{fill:none;stroke:#972C23;stroke-width:33.4712;stroke-linecap:round;stroke-linejoin:round;}
+ .st7{fill:none;stroke:#972C23;stroke-width:2.5;stroke-miterlimit:3.8072;}
+ .st8{fill:#972C23;}
+
+ .st9{fill-rule:evenodd;clip-rule:evenodd;fill:#B73822;stroke:#972C23;stroke-width:5;stroke-linejoin:round;stroke-miterlimit:3.8974;}
+ .st10{fill:none;stroke:#972C23;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+
+<g
+ id="help_hover-9"
+ inkscape:label="#g3538"
+ transform="matrix(2.6666665,0,0,2.6666667,-1784.7408,-2075.0857)"><path
+ d="m 678.9501,935.65713 h 136.1555 v 67.49997 H 678.9501 c -5.35843,0 -9.67225,-4.31379 -9.67225,-9.67222 v -48.1555 c 0,-5.35843 4.31382,-9.67225 9.67225,-9.67225 z"
+ style="color:#000000;display:block;visibility:visible;fill:#c0351d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ id="path3101-6" /><path
+ d="m 894.27785,787.82938 v 136.1555 h -67.5 v -136.1555 c 0,-5.35843 4.31382,-9.67225 9.67225,-9.67225 h 48.15545 c 5.3585,0 9.6723,4.31382 9.6723,9.67225 z"
+ style="color:#000000;display:block;visibility:visible;fill:#c0351d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ id="path3105-9" /><g
+ inkscape:label="#help_hover"
+ transform="translate(97.852176,324.58283)"
+ id="help_2-4"><path
+ id="path3107-8"
+ style="color:#000000;display:block;visibility:visible;fill:#c0351d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ d="m 716.09793,588.5743 h 70.65544 c 5.3585,0 9.6723,4.31383 9.6723,9.67226 v 70.65548 c 0,5.35843 -4.3138,9.67226 -9.6723,9.67226 h -70.65544 c -5.35843,0 -9.67226,-4.31383 -9.67226,-9.67226 v -70.65548 c 0,-5.35843 4.31383,-9.67226 9.67226,-9.67226 z" /><g
+ id="g3109-3"
+ transform="matrix(1.382752,0,0,1.382752,-554.83363,121.5393)"><path
+ id="path3111-1"
+ style="font-size:12px;fill:#9e2800;fill-opacity:1;fill-rule:evenodd;stroke:#9e2800;stroke-width:3.61598;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1"
+ d="m 944.68089,344.98856 c -13.9699,0 -25.3129,11.34303 -25.3129,25.31286 0,13.96983 11.343,25.31286 25.3129,25.31286 13.9698,0 25.3128,-11.34303 25.3128,-25.31286 0,-13.96983 -11.343,-25.31286 -25.3128,-25.31286 z" /><path
+ id="path3113-6"
+ style="font-size:12px;fill:#c0351d;fill-opacity:1;fill-rule:evenodd;stroke:#c0351d;stroke-width:1.26137;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1"
+ d="m 944.19579,350.76624 c 1.6898,0 3.2955,0.25906 4.7601,0.75798 1.4645,0.49891 2.7542,1.21732 3.8808,2.18297 1.1266,0.96565 2.0101,2.13293 2.6378,3.51702 0.6437,1.368 0.9399,2.91863 0.9399,4.60851 0,3.0257 -0.745,5.36025 -2.274,7.03404 -1.5128,1.6577 -3.5902,2.75759 -6.2457,3.30479 v 0.97021 c 0,0.77252 -0.1775,1.46621 -0.5154,2.0617 -0.3219,0.57939 -0.7992,1.02639 -1.3947,1.36436 -0.5794,0.32189 -1.2304,0.48511 -1.9708,0.48511 -0.7403,0 -1.4055,-0.16323 -2.001,-0.48511 -0.5794,-0.33797 -1.0264,-0.78497 -1.3644,-1.36436 -0.3219,-0.59549 -0.4851,-1.28918 -0.4851,-2.0617 v -3.97181 c 0,-0.8208 0.1471,-1.47556 0.4851,-1.9101 0.338,-0.43454 0.7603,-0.71832 1.2431,-0.87926 0.4828,-0.16094 1.2173,-0.33839 2.183,-0.51542 2.5107,-0.46673 3.7596,-1.72985 3.7596,-3.78989 0,-0.8369 -0.3549,-1.58376 -1.0309,-2.24362 -0.6599,-0.67596 -1.5291,-1.00053 -2.6074,-1.00053 -0.9979,0 -1.7855,0.1898 -2.3649,0.57606 -0.5633,0.38626 -1.1043,0.95754 -1.6676,1.69787 -0.5472,0.72424 -1.0919,1.2813 -1.6069,1.66756 -0.4989,0.37016 -1.1926,0.54574 -2.0617,0.54574 -0.9817,0 -1.8084,-0.38148 -2.5165,-1.12181 -0.692,-0.75642 -1.0308,-1.62383 -1.0308,-2.63776 0,-1.77036 0.5694,-3.33334 1.7281,-4.66915 1.1588,-1.33582 2.5975,-2.35409 4.3357,-3.06223 1.7381,-0.70814 3.4625,-1.06117 5.1846,-1.06117 z m -0.2426,30.36877 c 0.7564,0 1.4625,0.20402 2.1223,0.60638 0.6599,0.40235 1.1601,0.93096 1.5463,1.60691 0.4024,0.65987 0.6064,1.38014 0.6064,2.15266 0,0.78861 -0.204,1.53734 -0.6064,2.2133 -0.3862,0.65986 -0.9006,1.16002 -1.5766,1.54628 -0.6598,0.38626 -1.3517,0.57606 -2.092,0.57606 -0.7403,0 -1.4464,-0.1898 -2.1223,-0.57606 -0.6599,-0.38626 -1.2046,-0.88642 -1.607,-1.54628 -0.3862,-0.67596 -0.576,-1.42469 -0.576,-2.2133 0,-0.77252 0.1898,-1.49279 0.576,-2.15266 0.4024,-0.67595 0.9471,-1.20455 1.607,-1.60691 0.6598,-0.40236 1.3659,-0.60638 2.1223,-0.60638 z" /></g></g><g
+ transform="translate(97.852176,324.58283)"
+ id="g2683-8"><rect
+ id="rect3103-3"
+ style="color:#000000;display:block;visibility:visible;fill:#c0351d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ y="611.07428"
+ x="571.42566"
+ ry="9.6722517"
+ rx="9.6722517"
+ height="67.5"
+ width="67.5" /><g
+ id="g3119-0"
+ transform="matrix(0.19122,0,0,0.186277,554.15057,579.624)"><path
+ id="path3121-5"
+ style="fill:#9e2800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 182.8125,241.875 -25.90625,25.9375 22.625,35.90625 c -3.81735,6.61524 -6.85829,13.71659 -9.0625,21.1875 l -40.90625,10.5625 v 33.09375 l 41.125,12.875 c 2.12042,6.95968 5.00463,13.56753 8.53125,19.78125 l -23.75,32.125 27.34375,27.34375 30.53125,-24.15625 c 7.44407,4.59257 15.51144,8.22743 24.09375,10.71875 l 9.4375,43.6875 h 34.5625 l 7.3125,-42.71875 c 8.66254,-2.16693 16.85304,-5.51868 24.4375,-9.8125 L 345.5,462.125 l 25.90625,-28.78125 -5.33043,-14.22336 -16.21875,6.84375 -19.95082,-25.74539 c -14.6732,20.04461 -38.30889,33.125 -65.03125,33.125 -44.49989,0 -80.625,-36.12511 -80.625,-80.625 0,-31.55572 18.22047,-58.80241 44.65625,-72.03125 V 261.125 c -3.87571,1.52469 -7.60008,3.32993 -11.21875,5.3125 z" /><path
+ id="path3123-1"
+ style="fill:#9e2800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 237.17711,215.20644 v 197.9869 l 51.40534,-3.56274 0.50896,-83.97902 63.11151,91.61347 51.9143,-17.30476 -64.6384,-90.08659 v -1.01793 l 62.60255,-82.45213 -52.93223,-17.30476 -63.11151,88.05073 2.5,-87.54177 z" /></g></g><g
+ transform="translate(97.852176,324.58283)"
+ id="g2666-2"><rect
+ id="rect3115-1"
+ style="color:#000000;display:block;visibility:visible;fill:#c0351d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ y="611.07428"
+ x="638.92566"
+ ry="9.6722517"
+ rx="9.6722517"
+ height="67.5"
+ width="67.5" /><g
+ id="g3125-0"
+ transform="matrix(0.951792,0,0,0.951792,644.12217,616.2705)"><path
+ id="path3127-4"
+ style="color:#000000;fill:#c0351d;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ transform="matrix(0.103616,0.05982302,-0.04021123,0.06964794,-9.616397,-30.8242)"
+ d="m 774.28194,408.68011 c 0,128.80657 -104.53867,233.34523 -233.34523,233.34523 -128.80657,0 -233.34523,-104.53866 -233.34523,-233.34523 0,-128.80656 104.53866,-233.34523 233.34523,-233.34523 128.80656,0 233.34523,104.53867 233.34523,233.34523 z" /><path
+ id="path3129-7"
+ style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#9e2800;stroke-width:38.2315;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ transform="matrix(0.05949876,0.0343516,-0.0343516,0.05949876,10.50202,-13.95859)"
+ d="m 781.353,412.21564 c 0,119.0485 -96.61908,215.66757 -215.66758,215.66757 -119.04849,0 -215.66757,-96.61907 -215.66757,-215.66757 0,-119.0485 96.61908,-215.66757 215.66757,-215.66757 119.0485,0 215.66758,96.61907 215.66758,215.66757 z" /><path
+ id="path3131-4"
+ style="color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#9e2800;stroke-width:33.4712;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ transform="matrix(0.103616,0.05982302,-0.04021123,0.06964794,-9.616397,-30.8242)"
+ d="m 774.28194,408.68011 c 0,128.80657 -104.53867,233.34523 -233.34523,233.34523 -128.80657,0 -233.34523,-104.53866 -233.34523,-233.34523 0,-128.80656 104.53866,-233.34523 233.34523,-233.34523 128.80656,0 233.34523,104.53867 233.34523,233.34523 z" /><path
+ id="path3133-1"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#9e2800;stroke-width:2.62663;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
+ d="m 20.587041,46.30389 2.218451,-3.842473" /><path
+ id="path3135-3"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#9e2800;stroke-width:2.62663;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
+ d="m 37.194684,17.538588 2.21845,-3.84247" /><path
+ id="path3137-9"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#9e2800;stroke-width:2.62663;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
+ d="m 5.7609455,16.005538 11.7777275,6.79987" /><path
+ id="path3139-5"
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#9e2800;stroke-width:2.62663;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1"
+ d="M 42.461503,37.1946 54.23923,43.994469" /><g
+ id="g3141-9"
+ transform="matrix(0.383581,0.221461,-0.221461,0.383581,8.370102,-2.862335)"><path
+ id="path3144-6"
+ style="font-size:24px;font-family:'VAG Rounded Black SSi';fill:#9e2800;fill-opacity:1;stroke-width:1pt"
+ d="M 74.967284,42.730939 V 21.67593 c 0,-0.775896 0.252754,-1.422477 0.758263,-1.939741 0.505509,-0.517265 1.157967,-0.775897 1.95737,-0.775897 0.799412,0 1.445993,0.240999 1.939745,0.722995 0.505518,0.481996 0.758263,1.14621 0.758263,1.992643 v 7.864769 l 8.887539,-9.557634 c 0.623068,-0.681848 1.322546,-1.022773 2.098453,-1.022773 0.717114,0 1.340182,0.246876 1.869204,0.740629 0.540779,0.481996 0.81116,1.11682 0.81116,1.904473 0,0.446728 -0.141073,0.887579 -0.423211,1.322551 -0.282137,0.423217 -0.734749,0.946358 -1.357817,1.569426 l -6.824365,6.789095 8.340891,8.799371 c 0.481995,0.458485 0.852308,0.905213 1.110932,1.340186 0.27039,0.423216 0.405593,0.893456 0.405593,1.410721 0,0.82292 -0.270398,1.451866 -0.811168,1.886838 -0.540779,0.423217 -1.210866,0.634825 -2.010277,0.634825 -0.470238,0 -0.899336,-0.129316 -1.287285,-0.387948 -0.376193,-0.258633 -0.858187,-0.693605 -1.445985,-1.304917 L 80.380925,33.437815 v 9.293124 c 0,0.740629 -0.252745,1.363697 -0.758263,1.869205 -0.505499,0.505509 -1.152081,0.758263 -1.939745,0.758263 -0.787655,0 -1.440111,-0.240998 -1.95737,-0.722995 -0.505509,-0.481996 -0.758263,-1.116819 -0.758263,-1.904473 z" /><path
+ id="path3146-9"
+ style="font-size:12px;fill:#9e2800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.592158;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1"
+ d="m 61.699541,52.988338 3.46878,3.633952 5.616089,-3.799151 4.459855,1.982189 1.486616,6.510433 4.752861,0.0027 1.358774,-6.726702 4.459853,-1.768591 5.616085,3.633983 2.642874,-3.633983 -6.373569,-6.014866 C 72.851667,61.791746 58.734768,38.698541 72.766573,29.037295 l -1.8e-5,-3.468781 -1.651782,0.825896 -5.120569,-3.255228 -3.537201,2.904804 3.206844,5.470982 -1.816972,4.459848 -6.257519,1.039474 -0.01931,4.741815 6.442017,1.651798 1.775676,4.335986 z" /></g><path
+ id="path3148-4"
+ style="fill:#9e2800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 19.965042,8.6470185 4.44735,7.7258455 c 0.353344,-0.144114 0.691878,-0.339608 1.054821,-0.456138 L 21.27644,8.6755271 c -0.432996,0.00221 -0.887398,-0.050745 -1.311398,-0.028509 z m 3.677617,0.085526 3.934194,6.7565515 c 0.391859,-0.06569 0.77218,-0.165171 1.168855,-0.199561 L 25.039583,8.8750877 C 24.568468,8.81779 24.106979,8.7679947 23.642659,8.7325445 Z M 17.769875,8.932105 c -0.403196,0.057638 -0.805774,0.093852 -1.197363,0.171052 l 5.046032,8.752157 c 0.312028,-0.214042 0.614774,-0.438587 0.940786,-0.62719 z m 9.863996,0.3706126 3.535073,6.1008524 c 0.446082,0.03397 0.895476,0.01055 1.339907,0.08553 L 29.087812,9.5592955 C 28.594356,9.4374818 28.123428,9.4016357 27.633871,9.3027176 Z M 14.519889,9.5878041 c -0.37117,0.1130223 -0.698094,0.2948745 -1.054821,0.4276299 l 5.73024,9.921012 c 0.265219,-0.284424 0.542678,-0.538328 0.826751,-0.798242 z m 17.447297,0.9122769 3.39253,5.872783 c 0.563326,0.219731 1.137292,0.360838 1.682011,0.655699 L 33.59218,11.041746 C 33.0478,10.832311 32.510958,10.681892 31.967186,10.500081 Z M 11.52648,10.756659 c -0.343688,0.176736 -0.615964,0.428916 -0.940786,0.62719 l 6.528482,11.317937 c 0.02135,-0.03772 0.03531,-0.07643 0.05702,-0.114035 0.191804,-0.332214 0.471212,-0.572396 0.684207,-0.883768 z m -2.6227966,1.796045 c -0.2876989,0.237355 -0.5895433,0.453963 -0.8552597,0.712717 l 3.6776163,6.385939 1.938589,1.111837 z" /><path
+ id="path3150-3"
+ style="fill:#9e2800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 42.771967,37.497779 c -0.18885,0.294778 -0.430565,0.539783 -0.62719,0.826751 l 3.249986,12.116179 c 0.381015,-0.114345 0.717348,-0.292549 1.083329,-0.42763 L 43.199597,37.754357 Z m 1.168855,0.684207 3.107443,11.603023 c 0.354471,-0.144693 0.745174,-0.234159 1.083329,-0.399121 L 45.337746,39.008737 Z m -2.252184,0.798243 c -0.289457,0.377179 -0.564314,0.769337 -0.883768,1.111837 l 2.879374,10.747764 c 0.37093,-0.0778 0.780961,-0.07542 1.140346,-0.171052 z m 4.418842,0.456138 2.594288,9.692943 c 0.370016,-0.198123 0.650504,-0.489058 0.997802,-0.712716 L 47.504404,40.23461 Z m -5.815766,1.111838 c -0.307817,0.300483 -0.611032,0.613876 -0.940786,0.883768 l 2.594288,9.692943 c 0.386889,-0.05044 0.763746,-0.102707 1.140346,-0.171052 z m 7.953915,0.114034 1.995606,7.383742 c 0.334548,-0.237285 0.688177,-0.450042 0.997803,-0.712716 L 49.671062,41.48899 Z m -27.054715,1.140347 1.453942,5.445153 c 0.452854,0.229569 0.913502,0.445682 1.368415,0.655699 l -1.339907,-5.046032 c -0.02654,-0.01511 -0.05904,-0.01322 -0.08553,-0.02851 -0.511693,-0.295426 -0.93457,-0.681545 -1.396924,-1.026311 z m 17.646858,0.08553 c -0.326701,0.24298 -0.682679,0.413233 -1.026311,0.62719 l 2.366218,8.837684 c 0.393447,-0.02251 0.755591,-0.102721 1.140347,-0.142544 z m 11.574515,0.02851 1.368415,5.046032 c 0.345565,-0.322805 0.575415,-0.754568 0.883769,-1.111837 l -0.826751,-3.10745 z m -13.142491,0.940786 c -0.350871,0.197748 -0.690252,0.402132 -1.05482,0.570173 l 2.109641,7.896898 c 0.406886,0.0039 0.826568,0.01365 1.225872,0 z m 15.309149,0.313595 0.570173,2.109641 c 0.292345,-0.381241 0.633437,-0.723795 0.883768,-1.140346 l -0.02851,-0.171052 z m -29.192864,0.02851 1.368415,5.074541 c 0.448409,0.196186 0.890806,0.421343 1.339907,0.598681 l -1.339907,-5.07454 c -0.461193,-0.175502 -0.91895,-0.37367 -1.368415,-0.598682 z m 12.258722,0.42763 c -0.354373,0.146261 -0.719205,0.28075 -1.083329,0.399121 l 1.938589,7.241199 c 0.407214,0.02942 0.79627,0.01591 1.197363,0.02851 z m -10.206099,0.456138 1.368415,5.017524 c 0.438344,0.163361 0.873599,0.282142 1.311399,0.42763 l -1.396924,-5.160067 c -0.425709,-0.09708 -0.862757,-0.148783 -1.28289,-0.285087 z m 8.552597,0.171052 c -0.374921,0.105124 -0.758189,0.152563 -1.140346,0.228069 l 1.767536,6.5855 c 0.417585,0.05556 0.841861,0.161094 1.254381,0.199561 z m -6.5855,0.285087 1.396924,5.217084 c 0.437208,0.136162 0.875912,0.309264 1.311399,0.42763 l -1.48245,-5.473662 c -0.410556,-0.03801 -0.816999,-0.09811 -1.225873,-0.171052 z m 4.846472,0.02851 c -0.383528,0.05965 -0.75248,0.170031 -1.140346,0.199561 l 1.596484,5.986818 c 0.420157,0.08077 0.838096,0.135663 1.254381,0.19956 z m -2.9649,0.171052 1.510958,5.673223 c 0.426295,0.107043 0.830876,0.138099 1.254381,0.228069 l -1.567976,-5.872783 c -0.400007,0.0135 -0.795822,-0.0094 -1.197363,-0.02851 z" /><path
+ id="path3152-8"
+ style="fill:#9e2800;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 38.011021,17.598737 c 0.542572,0.350359 1.057977,0.732777 1.539468,1.140346 h 6.585499 c -0.425394,-0.382895 -0.833107,-0.770531 -1.282889,-1.140346 z m 3.934195,3.649108 c 0.265926,0.362893 0.453852,0.757106 0.684208,1.140346 h 7.041638 C 49.351173,22.005945 49.04412,21.622497 48.701768,21.247845 Z m 1.881571,3.649108 c 0.139643,0.379207 0.319304,0.751338 0.42763,1.140346 h 8.06795 C 52.087489,25.65696 51.8949,25.273401 51.638159,24.896953 Z m 0.883769,3.649108 c 0.03797,0.380432 0.04852,0.756961 0.05702,1.140346 h 9.436365 c -0.158945,-0.377879 -0.24645,-0.763417 -0.42763,-1.140346 z m -0.08553,3.649108 c -0.05725,0.381122 -0.16877,0.761873 -0.256578,1.140346 h 11.03285 c -0.08562,-0.380199 -0.118242,-0.758318 -0.22807,-1.140346 z m -1.083329,3.649108 c -0.164919,0.382192 -0.313311,0.767211 -0.513156,1.140346 h 12.857404 c -0.0041,-0.378581 -0.05437,-0.756802 -0.08553,-1.140346 z m 3.249987,3.649108 c -0.344308,0.411191 1.823608,0.77443 1.442538,1.140346 h 7.252602 c 0.09342,-0.372151 0.109054,-0.75927 0.171052,-1.140346 z" /></g></g><g
+ transform="translate(97.852176,257.08279)"
+ id="g4852-6"><rect
+ id="rect3117-1"
+ style="color:#000000;display:block;visibility:visible;fill:#c0351d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ y="521.07434"
+ x="728.92566"
+ ry="9.6722517"
+ rx="9.6722517"
+ height="67.5"
+ width="67.5" /><g
+ id="g3154-6"
+ transform="matrix(0.974352,0,0,0.974352,734.36497,524.5912)"><path
+ id="path3156-6"
+ style="font-size:12px;fill:#c0351d;fill-opacity:1;fill-rule:evenodd;stroke:#9e2800;stroke-width:5.13162;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 30.955756,15.427747 c -1.087318,0.510106 -1.96266,1.889245 -1.96266,3.092241 v 35.444461 c 0,1.203005 0.875342,1.760821 1.96266,1.250725 l 18.300763,-8.585527 c 1.087316,-0.510097 1.96266,-1.889236 1.96266,-3.092241 V 8.092949 c 0,-1.2030017 -0.875344,-1.7608261 -1.96266,-1.2507279 z" /><path
+ id="path3158-0"
+ style="font-size:12px;fill:#c0351d;fill-opacity:1;fill-rule:evenodd;stroke:#9e2800;stroke-width:5.13162;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 27.156248,15.428044 c 1.087317,0.510099 1.962671,1.887649 1.962671,3.088674 v 35.451591 c 0,1.201024 -0.875354,1.757258 -1.962671,1.247159 L 8.8554464,46.629917 C 7.7681288,46.119817 6.8927769,44.742267 6.8927769,43.541243 V 8.0896519 c 0,-1.2010253 0.8753519,-1.7572579 1.9626695,-1.2471589 z" /></g></g><g
+ transform="translate(137.85218,392.08282)"
+ id="g4858-8"><rect
+ id="rect3189-7"
+ style="color:#000000;display:block;visibility:visible;fill:#c0351d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
+ y="453.57431"
+ x="688.92566"
+ ry="9.6722517"
+ rx="9.6722517"
+ height="67.5"
+ width="67.5" /><g
+ id="g4741-5"
+ transform="translate(-42.608491,3.9217495)"><path
+ sodipodi:nodetypes="cccc"
+ id="path3764-3"
+ d="m 755.1875,488.15625 -13.65625,14.8125 m 9.65625,4.1875 11.40625,-12.40625"
+ style="font-size:12px;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#9e2800;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" /><path
+ style="font-size:12px;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#9e2800;stroke-width:5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1"
+ d="m 753.04993,483.66441 c 3.03987,9.74125 13.37683,15.16112 23.11808,12.12125 7.40643,-2.31126 12.2725,-8.85067 12.86904,-16.13948 l -18.24287,5.69289 -4.26082,-13.65382 18.0729,-5.63985 c -4.66921,-5.38092 -12.22063,-7.81259 -19.45276,-5.55572 -9.74125,3.03986 -15.14344,13.43349 -12.10357,23.17473 z"
+ id="path586-4" /></g></g></g></svg>
diff --git a/images/ui/rightArrow.svg b/images/ui/rightArrow.svg
new file mode 100644
index 0000000..28a12e7
--- /dev/null
+++ b/images/ui/rightArrow.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" id="layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 792 612" style="enable-background:new 0 0 792 612;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#B73822;}
+</style>
+<path id="arrow_right" class="st0" d="M393.28,285.32c-2.05,0.03-3.69,1.7-3.69,3.75v32.52c0.01,1.44,0.84,2.75,2.14,3.37
+ s2.83,0.44,3.96-0.46l20.03-16.26c0.87-0.71,1.38-1.78,1.38-2.91s-0.51-2.2-1.38-2.91l-20.03-16.26
+ C395.01,285.61,394.15,285.31,393.28,285.32L393.28,285.32z"/>
+</svg>
diff --git a/sounds/CMakeLists.txt b/sounds/CMakeLists.txt
index 4238d0d..c879625 100644
--- a/sounds/CMakeLists.txt
+++ b/sounds/CMakeLists.txt
@@ -1,2 +1,17 @@
-install( FILES 1.wav 2.wav 3.wav 4.wav lose.wav DESTINATION ${KDE_INSTALL_DATADIR}/blinken/sounds )
+set(SOUND_FILES
+ 1.wav
+ 2.wav
+ 3.wav
+ 4.wav
+ lose.wav
+)
+if(QML_VERSION)
+ qt_add_resources(blinken "sounds"
+ PREFIX "/"
+ FILES
+ ${SOUND_FILES}
+ )
+else()
+ install( FILES ${SOUND_FILES} DESTINATION ${KDE_INSTALL_DATADIR}/blinken/sounds )
+endif()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index db46c9d..2a224cc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,47 +1,107 @@
+if(QML_VERSION)
+ ecm_setup_version(${RELEASE_SERVICE_VERSION} VARIABLE_PREFIX BLINKEN VERSION_HEADER blinken_version.h)
+ set(BLINKEN_SRCS
+ main.cpp
+ maskedmousearea.cpp
+ blinkengame.cpp
+ soundsplayer.cpp
+ highScoreManager.cpp
+ settings.cpp
+ )
+ kconfig_add_kcfg_files(BLINKEN_SRCS settings.kcfgc
+ GENERATE_MOC
+ )
+ set(BLINKEN_QML_UI_SRCS
+ ui/Blinken.qml
+ ui/BlinkenMenu.qml
+ ui/ExitButton.qml
+ ui/HighScoreButton.qml
+ ui/ScoreAndCounter.qml
+ ui/Numbers.qml
+ ui/ScoreList.qml
+ ui/HighScoreLists.qml
+ ui/GameOptions.qml
+ ui/GameButtons.qml
+ ui/GameButton.qml
+ )
-########### next target ###############
-
-ecm_setup_version(${RELEASE_SERVICE_VERSION} VARIABLE_PREFIX BLINKEN VERSION_HEADER blinken_version.h)
-
-set(blinken_SRCS
- blinken.cpp
- blinkengame.cpp
- blinkengame.h
- blinken.h
- button.cpp
- button.h
- counter.cpp
- counter.h
- highscoredialog.cpp
- highscoredialog.h
- main.cpp
- number.cpp
- number.h
- soundsplayer.cpp
- soundsplayer.h )
-
-kconfig_add_kcfg_files(blinken_SRCS settings.kcfgc )
-
-file(GLOB ICON_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/*-apps-blinken.png")
-ecm_add_app_icon(blinken_SRCS ICONS ${ICON_SRCS})
-add_executable(blinken ${blinken_SRCS})
-
-target_link_libraries(blinken
- KF6::CoreAddons
- KF6::I18n
- KF6::XmlGui
- KF6::GuiAddons
- Qt::Svg
- KF6::DBusAddons
- KF6::Crash
- Phonon::phonon4qt6
+ add_executable(blinken
+ ${BLINKEN_SRCS}
)
+ target_compile_definitions(blinken PRIVATE QML_VERSION)
-install(TARGETS blinken EXPORT blinken ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} )
+ ecm_add_qml_module(blinken
+ URI "org.kde.blinken"
+ VERSION 1.0
+ )
+ ecm_target_qml_sources(blinken SOURCES
+ ${BLINKEN_QML_UI_SRCS}
+ VERSION 1.0
+ )
+ if(ANDROID)
+ include(ECMAddAndroidApk)
+ ecm_add_android_apk(blinken ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android)
+ endif()
+
+ target_link_libraries(blinken PRIVATE
+ Qt6::Quick
+ Qt6::Core
+ Qt6::Gui
+ Qt6::Qml
+ Qt6::Multimedia
+
+ KF6::CoreAddons
+ KF6::I18n
+ KF6::GuiAddons
+ KF6::Kirigami
+ KF6::ConfigCore
+ KF6::ConfigWidgets
+ )
+else()
+ ecm_setup_version(${RELEASE_SERVICE_VERSION} VARIABLE_PREFIX BLINKEN VERSION_HEADER blinken_version.h)
-########### install files ###############
+ set(blinken_SRCS
+ blinken.cpp
+ blinkengame.cpp
+ blinkengame.h
+ blinken.h
+ button.cpp
+ button.h
+ counter.cpp
+ counter.h
+ highscoredialog.cpp
+ highscoredialog.h
+ highScoreManager.cpp
+ highScoreManager.h
+ main.cpp
+ number.cpp
+ number.h
+ soundsplayer.cpp
+ soundsplayer.h )
+
+ kconfig_add_kcfg_files(blinken_SRCS settings.kcfgc GENERATE_MOC)
+ file(GLOB ICON_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/*-apps-blinken.png")
+ ecm_add_app_icon(blinken_SRCS ICONS ${ICON_SRCS})
+ add_executable(blinken ${blinken_SRCS})
+
+ target_link_libraries(blinken
+ KF6::CoreAddons
+ KF6::I18n
+ KF6::XmlGui
+ KF6::GuiAddons
+ Qt::Svg
+ Qt6::Qml
+ KF6::DBusAddons
+ KF6::Crash
+ Phonon::phonon4qt6
+ )
+
+endif()
+
+install(TARGETS blinken EXPORT blinken ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} )
+
+########### install files ###############
install( PROGRAMS org.kde.blinken.desktop DESTINATION ${KDE_INSTALL_APPDIR} )
install( FILES blinken.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR} )
-
diff --git a/src/android/AndroidManifest.xml b/src/android/AndroidManifest.xml
new file mode 100644
index 0000000..b3742ad
--- /dev/null
+++ b/src/android/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.kde.blinken" android:installLocation="auto" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:versionName="-- %%INSERT_VERSION_NAME%% --">
+ <!-- %%INSERT_PERMISSIONS -->
+ <!-- %%INSERT_FEATURES -->
+ <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true"/>
+ <application
+ android:name="org.qtproject.qt.android.bindings.QtApplication"
+ android:hardwareAccelerated="true"
+ android:label="blinken"
+ android:requestLegacyExternalStorage="true"
+ android:appCategory="game"
+ android:allowBackup="true"
+ android:fullBackupOnly="false"
+ android:icon="@drawable/icon"
+ android:theme="@style/FullScreen"
+ >
+ <activity
+ android:name="org.qtproject.qt.android.bindings.QtActivity"
+ android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
+ android:launchMode="singleTop"
+ android:screenOrientation="sensorLandscape"
+ android:exported="true"
+ android:label="blinken"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
+ <meta-data android:name="android.app.arguments" android:value="-- %%INSERT_APP_ARGUMENTS%% --"/>
+
+ <!-- Background running -->
+ <meta-data android:name="android.app.background_running" android:value="false"/>
+
+ <!-- auto screen scale factor -->
+ <meta-data android:name="android.app.auto_screen_scale_factor" android:value="true"/>
+
+ </activity>
+
+ <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.qtprovider" android:exported="false" android:grantUriPermissions="true">
+ <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths"/>
+ </provider>
+ </application>
+</manifest>
diff --git a/src/android/res/drawable-hdpi/icon.png b/src/android/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..c6af5ad
Binary files /dev/null and b/src/android/res/drawable-hdpi/icon.png differ
diff --git a/src/android/res/drawable-ldpi/icon.png b/src/android/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..f6ab491
Binary files /dev/null and b/src/android/res/drawable-ldpi/icon.png differ
diff --git a/src/android/res/drawable-mdpi/icon.png b/src/android/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..311cacd
Binary files /dev/null and b/src/android/res/drawable-mdpi/icon.png differ
diff --git a/src/android/res/drawable-xhdpi/icon.png b/src/android/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..09f0db1
Binary files /dev/null and b/src/android/res/drawable-xhdpi/icon.png differ
diff --git a/src/android/res/drawable-xxhdpi/icon.png b/src/android/res/drawable-xxhdpi/icon.png
new file mode 100644
index 0000000..9e69adf
Binary files /dev/null and b/src/android/res/drawable-xxhdpi/icon.png differ
diff --git a/src/android/res/drawable-xxxhdpi/icon.png b/src/android/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 0000000..480e41a
Binary files /dev/null and b/src/android/res/drawable-xxxhdpi/icon.png differ
diff --git a/src/android/res/values/styles.xml b/src/android/res/values/styles.xml
new file mode 100644
index 0000000..640090a
--- /dev/null
+++ b/src/android/res/values/styles.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="FullScreen" >
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/src/blinken.cpp b/src/blinken.cpp
index fa5f7b2..b7aff87 100644
--- a/src/blinken.cpp
+++ b/src/blinken.cpp
@@ -25,6 +25,7 @@
#include "counter.h"
#include "number.h"
#include "highscoredialog.h"
+#include "highScoreManager.h"
#include "settings.h"
static const double centerX = 2.0;
@@ -36,14 +37,14 @@ static const double ellipseBigAxisY = 2.74;
static const double nonButtonRibbonX = 150.0;
static const double nonButtonRibbonY = 125.0;
-blinken::blinken() : m_overHighscore(false), m_overQuit(false), m_overCentralText(false), m_overMenu(false), m_overAboutKDE(false), m_overAboutBlinken(false), m_overSettings(false), m_overManual(false), m_overCentralLetters(false), m_overCounter(false), m_overFont(false), m_overSound(false), m_showPreferences(false), m_updateButtonHighlighting(false), m_highlighted(blinkenGame::none)
+blinken::blinken() : m_overHighscore(false), m_overQuit(false), m_overCentralText(false), m_overMenu(false), m_overAboutKDE(false), m_overAboutBlinken(false), m_overSettings(false), m_overManual(false), m_overCentralLetters(false), m_overCounter(false), m_overFont(false), m_overSound(false), m_showPreferences(false), m_updateButtonHighlighting(false), m_highlighted(BlinkenGame::None)
{
m_renderer = new QSvgRenderer(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("images/blinken.svg")));
- m_buttons[0] = new button(blinkenGame::blue);
- m_buttons[1] = new button(blinkenGame::yellow);
- m_buttons[2] = new button(blinkenGame::red);
- m_buttons[3] = new button(blinkenGame::green);
+ m_buttons[0] = new button(BlinkenGame::Blue);
+ m_buttons[1] = new button(BlinkenGame::Yellow);
+ m_buttons[2] = new button(BlinkenGame::Red);
+ m_buttons[3] = new button(BlinkenGame::Green);
m_fillColor = QColor(40, 40, 40);
m_fontColor = QColor(126, 126, 126);
@@ -56,9 +57,9 @@ blinken::blinken() : m_overHighscore(false), m_overQuit(false), m_overCentralTex
m_unhighlighter = new QTimer(this);
connect(m_unhighlighter, &QTimer::timeout, this, &blinken::unhighlight);
- connect(&m_game, &blinkenGame::gameEnded, this, &blinken::checkHS);
+ connect(&m_game, &BlinkenGame::gameEnded, this, &blinken::checkHS);
connect(&m_game, SIGNAL(phaseChanged()), this, SLOT(update()));
- connect(&m_game, &blinkenGame::highlight, this, &blinken::highlight);
+ connect(&m_game, &BlinkenGame::highlight, this, &blinken::highlight);
m_helpMenu = new KHelpMenu(this, KAboutData::applicationData());
m_helpMenu->menu(); // ensures the actions are created
@@ -112,16 +113,16 @@ void blinken::paintEvent(QPaintEvent *)
const auto sz = QSize((int)(width() * xScaleButtons), (int)(height() * yScaleButtons));
- auto getPixmapFor = [this, sz](blinkenGame::color color, const QString& pixmapName) -> QPixmap {
+ auto getPixmapFor = [this, sz](BlinkenGame::Color color, const QString& pixmapName) -> QPixmap {
return getPixmap( m_highlighted & color ?
QStringLiteral("%1_highlight").arg(pixmapName) :
QStringLiteral("%1_normal").arg(pixmapName), sz);
};
- p.drawPixmap(QPointF(width() / 1.975, height() / 28.0), getPixmapFor(blinkenGame::red, QStringLiteral("red")));
- p.drawPixmap(QPointF(width() / 1.975, height() / 2.45), getPixmapFor(blinkenGame::green, QStringLiteral("green")));
- p.drawPixmap(QPointF(width() / 30.0, height() / 28.0), getPixmapFor(blinkenGame::yellow, QStringLiteral("yellow")));
- p.drawPixmap(QPointF(width() / 30.0, height() / 2.45), getPixmapFor(blinkenGame::blue, QStringLiteral("blue")));
+ p.drawPixmap(QPointF(width() / 1.975, height() / 28.0), getPixmapFor(BlinkenGame::Red, QStringLiteral("red")));
+ p.drawPixmap(QPointF(width() / 1.975, height() / 2.45), getPixmapFor(BlinkenGame::Green, QStringLiteral("green")));
+ p.drawPixmap(QPointF(width() / 30.0, height() / 28.0), getPixmapFor(BlinkenGame::Yellow, QStringLiteral("yellow")));
+ p.drawPixmap(QPointF(width() / 30.0, height() / 2.45), getPixmapFor(BlinkenGame::Blue, QStringLiteral("blue")));
drawMenuQuit(p);
p.resetTransform();
@@ -157,7 +158,7 @@ void blinken::paintEvent(QPaintEvent *)
m_soundRect = QRect(181, 197, 25, 25);
m_fontRect = QRect(432, 197, 25, 25);
p.drawRect(m_soundRect);
- if (blinkenSettings::playSounds())
+ if (BlinkenSettings::playSounds())
{
p.drawLine(186, 202, 201, 217);
p.drawLine(186, 217, 201, 202);
@@ -165,7 +166,7 @@ void blinken::paintEvent(QPaintEvent *)
if (!m_alwaysUseNonCoolFont)
{
p.drawRect(m_fontRect);
- if (blinkenSettings::customFont())
+ if (BlinkenSettings::customFont())
{
p.drawLine(437, 202, 452, 217);
p.drawLine(437, 217, 452, 202);
@@ -219,19 +220,19 @@ void blinken::paintEvent(QPaintEvent *)
aux4 = (double)height() / 105.0;
switch (m_game.phase())
{
- case blinkenGame::starting:
+ case BlinkenGame::Starting:
drawText(p, i18nc("@action:button Start a new game", "Start"), QPointF(aux1, aux2), true, aux3, aux4, &m_centralTextRect, m_overCentralText, true);
break;
- case blinkenGame::choosingLevel:
+ case BlinkenGame::ChoosingLevel:
drawLevel(p);
break;
- case blinkenGame::waiting3:
- case blinkenGame::waiting2:
- case blinkenGame::waiting1:
- case blinkenGame::learningTheSequence:
- case blinkenGame::typingTheSequence:
+ case BlinkenGame::Waiting3:
+ case BlinkenGame::Waiting2:
+ case BlinkenGame::Waiting1:
+ case BlinkenGame::LearningTheSequence:
+ case BlinkenGame::TypingTheSequence:
drawText(p, i18n("Restart"), QPointF(aux1, aux2), true, aux3, aux4, &m_centralTextRect, m_overCentralText, true);
break;
}
@@ -279,7 +280,7 @@ void blinken::keyPressEvent(QKeyEvent *e)
}
else
{
- if (m_game.phase() == blinkenGame::starting)
+ if (m_game.phase() == BlinkenGame::Starting)
{
if (e -> key() == Qt::Key_Return || e -> key() == Qt::Key_Enter)
{
@@ -287,7 +288,7 @@ void blinken::keyPressEvent(QKeyEvent *e)
return;
}
}
- else if (m_game.phase() == blinkenGame::choosingLevel)
+ else if (m_game.phase() == BlinkenGame::ChoosingLevel)
{
if (e -> key() == Qt::Key_1)
{
@@ -306,10 +307,10 @@ void blinken::keyPressEvent(QKeyEvent *e)
}
}
- if (e -> key() == m_buttons[0] -> key()) pressedColor(blinkenGame::color::blue);
- else if (e -> key() == m_buttons[1] -> key()) pressedColor(blinkenGame::color::yellow);
- else if (e -> key() == m_buttons[2] -> key()) pressedColor(blinkenGame::color::red);
- else if (e -> key() == m_buttons[3] -> key()) pressedColor(blinkenGame::color::green);
+ if (e -> key() == m_buttons[0] -> key()) pressedColor(BlinkenGame::Color::Blue);
+ else if (e -> key() == m_buttons[1] -> key()) pressedColor(BlinkenGame::Color::Yellow);
+ else if (e -> key() == m_buttons[2] -> key()) pressedColor(BlinkenGame::Color::Red);
+ else if (e -> key() == m_buttons[3] -> key()) pressedColor(BlinkenGame::Color::Green);
}
}
@@ -325,7 +326,7 @@ void blinken::keyReleaseEvent(QKeyEvent *e)
void blinken::togglePreferences()
{
- if (m_game.phase() == blinkenGame::starting || m_game.phase() == blinkenGame::choosingLevel)
+ if (m_game.phase() == BlinkenGame::Starting || m_game.phase() == BlinkenGame::ChoosingLevel)
{
m_showPreferences = !m_showPreferences;
for (int i = 0; i < 4; i++) m_buttons[i] -> setSelected(false);
@@ -349,14 +350,14 @@ void blinken::mousePressEvent(QMouseEvent *e)
}
else if (m_showPreferences && m_fontRect.contains(e -> pos()) && !m_alwaysUseNonCoolFont)
{
- blinkenSettings::setCustomFont(!blinkenSettings::customFont());
- blinkenSettings::self()->save();
+ BlinkenSettings::setCustomFont(!BlinkenSettings::customFont());
+ BlinkenSettings::self()->save();
update();
}
else if (m_showPreferences && m_soundRect.contains(e -> pos()))
{
- blinkenSettings::setPlaySounds(!blinkenSettings::playSounds());
- blinkenSettings::self()->save();
+ BlinkenSettings::setPlaySounds(!BlinkenSettings::playSounds());
+ BlinkenSettings::self()->save();
update();
}
else if (m_overQuit) qApp->quit();
@@ -364,11 +365,11 @@ void blinken::mousePressEvent(QMouseEvent *e)
else if (m_overAboutKDE) m_helpMenu -> aboutKDE();
else if (m_overSettings) togglePreferences();
else if (m_overManual) m_helpMenu -> appHelpActivated();
- else if (m_game.phase() != blinkenGame::choosingLevel && m_overCentralText)
+ else if (m_game.phase() != BlinkenGame::ChoosingLevel && m_overCentralText)
{
startGamePressed();
}
- else if (m_game.phase() == blinkenGame::choosingLevel)
+ else if (m_game.phase() == BlinkenGame::ChoosingLevel)
{
int level = 0;
if (m_levelsRect[1].contains(e -> pos())) level = 1;
@@ -385,28 +386,28 @@ void blinken::mousePressEvent(QMouseEvent *e)
if (insideGreen(p))
{
if (m_showPreferences) selectButton(3);
- else pressedColor(blinkenGame::color::green);
+ else pressedColor(BlinkenGame::Color::Green);
}
else if (insideBlue(p))
{
if (m_showPreferences) selectButton(0);
- else pressedColor(blinkenGame::color::blue);
+ else pressedColor(BlinkenGame::Color::Blue);
}
else if (insideYellow(p))
{
if (m_showPreferences) selectButton(1);
- else pressedColor(blinkenGame::color::yellow);
+ else pressedColor(BlinkenGame::Color::Yellow);
}
else if (insideRed(p))
{
if (m_showPreferences) selectButton(2);
- else pressedColor(blinkenGame::color::red);
+ else pressedColor(BlinkenGame::Color::Red);
}
}
void blinken::checkHS()
{
- highScoreManager hsm;
+ HighScoreManager hsm;
if (hsm.scoreGoodEnough(m_game.level(), m_game.score()))
{
bool ok;
@@ -421,12 +422,12 @@ void blinken::checkHS()
}
}
-void blinken::highlight(blinkenGame::color c, bool unhighlight)
+void blinken::highlight(BlinkenGame::Color c, bool unhighlight)
{
m_highlighted = c;
update();
if (unhighlight) m_unhighlighter -> start(250);
- else if (c == blinkenGame::none)
+ else if (c == BlinkenGame::None)
{
m_unhighlighter -> stop();
updateCursor(mapFromGlobal(QCursor::pos()));
@@ -435,10 +436,10 @@ void blinken::highlight(blinkenGame::color c, bool unhighlight)
void blinken::unhighlight()
{
- highlight(blinkenGame::none, false);
+ highlight(BlinkenGame::None, false);
}
-void blinken::pressedColor(blinkenGame::color color)
+void blinken::pressedColor(BlinkenGame::Color color)
{
if (m_game.canType())
{
@@ -449,10 +450,10 @@ void blinken::pressedColor(blinkenGame::color color)
void blinken::startGamePressed()
{
- highlight(blinkenGame::none, true);
+ highlight(BlinkenGame::None, true);
m_overCentralText = false;
for(int i = 0; i < 3; i++) m_overLevels[i] = false;
- m_game.setPhase(blinkenGame::choosingLevel);
+ m_game.setPhase(BlinkenGame::ChoosingLevel);
m_updateButtonHighlighting = true;
}
@@ -650,19 +651,19 @@ void blinken::drawScoreAndCounter(QPainter &p)
switch (m_game.phase())
{
- case blinkenGame::waiting3:
+ case BlinkenGame::Waiting3:
c1 = Qt::red;
c2 = Qt::red;
c3 = Qt::red;
break;
- case blinkenGame::waiting2:
+ case BlinkenGame::Waiting2:
c1 = m_countDownColor;
c2 = Qt::red;
c3 = Qt::red;
break;
- case blinkenGame::waiting1:
+ case BlinkenGame::Waiting1:
c1 = m_countDownColor;
c2 = c1;
c3 = Qt::red;
@@ -675,7 +676,7 @@ void blinken::drawScoreAndCounter(QPainter &p)
break;
}
- counter::paint(p, m_game.phase() != blinkenGame::starting, m_game.score(), true, c1, c2, c3, m_renderer);
+ counter::paint(p, m_game.phase() != BlinkenGame::Starting, m_game.score(), true, c1, c2, c3, m_renderer);
p.translate(-268, -110);
}
@@ -707,15 +708,15 @@ void blinken::drawStatusText(QPainter &p)
{
switch (m_game.phase())
{
- case blinkenGame::starting:
+ case BlinkenGame::Starting:
text = i18n("Press Start to begin");
break;
- case blinkenGame::choosingLevel:
+ case BlinkenGame::ChoosingLevel:
text = i18n("Set the Difficulty Level...");
break;
- case blinkenGame::waiting3:
+ case BlinkenGame::Waiting3:
if (m_overCentralText)
{
text = restartText;
@@ -726,7 +727,7 @@ void blinken::drawStatusText(QPainter &p)
}
break;
- case blinkenGame::waiting2:
+ case BlinkenGame::Waiting2:
if (m_overCentralText)
{
text = restartText;
@@ -744,7 +745,7 @@ void blinken::drawStatusText(QPainter &p)
}
break;
- case blinkenGame::waiting1:
+ case BlinkenGame::Waiting1:
if (m_overCentralText)
{
text = restartText;
@@ -762,7 +763,7 @@ void blinken::drawStatusText(QPainter &p)
}
break;
- case blinkenGame::learningTheSequence:
+ case BlinkenGame::LearningTheSequence:
if (m_overCentralText)
{
text = restartText;
@@ -773,7 +774,7 @@ void blinken::drawStatusText(QPainter &p)
}
break;
- case blinkenGame::typingTheSequence:
+ case BlinkenGame::TypingTheSequence:
if (m_overCentralText)
{
text = restartText;
@@ -787,7 +788,7 @@ void blinken::drawStatusText(QPainter &p)
}
QFont f;
- if (blinkenSettings::customFont() && !m_alwaysUseNonCoolFont) f = QFont(QStringLiteral("Steve"));
+ if (BlinkenSettings::customFont() && !m_alwaysUseNonCoolFont) f = QFont(QStringLiteral("Steve"));
p.setFont(f);
f.setPointSize(KFontUtils::adaptFontSize(p, text, 380, 30, 28, 1, KFontUtils::DoNotAllowWordWrap));
p.setFont(f);
@@ -1014,12 +1015,12 @@ void blinken::updateButtonHighlighting(const QPoint &p)
switch (m_game.phase())
{
- case blinkenGame::starting:
- case blinkenGame::waiting3:
- case blinkenGame::waiting2:
- case blinkenGame::waiting1:
- case blinkenGame::learningTheSequence:
- case blinkenGame::typingTheSequence:
+ case BlinkenGame::Starting:
+ case BlinkenGame::Waiting3:
+ case BlinkenGame::Waiting2:
+ case BlinkenGame::Waiting1:
+ case BlinkenGame::LearningTheSequence:
+ case BlinkenGame::TypingTheSequence:
if (m_centralTextRect.contains(p))
{
if (!m_overCentralText)
@@ -1035,7 +1036,7 @@ void blinken::updateButtonHighlighting(const QPoint &p)
}
break;
- case blinkenGame::choosingLevel:
+ case BlinkenGame::ChoosingLevel:
for (int i = 0; i < 3; i++)
{
if (m_levelsRect[i].contains(p))
diff --git a/src/blinken.h b/src/blinken.h
index 4067274..005bfe6 100644
--- a/src/blinken.h
+++ b/src/blinken.h
@@ -37,10 +37,10 @@ Q_OBJECT
private Q_SLOTS:
void checkHS();
- void highlight(blinkenGame::color c, bool unhighlight);
+ void highlight(BlinkenGame::Color c, bool unhighlight);
void unhighlight();
- void pressedColor(blinkenGame::color c);
+ void pressedColor(BlinkenGame::Color c);
private:
void startGamePressed();
@@ -81,12 +81,12 @@ Q_OBJECT
// use always the non-cool font?
bool m_alwaysUseNonCoolFont;
- blinkenGame::color m_highlighted;
+ BlinkenGame::Color m_highlighted;
QTimer *m_unhighlighter;
QString m_lastName;
- blinkenGame m_game;
+ BlinkenGame m_game;
KHelpMenu *m_helpMenu;
diff --git a/src/blinken.kcfg b/src/blinken.kcfg
index 362b9c5..c98c43e 100644
--- a/src/blinken.kcfg
+++ b/src/blinken.kcfg
@@ -3,15 +3,15 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
- <kcfgfile name="blinkenrc"/>
- <group name="general">
- <entry name="playSounds" type="Bool">
+ <kcfgfile name="blinkenrc"/>
+ <group name="general">
+ <entry name="playSounds" type="Bool">
<label>Play sounds</label>
- <default>true</default>
+ <default>true</default>
</entry>
- <entry name="customFont" type="Bool">
+ <entry name="customFont" type="Bool">
<label>Use custom font for status text</label>
- <default>true</default>
+ <default>true</default>
</entry>
</group>
</kcfg>
diff --git a/src/blinkengame.cpp b/src/blinkengame.cpp
index c3f8200..a07b8d8 100644
--- a/src/blinkengame.cpp
+++ b/src/blinkengame.cpp
@@ -11,42 +11,42 @@
#include "soundsplayer.h"
-blinkenGame::blinkenGame() : m_phase(starting)
+BlinkenGame::BlinkenGame() : m_phase(Starting)
{
m_soundsPlayer = new soundsPlayer;
m_waitTimer = new QTimer(this);
- connect(m_waitTimer, &QTimer::timeout, this, &blinkenGame::waiting);
+ connect(m_waitTimer, &QTimer::timeout, this, &BlinkenGame::waiting);
}
-blinkenGame::~blinkenGame()
+BlinkenGame::~BlinkenGame()
{
delete m_soundsPlayer;
}
-int blinkenGame::level() const
+int BlinkenGame::level() const
{
return m_level;
}
-bool blinkenGame::canType() const
+bool BlinkenGame::canType() const
{
- return m_phase == typingTheSequence || m_phase == starting;
+ return m_phase == TypingTheSequence || m_phase == Starting;
}
-blinkenGame::gamePhase blinkenGame::phase() const
+BlinkenGame::GamePhase BlinkenGame::phase() const
{
return m_phase;
}
-int blinkenGame::score() const
+int BlinkenGame::score() const
{
- if (m_phase == starting || m_phase == choosingLevel) return 0;
+ if (m_phase == Starting || m_phase == ChoosingLevel) return 0;
return m_sequenceLength - 1;
}
-void blinkenGame::clicked(color c)
+void BlinkenGame::clicked(Color c)
{
- if (m_phase == starting)
+ if (m_phase == Starting)
{
m_soundsPlayer -> play(c);
return;
@@ -64,21 +64,21 @@ void blinkenGame::clicked(color c)
}
else
{
- m_soundsPlayer -> play(all);
- Q_EMIT highlight(all, true);
+ m_soundsPlayer -> play(All);
+ Q_EMIT highlight(All, true);
Q_EMIT gameEnded();
- setPhase(choosingLevel);
+ setPhase(ChoosingLevel);
}
}
-void blinkenGame::setPhase(gamePhase p)
+void BlinkenGame::setPhase(GamePhase p)
{
- if (p != waiting3 && p != waiting2 && p != waiting1) m_waitTimer -> stop();
+ if (p != Waiting3 && p != Waiting2 && p != Waiting1) m_waitTimer -> stop();
m_phase = p;
Q_EMIT phaseChanged();
}
-void blinkenGame::start(int level)
+void BlinkenGame::start(int level)
{
m_level = level;
m_sequenceLength = 1;
@@ -88,11 +88,11 @@ void blinkenGame::start(int level)
m_sequence.clear();
}
-void blinkenGame::nextSound()
+void BlinkenGame::nextSound()
{
if (m_nextColor != m_sequence.constEnd())
{
- color c;
+ Color c;
c = *m_nextColor;
++m_nextColor;
m_soundsPlayer -> play(c);
@@ -100,29 +100,29 @@ void blinkenGame::nextSound()
}
else
{
- setPhase(typingTheSequence);
+ setPhase(TypingTheSequence);
m_nextColor = m_sequence.constBegin();
- Q_EMIT highlight(none, false);
+ Q_EMIT highlight(None, false);
m_soundsPlayer->disconnect();
}
}
-void blinkenGame::soundEnded()
+void BlinkenGame::soundEnded()
{
- QTimer::singleShot(100, this, &blinkenGame::nextSound);
- QTimer::singleShot(50, this, &blinkenGame::unhighlight);
+ QTimer::singleShot(100, this, &BlinkenGame::nextSound);
+ QTimer::singleShot(50, this, &BlinkenGame::unhighlight);
}
-void blinkenGame::unhighlight()
+void BlinkenGame::unhighlight()
{
- Q_EMIT highlight(none, false);
+ Q_EMIT highlight(None, false);
}
-void blinkenGame::waiting()
+void BlinkenGame::waiting()
{
- if (m_phase == waiting1)
+ if (m_phase == Waiting1)
{
- setPhase(blinkenGame::learningTheSequence);
+ setPhase(BlinkenGame::LearningTheSequence);
if (m_level == 3)
{
m_sequence.clear();
@@ -130,43 +130,43 @@ void blinkenGame::waiting()
}
else m_sequence.append(generateColor());
- connect(m_soundsPlayer, &soundsPlayer::ended, this, &blinkenGame::soundEnded);
+ connect(m_soundsPlayer, &soundsPlayer::ended, this, &BlinkenGame::soundEnded);
m_nextColor = m_sequence.constBegin();
soundEnded();
}
- else if (m_phase == waiting3) setPhase(waiting2);
- else /* m_phase == waiting2 */ setPhase(waiting1);
+ else if (m_phase == Waiting3) setPhase(Waiting2);
+ else /* m_phase == waiting2 */ setPhase(Waiting1);
}
-void blinkenGame::nextRound()
+void BlinkenGame::nextRound()
{
- if (m_level == 1) setPhase(waiting3);
- else setPhase(waiting2);
+ if (m_level == 1) setPhase(Waiting3);
+ else setPhase(Waiting2);
m_waitTimer -> start(1000);
}
-blinkenGame::color blinkenGame::generateColor()
+BlinkenGame::Color BlinkenGame::generateColor()
{
// make the compiler happy :-D
- color c = none;
+ Color c = None;
const int r = QRandomGenerator::global()->bounded(1, 5); // rand [1, 5)
switch(r)
{
case 1:
- c = red;
+ c = Red;
break;
case 2:
- c = green;
+ c = Green;
break;
case 3:
- c = blue;
+ c = Blue;
break;
case 4:
- c = yellow;
+ c = Yellow;
break;
}
return c;
diff --git a/src/blinkengame.h b/src/blinkengame.h
index 9ae2d67..e197b3a 100644
--- a/src/blinkengame.h
+++ b/src/blinkengame.h
@@ -10,41 +10,59 @@
#include <QObject>
#include <QList>
+#ifdef QML_VERSION
+#include <QQmlEngine>
+#endif
+
+#include <QtQml/qqmlregistration.h>
+
class QTimer;
class soundsPlayer;
-class blinkenGame : public QObject
+class BlinkenGame : public QObject
{
Q_OBJECT
+QML_ELEMENT
+QML_SINGLETON
public:
- blinkenGame();
- ~blinkenGame() override;
-
- enum gamePhase { starting, choosingLevel, waiting3, waiting2, waiting1, learningTheSequence, typingTheSequence };
- enum color
+ BlinkenGame();
+ ~BlinkenGame() override;
+
+ enum GamePhase {
+ Starting,
+ ChoosingLevel,
+ Waiting3,
+ Waiting2,
+ Waiting1,
+ LearningTheSequence,
+ TypingTheSequence };
+ Q_ENUM(GamePhase)
+
+ enum Color
{
- none = 0,
- red = 1,
- green = 2,
- blue = 4,
- yellow = 8,
- all = 15};
-
- int level() const;
- bool canType() const;
- gamePhase phase() const;
- int score() const;
-
- void clicked(color c);
- void setPhase(gamePhase p);
- void start(int level);
-
- Q_SIGNALS:
- void gameEnded();
- void phaseChanged();
- void highlight(blinkenGame::color c, bool unhighlight);
-
+ None = 0,
+ Red = 1,
+ Green = 2,
+ Blue = 4,
+ Yellow = 8,
+ All = 15};
+ Q_ENUM(Color)
+ Q_PROPERTY(GamePhase phase READ phase WRITE setPhase NOTIFY phaseChanged)
+
+ Q_INVOKABLE int level() const;
+ Q_INVOKABLE bool canType() const;
+ Q_INVOKABLE GamePhase phase() const;
+ Q_INVOKABLE int score() const;
+ Q_INVOKABLE void clicked(Color c);
+ Q_INVOKABLE void setPhase(GamePhase p);
+ Q_INVOKABLE void start(int level);
+
+ Q_SIGNALS:
+ void gameEnded();
+ void phaseChanged();
+ void highlight(BlinkenGame::Color c, bool unhighlight);
+
private Q_SLOTS:
void nextSound();
void soundEnded();
@@ -53,17 +71,17 @@ Q_OBJECT
private:
void nextRound();
- color generateColor();
-
- gamePhase m_phase;
+ Color generateColor();
+
+ GamePhase m_phase;
int m_level;
int m_sequenceLength;
QTimer *m_waitTimer;
soundsPlayer *m_soundsPlayer;
- QList<color> m_sequence;
- QList<color>::const_iterator m_nextColor;
+ QList<Color> m_sequence;
+ QList<Color>::const_iterator m_nextColor;
};
#endif
diff --git a/src/button.cpp b/src/button.cpp
index 99412a4..b8f697b 100644
--- a/src/button.cpp
+++ b/src/button.cpp
@@ -12,26 +12,26 @@
#include <QKeySequence>
-button::button(blinkenGame::color c) : m_selected(false), m_color(c)
+button::button(BlinkenGame::Color c) : m_selected(false), m_color(c)
{
KConfigGroup kc(KSharedConfig::openConfig(), QStringLiteral("General"));
QString cs = getColorString();
switch (c)
{
- case blinkenGame::blue:
+ case BlinkenGame::Blue:
m_key = kc.readEntry(cs, int(Qt::Key_3));
break;
- case blinkenGame::yellow:
+ case BlinkenGame::Yellow:
m_key = kc.readEntry(cs, int(Qt::Key_1));
break;
- case blinkenGame::red:
+ case BlinkenGame::Red:
m_key =kc.readEntry(cs, int(Qt::Key_2));
break;
- case blinkenGame::green:
+ case BlinkenGame::Green:
m_key = kc.readEntry(cs, int(Qt::Key_4));
break;
@@ -79,16 +79,16 @@ QString button::getColorString() const
{
switch (m_color)
{
- case blinkenGame::blue:
+ case BlinkenGame::Blue:
return QStringLiteral("blue");
- case blinkenGame::yellow:
+ case BlinkenGame::Yellow:
return QStringLiteral("yellow");
- case blinkenGame::red:
+ case BlinkenGame::Red:
return QStringLiteral("red");
- case blinkenGame::green:
+ case BlinkenGame::Green:
return QStringLiteral("green");
default:
diff --git a/src/button.h b/src/button.h
index 6de5854..cda1f1d 100644
--- a/src/button.h
+++ b/src/button.h
@@ -12,7 +12,7 @@
class button
{
public:
- explicit button(blinkenGame::color c);
+ explicit button(BlinkenGame::Color c);
~button();
void setShortcut(int key);
@@ -26,7 +26,7 @@ class button
bool m_selected;
int m_key;
- blinkenGame::color m_color;
+ BlinkenGame::Color m_color;
};
#endif
diff --git a/src/highScoreManager.cpp b/src/highScoreManager.cpp
new file mode 100644
index 0000000..72985c9
--- /dev/null
+++ b/src/highScoreManager.cpp
@@ -0,0 +1,103 @@
+/*
+ SPDX-FileCopyrightText: 2005-2006 Albert Astals Cid <aacid at kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "highScoreManager.h"
+
+#include <KConfig>
+#include <KSharedConfig>
+
+#include "settings.h"
+
+static QSet<HighScoreManager *> s_allHSM;
+
+/* HighScoreManager */
+
+HighScoreManager::HighScoreManager()
+{
+ s_allHSM << this;
+ update();
+}
+
+HighScoreManager::~HighScoreManager()
+{
+ s_allHSM.remove(this);
+}
+
+bool HighScoreManager::scoreGoodEnough(int level, int score)
+{
+ level--;
+ QList< QPair<int, QString> >::iterator it, itEnd;
+ it = m_scores[level].begin();
+ itEnd = m_scores[level].end();
+ while (it != itEnd && (*it).first >= score) ++it;
+
+ return (it != itEnd);
+}
+
+void HighScoreManager::addScore(int level, int score, const QString &name)
+{
+ level--;
+ QList< QPair<int, QString> >::iterator it, itEnd;
+ it = m_scores[level].begin();
+ itEnd = m_scores[level].end();
+ while (it != itEnd && (*it).first >= score) ++it;
+
+ if (it != itEnd)
+ {
+ m_scores[level].insert(it, qMakePair(score, name));
+ m_scores[level].erase(--m_scores[level].end());
+
+ KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("Level%1").arg(level + 1));
+ int j;
+ for (it = m_scores[level].begin(), j = 1; it != m_scores[level].end(); ++it, j++)
+ {
+ cfg.writeEntry(QStringLiteral("Score%1").arg(j), (*it).first);
+ cfg.writeEntry(QStringLiteral("Name%1").arg(j), (*it).second);
+ }
+ cfg.sync();
+
+ for (HighScoreManager *hsm : std::as_const(s_allHSM))
+ {
+ if (hsm != this)
+ {
+ hsm->update();
+ }
+ }
+ }
+}
+
+void HighScoreManager::update()
+{
+ for (int i = 0; i < 3; ++i)
+ {
+ m_scores[i].clear();
+ }
+ for (int i = 1; i <= 3; i++)
+ {
+ KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("Level%1").arg(i));
+ for (int j = 1; j <= 5; j++)
+ {
+ m_scores[i-1].append(qMakePair(cfg.readEntry(QStringLiteral("Score%1").arg(j),QVariant(0)).toInt(),cfg.readEntry(QStringLiteral("Name%1").arg(j),QString())));
+ }
+ }
+}
+
+QList< QPair<int, QString> > HighScoreManager::scores(int level) const
+{
+ return m_scores[level];
+}
+
+int HighScoreManager::score(int level, int position) const
+{
+ return m_scores[level][position].first;
+}
+
+QString HighScoreManager::name(int level, int position) const
+{
+ return m_scores[level][position].second;
+}
+
+#include "moc_highScoreManager.cpp"
diff --git a/src/highScoreManager.h b/src/highScoreManager.h
new file mode 100644
index 0000000..3afa3e8
--- /dev/null
+++ b/src/highScoreManager.h
@@ -0,0 +1,38 @@
+/*
+ SPDX-FileCopyrightText: 2005-2006 Albert Astals Cid <aacid at kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef HIGHSCOREMANAGER_H
+#define HIGHSCOREMANAGER_H
+
+#include <QObject>
+
+#include <QPair>
+#include <QList>
+#include <QtQml/qqmlregistration.h>
+
+class HighScoreManager : public QObject
+{
+Q_OBJECT
+QML_ELEMENT
+QML_SINGLETON
+ public:
+ HighScoreManager();
+ ~HighScoreManager() override;
+
+ QList< QPair<int, QString> > scores(int level) const;
+
+ Q_INVOKABLE bool scoreGoodEnough(int level, int score);
+ Q_INVOKABLE void addScore(int level, int score, const QString &name);
+ Q_INVOKABLE int score(int level, int position) const;
+ Q_INVOKABLE QString name(int level, int position) const;
+
+ private:
+ void update();
+
+ QList< QPair<int, QString> > m_scores[3];
+};
+
+#endif
diff --git a/src/highscoredialog.cpp b/src/highscoredialog.cpp
index 6e2615a..74f9df9 100644
--- a/src/highscoredialog.cpp
+++ b/src/highscoredialog.cpp
@@ -20,13 +20,12 @@
#include "counter.h"
#include "settings.h"
+#include "highScoreManager.h"
static const int margin = 15;
static const int smallMargin = 5;
static const int namesFontSize = 25;
-static QSet<highScoreManager *> s_allHSM;
-
/* scoresWidget */
class scoresWidget : public QWidget
@@ -64,7 +63,7 @@ void scoresWidget::paintEvent(QPaintEvent *)
p.setPen(Qt::black);
- if (blinkenSettings::customFont()) f = QFont(QStringLiteral("Steve"));
+ if (BlinkenSettings::customFont()) f = QFont(QStringLiteral("Steve"));
p.setFont(f);
f.setPointSize(KFontUtils::adaptFontSize(p, QStringLiteral("A"), 1000, namesFontSize, 28, 1, KFontUtils::DoNotAllowWordWrap));
p.setFont(f);
@@ -93,7 +92,7 @@ QSize scoresWidget::calcSize()
QPainter p(&dummyPixmap);
QFont f;
- if (blinkenSettings::customFont()) f = QFont(QStringLiteral("Steve"));
+ if (BlinkenSettings::customFont()) f = QFont(QStringLiteral("Steve"));
p.setFont(f);
f.setPointSize(KFontUtils::adaptFontSize(p, QStringLiteral("A"), 1000, namesFontSize, 28, 1, KFontUtils::DoNotAllowWordWrap));
p.setFont(f);
@@ -144,7 +143,7 @@ highScoreDialog::highScoreDialog(QWidget *parent, QSvgRenderer *renderer) : QDia
connect(buttonBox, &QDialogButtonBox::rejected, this, &highScoreDialog::close);
layout()->addWidget(buttonBox);
- highScoreManager hsm;
+ HighScoreManager hsm;
m_tw -> addTab(new scoresWidget(nullptr, hsm.scores(0), renderer), i18nc("@title:group High scores Level 1 tab title", "Level 1"));
m_tw -> addTab(new scoresWidget(nullptr, hsm.scores(1), renderer), i18nc("@title:group High scores Level 2 tab title", "Level 2"));
@@ -166,91 +165,4 @@ void highScoreDialog::showLevel(int level)
exec();
}
-/* highScoreManager */
-
-highScoreManager::highScoreManager()
-{
- s_allHSM << this;
- update();
-}
-
-highScoreManager::~highScoreManager()
-{
- s_allHSM.remove(this);
-}
-
-bool highScoreManager::scoreGoodEnough(int level, int score)
-{
- level--;
- QList< QPair<int, QString> >::iterator it, itEnd;
- it = m_scores[level].begin();
- itEnd = m_scores[level].end();
- while (it != itEnd && (*it).first >= score) ++it;
-
- return (it != itEnd);
-}
-
-void highScoreManager::addScore(int level, int score, const QString &name)
-{
- level--;
- QList< QPair<int, QString> >::iterator it, itEnd;
- it = m_scores[level].begin();
- itEnd = m_scores[level].end();
- while (it != itEnd && (*it).first >= score) ++it;
-
- if (it != itEnd)
- {
- m_scores[level].insert(it, qMakePair(score, name));
- m_scores[level].erase(--m_scores[level].end());
-
- KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("Level%1").arg(level + 1));
- int j;
- for (it = m_scores[level].begin(), j = 1; it != m_scores[level].end(); ++it, j++)
- {
- cfg.writeEntry(QStringLiteral("Score%1").arg(j), (*it).first);
- cfg.writeEntry(QStringLiteral("Name%1").arg(j), (*it).second);
- }
- cfg.sync();
-
- for (highScoreManager *hsm : std::as_const(s_allHSM))
- {
- if (hsm != this)
- {
- hsm->update();
- }
- }
- }
-}
-
-void highScoreManager::update()
-{
- for (int i = 0; i < 3; ++i)
- {
- m_scores[i].clear();
- }
- for (int i = 1; i <= 3; i++)
- {
- KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("Level%1").arg(i));
- for (int j = 1; j <= 5; j++)
- {
- m_scores[i-1].append(qMakePair(cfg.readEntry(QStringLiteral("Score%1").arg(j),QVariant(0)).toInt(),cfg.readEntry(QStringLiteral("Name%1").arg(j),QString())));
- }
- }
-}
-
-QList< QPair<int, QString> > highScoreManager::scores(int level) const
-{
- return m_scores[level];
-}
-
-int highScoreManager::score(int level, int position) const
-{
- return m_scores[level][position].first;
-}
-
-QString highScoreManager::name(int level, int position) const
-{
- return m_scores[level][position].second;
-}
-
#include "moc_highscoredialog.cpp"
diff --git a/src/highscoredialog.h b/src/highscoredialog.h
index 5144924..469ba22 100644
--- a/src/highscoredialog.h
+++ b/src/highscoredialog.h
@@ -12,6 +12,7 @@
#include <QPair>
#include <QList>
+
class QSvgRenderer;
class myTabWidget;
@@ -26,25 +27,4 @@ class highScoreDialog : private QDialog
myTabWidget *m_tw;
};
-class highScoreManager : public QObject
-{
-Q_OBJECT
- public:
- highScoreManager();
- ~highScoreManager() override;
-
- bool scoreGoodEnough(int level, int score);
- void addScore(int level, int score, const QString &name);
-
- QList< QPair<int, QString> > scores(int level) const;
-
- Q_INVOKABLE int score(int level, int position) const;
- Q_INVOKABLE QString name(int level, int position) const;
-
- private:
- void update();
-
- QList< QPair<int, QString> > m_scores[3];
-};
-
#endif
diff --git a/src/main.cpp b/src/main.cpp
index 227bcad..1cef259 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,34 +4,64 @@
SPDX-License-Identifier: GPL-2.0-or-later
*/
-#include "blinken.h"
-
#include "blinken_version.h"
#include <KAboutData>
#include <KLocalizedString>
-#include <KCrash>
-#include <KDBusService>
+
+#include "blinkengame.h"
+#include "highScoreManager.h"
+#include "settings.h"
+
+#ifdef QML_VERSION
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include "maskedmousearea.h"
+
+#else
#include <QApplication>
#include <QCommandLineParser>
-
#include <QFontDatabase>
#include <QFontInfo>
#include <QStandardPaths>
+#include <KCrash>
+#include <KDBusService>
+
+#include "blinken.h"
+
+#endif
+
+Q_DECL_EXPORT
int main(int argc, char *argv[])
{
+
+#ifdef QML_VERSION
+ QGuiApplication app{argc, argv};
+#else
+ QApplication app{argc, argv};
+#endif
+
KLocalizedString::setApplicationDomain(QByteArrayLiteral("blinken"));
- QApplication app(argc, argv);
KAboutData about(QStringLiteral("blinken"), i18n("Blinken"), QStringLiteral(BLINKEN_VERSION_STRING), i18n("A memory enhancement game"), KAboutLicense::GPL, i18n("© 2005-2007 Albert Astals Cid\nSPDX-FileCopyrightText: 2005-2007 Danny Allen "));
about.addAuthor(i18n("Albert Astals Cid"), i18n("Coding"), QStringLiteral("aacid at kde.org"));
about.addAuthor(i18n("Danny Allen"), i18n("Design, Graphics and Sounds"), QStringLiteral("danny at dannyallen.co.uk"));
about.addCredit(i18n("Steve Jordi"), i18n("GPL'ed his 'Steve' font so that we could use it"), QStringLiteral("steve at sjordi.com"));
-
KAboutData::setApplicationData(about);
+#ifdef QML_VERSION
+ QQmlApplicationEngine engine;
+
+ QQmlContext *context = engine.rootContext();
+ context->setContextObject(new KLocalizedContext{&engine});
+
+ engine.loadFromModule("org.kde.blinken", "Blinken");
+
+#else
KCrash::initialize();
QCommandLineParser parser;
@@ -41,7 +71,7 @@ int main(int argc, char *argv[])
app.setWindowIcon(QIcon::fromTheme(QStringLiteral("blinken")));
- QFont f(QStringLiteral("Steve"), 12, QFont::Normal, true);
+ QFont f(QStringLiteral("Steve"), 12, QFont::Normal, true);
// Works with Steve may need some tweaking to work with other fonts
if (!QFontInfo(f).exactMatch())
{
@@ -49,5 +79,7 @@ int main(int argc, char *argv[])
}
KDBusService service;
new blinken();
+#endif
+
return app.exec();
}
diff --git a/src/maskedmousearea.cpp b/src/maskedmousearea.cpp
new file mode 100644
index 0000000..225a692
--- /dev/null
+++ b/src/maskedmousearea.cpp
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "maskedmousearea.h"
+
+#include <QGuiApplication>
+#include <QStyleHints>
+#include <qqmlfile.h>
+
+
+MaskedMouseArea::MaskedMouseArea(QQuickItem *parent)
+ : QQuickItem(parent), m_pressed(false), m_alphaThreshold(0.0),
+ m_containsMouse(false) {
+ setAcceptHoverEvents(true);
+ setAcceptedMouseButtons(Qt::LeftButton);
+}
+
+void MaskedMouseArea::setPressed(bool pressed) {
+ if (m_pressed != pressed) {
+ m_pressed = pressed;
+ Q_EMIT pressedChanged();
+ }
+}
+
+void MaskedMouseArea::setContainsMouse(bool containsMouse) {
+ if (m_containsMouse != containsMouse) {
+ m_containsMouse = containsMouse;
+ Q_EMIT containsMouseChanged();
+ }
+}
+
+void MaskedMouseArea::setMaskSource(const QUrl &source) {
+ if (m_maskSource != source) {
+ m_maskSource = source;
+ m_maskImage = QImage(QQmlFile::urlToLocalFileOrQrc(source));
+ Q_EMIT maskSourceChanged();
+ }
+}
+
+void MaskedMouseArea::setAlphaThreshold(qreal threshold) {
+ if (m_alphaThreshold != threshold) {
+ m_alphaThreshold = threshold;
+ Q_EMIT alphaThresholdChanged();
+ }
+}
+
+bool MaskedMouseArea::contains(const QPointF &point) const {
+ if (!QQuickItem::contains(point) || m_maskImage.isNull())
+ return false;
+
+ QPoint p = point.toPoint();
+
+ //get the scaled image
+ QImage transImage=m_maskImage.scaled(QSize(this->width(),this->height()),Qt::IgnoreAspectRatio,Qt::FastTransformation);
+ if (p.x() < 0 || p.x() >= transImage.width() || p.y() < 0 ||
+ p.y() >= transImage.height())
+ return false;
+
+ qreal r = qBound<int>(0, m_alphaThreshold * 255, 255);
+
+ return qAlpha(transImage.pixel(p)) > r;
+}
+
+void MaskedMouseArea::mousePressEvent(QMouseEvent *event) {
+ setPressed(true);
+ m_pressPoint = event->position().toPoint();
+ Q_EMIT pressed();
+}
+
+void MaskedMouseArea::mouseReleaseEvent(QMouseEvent *event) {
+ setPressed(false);
+ Q_EMIT released();
+
+ const int threshold = qApp->styleHints()->startDragDistance();
+ const bool isClick =
+ (threshold >= qAbs(event->position().toPoint().x() - m_pressPoint.x()) &&
+ threshold >= qAbs(event->position().toPoint().y() - m_pressPoint.y()));
+
+ if (isClick)
+ Q_EMIT clicked();
+}
+
+void MaskedMouseArea::mouseUngrabEvent() {
+ setPressed(false);
+ Q_EMIT canceled();
+}
+
+void MaskedMouseArea::hoverEnterEvent(QHoverEvent *event) {
+ Q_UNUSED(event);
+ setContainsMouse(true);
+}
+
+void MaskedMouseArea::hoverLeaveEvent(QHoverEvent *event) {
+ Q_UNUSED(event);
+ setContainsMouse(false);
+}
diff --git a/src/maskedmousearea.h b/src/maskedmousearea.h
new file mode 100644
index 0000000..400a532
--- /dev/null
+++ b/src/maskedmousearea.h
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MASKEDMOUSEAREA_H
+#define MASKEDMOUSEAREA_H
+
+#include <QImage>
+#include <QQuickItem>
+
+class MaskedMouseArea : public QQuickItem {
+Q_OBJECT
+QML_ELEMENT
+ Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged)
+ Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged)
+ Q_PROPERTY(QUrl maskSource READ maskSource WRITE setMaskSource NOTIFY
+ maskSourceChanged)
+ Q_PROPERTY(qreal alphaThreshold READ alphaThreshold WRITE setAlphaThreshold
+ NOTIFY alphaThresholdChanged)
+
+public:
+ MaskedMouseArea(QQuickItem *parent = nullptr);
+
+ bool contains(const QPointF &point) const override;
+
+ bool isPressed() const { return m_pressed; }
+ bool containsMouse() const { return m_containsMouse; }
+
+ QUrl maskSource() const { return m_maskSource; }
+ void setMaskSource(const QUrl &source);
+
+ qreal alphaThreshold() const { return m_alphaThreshold; }
+ void setAlphaThreshold(qreal threshold);
+
+Q_SIGNALS:
+ void pressed();
+ void released();
+ void clicked();
+ void canceled();
+ void pressedChanged();
+ void maskSourceChanged();
+ void containsMouseChanged();
+ void alphaThresholdChanged();
+
+protected:
+ void setPressed(bool pressed);
+ void setContainsMouse(bool containsMouse);
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void hoverEnterEvent(QHoverEvent *event) override;
+ void hoverLeaveEvent(QHoverEvent *event) override;
+ void mouseUngrabEvent() override;
+
+private:
+ bool m_pressed;
+ QUrl m_maskSource;
+ QImage m_maskImage;
+ QPointF m_pressPoint;
+ qreal m_alphaThreshold;
+ bool m_containsMouse;
+};
+
+#endif
diff --git a/src/settings.kcfgc b/src/settings.kcfgc
index 2bd3849..f102e2d 100644
--- a/src/settings.kcfgc
+++ b/src/settings.kcfgc
@@ -1,4 +1,6 @@
File=blinken.kcfg
-ClassName=blinkenSettings
+ClassName=BlinkenSettings
Singleton=true
Mutators=true
+GenerateProperties=true
+QmlRegistration=true
diff --git a/src/soundsplayer.cpp b/src/soundsplayer.cpp
index 5cd487c..9915581 100644
--- a/src/soundsplayer.cpp
+++ b/src/soundsplayer.cpp
@@ -10,51 +10,123 @@
#include <QStandardPaths>
+#ifdef QML_VERSION
+//for QML version
+soundsPlayer::soundsPlayer()
+{
+ addSoundsFile();
+ m_soundsPlayer.setAudioOutput(new QAudioOutput);
+ connect(&m_soundsPlayer, &QMediaPlayer::playingChanged,this,&soundsPlayer::soundEffectPlayEnded);
+
+ connect(&m_warnTimer, &QTimer::timeout, this, &soundsPlayer::ended);
+ m_warnTimer.setSingleShot(true);
+}
+#else
soundsPlayer::soundsPlayer()
: m_audioOutput(Phonon::GameCategory)
{
+ addSoundsFile();
m_audioOutput.setVolume( 0.8f );
Phonon::createPath(&m_mediaObject, &m_audioOutput);
connect(&m_mediaObject, &Phonon::MediaObject::finished, this, &soundsPlayer::playEnded);
+ connect(&m_warnTimer, &QTimer::timeout, this, &soundsPlayer::ended);
+ m_warnTimer.setSingleShot(true);
+}
+#endif
+
+
+
+void soundsPlayer::addSoundsFile()
+{
+#if defined(Q_OS_ANDROID) || defined(QML_VERSION)
+ m_allSound=QStringLiteral("qrc:lose.wav");
+ m_greenSound=QStringLiteral("qrc:1.wav");
+ m_redSound=QStringLiteral("qrc:2.wav");
+ m_blueSound=QStringLiteral("qrc:3.wav");
+ m_yellowSound=QStringLiteral("qrc:4.wav");
+#else
m_allSound = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("sounds/lose.wav"));
m_greenSound = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("sounds/1.wav"));
m_redSound = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("sounds/2.wav"));
m_blueSound = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("sounds/3.wav"));
m_yellowSound = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("sounds/4.wav"));
-
- connect(&m_warnTimer, &QTimer::timeout, this, &soundsPlayer::ended);
- m_warnTimer.setSingleShot(true);
+#endif
}
soundsPlayer::~soundsPlayer()
{
}
-void soundsPlayer::play(blinkenGame::color c)
+void soundsPlayer::play(BlinkenGame::Color c)
{
- if (blinkenSettings::playSounds())
+#ifdef QML_VERSION
+ //QML version
+ if(m_soundsPlayer.isPlaying())return;
+ if(BlinkenSettings::playSounds())
{
QString soundFile;
switch (c)
{
- case blinkenGame::red:
+ case BlinkenGame::Red:
+ soundFile = m_redSound;
+ break;
+
+ case BlinkenGame::Green:
+ soundFile = m_greenSound;
+ break;
+
+ case BlinkenGame::Blue:
+ soundFile = m_blueSound;
+ break;
+
+ case BlinkenGame::Yellow:
+ soundFile = m_yellowSound;
+ break;
+
+ case BlinkenGame::All:
+ soundFile = m_allSound;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!soundFile.isEmpty())
+ {
+ m_soundsPlayer.setSource(QUrl(soundFile));
+ m_soundsPlayer.setLoops(1);
+ m_soundsPlayer.play();
+ }else{
+
+ qDebug()<<"can't find sound file "<<soundFile;
+ }
+ }else{
+ m_warnTimer.start(250);
+ }
+#else
+ if (BlinkenSettings::playSounds())
+ {
+ QString soundFile;
+ switch (c)
+ {
+ case BlinkenGame::Red:
soundFile = m_redSound;
break;
- case blinkenGame::green:
+ case BlinkenGame::Green:
soundFile = m_greenSound;
break;
- case blinkenGame::blue:
+ case BlinkenGame::Blue:
soundFile = m_blueSound;
break;
- case blinkenGame::yellow:
+ case BlinkenGame::Yellow:
soundFile = m_yellowSound;
break;
- case blinkenGame::all:
+ case BlinkenGame::All:
soundFile = m_allSound;
break;
@@ -71,14 +143,28 @@ void soundsPlayer::play(blinkenGame::color c)
{
m_warnTimer.start(250);
}
+#endif
}
+#ifdef QML_VERSION
+
+void soundsPlayer::soundEffectPlayEnded()
+{
+ if(!m_soundsPlayer.isPlaying())
+ {
+ m_warnTimer.start(250);
+ }
+}
+
+#else
void soundsPlayer::playEnded()
{
- if (blinkenSettings::playSounds())
+ if (BlinkenSettings::playSounds())
{
m_warnTimer.start(250);
}
}
+#endif
+
#include "moc_soundsplayer.cpp"
diff --git a/src/soundsplayer.h b/src/soundsplayer.h
index b3e26a6..ddadfbf 100644
--- a/src/soundsplayer.h
+++ b/src/soundsplayer.h
@@ -7,8 +7,15 @@
#ifndef SOUNDSPLAYER_H
#define SOUNDSPLAYER_H
+#include <QtSystemDetection>
+
+#ifdef QML_VERSION
+#include <QMediaPlayer>
+#include <QAudioOutput>
+#else
#include <phonon/MediaObject>
#include <phonon/audiooutput.h>
+#endif
#include <QTimer>
@@ -21,19 +28,28 @@ Q_OBJECT
soundsPlayer();
~soundsPlayer() override;
- void play(blinkenGame::color c);
+ void play(BlinkenGame::Color c);
Q_SIGNALS:
void ended();
private Q_SLOTS:
+
+#ifdef QML_VERSION
+ void soundEffectPlayEnded();
+#else
void playEnded();
-
+#endif
private:
+ void addSoundsFile();
QString m_greenSound, m_redSound, m_blueSound, m_yellowSound, m_allSound;
+
+#ifdef QML_VERSION
+ QMediaPlayer m_soundsPlayer;
+#else
Phonon::MediaObject m_mediaObject;
Phonon::AudioOutput m_audioOutput;
-
+#endif
QTimer m_warnTimer;
};
diff --git a/src/ui/Blinken.qml b/src/ui/Blinken.qml
new file mode 100644
index 0000000..0a12b1f
--- /dev/null
+++ b/src/ui/Blinken.qml
@@ -0,0 +1,323 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import org.kde.kirigami as Kirigami
+import org.kde.kirigamiaddons.formcard as FormCard
+import org.kde.blinken
+
+Kirigami.AbstractApplicationWindow {
+ id: blinken
+ title: i18n("Blinken")
+ color: "black"
+ readonly property int blinkenBackgroundWidth: 814
+ readonly property int blinkenBackgroundHeight: 696
+ Rectangle {
+ id: rootRectangle
+ height: parent.height<parent.width*(3/4)?parent.height:parent.width*(3/4)
+ width: parent.height<parent.width*(3/4)?height * (4 / 3):parent.width
+ anchors.centerIn: parent
+
+ Image {
+ id: background
+ anchors.fill: parent
+ source: "qrc:ui/background.svg"
+ fillMode: Image.Stretch
+
+ GameButtons {
+ id: gameButtons
+ anchors.fill: parent
+ }
+ }
+
+ ExitButton {
+ id: exitButton
+ width: rootRectangle.width * (80 / blinken.blinkenBackgroundWidth)
+ height: width
+ anchors.right: parent.right
+ anchors.top: parent.top
+ scale: rootRectangle.scale
+ anchors.rightMargin: rootRectangle.width * (8 / blinken.blinkenBackgroundWidth)
+ anchors.topMargin: rootRectangle.height * (8 / blinken.blinkenBackgroundHeight)
+ }
+
+ BlinkenMenu {
+ id: blinkenMenu
+ width: rootRectangle.width * (230 / blinken.blinkenBackgroundWidth)
+ height: width
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: rootRectangle.height * (8 / blinken.blinkenBackgroundHeight)
+ anchors.rightMargin: rootRectangle.width * (8 / blinken.blinkenBackgroundWidth)
+ }
+
+ HighScoreButton {
+ id: highScoreButton
+ width: rootRectangle.width * (80 / blinken.blinkenBackgroundWidth)
+ height: width
+ anchors.left: parent.left
+ anchors.top: parent.top
+ scale: rootRectangle.scale
+ anchors.leftMargin: rootRectangle.width * (8 / blinken.blinkenBackgroundWidth)
+ anchors.topMargin: rootRectangle.height * (8 / blinken.blinkenBackgroundHeight)
+ mouseArea.onPressed: highScoreButtonImage.source = "qrc:ui/highScore_highlight.svg"
+ mouseArea.onReleased: {
+ highScoreButtonImage.source = "qrc:ui/highScore.svg";
+ highScoreLists.openLists();
+ }
+ }
+ HighScoreLists {
+ id: highScoreLists
+ highScoreListsPopup.anchors.centerIn: Overlay.overlay
+ highScoreListsPopup.width: rootRectangle.width * 0.5
+ highScoreListsPopup.height: rootRectangle.height * 0.8
+ }
+ FontLoader {
+ id: steveFontloader
+ source: "qrc:steve.ttf"
+ }
+ Text {
+ id: statusText
+ width: rootRectangle.width * (500/ blinken.blinkenBackgroundWidth)
+ height: rootRectangle.height * (160 / blinken.blinkenBackgroundHeight)
+ color: "#0062f7" //To look more like the original
+ font.family: BlinkenSettings.customFont ? steveFontloader.name : ""
+ text: i18n("Press Start to begin")
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: rootRectangle.height * (0 / blinken.blinkenBackgroundHeight)
+ anchors.leftMargin: rootRectangle.width * (30 / blinken.blinkenBackgroundWidth)
+
+ font.pixelSize: 40
+ fontSizeMode: Text.HorizontalFit
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignBottom
+ wrapMode:Text.WrapAnywhere
+ transform: Rotation { origin.x: -3; origin.y: -3; angle: -3.3}
+ }
+ ScoreAndCounter {
+ id: scoreAndCounter
+ width: rootRectangle.width * (150 / blinken.blinkenBackgroundWidth)
+ height: rootRectangle.height * (85 / blinken.blinkenBackgroundHeight)
+ anchors.top: parent.top
+ anchors.topMargin: parent.height * 0.17
+ anchors.horizontalCenter: parent.horizontalCenter
+ MouseArea {
+ anchors.fill: parent
+ onClicked: highScoreLists.openLists()
+ }
+ }
+
+ GameOptions {
+ id: gameOptions
+ anchors.horizontalCenter: rootRectangle.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: parent.height * 0.43
+ startButton.width: rootRectangle.width * (95 / blinken.blinkenBackgroundWidth)
+ startButton.height: rootRectangle.height * (65 / blinken.blinkenBackgroundHeight)
+
+ optionsList.width: rootRectangle.width * (250 / blinken.blinkenBackgroundWidth)
+ optionsList.height: rootRectangle.height * (90 / blinken.blinkenBackgroundHeight)
+ optionsList.spacing: rootRectangle.width * (45 / blinken.blinkenBackgroundWidth)
+ }
+ }
+
+ property string playerName: ""
+ property int lastGameScore: -1
+ Dialog {
+ id: nameDialog
+ modal: true
+ closePolicy: Popup.NoAutoClose
+ anchors.centerIn: parent
+ title: i18n("Enter Your Name")
+ standardButtons: Dialog.Ok
+ RowLayout {
+ Label {
+ text: i18nc("@label:textbox refers to the user's name", "Name:")
+ }
+ TextInput {
+ id: nameInput
+ focus: true
+ font.bold: true
+ text: blinken.playerName
+ //old Blinken doesn't have the limit, but whenever the name is too long, it can't be displayed very well
+ maximumLength: 20
+ }
+ }
+ onAccepted: {
+ let name = nameInput.text;
+ blinken.playerName = nameInput.text;
+ if (name != "" && lastGameScore > 0)
+ HighScoreManager.addScore(BlinkenGame.level(), lastGameScore, name);
+ lastGameScore = -1;
+ }
+ }
+
+ //about Blinken Page
+ Popup {
+ id: aboutBlinkenPage
+ anchors.centerIn: parent
+ height: rootRectangle.height * 0.9
+ width: rootRectangle.width * 0.7
+ FormCard.AboutPage {
+ anchors.fill: parent
+ }
+ }
+ //about KDE Page
+ Popup {
+ id: aboutKDEPage
+ anchors.centerIn: parent
+ height: rootRectangle.height * 0.9
+ width: rootRectangle.width * 0.7
+ FormCard.AboutKDE {
+ anchors.fill: parent
+ }
+ }
+ // Settting
+ Popup {
+ id: settingsPage
+ anchors.centerIn: parent
+ height: rootRectangle.height * (170 / blinken.blinkenBackgroundHeight)
+ width: rootRectangle.width * (450 / blinken.blinkenBackgroundWidth)
+ Column {
+ Switch {
+ text: i18n("Sounds")
+ checked: BlinkenSettings.playSounds
+ onToggled: {
+ BlinkenSettings.playSounds = !BlinkenSettings.playSounds;
+ BlinkenSettings.save();
+ }
+ }
+ Switch {
+ text: i18n("Use custom font for status text")
+ checked: BlinkenSettings.customFont
+ onToggled: {
+ var aux = i18nc("If the Steve font that is used by Blinken by default to show status messages does not support any of the characters of your language, please translate that message to 1 and KDE standard font will be used to show the texts, if not translate it to 0", "0");
+ if (BlinkenSettings.customFont) {
+ statusText.font.family = "";
+ } else {
+ if (aux == "0")
+ statusText.font.family = steveFontloader.name;
+ }
+ BlinkenSettings.customFont = !BlinkenSettings.customFont;
+ BlinkenSettings.save();
+ }
+ }
+ }
+ }
+ Connections {
+ target: blinkenMenu
+ function onOpenAboutBlinkenPage() {
+ aboutBlinkenPage.open();
+ }
+ function onOpenAboutKDEPage() {
+ aboutKDEPage.open();
+ }
+ function onOpenSettingsPage() {
+ settingsPage.open();
+ }
+ function onOpenHandBookPage() {
+ Qt.openUrlExternally("help:/");
+ }
+ }
+
+ Connections {
+ target: BlinkenGame
+ function onGameEnded() {
+ let score = BlinkenGame.score();
+ let level = BlinkenGame.level();
+ if (HighScoreManager.scoreGoodEnough(level, score)) {
+ lastGameScore = score;
+ nameDialog.open();
+ }
+ }
+ function onPhaseChanged() {
+ switch (BlinkenGame.phase) {
+ case (BlinkenGame.Starting):
+ {
+ statusText.text = i18n("Press Start to begin");
+ scoreAndCounter.setScore(0);
+ scoreAndCounter.counter.state = "zero";
+ break;
+ }
+ case (BlinkenGame.ChoosingLevel):
+ {
+ statusText.text = i18n("Set the Difficulty Level...");
+ scoreAndCounter.setScore(0);
+ scoreAndCounter.counter.state = "zero";
+ gameOptions.blinkenState.state = "selectLevelState";
+ break;
+ }
+ case (BlinkenGame.Waiting3):
+ {
+ statusText.text = i18n("Next sequence in 3...");
+ scoreAndCounter.setScore(BlinkenGame.score());
+ scoreAndCounter.counter.state = "three";
+ break;
+ }
+ case (BlinkenGame.Waiting2):
+ {
+ if (BlinkenGame.level() === 1) {
+ statusText.text = i18n("Next sequence in 3, 2...");
+ } else {
+ statusText.text = i18n("Next sequence in 2...");
+ }
+ scoreAndCounter.setScore(BlinkenGame.score());
+ scoreAndCounter.counter.state = "two";
+ break;
+ }
+ case (BlinkenGame.Waiting1):
+ {
+ if (BlinkenGame.level() === 1) {
+ statusText.text = i18n("Next sequence in 3, 2, 1...");
+ } else {
+ statusText.text = i18n("Next sequence in 2, 1...");
+ }
+ scoreAndCounter.counter.state = "one";
+ break;
+ }
+ case (BlinkenGame.LearningTheSequence):
+ {
+ statusText.text = i18n("Remember this sequence...");
+ scoreAndCounter.counter.state = "zero";
+ break;
+ }
+ case (BlinkenGame.TypingTheSequence):
+ {
+ statusText.text = i18n("Repeat the sequence");
+ break;
+ }
+ default:
+ {
+ // never happens
+ break;
+ }
+ }
+ }
+ function onHighlight(aimColor, unhighlight) {
+ gameButtons.highLight(aimColor);
+ if (aimColor == 15)
+ timer.setTimeout(function () {
+ gameButtons.highLight(0);
+ }, 250);
+ }
+ }
+ Timer {
+ id: timer
+ function setTimeout(cb, delayTime) {
+ timer.interval = delayTime;
+ timer.repeat = false;
+ timer.triggered.connect(cb);
+ timer.triggered.connect(function release() {
+ timer.triggered.disconnect(cb);
+ timer.triggered.disconnect(release);
+ });
+ timer.start();
+ }
+ }
+}
diff --git a/src/ui/BlinkenMenu.qml b/src/ui/BlinkenMenu.qml
new file mode 100644
index 0000000..8e46348
--- /dev/null
+++ b/src/ui/BlinkenMenu.qml
@@ -0,0 +1,131 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+
+Item {
+ id: blinkenMenu
+ width: 150 //default
+ height: width
+ signal openAboutBlinkenPage
+ signal openAboutKDEPage
+ signal openSettingsPage
+ signal openHandBookPage
+
+ Image {
+ id: menuImage
+ anchors.fill: parent
+ source: "qrc:ui/menu.svg"
+ scale: blinkenMenu.scale
+ fillMode: Image.Stretch
+ }
+
+ MouseArea {
+ id: questionMouseArea
+
+ width: blinkenMenu.width * (60 / 150)
+ height: questionMouseArea.width
+
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ scale: blinkenMenu.scale
+ anchors.bottomMargin: 0
+ anchors.rightMargin: 0
+
+ onClicked: {
+ if (menuState.state == "fold") {
+ menuState.state = "unfold";
+ } else {
+ menuState.state = "fold";
+ }
+ }
+ }
+
+ MouseArea {
+ id: aboutKDE
+ width: blinkenMenu.width * 0.3
+ height: blinkenMenu.height * 0.3
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 0
+ anchors.leftMargin: 0
+ onClicked: {
+ blinkenMenu.openAboutKDEPage();
+ }
+ }
+
+ MouseArea {
+ id: aboutBlinken
+
+ width: blinkenMenu.width * 0.3
+ height: blinkenMenu.height * 0.3
+
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: aboutKDE.width
+ onClicked: {
+ blinkenMenu.openAboutBlinkenPage();
+ }
+ }
+
+ MouseArea {
+ id: settings
+
+ width: blinkenMenu.width * 0.3
+ height: blinkenMenu.height * 0.3
+
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: questionMouseArea.height
+ onClicked: {
+ blinkenMenu.openSettingsPage();
+ }
+ }
+
+ MouseArea {
+ id: handBook
+
+ width: blinkenMenu.width * 0.3
+ height: blinkenMenu.height * 0.3
+
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.rightMargin: 0
+ anchors.topMargin: 0
+ onClicked: {
+ blinkenMenu.openHandBookPage();
+ }
+ }
+
+ StateGroup {
+ id: menuState
+ state: "fold"
+ states: [
+ State {
+ name: "fold"
+ PropertyChanges {
+ menuImage.source: "qrc:ui/menu.svg"
+ aboutKDE.enabled: false
+ aboutBlinken.enabled: false
+ settings.enabled: false
+ handBook.enabled: false
+ }
+ },
+ State {
+ name: "unfold"
+ PropertyChanges {
+ menuImage.source: "qrc:ui/menu_list.svg"
+ aboutKDE.enabled: true
+ aboutBlinken.enabled: true
+ settings.enabled: true
+ handBook.enabled: true
+ }
+ }
+ ]
+ }
+}
diff --git a/src/ui/ExitButton.qml b/src/ui/ExitButton.qml
new file mode 100644
index 0000000..7c9e200
--- /dev/null
+++ b/src/ui/ExitButton.qml
@@ -0,0 +1,37 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+
+Item {
+ height: 768 //default size
+ width: height
+
+ Rectangle {
+ id: rectangle
+ color: "#00ffffff"
+ border.color: "#00000000"
+ anchors.fill: parent
+
+ Image {
+ id: image
+ anchors.fill: parent
+ source: "qrc:ui/exit.svg"
+ fillMode: Image.Stretch
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ onPressed: {
+ image.source = "qrc:ui/exit_highlight.svg";
+ }
+ onReleased: {
+ Qt.quit();
+ }
+ }
+ }
+}
diff --git a/src/ui/GameButton.qml b/src/ui/GameButton.qml
new file mode 100644
index 0000000..6c9e2be
--- /dev/null
+++ b/src/ui/GameButton.qml
@@ -0,0 +1,43 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+import org.kde.blinken
+
+Item {
+ id: gameButton
+ required property string normalImageSource
+ required property string highLightImageSource
+ required property int gameColor
+ default property bool isHighLight: false
+
+ Image {
+ id: image
+ anchors.fill: parent
+ scale: gameButton.scale
+ source: gameButton.isHighLight ? gameButton.highLightImageSource : gameButton.normalImageSource
+ fillMode: Image.Stretch
+
+ MaskedMouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ alphaThreshold: 0.5
+ maskSource: image.source
+ scale: image.scale
+ onPressedChanged: {
+ if (mouseArea.released) {
+ gameButton.isHighLight = false;
+ }
+ if (!BlinkenGame.canType())
+ return;
+ if (mouseArea.pressed) {
+ gameButton.isHighLight = true;
+ BlinkenGame.clicked(gameColor);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ui/GameButtons.qml b/src/ui/GameButtons.qml
new file mode 100644
index 0000000..4b8ca28
--- /dev/null
+++ b/src/ui/GameButtons.qml
@@ -0,0 +1,118 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+import org.kde.blinken
+
+Item {
+ id: gameButtons
+
+ GameButton {
+ id: redButton
+ width: yellowButton.width
+ height: yellowButton.height
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.topMargin: yellowButton.anchors.topMargin
+ anchors.rightMargin: gameButtons.width * 0.02929
+ scale: gameButtons.scale
+
+ normalImageSource: "qrc:color/red.svg"
+ highLightImageSource: "qrc:color/red_highlight.svg"
+ gameColor: BlinkenGame.Red
+ }
+
+ GameButton {
+ id: yellowButton
+ width: gameButtons.width * 0.4609
+ height: gameButtons.height * 0.3593
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.leftMargin: gameButtons.width * 0.0332
+ anchors.topMargin: gameButtons.height * 0.0338
+ scale: gameButtons.scale
+
+ normalImageSource: "qrc:color/yellow.svg"
+ highLightImageSource: "qrc:color/yellow_highlight.svg"
+ gameColor: BlinkenGame.Yellow
+ }
+
+ GameButton {
+ id: blueButton
+ width: yellowButton.width
+ height: yellowButton.height
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: gameButtons.height * 0.2291
+ anchors.leftMargin: yellowButton.anchors.leftMargin
+ scale: gameButtons.scale
+
+ normalImageSource: "qrc:color/blue.svg"
+ highLightImageSource: "qrc:color/blue_highlight.svg"
+ gameColor: BlinkenGame.Blue
+ }
+
+ GameButton {
+ id: greenButton
+ width: yellowButton.width
+ height: yellowButton.height
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.rightMargin: redButton.anchors.rightMargin
+ anchors.bottomMargin: blueButton.anchors.bottomMargin
+ scale: gameButtons.scale
+
+ normalImageSource: "qrc:color/green.svg"
+ highLightImageSource: "qrc:color/green_highlight.svg"
+ gameColor: BlinkenGame.Green
+ }
+
+ function highLight(aimColor) {
+ switch (aimColor) {
+ case (BlinkenGame.None):
+ {
+ redButton.isHighLight = false;
+ greenButton.isHighLight = false;
+ blueButton.isHighLight = false;
+ yellowButton.isHighLight = false;
+ break;
+ }
+ case (BlinkenGame.Red):
+ {
+ redButton.isHighLight = true;
+ break;
+ }
+ case (BlinkenGame.Green):
+ {
+ greenButton.isHighLight = true;
+ break;
+ }
+ case (BlinkenGame.Blue):
+ {
+ blueButton.isHighLight = true;
+ break;
+ }
+ case (BlinkenGame.Yellow):
+ {
+ yellowButton.isHighLight = "true";
+ break;
+ }
+ case (BlinkenGame.All):
+ {
+ redButton.isHighLight = true;
+ greenButton.isHighLight = true;
+ blueButton.isHighLight = true;
+ yellowButton.isHighLight = true;
+ break;
+ }
+ default:
+ {
+ // never happens
+ break;
+ }
+ }
+ }
+}
diff --git a/src/ui/GameOptions.qml b/src/ui/GameOptions.qml
new file mode 100644
index 0000000..93482b9
--- /dev/null
+++ b/src/ui/GameOptions.qml
@@ -0,0 +1,218 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+import org.kde.blinken
+
+Item {
+ id: gameOptions
+ property alias startButton: startButton
+ property alias optionsList: optionsList
+ property alias blinkenState: blinkenState
+
+ Rectangle {
+ id: startButton
+ color: "#282828"
+ border.color: "black"
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ border.width: 5
+ radius: 15
+
+ Text {
+ id: text1
+ color: "#ffffff"
+ text: i18nc("@action:button Start a new game", "Start")
+ anchors.fill: parent
+ font.pixelSize: 22
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ id: startMouseArea
+ anchors.fill: parent
+ onClicked: {
+ if (blinkenState.state === "default") {
+ blinkenState.state = "selectLevelState";
+ }
+ BlinkenGame.setPhase(BlinkenGame.ChoosingLevel);
+ }
+ }
+ }
+
+ Row {
+ id: optionsList
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+
+ visible: false
+
+ Rectangle {
+ id: plane
+ color: "#282828"
+ border.color: "black"
+
+ width: optionsList.width * (43 / 220)
+ height: optionsList.height * (48 / 70)
+ radius: 8
+ border.width: 4
+
+ Text {
+ id: text2
+ color: "#ffffff"
+ text: i18n("1")
+ anchors.fill: parent
+ font.pixelSize: 32
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ id: level1MouseArea
+ anchors.fill: parent
+ onClicked: {
+ if (blinkenState.state === "selectLevelState") {
+ BlinkenGame.start(1);
+ blinkenState.state = "gameStart";
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: plane1
+ color: "#282828"
+ border.color: "black"
+
+ width: plane.width
+ height: plane.height
+ radius: plane.radius
+ border.width: plane.border.width
+
+ Text {
+ id: text3
+ color: "#ffffff"
+ text: i18n("2")
+ anchors.fill: parent
+ font.pixelSize: 32
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ id: level2MouseArea
+ anchors.fill: parent
+ onClicked: {
+ if (blinkenState.state === "selectLevelState") {
+ BlinkenGame.start(2);
+ blinkenState.state = "gameStart";
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: plane2
+ color: "#282828"
+ border.color: "black"
+
+ width: plane.width
+ height: plane.height
+ radius: plane.radius
+ border.width: plane.border.width
+
+ Text {
+ id: text4
+ color: "#ffffff"
+ text: i18n("?")
+ anchors.fill: parent
+ font.pixelSize: 32
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ id: level3MouseArea
+ anchors.fill: parent
+ onClicked: {
+ if (blinkenState.state === "selectLevelState") {
+ BlinkenGame.start(3);
+ blinkenState.state = "gameStart";
+ }
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: restartButton
+ color: "#282828"
+ border.color: "black"
+
+ x: startButton.x
+ y: startButton.y
+ width: startButton.width
+ height: startButton.height
+ visible: false
+ radius: startButton.radius
+ border.width: startButton.border.width
+
+ Text {
+ id: text5
+ anchors.fill: parent
+ color: "#ffffff"
+ text: i18n("Restart")
+ font.pixelSize: 15
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+
+ onClicked: {
+ if (blinkenState.state === "gameStart") {
+ BlinkenGame.setPhase(BlinkenGame.Starting);
+ blinkenState.state = "selectLevelState";
+ }
+ }
+ }
+ }
+
+ StateGroup {
+ id: blinkenState
+ state: "default"
+ states: [
+ State {
+ name: "default"
+ PropertyChanges {
+ startButton.visible: true
+ optionsList.visible: false
+ restartButton.visible: false
+ }
+ },
+ State {
+ name: "selectLevelState"
+ PropertyChanges {
+ startButton.visible: false
+ optionsList.visible: true
+ restartButton.visible: false
+ }
+ },
+ State {
+ name: "gameStart"
+ PropertyChanges {
+ startButton.visible: false
+ restartButton.visible: true
+ optionsList.visible: false
+ }
+ }
+ ]
+ }
+}
diff --git a/src/ui/HighScoreButton.qml b/src/ui/HighScoreButton.qml
new file mode 100644
index 0000000..743aec3
--- /dev/null
+++ b/src/ui/HighScoreButton.qml
@@ -0,0 +1,35 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+
+Item {
+ id: highScoreButton
+ property alias mouseArea: mouseArea
+ property alias highScoreButtonImage: image
+
+ Rectangle {
+ id: rectangle
+ color: "#00ffffff"
+ border.color: "#00000000"
+ anchors.fill: parent
+ scale: highScoreButton.scale
+
+ Image {
+ id: image
+ anchors.fill: parent
+ source: "qrc:ui/highScore.svg"
+ scale: highScoreButton.scale
+ fillMode: Image.PreserveAspectFit
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ scale: highScoreButton.scale
+ }
+ }
+}
diff --git a/src/ui/HighScoreLists.qml b/src/ui/HighScoreLists.qml
new file mode 100644
index 0000000..c0d06a0
--- /dev/null
+++ b/src/ui/HighScoreLists.qml
@@ -0,0 +1,95 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Item {
+ id: highScoreLists
+ property alias highScoreListsPopup: highScoreListsPopup
+
+ function openLists() {
+ highScoreListsPopup.open();
+ level1List.levelListModel.clear();
+ level2List.levelListModel.clear();
+ level3List.levelListModel.clear();
+ for (let i = 0; i < 5; i++) {
+ let scoreItem = HighScoreManager.score(0, i);
+ let nameItem = HighScoreManager.name(0, i);
+ if (scoreItem == 0)
+ break;
+ level1List.levelListModel.set(i, {
+ score: scoreItem,
+ name: nameItem
+ });
+ }
+ for (let i = 0; i < 5; i++) {
+ let scoreItem = HighScoreManager.score(1, i);
+ let nameItem = HighScoreManager.name(1, i);
+ if (scoreItem == 0)
+ break;
+ level2List.levelListModel.set(i, {
+ score: scoreItem,
+ name: nameItem
+ });
+ }
+ for (let i = 0; i < 5; i++) {
+ let scoreItem = HighScoreManager.score(2, i);
+ let nameItem = HighScoreManager.name(2, i);
+ if (scoreItem == 0)
+ break;
+ level3List.levelListModel.set(i, {
+ score: scoreItem,
+ name: nameItem
+ });
+ }
+ }
+ Popup {
+ id: highScoreListsPopup
+
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+ padding: 10
+ modal: true
+
+ contentItem: Page {
+ id: scorePage
+ header: TabBar {
+ id: bar
+ width: highScoreListsPopup.contentItem.width
+
+ TabButton {
+ text: i18nc("@title:group High scores Level 1 tab title", "Level 1")
+ width: (highScoreListsPopup.contentItem.width) / 3
+ }
+ TabButton {
+ text: i18nc("@title:group High scores Level 2 tab title", "Level 2")
+ width: (highScoreListsPopup.contentItem.width) / 3
+ }
+ TabButton {
+ text: i18nc("@title:group High scores Level ? tab tible", "Level ?")
+ width: (highScoreListsPopup.contentItem.width) / 3
+ }
+ }
+
+ StackLayout {
+ id: stackLayout
+ width: parent.width
+ height: parent.height
+ currentIndex: bar.currentIndex
+ ScoreList {
+ id: level1List
+ }
+ ScoreList {
+ id: level2List
+ }
+ ScoreList {
+ id: level3List
+ }
+ }
+ }
+ }
+}
diff --git a/src/ui/Numbers.qml b/src/ui/Numbers.qml
new file mode 100644
index 0000000..6ba98bd
--- /dev/null
+++ b/src/ui/Numbers.qml
@@ -0,0 +1,42 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick
+
+Rectangle {
+ id: numbers
+ color: "transparent"
+ Row {
+ anchors.fill: parent
+ leftPadding: parent.width * 0.09589
+ topPadding: parent.height * 0.14
+ spacing: parent.width * 0.05479
+ scale: parent.scale
+
+ Image {
+ id: number2
+ width: parent.width * 0.4
+ height: parent.height * 0.7
+ source: "qrc:numbers/0.svg"
+ scale: parent.scale
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Image {
+ id: number1
+ width: number2.width
+ height: number2.height
+ source: "qrc:numbers/0.svg"
+ scale: parent.scale
+ fillMode: Image.PreserveAspectFit
+ }
+ }
+ function setNumbers(number: int) {
+ var num1 = number % 10;
+ var num2 = Math.floor(number / 10);
+ number1.source = "qrc:numbers/" + num1 + ".svg";
+ number2.source = "qrc:numbers/" + num2 + ".svg";
+ }
+}
diff --git a/src/ui/ScoreAndCounter.qml b/src/ui/ScoreAndCounter.qml
new file mode 100644
index 0000000..14c4e12
--- /dev/null
+++ b/src/ui/ScoreAndCounter.qml
@@ -0,0 +1,114 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick
+
+Item {
+ id: scoreAndCounter
+ property alias counter: counter
+
+ Rectangle {
+ id: plane
+ color: "#282828"
+ border.color: "black"
+
+ radius: scoreAndCounter.height * 0.20
+ border.width: scoreAndCounter.height * 0.075
+ anchors.fill: parent
+ scale: scoreAndCounter.scale
+
+ Numbers {
+ id: score
+ width: scoreAndCounter.width * 0.7
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ }
+
+ Column {
+ id: countDowns
+ width: scoreAndCounter.width * 0.219
+ height: scoreAndCounter.height * 0.8
+
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.rightMargin: scoreAndCounter.width * 0.01
+ anchors.topMargin: scoreAndCounter.height * 0.18
+
+ spacing: scoreAndCounter.height * 0.02
+ scale: scoreAndCounter.scale
+
+ Image {
+ id: countDown3
+ width: scoreAndCounter.width * 0.1
+ height: countDown3.width
+ source: "qrc:numbers/blackBlock.svg"
+ sourceSize.width: scoreAndCounter.width * 0.1369
+ scale: scoreAndCounter.scale
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Image {
+ id: countDown2
+ width: countDown3.width
+ height: countDown3.width
+ source: "qrc:numbers/blackBlock.svg"
+ scale: scoreAndCounter.scale
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Image {
+ id: countDown1
+ width: countDown3.width
+ height: countDown3.width
+ source: "qrc:numbers/blackBlock.svg"
+ scale: scoreAndCounter.scale
+ fillMode: Image.PreserveAspectFit
+ }
+ }
+ }
+ function setScore(scores: int) {
+ score.setNumbers(scores);
+ }
+
+ StateGroup {
+ id: counter
+ state: "zero"
+ states: [
+ State {
+ name: "zero"
+ PropertyChanges {
+ countDown3.source: "qrc:numbers/blackBlock.svg"
+ countDown2.source: "qrc:numbers/blackBlock.svg"
+ countDown1.source: "qrc:numbers/blackBlock.svg"
+ }
+ },
+ State {
+ name: "three"
+ PropertyChanges {
+ countDown3.source: "qrc:numbers/redBlock.svg"
+ countDown2.source: "qrc:numbers/redBlock.svg"
+ countDown1.source: "qrc:numbers/redBlock.svg"
+ }
+ },
+ State {
+ name: "two"
+ PropertyChanges {
+ countDown3.source: "qrc:/numbers/blackBlock.svg"
+ countDown2.source: "qrc:/numbers/redBlock.svg"
+ countDown1.source: "qrc:/numbers/redBlock.svg"
+ }
+ },
+ State {
+ name: "one"
+ PropertyChanges {
+ countDown3.source: "qrc:numbers/blackBlock.svg"
+ countDown1.source: "qrc:numbers/redBlock.svg"
+ countDown2.source: "qrc:numbers/blackBlock.svg"
+ }
+ }
+ ]
+ }
+}
diff --git a/src/ui/ScoreList.qml b/src/ui/ScoreList.qml
new file mode 100644
index 0000000..9dfde30
--- /dev/null
+++ b/src/ui/ScoreList.qml
@@ -0,0 +1,72 @@
+/*
+ SPDX-FileCopyrightText: 2024 Hanyang Zhang <hanyangzhang at qq.com>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+pragma ComponentBehavior: Bound
+import QtQuick
+
+Item {
+ property alias levelListModel: levelListModel
+ // List Data
+ ListModel {
+ id: levelListModel
+ }
+
+ ListView {
+ id: listView
+ anchors.fill: parent
+ model: levelListModel
+ spacing: 10
+ orientation: ListView.Vertical
+ snapMode: ListView.SnapToItem
+ clip: true
+ header: Rectangle {
+ // Making the first item away from tab buttons looks better
+ height: listView.spacing
+ }
+ delegate: Rectangle {
+ id: scoreLine
+ required property string name
+ required property string score
+ color: "#00ffffff"
+ width: listView.width
+ height: listView.height * 0.18
+
+ Rectangle {
+ id: numersRect
+ width: scoreLine.width * 0.25
+ height: scoreLine.height
+ color: "#00ffffff"
+ border.color: "#000000"
+ border.width: 2
+ Numbers {
+ id: scoreNumbers
+ anchors.fill: numersRect
+ Component.onCompleted: {
+ setNumbers(scoreLine.score);
+ }
+ }
+ }
+ Rectangle {
+ id: nameRect
+ color: "#00ffffff"
+ height: scoreLine.height
+ anchors.left: numersRect.right
+ anchors.right: scoreLine.right
+ anchors.leftMargin: 10
+
+ FontLoader {
+ id: steveFontloader
+ source: "qrc:steve.ttf"
+ }
+ Text {
+ text: scoreLine.name
+ font.pixelSize: 18
+ font.family: BlinkenSettings.customFont ? steveFontloader.name : ""
+ anchors.verticalCenter: nameRect.verticalCenter
+ }
+ }
+ }
+ }
+}
More information about the kde-doc-english
mailing list