Proper file for layout instability / dropped spaces

This commit is contained in:
Sarah Jamie Lewis 2022-10-09 22:06:02 -07:00
parent 27430fa5f2
commit ca872f2b7a
5 changed files with 67 additions and 42 deletions

View File

@ -225,6 +225,7 @@ class LineFile extends ChangeNotifier {
cursor.publishCursor(backingLines);
}
}
parse();
return KeyEventResult.handled;
}

View File

@ -3,9 +3,7 @@ import 'package:subwrite/sourcelink.dart';
import 'package:subwrite/theme.dart';
import 'annotation.dart';
double fontSize = 14;
double gutterFontSize = 12;
import 'line_info.dart';
class LineDecoration {
int start = 0;
@ -22,7 +20,8 @@ class LineDecoration {
class Highlighter {
Highlighter() {}
List<InlineSpan> run(BuildContext context, String text, int line, bool highlightRisk, TextStyle style, List<LineAnnotation>? insights, int? cursorStart, int? cursorEnd, bool lineSelected) {
List<InlineSpan> run(BuildContext context, String text, int line, bool highlightRisk, TextStyle style, List<LineAnnotation>? insights, int? cursorStart, int? cursorEnd, bool lineSelected,
int maxCharsBeforeWrapping) {
TextStyle defaultStyle = style.copyWith(fontFamily: 'Iosevka', fontSize: fontSize, color: foreground, height: 1.2);
List<InlineSpan> res = <InlineSpan>[];
List<LineDecoration> decors = <LineDecoration>[];
@ -105,11 +104,26 @@ class Highlighter {
}
if (current == " ") {
res.add(TextSpan(text: "_", style: style.copyWith(color: style.backgroundColor), semanticsLabel: " ", mouseCursor: SystemMouseCursors.text));
// NOTE: This is really annoying
// Ideally we could put a non breaking space here and force richtext to just do the thing we want and fill all available space...
// however richtext really doesn't want to do that...
// if this is a space of any kind (even full-width non-breaking spaces), richtext will invisibly drop the space...
// the space happens if this is a size block.
// HOWEVER: if this is an printable char other than a space the algorithm works as expected....EXCEPT
// if we try to change the color to hide that fact the renderer freaks out on lines with more than ~87-90 words and will cause
// the line to shake...
// so we first add the space...
res.add(TextSpan(text: ' \u200B', style: style, mouseCursor: SystemMouseCursors.text));
// and then if we are going to drop the space...
if (i % maxCharsBeforeWrapping == 0) {
// add another space...that we definitely won't drop...
res.add(TextSpan(text: '_\u200B', style: style.copyWith(color: style.backgroundColor), mouseCursor: SystemMouseCursors.text));
}
} else {
// we need to add a '\ufeff' (Zero Width No-Break Space) here to avoid RichText doing ridiculous
// breaking logic with hyphens / slashes etc.
res.add(TextSpan(text: '$current\ufeff', style: style, mouseCursor: SystemMouseCursors.text));
res.add(TextSpan(text: '$current\u200B', style: style, mouseCursor: SystemMouseCursors.text));
}
}
return res;

View File

@ -10,9 +10,13 @@ import 'cursor.dart';
import 'file.dart';
import 'highlighter.dart';
double gutterWidth = 64;
double rightMargin = 32;
double leftMargin = 5.0;
const double fontSize = 14;
const double gutterFontSize = 12;
const double gutterWidth = 64;
const double rightMargin = 32;
const double leftMargin = 5.0;
const charWidth = (fontSize / 2.0);
const lineHeight = (fontSize * 1.2);
class LineInfo extends ChangeNotifier {
String path;
@ -31,14 +35,40 @@ class LineInfo extends ChangeNotifier {
var backgroundStyle = TextStyle(backgroundColor: cursorStart != null ? sidebarHighlight : background);
Highlighter hl = Highlighter();
List<InlineSpan> spans = hl.run(context, text, lineNumber, highlightRisk, backgroundStyle, annotations, cursorStart, cursorEnd, lineSelected);
final gutterStyle = TextStyle(fontFamily: 'Iosevka', decoration: null, decorationColor: null, fontSize: gutterFontSize, color: comment, backgroundColor: sidebarAlt, height: 1.0);
var sidebarContent = List<Widget>.empty(growable: true);
sidebarContent.add(Text('$lineNumber'.padLeft(5), style: gutterStyle));
var icons = annotations.where((element) => element.annotationType == AnnotationType.note).map((e) {
highlightRisk = true;
return Padding(padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: Tooltip(message: e.description!, child: e.getIcon(15.0)));
}).toList();
sidebarContent.addAll(icons);
icons = annotations.where((element) => element.annotationType == AnnotationType.todo).map((e) {
highlightRisk = true;
return Padding(padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: e.getIcon(15.0));
}).toList();
sidebarContent.addAll(icons);
return LayoutBuilder(
builder: (context, constraints) {
var usableSize = (constraints.maxWidth - gutterWidth - rightMargin - leftMargin).floorToDouble();
var charWidth = (fontSize / 2.0);
var lineHeight = (fontSize * 1.2);
var maxCharsBeforeWrapping = (usableSize / charWidth).floor();
Highlighter hl = Highlighter();
List<InlineSpan> spans = hl.run(context, text, lineNumber, highlightRisk, backgroundStyle, annotations, cursorStart, cursorEnd, lineSelected, maxCharsBeforeWrapping);
var highlightedLine = RichText(
softWrap: true,
textWidthBasis: TextWidthBasis.longestLine,
strutStyle: StrutStyle.disabled,
textAlign: TextAlign.left,
text: TextSpan(
children: spans,
style: backgroundStyle,
@ -47,31 +77,12 @@ class LineInfo extends ChangeNotifier {
List<Widget> stackElements = List.empty(growable: true);
var charWidth = (fontSize / 2.0);
var lineHeight = (fontSize * 1.2);
var usableSize = (constraints.maxWidth.floorToDouble() - gutterWidth - rightMargin - leftMargin).floorToDouble();
;
var sidebarContent = List<Widget>.empty(growable: true);
sidebarContent.add(Text('$lineNumber'.padLeft(5), style: gutterStyle));
var icons = annotations.where((element) => element.annotationType == AnnotationType.note).map((e) {
highlightRisk = true;
return Padding(padding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 0.0), child: Tooltip(message: e.description!, child: e.getIcon(15.0)));
}).toList();
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();
sidebarContent.addAll(icons);
var idealHeight = (lineHeight * (1.0 + ((text.length * charWidth) / usableSize).floor())).ceilToDouble();
var idealHeight = (lineHeight * (1.0 + ((text.length * charWidth) / usableSize).floorToDouble())).ceilToDouble();
var highlightedLineContainer = Container(
padding: EdgeInsets.only(right: 0, top: 0, bottom: 0, left: 0),
padding: EdgeInsets.zero,
margin: EdgeInsets.only(right: rightMargin, top: 0, bottom: 0, left: leftMargin),
decoration: BoxDecoration(
color: backgroundStyle.backgroundColor,
@ -94,8 +105,6 @@ class LineInfo extends ChangeNotifier {
}
var cursorOverlay = Stack(children: stackElements);
//print("rebuilding line $lineNumber $idealHeight");
// print("$charWidth $lineHeight $usableSize $idealHeight ${constraints.maxWidth}");
return Listener(
onPointerDown: (event) {
@ -165,13 +174,14 @@ class LineInfo extends ChangeNotifier {
width: (usableSize + gutterWidth + rightMargin + leftMargin).floorToDouble(),
child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
Container(
padding: EdgeInsets.zero,
decoration: BoxDecoration(color: sidebarAlt),
// row can only have gutterWidth+margin wide
width: (gutterWidth - 2).floorToDouble(),
height: idealHeight,
margin: const EdgeInsets.only(right: 1, top: 0, bottom: 0, left: 0),
padding: EdgeInsets.zero,
alignment: Alignment.centerLeft,
margin: EdgeInsets.only(right: 1, top: 0, bottom: 0, left: 0),
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: sidebarContent)),
child: Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: sidebarContent)),
cursorOverlay,
]))));
},

View File

@ -52,13 +52,13 @@ class _Outline extends State<Outline> {
Row(mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [
TextButton(
onPressed: () {
LineFile doc = Provider.of<LineFile>(context);
LineFile doc = Provider.of<LineFile>(context, listen: false);
newFile(doc);
},
child: Text("New", style: TextStyle(fontSize: 10.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))),
TextButton(
onPressed: () {
LineFile doc = Provider.of<LineFile>(context);
LineFile doc = Provider.of<LineFile>(context, listen: false);
openFile(doc);
},
child: Text("Open", style: TextStyle(fontSize: 10.0, color: foreground, fontWeight: FontWeight.bold, fontFamily: "Iosevka"))),

View File

@ -77,6 +77,7 @@ class _View extends State<View> with SingleTickerProviderStateMixin {
}
},
child: Stack(
fit: StackFit.expand,
children: [
ScrollablePositionedList.builder(
physics: const AlwaysScrollableScrollPhysics(),
@ -84,7 +85,6 @@ class _View extends State<View> with SingleTickerProviderStateMixin {
itemCount: doc.lines(),
padding: EdgeInsets.zero,
minCacheExtent: 10,
addRepaintBoundaries: true,
itemBuilder: (BuildContext bcontext, int index) {
return ChangeNotifierProvider.value(
value: doc.backingLines[index],