Phase 2: 10-icon menu ring

Rotating ring modeled after the watch Controls menu: UP/DOWN spin,
START/STOP selects. Selection point sits at −30° (≈ 2 o'clock) so it
lines up with the physical enter button on 5-button round Garmins.
Icons are rasterized at 80×80 with automaticPalette="false" and
scaled via drawBitmap2 to stay crisp at any display resolution. Long
German compounds ("Einsatzbeginn", "Beweismittel", "Letzten löschen")
wrap to two lines via a Config array so the center label never
overlaps the surrounding icons. Selected index is persisted in
Application.Storage and restored on next launch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
EiSiMo 2026-04-11 20:10:11 +02:00
parent 43f970d764
commit d3494acc0d
18 changed files with 203 additions and 75 deletions

View file

@ -12,6 +12,13 @@ module Config {
const GPS_TIMEOUT_MS = 10000;
const GPS_TARGET_ACCURACY_M = 5;
// --- Menu ring -----------------------------------------------------
// Angle (degrees) at which the selected icon sits relative to the
// screen center. 0° = 3 o'clock, negative = upper half. 30° 2
// o'clock which lines up with the START/STOP button on most 5-
// button round Garmins (Forerunner 265, Fenix 7, Epix 2, Venu 3 ).
const SELECTION_ANGLE_DEG = -30.0;
// --- Animation / UI timings ----------------------------------------
const SUCCESS_DISPLAY_MS = 2500;
const ERROR_DISPLAY_MS = 5000;
@ -44,19 +51,20 @@ module Config {
const ACTION_DELETE = "delete";
// Menu item metadata. Order matches display order (index 0 is first
// selected). Each entry: key, drawable id, string id, highlight color.
// selected). Long German compounds are split into two lines so the
// center label never overlaps the surrounding icons.
function menuItems() as Array<Dictionary> {
return [
{ :key => ACTION_HISTORY, :icon => Rez.Drawables.IconHistory, :label => Rez.Strings.menu_history, :color => 0xFFFFFF },
{ :key => EVENT_GENERAL, :icon => Rez.Drawables.IconEvent, :label => Rez.Strings.menu_general, :color => 0xFFAA00 },
{ :key => EVENT_START, :icon => Rez.Drawables.IconStart, :label => Rez.Strings.menu_start, :color => 0x00FF00 },
{ :key => EVENT_END, :icon => Rez.Drawables.IconEnd, :label => Rez.Strings.menu_end, :color => 0x00AAFF },
{ :key => EVENT_ARRIVAL, :icon => Rez.Drawables.IconArrival, :label => Rez.Strings.menu_arrival, :color => 0xFFFF00 },
{ :key => EVENT_ARREST, :icon => Rez.Drawables.IconArrest, :label => Rez.Strings.menu_arrest, :color => 0xFF0088 },
{ :key => EVENT_FORCE, :icon => Rez.Drawables.IconForce, :label => Rez.Strings.menu_force, :color => 0xAA00FF },
{ :key => EVENT_EVIDENCE, :icon => Rez.Drawables.IconEvidence, :label => Rez.Strings.menu_evidence, :color => 0x00FFFF },
{ :key => EVENT_SIGHTING, :icon => Rez.Drawables.IconSighting, :label => Rez.Strings.menu_sighting, :color => 0xFF8800 },
{ :key => ACTION_DELETE, :icon => Rez.Drawables.IconDelete, :label => Rez.Strings.menu_delete, :color => 0xFF2222 }
{ :key => ACTION_HISTORY, :icon => Rez.Drawables.IconHistory, :lines => ["Verlauf"], :color => 0xFFFFFF },
{ :key => EVENT_GENERAL, :icon => Rez.Drawables.IconEvent, :lines => ["Ereignis"], :color => 0xFFAA00 },
{ :key => EVENT_START, :icon => Rez.Drawables.IconStart, :lines => ["Einsatz", "beginn"], :color => 0x00FF00 },
{ :key => EVENT_END, :icon => Rez.Drawables.IconEnd, :lines => ["Einsatz", "ende"], :color => 0x00AAFF },
{ :key => EVENT_ARRIVAL, :icon => Rez.Drawables.IconArrival, :lines => ["Eintreffen"], :color => 0xFFFF00 },
{ :key => EVENT_ARREST, :icon => Rez.Drawables.IconArrest, :lines => ["Festnahme"], :color => 0xFF0088 },
{ :key => EVENT_FORCE, :icon => Rez.Drawables.IconForce, :lines => ["Zwang"], :color => 0xAA00FF },
{ :key => EVENT_EVIDENCE, :icon => Rez.Drawables.IconEvidence, :lines => ["Beweis", "mittel"], :color => 0x00FFFF },
{ :key => EVENT_SIGHTING, :icon => Rez.Drawables.IconSighting, :lines => ["Sichtung"], :color => 0xFF8800 },
{ :key => ACTION_DELETE, :icon => Rez.Drawables.IconDelete, :lines => ["Letzten", "löschen"], :color => 0xFF2222 }
];
}
}