You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/simplistic_editor/lib/app_state.dart

202 lines
6.5 KiB

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app_state_manager.dart';
import 'formatting_toolbar.dart' show ToggleButtonsState;
import 'replacements.dart';
class AppState {
const AppState({
required this.replacementsController,
required this.textEditingDeltaHistory,
required this.toggleButtonsState,
});
final ReplacementTextEditingController replacementsController;
final List<TextEditingDelta> textEditingDeltaHistory;
final Set<ToggleButtonsState> toggleButtonsState;
AppState copyWith({
ReplacementTextEditingController? replacementsController,
List<TextEditingDelta>? textEditingDeltaHistory,
Set<ToggleButtonsState>? toggleButtonsState,
}) {
return AppState(
replacementsController:
replacementsController ?? this.replacementsController,
textEditingDeltaHistory:
textEditingDeltaHistory ?? this.textEditingDeltaHistory,
toggleButtonsState: toggleButtonsState ?? this.toggleButtonsState,
);
}
}
class AppStateWidget extends StatefulWidget {
const AppStateWidget({super.key, required this.child});
final Widget child;
static AppStateWidgetState of(BuildContext context) {
return context.findAncestorStateOfType<AppStateWidgetState>()!;
}
@override
State<AppStateWidget> createState() => AppStateWidgetState();
}
class AppStateWidgetState extends State<AppStateWidget> {
AppState _data = AppState(
replacementsController: ReplacementTextEditingController(
text: 'The quick brown fox jumps over the lazy dog.'),
textEditingDeltaHistory: <TextEditingDelta>[],
toggleButtonsState: <ToggleButtonsState>{},
);
void updateTextEditingDeltaHistory(List<TextEditingDelta> textEditingDeltas) {
_data = _data.copyWith(textEditingDeltaHistory: <TextEditingDelta>[
..._data.textEditingDeltaHistory,
...textEditingDeltas
]);
setState(() {});
}
void updateToggleButtonsStateOnSelectionChanged(
TextSelection selection, ReplacementTextEditingController controller) {
// When the selection changes we want to check the replacements at the new
// selection. Enable/disable toggle buttons based on the replacements found
// at the new selection.
final List<TextStyle> replacementStyles =
controller.getReplacementsAtSelection(selection);
final Set<ToggleButtonsState> hasChanged = {};
if (replacementStyles.isEmpty) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..removeAll({
ToggleButtonsState.bold,
ToggleButtonsState.italic,
ToggleButtonsState.underline,
}),
);
}
for (final TextStyle style in replacementStyles) {
// See [_updateToggleButtonsStateOnButtonPressed] for how
// Bold, Italic and Underline are encoded into [style]
if (style.fontWeight != null &&
!hasChanged.contains(ToggleButtonsState.bold)) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..add(ToggleButtonsState.bold),
);
hasChanged.add(ToggleButtonsState.bold);
}
if (style.fontStyle != null &&
!hasChanged.contains(ToggleButtonsState.italic)) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..add(ToggleButtonsState.italic),
);
hasChanged.add(ToggleButtonsState.italic);
}
if (style.decoration != null &&
!hasChanged.contains(ToggleButtonsState.underline)) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..add(ToggleButtonsState.underline),
);
hasChanged.add(ToggleButtonsState.underline);
}
}
for (final TextStyle style in replacementStyles) {
if (style.fontWeight == null &&
!hasChanged.contains(ToggleButtonsState.bold)) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..remove(ToggleButtonsState.bold),
);
hasChanged.add(ToggleButtonsState.bold);
}
if (style.fontStyle == null &&
!hasChanged.contains(ToggleButtonsState.italic)) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..remove(ToggleButtonsState.italic),
);
hasChanged.add(ToggleButtonsState.italic);
}
if (style.decoration == null &&
!hasChanged.contains(ToggleButtonsState.underline)) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..remove(ToggleButtonsState.underline),
);
hasChanged.add(ToggleButtonsState.underline);
}
}
setState(() {});
}
void updateToggleButtonsStateOnButtonPressed(int index) {
Map<int, TextStyle> attributeMap = const <int, TextStyle>{
0: TextStyle(fontWeight: FontWeight.bold),
1: TextStyle(fontStyle: FontStyle.italic),
2: TextStyle(decoration: TextDecoration.underline),
};
final ReplacementTextEditingController controller =
_data.replacementsController;
final TextRange replacementRange = TextRange(
start: controller.selection.start,
end: controller.selection.end,
);
final targetToggleButtonState = ToggleButtonsState.values[index];
if (_data.toggleButtonsState.contains(targetToggleButtonState)) {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..remove(targetToggleButtonState),
);
} else {
_data = _data.copyWith(
toggleButtonsState: Set.from(_data.toggleButtonsState)
..add(targetToggleButtonState),
);
}
if (_data.toggleButtonsState.contains(targetToggleButtonState)) {
controller.applyReplacement(
TextEditingInlineSpanReplacement(
replacementRange,
(string, range) => TextSpan(text: string, style: attributeMap[index]),
true,
),
);
_data = _data.copyWith(replacementsController: controller);
setState(() {});
} else {
controller.disableExpand(attributeMap[index]!);
controller.removeReplacementsAtRange(
replacementRange, attributeMap[index]);
_data = _data.copyWith(replacementsController: controller);
setState(() {});
}
}
@override
Widget build(BuildContext context) {
return AppStateManager(
state: _data,
child: widget.child,
);
}
}