Proper file for layout instability / dropped spaces
This commit is contained in:
parent
27430fa5f2
commit
ca872f2b7a
|
@ -225,6 +225,7 @@ class LineFile extends ChangeNotifier {
|
|||
cursor.publishCursor(backingLines);
|
||||
}
|
||||
}
|
||||
|
||||
parse();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
]))));
|
||||
},
|
||||
|
|
|
@ -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"))),
|
||||
|
|
|
@ -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],
|
||||
|
|
Loading…
Reference in New Issue