複数レーンと拡大縮小 – 開発日記(4)

アイディア・レーン 開発日記アイキャッチ

初めてのソース分割 2023/03/19

これまでは1つのソースファイル(main.dart)だけで書いていたが、ChatGPTがソースの長さを受け入れられなくなってきたのでファイルを分割してみることにした。

インデントの線を書く部分を外出しにして2ファイルに。挙動自体は変わっていない。
これでChatGPTが読んでくれると良いのだが。

その後、画面(キャンバスと読んでいます)の拡大縮小ボタンを付けた。制御は甘いがなんとかなりそう。

main.dart

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: LaneManager()),
      ),
    );
  }
}

class LaneManager extends StatefulWidget {
  @override
  _LaneManagerState createState() => _LaneManagerState();
}

class _LaneManagerState extends State<LaneManager> {
  double _scale = 1.0;

  List<Lane> lanes = [
    Lane(ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)])
  ];

  void addLane() {
    setState(() {
      lanes.add(Lane(ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)]));
    });
  }

  void removeLane() {
    setState(() {
      if (lanes.length > 1) {
        lanes.removeLast();
      }
    });
  }

  void scaleUp() {
    setState(() {
      _scale += 0.1;
    });
  }

  void scaleDown() {
    setState(() {
      _scale = max(_scale - 0.1, 0.1);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Align(
          alignment: Alignment.topCenter,
          child: Container(
            margin: EdgeInsets.only(top: 10),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: addLane,
                  child: Text("レーン追加"),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: removeLane,
                  child: Text("レーン削除"),
                ),
                SizedBox(width: 30),
                ElevatedButton(
                  onPressed: scaleUp,
                  child: Text("拡大"),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: scaleDown,
                  child: Text("縮小"),
                ),
              ],
            ),
          ),
        ),
        const Divider(),
        Expanded(
          child: Transform.scale(
            scale: _scale,
            alignment: Alignment.topLeft,
            child: ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: lanes.length,
              itemBuilder: (context, index) {
                return Container(
                  width: MediaQuery.of(context).size.width * 0.4,
                  child: lanes[index],
                );
              },
            ),
          ),
        ),
      ],
    );
  }
}

class Lane extends StatefulWidget {
  final List<Idea> ideas;
  Lane({required this.ideas});

  @override
  _LaneState createState() => _LaneState();
}

class _LaneState extends State<Lane> {
  List<Idea> get ideas => widget.ideas;

  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: ValueKey('Idea ${index}'));

  @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 ((LaneInheritedWidget.of(context)
                    .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 = LaneInheritedWidget.of(context)
                .ideas[upperIndex]
                .key
                .currentState
                ?._indentLevel ??
            0;
        if (_indentLevel <= upperIndentLevel) {
          while (_indentLevel < upperIndentLevel && upperIndex >= 0) {
            startY -= (widget.textFieldHeight + widget.boxSpacing * 2);
            upperIndex--;
            upperIndentLevel = LaneInheritedWidget.of(context)
                    .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 (LaneInheritedWidget.of(context)
                          .ideas[i]
                          .key
                          .currentState
                          ?._indentLevel ==
                      _indentLevel - 1) {
                    startIndex = i;
                    break;
                  }
                }
                // Find the end index
                for (int i = widget.index + 1;
                    i < LaneInheritedWidget.of(context).ideas.length;
                    i++) {
                  if ((LaneInheritedWidget.of(context)
                              .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 (LaneInheritedWidget.of(context)
                        .ideas[i]
                        .key
                        .currentState
                        ?._indentLevel ==
                    _indentLevel - 1) {
                  startIndex = i;
                  break;
                }
              }
              // Find the end index
              for (int i = widget.index + 1;
                  i < LaneInheritedWidget.of(context).ideas.length;
                  i++) {
                if ((LaneInheritedWidget.of(context)
                            .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;
}

そして複数レーンに対応。なんとか動くようになった。
ただ、画面上は動くものの、複数レーンを制御するイメージがまだわかず先は長そうだ。

main.dart

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: LaneManager()),
      ),
    );
  }
}

class LaneManager extends StatefulWidget {
  @override
  _LaneManagerState createState() => _LaneManagerState();
}

class _LaneManagerState extends State<LaneManager> {

  List<Lane> lanes = [
    Lane(ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)])
  ];

  void addLane() {
    setState(() {
      lanes.add(Lane(ideas: [Idea(key: GlobalKey<_IdeaState>(), index: 0)]));
    });
  }

  void removeLane() {
    setState(() {
      if (lanes.length > 1) {
        lanes.removeLast();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: addLane,
              child: Text("Laneを横方向に1つ追加する"),
            ),
            SizedBox(width: 10),
            ElevatedButton(
              onPressed: removeLane,
              child: Text("Laneを1つ削除する"),
            ),
          ],
        ),
        Expanded(
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: lanes.length,
            itemBuilder: (context, index) {
              return Container(
                width: MediaQuery.of(context).size.width * 0.4,
                child: lanes[index],
              );
            },
          ),
        ),
      ],
    );
  }
}

class Lane extends StatefulWidget {
  final List<Idea> ideas;
  Lane({required this.ideas});

  @override
  _LaneState createState() => _LaneState();
}

class _LaneState extends State<Lane> {
  List<Idea> get ideas => widget.ideas;

  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: ValueKey('Idea ${index}'));

  @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 ( (LaneInheritedWidget.of(context).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 = LaneInheritedWidget.of(context).ideas[upperIndex].key.currentState?._indentLevel ?? 0;
        if (_indentLevel <= upperIndentLevel) {
          while (_indentLevel < upperIndentLevel && upperIndex >= 0) {
            startY -= (widget.textFieldHeight + widget.boxSpacing * 2);
            upperIndex--;
            upperIndentLevel = LaneInheritedWidget.of(context).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 (LaneInheritedWidget.of(context).ideas[i].key.currentState?._indentLevel ==
                      _indentLevel - 1) {
                    startIndex = i;
                    break;
                  }
                }
                // Find the end index
                for (int i = widget.index + 1; i < LaneInheritedWidget.of(context).ideas.length; i++) {
                  if ((LaneInheritedWidget.of(context).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 (LaneInheritedWidget.of(context).ideas[i].key.currentState?._indentLevel == _indentLevel - 1) {
                  startIndex = i;
                  break;
                }
              }
              // Find the end index
              for (int i = widget.index + 1; i < LaneInheritedWidget.of(context).ideas.length; i++) {
                if ((LaneInheritedWidget.of(context).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;
}

その後ファイルの入出力を付けたり、複数Laneに対応させようとしたがあまりうまくいかない。


※本記事は、過去の記録(メモ)をもとに、他の方が読んで分かるように加筆・補足して掲載しています

テキストベースの思考整理ツール「アイディア・レーン」最新版はこちら

コメント

タイトルとURLをコピーしました