mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-08 04:30:46 -06:00
[client] Better mouse handling
This commit is contained in:
@@ -5,6 +5,7 @@ enum SelectionMode {
|
||||
single,
|
||||
range,
|
||||
multi,
|
||||
toggle,
|
||||
}
|
||||
|
||||
class FocusUpIntent extends Intent {
|
||||
|
||||
@@ -24,7 +24,6 @@ class FolderContentsView extends StatefulWidget {
|
||||
class _FolderContentsViewState extends State<FolderContentsView> {
|
||||
final Map<String, FocusNode> nodes = {};
|
||||
late List<Resource> resources;
|
||||
int focusIndex = -1;
|
||||
int selectionStartIndex = -1;
|
||||
|
||||
@override
|
||||
@@ -55,33 +54,37 @@ class _FolderContentsViewState extends State<FolderContentsView> {
|
||||
PreviousFocusIntent: CallbackAction<PreviousFocusIntent>(onInvoke: (i) => null),
|
||||
FocusUpIntent: CallbackAction<FocusUpIntent>(onInvoke: (i) {
|
||||
if (resources.isEmpty) return;
|
||||
int focusIndex = context.read<FolderSelectionState>().focusIndex;
|
||||
final index = focusIndex == -1
|
||||
? resources.length
|
||||
: focusIndex > 0
|
||||
? min(focusIndex - 1, resources.length)
|
||||
: 0;
|
||||
updateFocus(index, i.mode);
|
||||
updateSelection(index, i.mode, true);
|
||||
return null;
|
||||
}),
|
||||
FocusDownIntent: CallbackAction<FocusDownIntent>(onInvoke: (i) {
|
||||
if (resources.isEmpty) return;
|
||||
int focusIndex = context.read<FolderSelectionState>().focusIndex;
|
||||
final index = min(focusIndex + 1, resources.length - 1);
|
||||
updateFocus(index, i.mode);
|
||||
updateSelection(index, i.mode, true);
|
||||
return null;
|
||||
}),
|
||||
ToggleSelectionIntent: CallbackAction<ToggleSelectionIntent>(onInvoke: (i) {
|
||||
toggleSelection();
|
||||
final focusIndex = context.read<FolderSelectionState>().focusIndex;
|
||||
updateSelection(focusIndex, SelectionMode.toggle, true);
|
||||
return null;
|
||||
}),
|
||||
SelectAllIntent: CallbackAction<SelectAllIntent>(onInvoke: (i) {
|
||||
context.read<FolderSelectionManager>().setSelected(Set.of(resources.map((r) => r.id)));
|
||||
context.read<FolderSelectionManager>().update(selected: Set.of(resources.map((r) => r.id)), showFocus: true);
|
||||
return null;
|
||||
}),
|
||||
DismissIntent: CallbackAction<DismissIntent>(onInvoke: (i) {
|
||||
int focusIndex = context.read<FolderSelectionState>().focusIndex;
|
||||
if (focusIndex >= 0) {
|
||||
context.read<FolderSelectionManager>().setSelected({resources[min(focusIndex, resources.length)].id});
|
||||
updateSelection(focusIndex, SelectionMode.single, true);
|
||||
} else {
|
||||
context.read<FolderSelectionManager>().setSelected(const {});
|
||||
context.read<FolderSelectionManager>().update(selected: const {}, showFocus: true);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
@@ -90,6 +93,7 @@ class _FolderContentsViewState extends State<FolderContentsView> {
|
||||
return null;
|
||||
}),
|
||||
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: (i) {
|
||||
int focusIndex = context.read<FolderSelectionState>().focusIndex;
|
||||
open(resources[focusIndex]);
|
||||
return null;
|
||||
}),
|
||||
@@ -100,55 +104,65 @@ class _FolderContentsViewState extends State<FolderContentsView> {
|
||||
},
|
||||
child: Focus(
|
||||
autofocus: true,
|
||||
descendantsAreFocusable: false,
|
||||
child: ListView.builder(
|
||||
itemCount: resources.length,
|
||||
itemBuilder: (context, index) {
|
||||
final r = resources[index];
|
||||
return Focus(
|
||||
focusNode: getFocusNode(r),
|
||||
child: ResourceDetailsRow(
|
||||
r: r,
|
||||
onTap: () {
|
||||
final mode = HardwareKeyboard.instance.isControlPressed
|
||||
? SelectionMode.multi
|
||||
: HardwareKeyboard.instance.isShiftPressed
|
||||
? SelectionMode.range
|
||||
: SelectionMode.single;
|
||||
updateFocus(index, mode);
|
||||
if (mode == SelectionMode.multi) {
|
||||
toggleSelection();
|
||||
}
|
||||
},
|
||||
onDoubleTap: () => open(r)),
|
||||
);
|
||||
bool deferHandling = false;
|
||||
return ResourceDetailsRow(
|
||||
r: r,
|
||||
onTapDown: (details) {
|
||||
final mode = HardwareKeyboard.instance.isControlPressed
|
||||
? SelectionMode.toggle
|
||||
: HardwareKeyboard.instance.isShiftPressed
|
||||
? SelectionMode.range
|
||||
: SelectionMode.single;
|
||||
if (context.read<FolderSelectionState>().isSelected(r.id) && mode == SelectionMode.single) {
|
||||
deferHandling = true;
|
||||
} else {
|
||||
deferHandling = false;
|
||||
updateSelection(index, mode, false);
|
||||
}
|
||||
},
|
||||
onTap: () {
|
||||
if (deferHandling) {
|
||||
updateSelection(index, SelectionMode.single, false);
|
||||
}
|
||||
},
|
||||
onDoubleTap: () => open(r));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateFocus(int index, SelectionMode mode) {
|
||||
void updateSelection(int index, SelectionMode mode, bool highlight) {
|
||||
Set<String>? selected;
|
||||
switch (mode) {
|
||||
case SelectionMode.range:
|
||||
if (selectionStartIndex == -1) {
|
||||
selectionStartIndex = index;
|
||||
}
|
||||
final selected = Set.of(resources.getRange(min(selectionStartIndex, index), max(selectionStartIndex, index) + 1).map((r) => r.id));
|
||||
context.read<FolderSelectionManager>().setSelected(selected);
|
||||
selected = Set.of(resources.getRange(min(selectionStartIndex, index), max(selectionStartIndex, index) + 1).map((r) => r.id));
|
||||
break;
|
||||
case SelectionMode.single:
|
||||
context.read<FolderSelectionManager>().setSelected({resources[index].id});
|
||||
selected = {resources[index].id};
|
||||
selectionStartIndex = index;
|
||||
break;
|
||||
case SelectionMode.toggle:
|
||||
selected = Set.of(context.read<FolderSelectionState>().selected);
|
||||
if (!selected.add(resources[index].id)) {
|
||||
selected.remove(resources[index].id);
|
||||
}
|
||||
selectionStartIndex = index;
|
||||
break;
|
||||
case SelectionMode.multi:
|
||||
break;
|
||||
}
|
||||
getFocusNode(resources[index]).requestFocus();
|
||||
focusIndex = index;
|
||||
}
|
||||
|
||||
void toggleSelection() {
|
||||
selectionStartIndex = context.read<FolderSelectionManager>().toggleSelected(resources[focusIndex].id) ? focusIndex : -1;
|
||||
context
|
||||
.read<FolderSelectionManager>()
|
||||
.update(selected: selected, focusId: index < resources.length ? resources[index].id : null, focusIndex: index, showFocus: highlight);
|
||||
}
|
||||
|
||||
void open(Resource r) {
|
||||
@@ -169,8 +183,4 @@ class _FolderContentsViewState extends State<FolderContentsView> {
|
||||
account.addAction(ResourceDeleteAction(r: r));
|
||||
}
|
||||
}
|
||||
|
||||
FocusNode getFocusNode(Resource r) {
|
||||
return nodes.putIfAbsent(r.id, () => FocusNode(descendantsAreTraversable: false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ import 'package:state_notifier/state_notifier.dart';
|
||||
|
||||
class FolderSelectionState {
|
||||
final Set<String> selected;
|
||||
final String focusId;
|
||||
final int focusIndex;
|
||||
final bool showFocus;
|
||||
|
||||
FolderSelectionState({required this.selected});
|
||||
FolderSelectionState({required this.selected, required this.focusId, required this.focusIndex, required this.showFocus});
|
||||
|
||||
bool isSelected(String id) {
|
||||
return selected.contains(id);
|
||||
@@ -11,20 +14,19 @@ class FolderSelectionState {
|
||||
}
|
||||
|
||||
class FolderSelectionManager extends StateNotifier<FolderSelectionState> {
|
||||
FolderSelectionManager() : super(FolderSelectionState(selected: {}));
|
||||
FolderSelectionManager() : super(FolderSelectionState(selected: {}, focusId: "", focusIndex: -1, showFocus: false));
|
||||
|
||||
void setSelected(Set<String> ids) {
|
||||
state = FolderSelectionState(selected: ids);
|
||||
}
|
||||
|
||||
bool toggleSelected(String id) {
|
||||
final selected = Set.of(state.selected);
|
||||
bool added = true;
|
||||
if (!selected.add(id)) {
|
||||
selected.remove(id);
|
||||
added = false;
|
||||
}
|
||||
state = FolderSelectionState(selected: selected);
|
||||
return added;
|
||||
void update({
|
||||
Set<String>? selected,
|
||||
String? focusId,
|
||||
int? focusIndex,
|
||||
bool? showFocus,
|
||||
}) {
|
||||
state = FolderSelectionState(
|
||||
selected: selected ?? state.selected,
|
||||
focusId: focusId ?? state.focusId,
|
||||
focusIndex: focusIndex ?? state.focusIndex,
|
||||
showFocus: showFocus ?? state.showFocus,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@ import 'package:provider/provider.dart';
|
||||
|
||||
class ResourceDetailsRow extends StatelessWidget {
|
||||
final Resource r;
|
||||
final Function(TapDownDetails)? onTapDown;
|
||||
final Function()? onTap;
|
||||
final Function()? onDoubleTap;
|
||||
final Function()? onSecondaryTap;
|
||||
|
||||
ResourceDetailsRow({required this.r, this.onTap, this.onDoubleTap, this.onSecondaryTap}) : super(key: ValueKey(r.id));
|
||||
ResourceDetailsRow({required this.r, this.onTapDown, this.onTap, this.onDoubleTap, this.onSecondaryTap}) : super(key: ValueKey(r.id));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -29,7 +30,7 @@ class ResourceDetailsRow extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget buildRow(BuildContext context) {
|
||||
final focussed = Focus.isAt(context);
|
||||
final focussed = context.select<FolderSelectionState, bool>((state) => state.focusId == r.id && state.showFocus);
|
||||
final selected = context.select<FolderSelectionState, bool>((state) => state.isSelected(r.id));
|
||||
|
||||
return Draggable(
|
||||
@@ -51,14 +52,11 @@ class ResourceDetailsRow extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}),
|
||||
childWhenDragging: Builder(builder: (context) {
|
||||
return Opacity(opacity: 0.5, child: buildListTile(context, focussed, selected));
|
||||
}),
|
||||
child: GestureDetector(
|
||||
onTapDown: selected ? null : (details) => onTap?.call(),
|
||||
onTap: selected ? () => onTap?.call() : null,
|
||||
onDoubleTap: () => onDoubleTap?.call(),
|
||||
onSecondaryTap: () => onSecondaryTap?.call(),
|
||||
onTapDown: onTapDown,
|
||||
onTap: onTap,
|
||||
onDoubleTap: onDoubleTap,
|
||||
onSecondaryTap: onSecondaryTap,
|
||||
child: buildListTile(context, focussed, selected),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user