mirror of https://github.com/Wox-launcher/Wox
226 lines
9.7 KiB
Dart
226 lines
9.7 KiB
Dart
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:from_css_color/from_css_color.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:uuid/v4.dart';
|
|
import 'package:wox/components/wox_list_item_view.dart';
|
|
import 'package:wox/controllers/wox_list_controller.dart';
|
|
import 'package:wox/entity/wox_hotkey.dart';
|
|
import 'package:wox/entity/wox_list_item.dart';
|
|
import 'package:wox/enums/wox_direction_enum.dart';
|
|
import 'package:wox/enums/wox_list_view_type_enum.dart';
|
|
import 'package:wox/utils/log.dart';
|
|
import 'package:wox/utils/wox_theme_util.dart';
|
|
|
|
class WoxListView<T> extends StatelessWidget {
|
|
final WoxListController<T> controller;
|
|
final WoxListViewType listViewType;
|
|
final bool showFilter;
|
|
final double maxHeight;
|
|
|
|
const WoxListView({
|
|
super.key,
|
|
required this.controller,
|
|
required this.listViewType,
|
|
this.showFilter = true,
|
|
required this.maxHeight,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
children: [
|
|
ConstrainedBox(
|
|
constraints: BoxConstraints(
|
|
maxHeight: maxHeight,
|
|
),
|
|
child: Scrollbar(
|
|
thumbVisibility: true,
|
|
controller: controller.scrollController,
|
|
child: Listener(
|
|
onPointerSignal: (event) {
|
|
if (event is PointerScrollEvent) {
|
|
controller.updateActiveIndexByDirection(
|
|
const UuidV4().generate(),
|
|
event.scrollDelta.dy > 0 ? WoxDirectionEnum.WOX_DIRECTION_DOWN.code : WoxDirectionEnum.WOX_DIRECTION_UP.code,
|
|
);
|
|
}
|
|
},
|
|
child: Obx(
|
|
() => AnimatedSwitcher(
|
|
duration: Duration.zero,
|
|
child: ListView.builder(
|
|
key: ValueKey(controller.items.length),
|
|
shrinkWrap: true,
|
|
controller: controller.scrollController,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: controller.items.length,
|
|
itemExtent: WoxThemeUtil.instance.getResultListViewHeightByCount(1),
|
|
addAutomaticKeepAlives: false,
|
|
addRepaintBoundaries: false,
|
|
addSemanticIndexes: false,
|
|
itemBuilder: (context, index) {
|
|
var item = controller.items[index];
|
|
return MouseRegion(
|
|
onEnter: (_) {
|
|
if (controller.isMouseMoved && !item.value.isGroup) {
|
|
Logger.instance.info(const UuidV4().generate(), "MOUSE: onenter, is mouse moved: ${controller.isMouseMoved}, is group: ${item.value.isGroup}");
|
|
controller.updateHoveredIndex(index);
|
|
}
|
|
},
|
|
onHover: (_) {
|
|
if (!controller.isMouseMoved && !item.value.isGroup) {
|
|
Logger.instance.info(const UuidV4().generate(), "MOUSE: onHover, is mouse moved: ${controller.isMouseMoved}, is group: ${item.value.isGroup}");
|
|
controller.isMouseMoved = true;
|
|
controller.updateHoveredIndex(index);
|
|
}
|
|
},
|
|
onExit: (_) {
|
|
if (!item.value.isGroup && controller.hoveredIndex.value == index) {
|
|
controller.clearHoveredResult();
|
|
}
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
if (!item.value.isGroup) {
|
|
controller.updateActiveIndex(const UuidV4().generate(), index);
|
|
controller.onItemActive?.call(const UuidV4().generate(), item.value);
|
|
}
|
|
},
|
|
onDoubleTap: () {
|
|
if (!item.value.isGroup) {
|
|
controller.onItemExecuted?.call(const UuidV4().generate(), item.value);
|
|
}
|
|
},
|
|
child: Obx(
|
|
() => WoxListItemView(
|
|
item: item.value,
|
|
woxTheme: WoxThemeUtil.instance.currentTheme.value,
|
|
isActive: controller.activeIndex.value == index,
|
|
isHovered: controller.hoveredIndex.value == index,
|
|
listViewType: listViewType,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
if (showFilter)
|
|
Focus(
|
|
onFocusChange: (hasFocus) {
|
|
if (!hasFocus) {
|
|
controller.onFilterBoxLostFocus?.call(const UuidV4().generate());
|
|
}
|
|
},
|
|
onKeyEvent: (FocusNode node, KeyEvent event) {
|
|
var traceId = const UuidV4().generate();
|
|
var isAnyModifierPressed = WoxHotkey.isAnyModifierPressed();
|
|
if (!isAnyModifierPressed) {
|
|
if (event is KeyDownEvent) {
|
|
switch (event.logicalKey) {
|
|
case LogicalKeyboardKey.escape:
|
|
controller.onFilterBoxEscPressed?.call(traceId);
|
|
return KeyEventResult.handled;
|
|
case LogicalKeyboardKey.arrowDown:
|
|
controller.updateActiveIndexByDirection(traceId, WoxDirectionEnum.WOX_DIRECTION_DOWN.code);
|
|
return KeyEventResult.handled;
|
|
case LogicalKeyboardKey.arrowUp:
|
|
controller.updateActiveIndexByDirection(traceId, WoxDirectionEnum.WOX_DIRECTION_UP.code);
|
|
return KeyEventResult.handled;
|
|
case LogicalKeyboardKey.enter:
|
|
if (controller.items.isNotEmpty && controller.activeIndex.value < controller.items.length) {
|
|
controller.onItemExecuted?.call(traceId, controller.items[controller.activeIndex.value].value);
|
|
}
|
|
return KeyEventResult.handled;
|
|
}
|
|
}
|
|
|
|
if (event is KeyRepeatEvent) {
|
|
switch (event.logicalKey) {
|
|
case LogicalKeyboardKey.arrowDown:
|
|
controller.updateActiveIndexByDirection(
|
|
traceId,
|
|
WoxDirectionEnum.WOX_DIRECTION_DOWN.code,
|
|
);
|
|
return KeyEventResult.handled;
|
|
case LogicalKeyboardKey.arrowUp:
|
|
controller.updateActiveIndexByDirection(
|
|
traceId,
|
|
WoxDirectionEnum.WOX_DIRECTION_UP.code,
|
|
);
|
|
return KeyEventResult.handled;
|
|
}
|
|
}
|
|
}
|
|
|
|
var pressedHotkey = WoxHotkey.parseNormalHotkeyFromEvent(event);
|
|
if (pressedHotkey == null) {
|
|
return KeyEventResult.ignored;
|
|
}
|
|
|
|
Rx<WoxListItem<T>>? itemMatchedHotkey = controller.items.firstWhereOrNull((element) {
|
|
if (element.value.hotkey == null || element.value.hotkey!.isEmpty) {
|
|
return false;
|
|
}
|
|
|
|
var elementHotkey = WoxHotkey.parseHotkeyFromString(element.value.hotkey!);
|
|
if (elementHotkey != null && WoxHotkey.equals(elementHotkey.normalHotkey, pressedHotkey)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
if (itemMatchedHotkey == null) {
|
|
return KeyEventResult.ignored;
|
|
} else {
|
|
controller.onItemExecuted?.call(traceId, itemMatchedHotkey.value);
|
|
return KeyEventResult.handled;
|
|
}
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(top: 6.0),
|
|
child: SizedBox(
|
|
height: 40.0,
|
|
child: TextField(
|
|
style: TextStyle(
|
|
fontSize: 14.0,
|
|
color: fromCssColor(WoxThemeUtil.instance.currentTheme.value.actionQueryBoxFontColor),
|
|
),
|
|
decoration: InputDecoration(
|
|
isCollapsed: true,
|
|
contentPadding: const EdgeInsets.only(
|
|
left: 8,
|
|
right: 8,
|
|
top: 20,
|
|
bottom: 18,
|
|
),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(WoxThemeUtil.instance.currentTheme.value.actionQueryBoxBorderRadius.toDouble()),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
filled: true,
|
|
fillColor: fromCssColor(WoxThemeUtil.instance.currentTheme.value.actionQueryBoxBackgroundColor),
|
|
hoverColor: Colors.transparent,
|
|
),
|
|
cursorColor: fromCssColor(WoxThemeUtil.instance.currentTheme.value.queryBoxCursorColor),
|
|
focusNode: controller.filterBoxFocusNode,
|
|
controller: controller.filterBoxController,
|
|
onChanged: (value) {
|
|
controller.filterItems(const UuidV4().generate(), value);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|