Browse Source

feat: customize appflowy editor selection menu style

Lucas.Xu 2 years ago
parent
commit
23a65bfa2a

+ 2 - 0
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

@@ -39,6 +39,8 @@ class MyApp extends StatelessWidget {
         primarySwatch: Colors.blue,
         // extensions: [HeadingPluginStyle.light],
       ),
+      darkTheme: ThemeData.dark(),
+      themeMode: ThemeMode.dark,
       home: const MyHomePage(title: 'AppFlowyEditor Example'),
     );
   }

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart

@@ -46,7 +46,7 @@ ShortcutEventHandler _ignorekHandler = (editorState, event) {
 
 SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
   name: () => 'Code Block',
-  icon: const Icon(
+  icon: (_, __) => const Icon(
     Icons.abc,
     color: Colors.black,
     size: 18.0,

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart

@@ -38,7 +38,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
 
 SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
   name: () => 'Horizontal rule',
-  icon: const Icon(
+  icon: (_, __) => const Icon(
     Icons.horizontal_rule,
     color: Colors.black,
     size: 18.0,

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart

@@ -6,7 +6,7 @@ import 'package:flutter_math_fork/flutter_math.dart';
 
 SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
   name: () => 'Tex',
-  icon: const Icon(
+  icon: (_, __) => const Icon(
     Icons.text_fields_rounded,
     color: Colors.black,
     size: 18.0,

+ 31 - 13
frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart

@@ -3,7 +3,7 @@ import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service
 import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
 import 'package:flutter/material.dart';
 
-class SelectionMenuItemWidget extends StatelessWidget {
+class SelectionMenuItemWidget extends StatefulWidget {
   const SelectionMenuItemWidget({
     Key? key,
     required this.editorState,
@@ -11,7 +11,6 @@ class SelectionMenuItemWidget extends StatelessWidget {
     required this.item,
     required this.isSelected,
     this.width = 140.0,
-    this.selectedColor = const Color(0xFFE0F8FF),
   }) : super(key: key);
 
   final EditorState editorState;
@@ -19,33 +18,52 @@ class SelectionMenuItemWidget extends StatelessWidget {
   final SelectionMenuItem item;
   final double width;
   final bool isSelected;
-  final Color selectedColor;
+
+  @override
+  State<SelectionMenuItemWidget> createState() =>
+      _SelectionMenuItemWidgetState();
+}
+
+class _SelectionMenuItemWidgetState extends State<SelectionMenuItemWidget> {
+  var _onHover = false;
 
   @override
   Widget build(BuildContext context) {
+    final editorStyle = widget.editorState.editorStyle;
     return Container(
       padding: const EdgeInsets.fromLTRB(8.0, 5.0, 8.0, 5.0),
       child: SizedBox(
-        width: width,
+        width: widget.width,
         child: TextButton.icon(
-          icon: item.icon,
+          icon: widget.item
+              .icon(widget.editorState, widget.isSelected || _onHover),
           style: ButtonStyle(
             alignment: Alignment.centerLeft,
-            overlayColor: MaterialStateProperty.all(selectedColor),
-            backgroundColor: isSelected
-                ? MaterialStateProperty.all(selectedColor)
+            overlayColor: MaterialStateProperty.all(
+                editorStyle.selectionMenuItemSelectedColor),
+            backgroundColor: widget.isSelected
+                ? MaterialStateProperty.all(
+                    editorStyle.selectionMenuItemSelectedColor)
                 : MaterialStateProperty.all(Colors.transparent),
           ),
           label: Text(
-            item.name(),
+            widget.item.name(),
             textAlign: TextAlign.left,
-            style: const TextStyle(
-              color: Colors.black,
-              fontSize: 14.0,
+            style: TextStyle(
+              color: widget.isSelected || _onHover
+                  ? editorStyle.selectionMenuItemSelectedTextColor
+                  : editorStyle.selectionMenuItemTextColor,
+              fontSize: 12.0,
             ),
           ),
           onPressed: () {
-            item.handler(editorState, menuService, context);
+            widget.item
+                .handler(widget.editorState, widget.menuService, context);
+          },
+          onHover: (value) {
+            setState(() {
+              _onHover = value;
+            });
           },
         ),
       ),

+ 23 - 11
frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart

@@ -131,7 +131,8 @@ List<SelectionMenuItem> get defaultSelectionMenuItems =>
 final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.text,
-    icon: _selectionMenuIcon('text'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('text', editorState, onSelected),
     keywords: ['text'],
     handler: (editorState, _, __) {
       insertTextNodeAfterSelection(editorState, {});
@@ -139,7 +140,8 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.heading1,
-    icon: _selectionMenuIcon('h1'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('h1', editorState, onSelected),
     keywords: ['heading 1, h1'],
     handler: (editorState, _, __) {
       insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h1);
@@ -147,7 +149,8 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.heading2,
-    icon: _selectionMenuIcon('h2'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('h2', editorState, onSelected),
     keywords: ['heading 2, h2'],
     handler: (editorState, _, __) {
       insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h2);
@@ -155,7 +158,8 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.heading3,
-    icon: _selectionMenuIcon('h3'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('h3', editorState, onSelected),
     keywords: ['heading 3, h3'],
     handler: (editorState, _, __) {
       insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h3);
@@ -163,13 +167,15 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.image,
-    icon: _selectionMenuIcon('image'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('image', editorState, onSelected),
     keywords: ['image'],
     handler: showImageUploadMenu,
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.bulletedList,
-    icon: _selectionMenuIcon('bulleted_list'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('bulleted_list', editorState, onSelected),
     keywords: ['bulleted list', 'list', 'unordered list'],
     handler: (editorState, _, __) {
       insertBulletedListAfterSelection(editorState);
@@ -177,7 +183,8 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.numberedList,
-    icon: _selectionMenuIcon('number'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('number', editorState, onSelected),
     keywords: ['numbered list', 'list', 'ordered list'],
     handler: (editorState, _, __) {
       insertNumberedListAfterSelection(editorState);
@@ -185,7 +192,8 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.checkbox,
-    icon: _selectionMenuIcon('checkbox'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('checkbox', editorState, onSelected),
     keywords: ['todo list', 'list', 'checkbox list'],
     handler: (editorState, _, __) {
       insertCheckboxAfterSelection(editorState);
@@ -193,7 +201,8 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
   SelectionMenuItem(
     name: () => AppFlowyEditorLocalizations.current.quote,
-    icon: _selectionMenuIcon('quote'),
+    icon: (editorState, onSelected) =>
+        _selectionMenuIcon('quote', editorState, onSelected),
     keywords: ['quote', 'refer'],
     handler: (editorState, _, __) {
       insertQuoteAfterSelection(editorState);
@@ -201,10 +210,13 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
   ),
 ];
 
-Widget _selectionMenuIcon(String name) {
+Widget _selectionMenuIcon(
+    String name, EditorState editorState, bool onSelected) {
   return FlowySvg(
     name: 'selection_menu/$name',
-    color: Colors.black,
+    color: onSelected
+        ? editorState.editorStyle.selectionMenuItemSelectedIconColor
+        : editorState.editorStyle.selectionMenuItemIconColor,
     width: 18.0,
     height: 18.0,
   );

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart

@@ -29,7 +29,7 @@ class SelectionMenuItem {
   }
 
   final String Function() name;
-  final Widget icon;
+  final Widget Function(EditorState editorState, bool onSelected) icon;
 
   /// Customizes keywords for item.
   ///
@@ -142,7 +142,7 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
       onKey: _onKey,
       child: Container(
         decoration: BoxDecoration(
-          color: Colors.white,
+          color: widget.editorState.editorStyle.selectionMenuBackgroundColor,
           boxShadow: [
             BoxShadow(
               blurRadius: 5,

+ 60 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart

@@ -14,6 +14,14 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
   final Color? cursorColor;
   final Color? selectionColor;
 
+  // Selection menu styles
+  final Color? selectionMenuBackgroundColor;
+  final Color? selectionMenuItemTextColor;
+  final Color? selectionMenuItemIconColor;
+  final Color? selectionMenuItemSelectedTextColor;
+  final Color? selectionMenuItemSelectedIconColor;
+  final Color? selectionMenuItemSelectedColor;
+
   // Text styles
   final EdgeInsets? textPadding;
   final TextStyle? textStyle;
@@ -33,6 +41,12 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
     required this.padding,
     required this.cursorColor,
     required this.selectionColor,
+    required this.selectionMenuBackgroundColor,
+    required this.selectionMenuItemTextColor,
+    required this.selectionMenuItemIconColor,
+    required this.selectionMenuItemSelectedTextColor,
+    required this.selectionMenuItemSelectedIconColor,
+    required this.selectionMenuItemSelectedColor,
     required this.textPadding,
     required this.textStyle,
     required this.placeholderTextStyle,
@@ -51,6 +65,12 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
     EdgeInsets? padding,
     Color? cursorColor,
     Color? selectionColor,
+    Color? selectionMenuBackgroundColor,
+    Color? selectionMenuItemTextColor,
+    Color? selectionMenuItemIconColor,
+    Color? selectionMenuItemSelectedTextColor,
+    Color? selectionMenuItemSelectedIconColor,
+    Color? selectionMenuItemSelectedColor,
     TextStyle? textStyle,
     TextStyle? placeholderTextStyle,
     TextStyle? bold,
@@ -66,6 +86,18 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
       padding: padding ?? this.padding,
       cursorColor: cursorColor ?? this.cursorColor,
       selectionColor: selectionColor ?? this.selectionColor,
+      selectionMenuBackgroundColor:
+          selectionMenuBackgroundColor ?? this.selectionMenuBackgroundColor,
+      selectionMenuItemTextColor:
+          selectionMenuItemTextColor ?? this.selectionMenuItemTextColor,
+      selectionMenuItemIconColor:
+          selectionMenuItemIconColor ?? this.selectionMenuItemIconColor,
+      selectionMenuItemSelectedTextColor: selectionMenuItemSelectedTextColor ??
+          selectionMenuItemSelectedTextColor,
+      selectionMenuItemSelectedIconColor: selectionMenuItemSelectedIconColor ??
+          selectionMenuItemSelectedIconColor,
+      selectionMenuItemSelectedColor:
+          selectionMenuItemSelectedColor ?? this.selectionMenuItemSelectedColor,
       textPadding: textPadding ?? textPadding,
       textStyle: textStyle ?? this.textStyle,
       placeholderTextStyle: placeholderTextStyle ?? this.placeholderTextStyle,
@@ -91,6 +123,22 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
       cursorColor: Color.lerp(cursorColor, other.cursorColor, t),
       textPadding: EdgeInsets.lerp(textPadding, other.textPadding, t),
       selectionColor: Color.lerp(selectionColor, other.selectionColor, t),
+      selectionMenuBackgroundColor: Color.lerp(
+          selectionMenuBackgroundColor, other.selectionMenuBackgroundColor, t),
+      selectionMenuItemTextColor: Color.lerp(
+          selectionMenuItemTextColor, other.selectionMenuItemTextColor, t),
+      selectionMenuItemIconColor: Color.lerp(
+          selectionMenuItemIconColor, other.selectionMenuItemIconColor, t),
+      selectionMenuItemSelectedTextColor: Color.lerp(
+          selectionMenuItemSelectedTextColor,
+          other.selectionMenuItemSelectedTextColor,
+          t),
+      selectionMenuItemSelectedIconColor: Color.lerp(
+          selectionMenuItemSelectedIconColor,
+          other.selectionMenuItemSelectedIconColor,
+          t),
+      selectionMenuItemSelectedColor: Color.lerp(selectionMenuItemSelectedColor,
+          other.selectionMenuItemSelectedColor, t),
       textStyle: TextStyle.lerp(textStyle, other.textStyle, t),
       placeholderTextStyle:
           TextStyle.lerp(placeholderTextStyle, other.placeholderTextStyle, t),
@@ -109,6 +157,12 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
     padding: const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0),
     cursorColor: const Color(0xFF00BCF0),
     selectionColor: const Color.fromARGB(53, 111, 201, 231),
+    selectionMenuBackgroundColor: const Color(0xFFFFFFFF),
+    selectionMenuItemTextColor: const Color(0xFF333333),
+    selectionMenuItemIconColor: const Color(0xFF333333),
+    selectionMenuItemSelectedTextColor: const Color(0xFF333333),
+    selectionMenuItemSelectedIconColor: const Color(0xFF333333),
+    selectionMenuItemSelectedColor: const Color(0xFFE0F8FF),
     textPadding: const EdgeInsets.symmetric(vertical: 8.0),
     textStyle: const TextStyle(fontSize: 16.0, color: Colors.black),
     placeholderTextStyle: const TextStyle(fontSize: 16.0, color: Colors.grey),
@@ -135,5 +189,11 @@ class EditorStyle extends ThemeExtension<EditorStyle> {
       fontSize: 16.0,
       color: Colors.white.withOpacity(0.3),
     ),
+    selectionMenuBackgroundColor: const Color(0xFF282E3A),
+    selectionMenuItemTextColor: const Color(0xFFBBC3CD),
+    selectionMenuItemIconColor: const Color(0xFFBBC3CD),
+    selectionMenuItemSelectedTextColor: const Color(0xFF131720),
+    selectionMenuItemSelectedIconColor: const Color(0xFF131720),
+    selectionMenuItemSelectedColor: const Color(0xFF00BCF0),
   );
 }

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart

@@ -137,7 +137,7 @@ Future<EditorWidgetTester> _prepare(WidgetTester tester) async {
   );
 
   for (final item in defaultSelectionMenuItems) {
-    expect(find.byWidget(item.icon), findsOneWidget);
+    expect(find.text(item.name()), findsOneWidget);
   }
 
   return Future.value(editor);

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart

@@ -30,7 +30,7 @@ void main() async {
       );
 
       for (final item in defaultSelectionMenuItems) {
-        expect(find.byWidget(item.icon), findsOneWidget);
+        expect(find.text(item.name()), findsOneWidget);
       }
 
       await editor.updateSelection(Selection.single(path: [1], startOffset: 0));