Browse Source

chore: disable moving column

appflowy 2 năm trước cách đây
mục cha
commit
4e8308b834

+ 8 - 0
frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
@@ -20,6 +21,12 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
           didReceiveCellUpdate: (content) {
             emit(state.copyWith(content: content));
           },
+          updateText: (text) {
+            if (text != state.content) {
+              cellController.saveCellData(text);
+              emit(state.copyWith(content: text));
+            }
+          },
         );
       },
     );
@@ -49,6 +56,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
 @freezed
 class BoardTextCellEvent with _$BoardTextCellEvent {
   const factory BoardTextCellEvent.initial() = _InitialCell;
+  const factory BoardTextCellEvent.updateText(String text) = _UpdateContent;
   const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
       _DidReceiveCellUpdate;
 }

+ 35 - 6
frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart

@@ -1,6 +1,7 @@
 import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
 import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -40,8 +41,9 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
         },
         builder: (context, state) {
           if (state.selectedOptions
-              .where((element) => element.id == widget.groupId)
-              .isNotEmpty) {
+                  .where((element) => element.id == widget.groupId)
+                  .isNotEmpty ||
+              state.selectedOptions.isEmpty) {
             return const SizedBox();
           } else {
             final children = state.selectedOptions
@@ -52,10 +54,17 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
                   ),
                 )
                 .toList();
-            return Align(
-              alignment: Alignment.centerLeft,
-              child: AbsorbPointer(
-                child: Wrap(children: children, spacing: 4, runSpacing: 2),
+
+            return IntrinsicHeight(
+              child: Stack(
+                alignment: AlignmentDirectional.center,
+                fit: StackFit.expand,
+                children: [
+                  Wrap(children: children, spacing: 4, runSpacing: 2),
+                  _SelectOptionDialog(
+                    controller: widget.cellControllerBuilder.build(),
+                  ),
+                ],
               ),
             );
           }
@@ -70,3 +79,23 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
     super.dispose();
   }
 }
+
+class _SelectOptionDialog extends StatelessWidget {
+  final GridSelectOptionCellController _controller;
+  const _SelectOptionDialog({
+    Key? key,
+    required IGridCellController controller,
+  })  : _controller = controller as GridSelectOptionCellController,
+        super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return InkWell(onTap: () {
+      SelectOptionCellEditor.show(
+        context,
+        _controller,
+        () {},
+      );
+    });
+  }
+}

+ 27 - 14
frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
+import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -19,14 +20,16 @@ class BoardTextCell extends StatefulWidget {
 
 class _BoardTextCellState extends State<BoardTextCell> {
   late BoardTextCellBloc _cellBloc;
+  late TextEditingController _controller;
+  SingleListenerFocusNode focusNode = SingleListenerFocusNode();
 
   @override
   void initState() {
     final cellController =
         widget.cellControllerBuilder.build() as GridCellController;
-
     _cellBloc = BoardTextCellBloc(cellController: cellController)
       ..add(const BoardTextCellEvent.initial());
+    _controller = TextEditingController(text: _cellBloc.state.content);
     super.initState();
   }
 
@@ -34,28 +37,38 @@ class _BoardTextCellState extends State<BoardTextCell> {
   Widget build(BuildContext context) {
     return BlocProvider.value(
       value: _cellBloc,
-      child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
-        buildWhen: (previous, current) => previous.content != current.content,
-        builder: (context, state) {
-          if (state.content.isEmpty) {
-            return const SizedBox();
-          } else {
-            return Align(
-              alignment: Alignment.centerLeft,
-              child: ConstrainedBox(
-                constraints: const BoxConstraints(maxHeight: 120),
-                child: FlowyText.medium(state.content, fontSize: 14),
-              ),
-            );
+      child: BlocListener<BoardTextCellBloc, BoardTextCellState>(
+        listener: (context, state) {
+          if (_controller.text != state.content) {
+            _controller.text = state.content;
           }
         },
+        child: TextField(
+          controller: _controller,
+          focusNode: focusNode,
+          onChanged: (value) => focusChanged(),
+          onEditingComplete: () => focusNode.unfocus(),
+          maxLines: 1,
+          style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
+          decoration: const InputDecoration(
+            contentPadding: EdgeInsets.symmetric(vertical: 6),
+            border: InputBorder.none,
+            isDense: true,
+          ),
+        ),
       ),
     );
   }
 
+  Future<void> focusChanged() async {
+    _cellBloc.add(BoardTextCellEvent.updateText(_controller.text));
+  }
+
   @override
   Future<void> dispose() async {
     _cellBloc.close();
+    _controller.dispose();
+    focusNode.dispose();
     super.dispose();
   }
 }

+ 17 - 7
frontend/app_flowy/lib/plugins/board/presentation/card/card.dart

@@ -68,6 +68,7 @@ class _BoardCardState extends State<BoardCard> {
               widget.openCard(context);
             },
             child: Column(
+              mainAxisSize: MainAxisSize.min,
               children: _makeCells(
                 context,
                 state.cells.map((cell) => cell.identifier).toList(),
@@ -83,15 +84,24 @@ class _BoardCardState extends State<BoardCard> {
     BuildContext context,
     List<GridCellIdentifier> cells,
   ) {
-    return cells.map(
-      (GridCellIdentifier cellId) {
+    final List<Widget> children = [];
+    cells.asMap().forEach(
+      (int index, GridCellIdentifier cellId) {
         final child = widget.cellBuilder.buildCell(widget.groupId, cellId);
-        return Padding(
-          padding: const EdgeInsets.only(left: 4, right: 4, top: 6),
-          child: child,
-        );
+        if (index != 0) {
+          children.add(Padding(
+            padding: const EdgeInsets.only(left: 4, right: 4, top: 8),
+            child: child,
+          ));
+        } else {
+          children.add(Padding(
+            padding: const EdgeInsets.only(left: 4, right: 4),
+            child: child,
+          ));
+        }
       },
-    ).toList();
+    );
+    return children;
   }
 
   @override

+ 4 - 0
frontend/app_flowy/linux/flutter/generated_plugin_registrant.cc

@@ -7,6 +7,7 @@
 #include "generated_plugin_registrant.h"
 
 #include <flowy_infra_ui/flowy_infra_u_i_plugin.h>
+#include <hotkey_manager/hotkey_manager_plugin.h>
 #include <rich_clipboard_linux/rich_clipboard_plugin.h>
 #include <url_launcher_linux/url_launcher_plugin.h>
 #include <window_size/window_size_plugin.h>
@@ -15,6 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
   g_autoptr(FlPluginRegistrar) flowy_infra_ui_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyInfraUIPlugin");
   flowy_infra_u_i_plugin_register_with_registrar(flowy_infra_ui_registrar);
+  g_autoptr(FlPluginRegistrar) hotkey_manager_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerPlugin");
+  hotkey_manager_plugin_register_with_registrar(hotkey_manager_registrar);
   g_autoptr(FlPluginRegistrar) rich_clipboard_linux_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "RichClipboardPlugin");
   rich_clipboard_plugin_register_with_registrar(rich_clipboard_linux_registrar);

+ 1 - 0
frontend/app_flowy/linux/flutter/generated_plugins.cmake

@@ -4,6 +4,7 @@
 
 list(APPEND FLUTTER_PLUGIN_LIST
   flowy_infra_ui
+  hotkey_manager
   rich_clipboard_linux
   url_launcher_linux
   window_size

+ 2 - 0
frontend/app_flowy/macos/Flutter/GeneratedPluginRegistrant.swift

@@ -9,6 +9,7 @@ import connectivity_plus_macos
 import device_info_plus_macos
 import flowy_infra_ui
 import flowy_sdk
+import hotkey_manager
 import package_info_plus_macos
 import path_provider_macos
 import rich_clipboard_macos
@@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
   FlowyInfraUIPlugin.register(with: registry.registrar(forPlugin: "FlowyInfraUIPlugin"))
   FlowySdkPlugin.register(with: registry.registrar(forPlugin: "FlowySdkPlugin"))
+  HotkeyManagerPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerPlugin"))
   FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
   RichClipboardPlugin.register(with: registry.registrar(forPlugin: "RichClipboardPlugin"))

+ 48 - 25
frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart

@@ -11,13 +11,13 @@ class MultiBoardListExample extends StatefulWidget {
 class _MultiBoardListExampleState extends State<MultiBoardListExample> {
   final AFBoardDataController boardDataController = AFBoardDataController(
     onMoveColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
-      debugPrint('Move column from $fromIndex to $toIndex');
+      // debugPrint('Move column from $fromIndex to $toIndex');
     },
     onMoveColumnItem: (columnId, fromIndex, toIndex) {
-      debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
+      // debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
     },
     onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
-      debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
+      // debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
     },
   );
 
@@ -96,7 +96,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
           },
           cardBuilder: (context, column, columnItem) {
             return AppFlowyColumnItemCard(
-              key: ObjectKey(columnItem),
+              key: ValueKey(columnItem.id),
               child: _buildCard(columnItem),
             );
           },
@@ -121,33 +121,56 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
     }
 
     if (item is RichTextItem) {
-      return Align(
-        alignment: Alignment.centerLeft,
-        child: Padding(
-          padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
-          child: Column(
-            crossAxisAlignment: CrossAxisAlignment.start,
-            children: [
-              Text(
-                item.title,
-                style: const TextStyle(fontSize: 14),
-                textAlign: TextAlign.left,
-              ),
-              const SizedBox(height: 10),
-              Text(
-                item.subtitle,
-                style: const TextStyle(fontSize: 12, color: Colors.grey),
-              )
-            ],
-          ),
-        ),
-      );
+      return RichTextCard(item: item);
     }
 
     throw UnimplementedError();
   }
 }
 
+class RichTextCard extends StatefulWidget {
+  final RichTextItem item;
+  const RichTextCard({
+    required this.item,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<RichTextCard> createState() => _RichTextCardState();
+}
+
+class _RichTextCardState extends State<RichTextCard> {
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Align(
+      alignment: Alignment.centerLeft,
+      child: Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
+        child: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text(
+              widget.item.title,
+              style: const TextStyle(fontSize: 14),
+              textAlign: TextAlign.left,
+            ),
+            const SizedBox(height: 10),
+            Text(
+              widget.item.subtitle,
+              style: const TextStyle(fontSize: 12, color: Colors.grey),
+            )
+          ],
+        ),
+      ),
+    );
+  }
+}
+
 class TextItem extends AFColumnItem {
   final String s;
 

+ 2 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
 const DART_LOG = "Dart_LOG";
 
 class Log {
-  static const enableLog = false;
+  static const enableLog = true;
 
   static void info(String? message) {
     if (enableLog) {
@@ -26,7 +26,7 @@ class Log {
 
   static void trace(String? message) {
     if (enableLog) {
-      debugPrint('❗️[Trace] - ${DateTime.now().second}=> $message');
+      // debugPrint('❗️[Trace] - ${DateTime.now().second}=> $message');
     }
   }
 }

+ 9 - 4
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart

@@ -64,7 +64,7 @@ class AFBoard extends StatelessWidget {
   final BoxConstraints columnConstraints;
 
   ///
-  final BoardPhantomController phantomController;
+  late final BoardPhantomController phantomController;
 
   final ScrollController? scrollController;
 
@@ -85,8 +85,12 @@ class AFBoard extends StatelessWidget {
     this.columnConstraints = const BoxConstraints(maxWidth: 200),
     this.config = const AFBoardConfig(),
     Key? key,
-  })  : phantomController = BoardPhantomController(delegate: dataController),
-        super(key: key);
+  }) : super(key: key) {
+    phantomController = BoardPhantomController(
+      delegate: dataController,
+      columnsState: _columnState,
+    );
+  }
 
   @override
   Widget build(BuildContext context) {
@@ -194,6 +198,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
           dataSource: widget.dataController,
           direction: Axis.horizontal,
           interceptor: interceptor,
+          reorderable: false,
           children: _buildColumns(),
         );
 
@@ -244,7 +249,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
           child: Consumer<AFBoardColumnDataController>(
             builder: (context, value, child) {
               final boardColumn = AFBoardColumnWidget(
-                key: PageStorageKey<String>(columnData.id),
+                // key: PageStorageKey<String>(columnData.id),
                 // key: GlobalObjectKey(columnData.id),
                 margin: _marginFromIndex(columnIndex),
                 itemMargin: widget.config.columnItemPadding,

+ 2 - 2
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart

@@ -92,7 +92,7 @@ class AFBoardColumnWidget extends StatefulWidget {
 
   final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
 
-  final GlobalKey globalKey;
+  final GlobalObjectKey globalKey;
 
   AFBoardColumnWidget({
     Key? key,
@@ -111,7 +111,7 @@ class AFBoardColumnWidget extends StatefulWidget {
     this.itemMargin = EdgeInsets.zero,
     this.cornerRadius = 0.0,
     this.backgroundColor = Colors.transparent,
-  })  : globalKey = GlobalKey(),
+  })  : globalKey = GlobalObjectKey(dataSource.columnData.id),
         config = const ReorderFlexConfig(),
         super(key: key);
 

+ 5 - 0
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart

@@ -75,6 +75,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
   final AnimationController deleteAnimationController;
 
   final bool useMoveAnimation;
+  final bool draggable;
 
   const ReorderDragTarget({
     Key? key,
@@ -88,6 +89,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
     required this.insertAnimationController,
     required this.deleteAnimationController,
     required this.useMoveAnimation,
+    required this.draggable,
     this.onAccept,
     this.onLeave,
     this.draggableTargetBuilder,
@@ -132,6 +134,9 @@ class _ReorderDragTargetState<T extends DragTargetData>
     List<T?> acceptedCandidates,
     List<dynamic> rejectedCandidates,
   ) {
+    if (!widget.draggable) {
+      return widget.child;
+    }
     Widget feedbackBuilder = Builder(builder: (BuildContext context) {
       BoxConstraints contentSizeConstraints =
           BoxConstraints.loose(_draggingFeedbackSize!);

+ 2 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart

@@ -131,6 +131,7 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
   final String reorderFlexId;
   final List<String> acceptedReorderFlexIds;
   final CrossReorderFlexDragTargetDelegate delegate;
+
   @override
   final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder;
 
@@ -188,7 +189,7 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
     );
 
     Log.debug(
-        '[$CrossReorderFlexDragTargetInterceptor] dargTargetIndex: $dragTargetIndex, reorderFlexId: $reorderFlexId');
+        '[$CrossReorderFlexDragTargetInterceptor] isNewDragTarget: $isNewDragTarget, dargTargetIndex: $dragTargetIndex, reorderFlexId: $reorderFlexId');
 
     if (isNewDragTarget == false) {
       delegate.updateDragTargetData(reorderFlexId, dragTargetIndex);

+ 20 - 15
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart

@@ -1,3 +1,4 @@
+import 'package:appflowy_board/appflowy_board.dart';
 import 'package:flutter/widgets.dart';
 
 import '../../utils/log.dart';
@@ -39,8 +40,12 @@ class BoardPhantomController extends OverlapDragTargetDelegate
     with CrossReorderFlexDragTargetDelegate {
   PhantomRecord? phantomRecord;
   final BoardPhantomControllerDelegate delegate;
-  final columnsState = ColumnPhantomStateController();
-  BoardPhantomController({required this.delegate});
+  final BoardColumnsState columnsState;
+  final phantomState = ColumnPhantomState();
+  BoardPhantomController({
+    required this.delegate,
+    required this.columnsState,
+  });
 
   bool isFromColumn(String columnId) {
     if (phantomRecord != null) {
@@ -59,19 +64,19 @@ class BoardPhantomController extends OverlapDragTargetDelegate
   }
 
   void columnStartDragging(String columnId) {
-    columnsState.setColumnIsDragging(columnId, true);
+    phantomState.setColumnIsDragging(columnId, true);
   }
 
   /// Remove the phantom in the column when the column is end dragging.
   void columnEndDragging(String columnId) {
-    columnsState.setColumnIsDragging(columnId, false);
+    phantomState.setColumnIsDragging(columnId, false);
 
     if (phantomRecord == null) return;
 
     final fromColumnId = phantomRecord!.fromColumnId;
     final toColumnId = phantomRecord!.toColumnId;
     if (fromColumnId == columnId) {
-      columnsState.notifyDidRemovePhantom(toColumnId);
+      phantomState.notifyDidRemovePhantom(toColumnId);
     }
 
     if (phantomRecord!.toColumnId == columnId) {
@@ -82,8 +87,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
         phantomRecord!.toColumnIndex,
       );
 
-      Log.debug(
-          "[$BoardPhantomController] did move ${phantomRecord.toString()}");
+      // Log.debug(
+      //     "[$BoardPhantomController] did move ${phantomRecord.toString()}");
       phantomRecord = null;
     }
   }
@@ -91,8 +96,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
   /// Remove the phantom in the column if it contains phantom
   void _removePhantom(String columnId) {
     if (delegate.removePhantom(columnId)) {
-      columnsState.notifyDidRemovePhantom(columnId);
-      columnsState.removeColumnListener(columnId);
+      phantomState.notifyDidRemovePhantom(columnId);
+      phantomState.removeColumnListener(columnId);
     }
   }
 
@@ -105,7 +110,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
       index: phantomIndex,
       dragTargetData: dragTargetData,
     );
-    columnsState.addColumnListener(toColumnId, phantomContext);
+    phantomState.addColumnListener(toColumnId, phantomContext);
 
     delegate.insertPhantom(
       toColumnId,
@@ -113,7 +118,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
       PhantomColumnItem(phantomContext),
     );
 
-    columnsState.notifyDidInsertPhantom(toColumnId, phantomIndex);
+    phantomState.notifyDidInsertPhantom(toColumnId, phantomIndex);
   }
 
   /// Reset or initial the [PhantomRecord]
@@ -150,7 +155,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
     if (phantomRecord == null) {
       _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex);
       _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex);
-      return false;
+
+      return true;
     }
 
     final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId;
@@ -204,7 +210,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
 
   @override
   int getInsertedIndex(String dragTargetId) {
-    if (columnsState.isDragging(dragTargetId)) {
+    if (phantomState.isDragging(dragTargetId)) {
       return -1;
     }
 
@@ -243,8 +249,7 @@ class PhantomRecord {
     if (fromColumnIndex == index) {
       return;
     }
-    Log.debug(
-        '[$PhantomRecord] Update Column:[$fromColumnId] remove position to $index');
+
     fromColumnIndex = index;
   }
 

+ 1 - 1
frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart

@@ -1,7 +1,7 @@
 import 'phantom_controller.dart';
 import 'package:flutter/material.dart';
 
-class ColumnPhantomStateController {
+class ColumnPhantomState {
   final _states = <String, ColumnState>{};
 
   void setColumnIsDragging(String columnId, bool isDragging) {