ChatGPTは早くも限界 2023/03/18
ツリーが付いた。苦労したもののなんとかきれいに描画出来るようになった。
ソースの生成は、ChatGPT 4.0になってChatGPT 3.5よりは良くなっている。
ただChatGPT 4.0でも、「このソースをこのように直してください」とプロンプトに指示するとき、渡すソース自体が長くなってきてこれ以上長いものは受け入れてくれないようだ。早くも限界。
描画エラーのとき調査しやすいよう、内部の管理情報も画面に表示できるようにした。
コードはこんな感じ。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: SafeArea(child: Lane()),
),
);
}
}
List<Idea> ideas = [Idea(key: GlobalKey<_IdeaState>(), index: 0)];
class Lane extends StatefulWidget {
@override
_LaneState createState() => _LaneState();
}
class _LaneState extends State<Lane> {
void addNewIdea(int currentIndex) {
setState(() {
int currentIndentLevel = ideas[currentIndex].key.currentState?._indentLevel ?? 0;
ideas.insert(
currentIndex + 1,
Idea(
key: GlobalKey<_IdeaState>(),
index: currentIndex + 1,
textFieldHeight: 48.0,
initialIndentLevel: currentIndentLevel,
),
);
for (int i = currentIndex + 2; i < ideas.length; i++) {
ideas[i].index = i;
}
});
}
void moveIdea(int currentIndex, int direction) {
if (currentIndex + direction >= 0 &&
currentIndex + direction < ideas.length) {
setState(() {
Idea temp = ideas[currentIndex];
ideas[currentIndex] = ideas[currentIndex + direction];
ideas[currentIndex + direction] = temp;
ideas[currentIndex].key.currentState?.updateIndex(currentIndex);
ideas[currentIndex + direction]
.key
.currentState
?.updateIndex(currentIndex + direction);
});
}
}
void deleteIdea(int currentIndex) {
if (ideas.length > 1) {
setState(() {
ideas.removeAt(currentIndex);
for (int i = currentIndex; i < ideas.length; i++) {
ideas[i].index = i;
}
if (currentIndex > 0) {
ideas[currentIndex - 1]
.key
.currentState
?._textFocusNode
.requestFocus();
} else {
ideas[currentIndex].key.currentState?._textFocusNode.requestFocus();
}
});
}
}
void focusAdjacentIdea(int currentIndex, int direction) {
if (currentIndex + direction >= 0 &&
currentIndex + direction < ideas.length) {
ideas[currentIndex + direction]
.key
.currentState
?._textFocusNode
.requestFocus();
}
}
void redrawIdeas() {
setState(() {});
}
void redrawAffectedIdeas(int startIndex, int endIndex) {
if(startIndex<0 || endIndex >= ideas.length)
return;
setState(() {
for (int i = startIndex; i <= endIndex; i++) {
ideas[i].key.currentState?.updateIndentLevel();
}
});
}
@override
Widget build(BuildContext context) {
return LaneInheritedWidget(
laneState: this,
child: ListView.builder(
itemCount: ideas.length,
itemBuilder: (context, index) {
return ideas[index];
},
),
);
}
}
class LaneInheritedWidget extends InheritedWidget {
final _LaneState laneState;
LaneInheritedWidget({required this.laneState, required Widget child})
: super(child: child);
@override
bool updateShouldNotify(LaneInheritedWidget oldWidget) => true;
static _LaneState of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<LaneInheritedWidget>()!
.laneState;
}
}
class Idea extends StatefulWidget {
int index;
final GlobalKey<_IdeaState> key;
final double indentWidth; //インデントの下げ幅
final double boxSpacing; //他のエディタとの間隔
final double textFieldHeight; //テキスト欄の高さ
final int initialIndentLevel;
Idea({
required this.key,
required this.index,
this.indentWidth = 40.0,
this.boxSpacing = 8.0,
this.textFieldHeight = 48.0, //デフォルトは48
this.initialIndentLevel = 0,
}) : super(key: key);
@override
_IdeaState createState() => _IdeaState();
}
class _IdeaState extends State<Idea> {
TextEditingController _textController = TextEditingController();
int _indentLevel = 0;
FocusNode _textFocusNode = FocusNode();
void updateIndex(int newIndex) {
setState(() {
widget.index = newIndex;
});
}
void updateIndentLevel() {
setState(() {
_indentLevel = widget.key.currentState?._indentLevel ?? _indentLevel;
});
}
// インデントの線を描画する
void _indentLinePaint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.grey
..strokeWidth = 1;
var leftX = widget.indentWidth / 2;
// 一行上のIdeaの_indentLevelを取得する
int upperIndentLevel = 0;
int upperIndex = widget.index - 1;
//描画:Canvasの起点は左側センター
if (_indentLevel > 0 && widget.index >0 /*一番上の時はツリーを描画しない*/) {
//一番上の親が居ない場合の特別対応:自分の上のどこかに、indentLevelが小さい親がいるかチェック
bool cancelFlg =false; //描画しない特別な場合用
var i=widget.index - 1;
while(i>=0) {
if ( (ideas[i].key.currentState?._indentLevel ?? 0) < _indentLevel) {
break;
}
i--;
}
if(i==-1) cancelFlg = true;
if (!cancelFlg) {
//縦の線:左側の基準
leftX = leftX + (widget.indentWidth * (_indentLevel-1) );
//縦の線:自分の領域内で上に引く
double startY = 0 - (widget.textFieldHeight /2) - (widget.boxSpacing*2);
//縦の線:自分の領域外でどのくらい上まで引くかを計算
upperIndentLevel = ideas[upperIndex].key.currentState?._indentLevel ?? 0;
if (_indentLevel <= upperIndentLevel) {
while (_indentLevel < upperIndentLevel && upperIndex >= 0) {
startY -= (widget.textFieldHeight + widget.boxSpacing * 2);
upperIndex--;
upperIndentLevel = ideas[upperIndex].key.currentState?._indentLevel ?? 0;
}
if (_indentLevel <= upperIndentLevel) {
startY -= widget.textFieldHeight/2;
}
}
//縦の線
canvas.drawLine(Offset(leftX, 0 ), Offset(leftX, startY), paint);
//横の線
canvas.drawLine(Offset(leftX, size.height),Offset(widget.indentWidth * _indentLevel, size.height), paint);
}
}
}
@override
void initState() {
super.initState();
_indentLevel = widget.initialIndentLevel;
WidgetsBinding.instance.addPostFrameCallback((_) {
FocusScope.of(context).requestFocus(_textFocusNode);
});
}
@override
Widget build(BuildContext context) {
return Focus(
onKey: (FocusNode node, RawKeyEvent event) {
if (event.runtimeType == RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.capsLock) { // CapsLockキーを無視する条件を追加
return KeyEventResult.ignored;
}
else if (event.isControlPressed) { //コントロールキー
bool needRepaint = false;
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
LaneInheritedWidget.of(context).moveIdea(widget.index, -1);
needRepaint = true;
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
LaneInheritedWidget.of(context).moveIdea(widget.index, 1);
needRepaint = true;
}
if (needRepaint) {
int startIndex = widget.index;
int endIndex = widget.index + 1; //Ctrlキーの場合、1つ下からチェックが必要
// Find the start index
for (int i = widget.index - 1; i >= 0; i--) {
if (ideas[i].key.currentState?._indentLevel ==
_indentLevel - 1) {
startIndex = i;
break;
}
}
// Find the end index
for (int i = widget.index + 1; i < ideas.length; i++) {
if ((ideas[i].key.currentState?._indentLevel ?? 0) >=
_indentLevel) {
endIndex = i;
break;
}
}
LaneInheritedWidget.of(context).redrawAffectedIdeas(
startIndex, endIndex);
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
LaneInheritedWidget.of(context)
.focusAdjacentIdea(widget.index, -1);
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
LaneInheritedWidget.of(context)
.focusAdjacentIdea(widget.index, 1);
} else if (event.logicalKey == LogicalKeyboardKey.tab) {
//TABキー
if (event.isShiftPressed) {
setState(() {
_indentLevel = max(0, _indentLevel - 1);
});
} else {
setState(() {
_indentLevel += 1;
});
}
// Find the range of affected ideas
int startIndex = widget.index;
int endIndex = widget.index;
// Find the start index
for (int i = widget.index - 1; i >= 0; i--) {
if (ideas[i].key.currentState?._indentLevel == _indentLevel - 1) {
startIndex = i;
break;
}
}
// Find the end index
for (int i = widget.index + 1; i < ideas.length; i++) {
if ((ideas[i].key.currentState?._indentLevel ?? 0) >= _indentLevel) {
endIndex = i;
break;
}
}
LaneInheritedWidget.of(context).redrawAffectedIdeas(startIndex, endIndex);
return KeyEventResult.handled; //デフォルトのタブの挙動を無効化して、フォーカスの移動を防ぐ
} else if ((event.logicalKey == LogicalKeyboardKey.delete ||
event.logicalKey == LogicalKeyboardKey.backspace) &&
_textController.text.isEmpty) {
LaneInheritedWidget.of(context).deleteIdea(widget.index);
}
}
return KeyEventResult.ignored;
},
child: RawKeyboardListener(
focusNode: FocusNode(),
child: Container(
margin: EdgeInsets.symmetric(
horizontal: 16, vertical: widget.boxSpacing),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 26, child: DecoratedBox( decoration: BoxDecoration( color:Colors.red))),
Expanded(
flex: 4,
child: Row(children: [
CustomPaint(
painter: _CustomPainter(_indentLinePaint),
child:
Container(width: widget.indentWidth * _indentLevel),
),
Expanded(
child: Container(
height: widget.textFieldHeight,
child: TextField(
controller: _textController,
focusNode: _textFocusNode,
maxLines: 1,
decoration: InputDecoration(
border: OutlineInputBorder(),
),
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
_textFocusNode.unfocus();
LaneInheritedWidget.of(context)
.addNewIdea(widget.index);
},
),
),
),
]),
),
],
),
),
)
);
}
}
class _CustomPainter extends CustomPainter {
final void Function(Canvas, Size) _paint;
_CustomPainter(this._paint);
@override
void paint(Canvas canvas, Size size) {
_paint(canvas, size);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
※本記事は、過去の記録(メモ)をもとに、他の方が読んで分かるように加筆・補足して掲載しています
テキストベースの思考整理ツール「アイディア・レーン」最新版はこちら。
コメント