Line Jump / Focus / Cursor Fixes / Home/End Selection bug / Cursor Blink
This commit is contained in:
parent
a5d326b8c3
commit
cdbe4d8c9b
|
@ -3,6 +3,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:subwrite/annotation.dart';
|
||||
|
||||
import 'package:subwrite/section.dart';
|
||||
|
@ -19,7 +20,7 @@ class LineFile extends ChangeNotifier {
|
|||
late String projectPath;
|
||||
late String path;
|
||||
|
||||
ScrollController scrollController = ScrollController();
|
||||
ItemScrollController scrollController = ItemScrollController();
|
||||
|
||||
void openFile(String path, int startLine) {
|
||||
this.path = path;
|
||||
|
@ -37,7 +38,6 @@ class LineFile extends ChangeNotifier {
|
|||
parse();
|
||||
cursor.moveCursorToDocStart();
|
||||
focus.requestFocus();
|
||||
scrollController = ScrollController(initialScrollOffset: (startLine * 17.0 - (17.0 * 10.0)).clamp(0.0, double.infinity));
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@ -57,12 +57,12 @@ class LineFile extends ChangeNotifier {
|
|||
cursor.moveCursor(0, 1, backingLines, keepAnchor: event.isShiftPressed);
|
||||
cursor.publishCursor(backingLines);
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.home) {
|
||||
cursor.clearSelection();
|
||||
cursor.moveCursorToLineStart();
|
||||
cursor.clearSelection();
|
||||
cursor.publishCursor(backingLines);
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.end) {
|
||||
cursor.clearSelection();
|
||||
cursor.moveCursorToLineEnd(backingLines[cursor.line - 1].text.length);
|
||||
cursor.clearSelection();
|
||||
cursor.publishCursor(backingLines);
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||
insertLine();
|
||||
|
@ -116,7 +116,7 @@ class LineFile extends ChangeNotifier {
|
|||
void saveFile() {
|
||||
String content = '';
|
||||
for (var l in backingLines) {
|
||||
content += l.text + '\n';
|
||||
content += '${l.text}\n';
|
||||
}
|
||||
File(path).writeAsStringSync(content);
|
||||
}
|
||||
|
@ -161,7 +161,6 @@ class LineFile extends ChangeNotifier {
|
|||
|
||||
// Delete 2nd to n-1 Lines
|
||||
for (var line = (ncursor.anchorLine - 1); line >= ncursor.line; line -= 1) {
|
||||
print("deleting $line ${backingLines[line].text}");
|
||||
backingLines.removeAt(line);
|
||||
}
|
||||
renumberFrom(ncursor.line);
|
||||
|
@ -306,19 +305,11 @@ class LineFile extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
String base64() {
|
||||
String content = '';
|
||||
backingLines.forEach((l) {
|
||||
content += l.text + '\n';
|
||||
});
|
||||
return base64Encode(utf8.encode(content));
|
||||
}
|
||||
|
||||
void copy() {
|
||||
var ncursor = cursor.normalized();
|
||||
var firstLine = backingLines[ncursor.line - 1].text.substring(ncursor.column) + "\n";
|
||||
var firstLine = "${backingLines[ncursor.line - 1].text.substring(ncursor.column)}\n";
|
||||
for (var line = ncursor.line + 1; line < ncursor.anchorLine; line++) {
|
||||
firstLine += backingLines[line - 1].text + "\n";
|
||||
firstLine += "${backingLines[line - 1].text}\n";
|
||||
}
|
||||
var textToCopy = firstLine;
|
||||
if (ncursor.anchorLine != ncursor.line) {
|
||||
|
@ -345,6 +336,7 @@ class LineFile extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void mouseDownUpdate(int line, int col) {
|
||||
focus.requestFocus();
|
||||
cursor.mouseDownUpdate(line, col, backingLines);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,18 +13,14 @@ class InputListener extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _InputListener extends State<InputListener> {
|
||||
late FocusNode focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
focusNode.dispose();
|
||||
}
|
||||
|
||||
void newTextDialog(context, String title, String hint, Function(String) callback) {
|
||||
|
@ -98,16 +94,11 @@ class _InputListener extends State<InputListener> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!focusNode.hasFocus) {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
LineFile doc = Provider.of<LineFile>(context);
|
||||
|
||||
return GestureDetector(
|
||||
child: Focus(
|
||||
child: widget.child,
|
||||
focusNode: focusNode,
|
||||
focusNode: doc.focus,
|
||||
autofocus: true,
|
||||
onKey: (FocusNode node, RawKeyEvent event) {
|
||||
doc.handleKey(event);
|
||||
|
@ -115,6 +106,7 @@ class _InputListener extends State<InputListener> {
|
|||
newTextDialog(context, "New Note", "Note", (note) => {doc.addNote(note)});
|
||||
}
|
||||
return KeyEventResult.handled;
|
||||
}));
|
||||
},
|
||||
child: widget.child));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:subwrite/theme.dart';
|
||||
|
@ -9,6 +11,7 @@ import 'highlighter.dart';
|
|||
|
||||
double gutterWidth = 64;
|
||||
double rightMargin = 32;
|
||||
double leftMargin = 5.0;
|
||||
|
||||
class LineInfo extends ChangeNotifier {
|
||||
String path;
|
||||
|
@ -17,11 +20,12 @@ class LineInfo extends ChangeNotifier {
|
|||
int? cursorStart;
|
||||
int? cursorEnd;
|
||||
bool lineSelected = false;
|
||||
|
||||
List<LineAnnotation> annotations = List.empty(growable: true);
|
||||
|
||||
LineInfo(this.path, this.lineNumber, this.text);
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, AnimationController animationController) {
|
||||
bool highlightRisk = false;
|
||||
|
||||
var backgroundStyle = TextStyle(backgroundColor: cursorStart != null ? sidebarHighlight : background);
|
||||
|
@ -41,11 +45,11 @@ class LineInfo extends ChangeNotifier {
|
|||
);
|
||||
|
||||
List<Widget> stackElements = List.empty(growable: true);
|
||||
stackElements.add(highlightedLine);
|
||||
|
||||
|
||||
var charWidth = (fontSize / 2.0);
|
||||
var lineHeight = (fontSize * 1.2);
|
||||
var usableSize = (constraints.maxWidth - gutterWidth - rightMargin);
|
||||
var usableSize = (constraints.maxWidth - gutterWidth - rightMargin - leftMargin);
|
||||
|
||||
var sidebarContent = List<Widget>.empty(growable: true);
|
||||
sidebarContent.add(Text('$lineNumber'.padLeft(5), style: gutterStyle));
|
||||
|
@ -55,35 +59,47 @@ class LineInfo extends ChangeNotifier {
|
|||
return Padding(padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: Tooltip(message: e.description!, child: e.getIcon(15.0)));
|
||||
}).toList();
|
||||
|
||||
if (icons != null) {
|
||||
sidebarContent.addAll(icons);
|
||||
}
|
||||
sidebarContent.addAll(icons);
|
||||
|
||||
icons = annotations.where((element) => element.annotationType == AnnotationType.todo).map((e) {
|
||||
highlightRisk = true;
|
||||
return Padding(padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: e.getIcon(15.0));
|
||||
}).toList();
|
||||
|
||||
if (icons != null) {
|
||||
sidebarContent.addAll(icons);
|
||||
}
|
||||
sidebarContent.addAll(icons);
|
||||
|
||||
var idealHeight = (lineHeight * (1.0 + ((text.length * charWidth) / usableSize).floor())).ceilToDouble();
|
||||
|
||||
var highlightedLineContainer = Container(
|
||||
padding: EdgeInsets.only(right: 0, top: 0, bottom: 0, left: 0),
|
||||
margin: EdgeInsets.only(right: rightMargin, top: 0, bottom: 0, left: leftMargin),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundStyle.backgroundColor,
|
||||
),
|
||||
width: constraints.maxWidth - rightMargin - gutterWidth - leftMargin,
|
||||
height: idealHeight,
|
||||
child: highlightedLine,
|
||||
);
|
||||
|
||||
stackElements.add(highlightedLineContainer);
|
||||
|
||||
if (cursorStart != null) {
|
||||
var maxCharsBeforeWrapping = (usableSize / charWidth).floorToDouble();
|
||||
var cursorPosTop = (cursorStart! / maxCharsBeforeWrapping).floorToDouble() * lineHeight;
|
||||
var cursorPosLeft = ((cursorStart! % maxCharsBeforeWrapping) * charWidth) - charWidth / 2.0;
|
||||
TextStyle cusrsorStyle = TextStyle(fontFamily: 'Iosevka', fontSize: fontSize, color: foreground, backgroundColor: Colors.transparent, letterSpacing: -2.0);
|
||||
var cursorPos = Positioned(top: cursorPosTop.toDouble(), left: cursorPosLeft.toDouble(), child: Text("|", style: cusrsorStyle));
|
||||
stackElements.add(cursorPos);
|
||||
TextStyle cursorStyle = TextStyle(fontFamily: 'Iosevka', fontSize: fontSize, color: foreground, backgroundColor: Colors.transparent, letterSpacing: -2.0);
|
||||
var cursorPos = Positioned(top: cursorPosTop.toDouble(), left: leftMargin+cursorPosLeft.toDouble(), child: FadeTransition(
|
||||
opacity: animationController, child: Text("|", style: cursorStyle)));
|
||||
stackElements.add( cursorPos);
|
||||
}
|
||||
|
||||
var idealHeight = (lineHeight * (1.0 + ((text.length * charWidth) / usableSize).floor())).ceilToDouble();
|
||||
|
||||
var cursorOverlay = Stack(children: stackElements);
|
||||
|
||||
|
||||
|
||||
return Listener(
|
||||
onPointerDown: (event) {
|
||||
//var c = (event.localPosition.dx - gutterWidth) / (fontSize / 2.0);
|
||||
var inMargin = (event.localPosition.dx - gutterWidth) > usableSize;
|
||||
var c = cursorFromMouse(usableSize, event.localPosition.dx - gutterWidth, event.localPosition.dy);
|
||||
var file = Provider.of<LineFile>(context, listen: false);
|
||||
|
@ -92,7 +108,6 @@ class LineInfo extends ChangeNotifier {
|
|||
}
|
||||
},
|
||||
onPointerMove: (event) {
|
||||
//var c = (event.localPosition.dx - gutterWidth) / (fontSize / 2.0)
|
||||
var inMargin = (event.localPosition.dx - gutterWidth) > usableSize;
|
||||
var c = cursorFromMouse(usableSize, event.localPosition.dx - gutterWidth, event.localPosition.dy);
|
||||
var file = Provider.of<LineFile>(context, listen: false);
|
||||
|
@ -104,7 +119,6 @@ class LineInfo extends ChangeNotifier {
|
|||
},
|
||||
child: MouseRegion(
|
||||
onEnter: (event) {
|
||||
//var c = (event.localPosition.dx - gutterWidth) / (fontSize / 2.0);
|
||||
var inMargin = (event.localPosition.dx - gutterWidth) > usableSize;
|
||||
var c = cursorFromMouse(usableSize, event.localPosition.dx - gutterWidth, event.localPosition.dy);
|
||||
var file = Provider.of<LineFile>(context, listen: false);
|
||||
|
@ -115,7 +129,6 @@ class LineInfo extends ChangeNotifier {
|
|||
}
|
||||
},
|
||||
onHover: (event) {
|
||||
//var c = (event.localPosition.dx - gutterWidth) / (fontSize / 2.0);
|
||||
var c = cursorFromMouse(usableSize, event.localPosition.dx - gutterWidth, event.localPosition.dy);
|
||||
var file = Provider.of<LineFile>(context, listen: false);
|
||||
if (event.down) {
|
||||
|
@ -145,16 +158,7 @@ class LineInfo extends ChangeNotifier {
|
|||
margin: EdgeInsets.only(right: 1, top: 0, bottom: 0, left: 0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.start, children: sidebarContent)),
|
||||
Container(
|
||||
padding: EdgeInsets.zero,
|
||||
margin: EdgeInsets.only(right: rightMargin, top: 0, bottom: 0, left: 0),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundStyle.backgroundColor,
|
||||
),
|
||||
width: constraints.maxWidth - rightMargin - gutterWidth,
|
||||
height: idealHeight,
|
||||
child: cursorOverlay,
|
||||
),
|
||||
cursorOverlay,
|
||||
]))));
|
||||
},
|
||||
);
|
||||
|
|
|
@ -8,7 +8,9 @@ import 'outline.dart';
|
|||
|
||||
void main(List<String> args) {
|
||||
var docfile = LineFile();
|
||||
docfile.openFile("./example.md", 0);
|
||||
//if (args.length > 1) {
|
||||
docfile.openFile("./example.md", 0);
|
||||
//}
|
||||
var doc = ChangeNotifierProvider.value(value: docfile);
|
||||
|
||||
runApp(MultiProvider(
|
||||
|
@ -26,7 +28,11 @@ class MyApp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'subwrite',
|
||||
theme: ThemeData(scrollbarTheme: ScrollbarThemeData(thumbColor: MaterialStateProperty.all(header))),
|
||||
theme: ThemeData(
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbColor: MaterialStateProperty.all(header),
|
||||
thumbVisibility: MaterialStateProperty.all(true),
|
||||
)),
|
||||
home: const SarahDownApp(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class _Outline extends State<Outline> {
|
|||
style: TextStyle(color: foreground, fontSize: 14.0 - sections[index].level, fontFamily: "Iosevka"),
|
||||
),
|
||||
onTap: () {
|
||||
Provider.of<LineFile>(context, listen: false).scrollController.jumpTo(sections[index].lineNumber * 17.0);
|
||||
Provider.of<LineFile>(context, listen: false).scrollController.jumpTo(index: sections[index].lineNumber - 1);
|
||||
},
|
||||
trailing: ReorderableDragStartListener(
|
||||
index: index,
|
||||
|
|
|
@ -1,23 +1,32 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'file.dart';
|
||||
import 'line_info.dart';
|
||||
|
||||
class View extends StatefulWidget {
|
||||
View({Key? key}) : super(key: key);
|
||||
|
||||
|
||||
@override
|
||||
_View createState() => _View();
|
||||
}
|
||||
|
||||
class _View extends State<View> {
|
||||
class _View extends State<View> with SingleTickerProviderStateMixin {
|
||||
|
||||
late AnimationController _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 300));
|
||||
_animationController.repeat(reverse: true);
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -25,22 +34,18 @@ class _View extends State<View> {
|
|||
Widget build(BuildContext context) {
|
||||
LineFile doc = Provider.of<LineFile>(context);
|
||||
|
||||
return Scrollbar(
|
||||
controller: doc.scrollController,
|
||||
trackVisibility: true,
|
||||
thumbVisibility: true,
|
||||
child: ListView.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: doc.scrollController,
|
||||
itemCount: doc.lines(),
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext bcontext, int index) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: doc.backingLines[index],
|
||||
builder: (lcontext, lineinfo) {
|
||||
return Provider.of<LineInfo>(lcontext).build(lcontext);
|
||||
});
|
||||
}));
|
||||
return ScrollablePositionedList.builder(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemScrollController: doc.scrollController,
|
||||
itemCount: doc.lines(),
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext bcontext, int index) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: doc.backingLines[index],
|
||||
builder: (lcontext, lineinfo) {
|
||||
return Provider.of<LineInfo>(lcontext).build(context,_animationController);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
scrollable_positioned_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: scrollable_positioned_list
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
@ -172,4 +179,4 @@ packages:
|
|||
version: "2.1.2"
|
||||
sdks:
|
||||
dart: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=1.16.0"
|
||||
flutter: ">=2.12.0"
|
||||
|
|
|
@ -37,6 +37,7 @@ dependencies:
|
|||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
provider: ^6.0.2
|
||||
scrollable_positioned_list: ^0.3.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue