Skip to content

Commit

Permalink
Refactor focusing to contextLine (#24674)
Browse files Browse the repository at this point in the history
* Refactor focusing to contextLine (#24613)

* Rename a widget (#24613)

* Minor reordering (#24613)

* Fix after review (#24613)
  • Loading branch information
alexeyinkin authored Dec 21, 2022
1 parent cef0cf7 commit 0917978
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class EmbeddedEditor extends StatelessWidget {
return SnippetEditor(
controller: snippetController,
isEditable: isEditable,
goToContextLine: false,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ class CodeTextAreaWrapper extends StatelessWidget {
child: SnippetEditor(
controller: snippetController,
isEditable: true,
goToContextLine: true,
),
),
Positioned(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* limitations under the License.
*/

import 'dart:math';

import 'package:flutter/widgets.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:get_it/get_it.dart';
Expand Down Expand Up @@ -77,6 +79,7 @@ class SnippetEditingController extends ChangeNotifier {
codeController.removeListener(_onCodeControllerChanged);
setSource(example.source);
_applyViewOptions(viewOptions);
_toStartOfContextLineIfAny();
codeController.addListener(_onCodeControllerChanged);

notifyListeners();
Expand All @@ -100,6 +103,31 @@ class SnippetEditingController extends ChangeNotifier {
}
}

void _toStartOfContextLineIfAny() {
final contextLine1Based = selectedExample?.contextLine;

if (contextLine1Based == null) {
return;
}

_toStartOfFullLine(max(contextLine1Based - 1, 0));
}

void _toStartOfFullLine(int line) {
if (line >= codeController.code.lines.length) {
return;
}

final fullPosition = codeController.code.lines.lines[line].textRange.start;
final visiblePosition = codeController.code.hiddenRanges.cutPosition(
fullPosition,
);

codeController.selection = TextSelection.collapsed(
offset: visiblePosition,
);
}

Example? get selectedExample => _selectedExample;

ExampleLoadingDescriptor? get descriptor => _descriptor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ extension ExampleTypeToString on ExampleType {
/// These objects are fetched as lists from [ExampleRepository].
class ExampleBase with Comparable<ExampleBase>, EquatableMixin {
final Complexity? complexity;

/// Index of the line to focus, 1-based.
final int contextLine;
final String description;
final bool isMultiFile;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,120 @@
* limitations under the License.
*/

import 'package:flutter/widgets.dart';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';

import '../controllers/snippet_editing_controller.dart';
import 'editor_textarea.dart';
import '../theme/theme.dart';

class SnippetEditor extends StatelessWidget {
class SnippetEditor extends StatefulWidget {
final SnippetEditingController controller;
final bool isEditable;
final bool goToContextLine;

const SnippetEditor({
SnippetEditor({
required this.controller,
required this.isEditable,
required this.goToContextLine,
});
}) : super(
// When the example is changed, will scroll to the context line again.
key: ValueKey(controller.selectedExample),
);

@override
State<SnippetEditor> createState() => _SnippetEditorState();
}

class _SnippetEditorState extends State<SnippetEditor> {
bool _didAutoFocus = false;
final _focusNode = FocusNode();
final _scrollController = ScrollController();

@override
void didChangeDependencies() {
super.didChangeDependencies();

if (!_didAutoFocus) {
_didAutoFocus = true;
SchedulerBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_scrollSoCursorIsOnTop();
}
});
}
}

void _scrollSoCursorIsOnTop() {
_focusNode.requestFocus();

final position = max(widget.controller.codeController.selection.start, 0);
final characterOffset = _getLastCharacterOffset(
text: widget.controller.codeController.text.substring(0, position),
style: kLightTheme.extension<BeamThemeExtension>()!.codeRootStyle,
);

_scrollController.jumpTo(
min(
characterOffset.dy,
_scrollController.position.maxScrollExtent,
),
);
}

@override
void dispose() {
_focusNode.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return EditorTextArea(
codeController: controller.codeController,
sdk: controller.sdk,
enabled: !(controller.selectedExample?.isMultiFile ?? false),
example: controller.selectedExample,
isEditable: isEditable,
goToContextLine: goToContextLine,
final ext = Theme.of(context).extension<BeamThemeExtension>()!;
final isMultiFile = widget.controller.selectedExample?.isMultiFile ?? false;
final isEnabled = widget.isEditable && !isMultiFile;

return Semantics(
container: true,
enabled: isEnabled,
label: 'widgets.codeEditor.label',
multiline: true,
readOnly: isEnabled,
textField: true,
child: FocusScope(
node: FocusScopeNode(canRequestFocus: isEnabled),
child: CodeTheme(
data: ext.codeTheme,
child: Container(
color: ext.codeTheme.styles['root']?.backgroundColor,
child: SingleChildScrollView(
controller: _scrollController,
child: CodeField(
key: ValueKey(widget.controller.codeController),
controller: widget.controller.codeController,
enabled: isEnabled,
focusNode: _focusNode,
textStyle: ext.codeRootStyle,
),
),
),
),
),
);
}
}

Offset _getLastCharacterOffset({
required String text,
required TextStyle style,
}) {
final textPainter = TextPainter(
textDirection: TextDirection.ltr,
text: TextSpan(text: text, style: style),
)..layout();

return textPainter.getOffsetForCaret(
TextPosition(offset: text.length),
Rect.zero,
);
}

0 comments on commit 0917978

Please sign in to comment.