From 99c99da7ae4c516bb696b96eee2118e3edf6fbb5 Mon Sep 17 00:00:00 2001 From: bbruce1 <94078487+bbruce1@users.noreply.github.com> Date: Tue, 8 Sep 2026 14:35:44 -0400 Subject: [PATCH] Allow typing an exact camera direction The direction could only be set by dragging the slider, which makes an exact bearing hard to hit. Render the current direction in the label as a small numeric field so it can be typed directly. Whole numbers from 0 to 359 are applied; anything else is discarded and the field resyncs. Fixes #64 --- lib/widgets/add_node_sheet.dart | 33 +++++++++++++++++++++++++++----- lib/widgets/edit_node_sheet.dart | 33 +++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lib/widgets/add_node_sheet.dart b/lib/widgets/add_node_sheet.dart index 38cdabb..eb68dce 100644 --- a/lib/widgets/add_node_sheet.dart +++ b/lib/widgets/add_node_sheet.dart @@ -26,6 +26,7 @@ class AddNodeSheet extends StatefulWidget { } class _AddNodeSheetState extends State { + int _directionEdits = 0; bool _showTutorial = false; bool _isCheckingTutorial = true; @@ -285,11 +286,33 @@ class _AddNodeSheetState extends State { const TextSpan(text: 'Directions: '), if (directionsText.isNotEmpty) ...directionsText.split('**').asMap().entries.map((entry) { - final isEven = entry.key % 2 == 0; - return TextSpan( - text: entry.value, - style: TextStyle( - fontWeight: isEven ? FontWeight.normal : FontWeight.bold, + if (entry.key % 2 == 0) return TextSpan(text: entry.value); + // The current direction is typeable. Keyed on the value and + // an edit counter so it resyncs and drops rejected text. + return WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: SizedBox( + width: 64, + child: TextFormField( + key: ValueKey('$_directionEdits:${session.directionDegrees.round()}'), + initialValue: session.directionDegrees.round().toString(), + enabled: enableDirectionControls, + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + decoration: const InputDecoration( + isDense: true, + contentPadding: EdgeInsets.symmetric(vertical: 4, horizontal: 4), + border: OutlineInputBorder(), + ), + onFieldSubmitted: (value) { + final degrees = int.tryParse(value); + if (degrees != null && degrees >= 0 && degrees <= 359) { + appState.updateSession(directionDeg: degrees.toDouble()); + } + setState(() => _directionEdits++); + }, + ), ), ); }), diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index 2131172..71fe158 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -28,6 +28,7 @@ class EditNodeSheet extends StatefulWidget { } class _EditNodeSheetState extends State { + int _directionEdits = 0; bool _showTutorial = false; bool _isCheckingTutorial = true; @@ -208,11 +209,33 @@ class _EditNodeSheetState extends State { const TextSpan(text: 'Directions: '), if (directionsText.isNotEmpty) ...directionsText.split('**').asMap().entries.map((entry) { - final isEven = entry.key % 2 == 0; - return TextSpan( - text: entry.value, - style: TextStyle( - fontWeight: isEven ? FontWeight.normal : FontWeight.bold, + if (entry.key % 2 == 0) return TextSpan(text: entry.value); + // The current direction is typeable. Keyed on the value and + // an edit counter so it resyncs and drops rejected text. + return WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: SizedBox( + width: 64, + child: TextFormField( + key: ValueKey('$_directionEdits:${session.directionDegrees.round()}'), + initialValue: session.directionDegrees.round().toString(), + enabled: enableDirectionControls, + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + decoration: const InputDecoration( + isDense: true, + contentPadding: EdgeInsets.symmetric(vertical: 4, horizontal: 4), + border: OutlineInputBorder(), + ), + onFieldSubmitted: (value) { + final degrees = int.tryParse(value); + if (degrees != null && degrees >= 0 && degrees <= 359) { + appState.updateEditSession(directionDeg: degrees.toDouble()); + } + setState(() => _directionEdits++); + }, + ), ), ); })