mirror of https://github.com/Wox-launcher/Wox
feat(quick-select): implement quick select functionality for item selection
* Added quick select mode to allow users to select items using number keys. * Introduced `isQuickSelectMode` and related methods in `WoxLauncherController`. * Updated `WoxListItem` to include `isShowQuickSelect` and `quickSelectNumber`. * Enhanced UI components to display quick select numbers. * Improved keyboard handling in `WoxQueryBoxView` for quick select actions.
This commit is contained in:
parent
096ee65d18
commit
38929cc506
|
@ -1656,6 +1656,25 @@ func (m *Manager) QueryMRU(ctx context.Context) []QueryResultUI {
|
|||
}
|
||||
|
||||
if restored := m.restoreFromMRU(ctx, pluginInstance, item); restored != nil {
|
||||
// Add "Remove from MRU" action to each MRU result
|
||||
removeMRUAction := QueryResultAction{
|
||||
Id: uuid.NewString(),
|
||||
Name: i18n.GetI18nManager().TranslateWox(ctx, "mru_remove_action"),
|
||||
Icon: common.NewWoxImageEmoji("🗑️"),
|
||||
Hotkey: "ctrl+d",
|
||||
Action: func(ctx context.Context, actionContext ActionContext) {
|
||||
err := setting.GetSettingManager().RemoveMRUItem(ctx, item.PluginID, item.Title, item.SubTitle)
|
||||
if err != nil {
|
||||
util.GetLogger().Error(ctx, fmt.Sprintf("failed to remove MRU item: %s", err.Error()))
|
||||
} else {
|
||||
util.GetLogger().Info(ctx, fmt.Sprintf("removed MRU item: %s - %s", item.Title, item.SubTitle))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Add the remove action to the result
|
||||
restored.Actions = append(restored.Actions, removeMRUAction)
|
||||
|
||||
polishedResult := m.PolishResult(ctx, pluginInstance, Query{}, *restored)
|
||||
results = append(results, polishedResult.ToUI())
|
||||
}
|
||||
|
|
|
@ -356,6 +356,7 @@
|
|||
"plugin_manager_remove_from_favorite": "Remove from favorite",
|
||||
"plugin_manager_add_to_favorite": "Add to favorite",
|
||||
"plugin_manager_invalid_query_type": "Invalid query type",
|
||||
"mru_remove_action": "Remove from MRU",
|
||||
"plugin_ai_chat_agents": "Agents",
|
||||
"plugin_ai_chat_agents_tooltip": "Configure AI agents with custom prompts and tools",
|
||||
"plugin_ai_chat_agent_name": "Name",
|
||||
|
|
|
@ -356,6 +356,7 @@
|
|||
"plugin_manager_remove_from_favorite": "从收藏夹移除",
|
||||
"plugin_manager_add_to_favorite": "添加到收藏夹",
|
||||
"plugin_manager_invalid_query_type": "无效的查询类型",
|
||||
"mru_remove_action": "从最近使用中移除",
|
||||
"plugin_ai_chat_agents": "智能体",
|
||||
"plugin_ai_chat_agents_tooltip": "配置具有自定义提示词和工具的AI智能体",
|
||||
"plugin_ai_chat_agent_name": "名称",
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
"wox/common"
|
||||
"wox/database"
|
||||
|
@ -36,7 +38,7 @@ func NewMRUManager(db *gorm.DB) *MRUManager {
|
|||
// AddMRUItem adds or updates an MRU item
|
||||
func (m *MRUManager) AddMRUItem(ctx context.Context, item MRUItem) error {
|
||||
hash := NewResultHash(item.PluginID, item.Title, item.SubTitle)
|
||||
|
||||
|
||||
// Serialize icon to JSON
|
||||
iconData, err := json.Marshal(item.Icon)
|
||||
if err != nil {
|
||||
|
@ -49,7 +51,7 @@ func (m *MRUManager) AddMRUItem(ctx context.Context, item MRUItem) error {
|
|||
// Check if record exists
|
||||
var existingRecord database.MRURecord
|
||||
err = m.db.Where("hash = ?", string(hash)).First(&existingRecord).Error
|
||||
|
||||
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// Create new record
|
||||
record := database.MRURecord{
|
||||
|
@ -80,18 +82,46 @@ func (m *MRUManager) AddMRUItem(ctx context.Context, item MRUItem) error {
|
|||
}
|
||||
}
|
||||
|
||||
// GetMRUItems retrieves MRU items sorted by usage
|
||||
// GetMRUItems retrieves MRU items sorted by usage with smart scoring
|
||||
func (m *MRUManager) GetMRUItems(ctx context.Context, limit int) ([]MRUItem, error) {
|
||||
var records []database.MRURecord
|
||||
|
||||
// Order by last_used DESC, then by use_count DESC for items with same last_used time
|
||||
err := m.db.Order("last_used DESC, use_count DESC").Limit(limit).Find(&records).Error
|
||||
|
||||
// Only return items with use_count >= 3 to ensure quality
|
||||
err := m.db.Where("use_count >= ?", 3).Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query MRU records: %w", err)
|
||||
}
|
||||
|
||||
items := make([]MRUItem, 0, len(records))
|
||||
// Calculate smart scores for each record
|
||||
type scoredRecord struct {
|
||||
record database.MRURecord
|
||||
score int64
|
||||
}
|
||||
|
||||
scoredRecords := make([]scoredRecord, 0, len(records))
|
||||
currentTimestamp := util.GetSystemTimestamp()
|
||||
|
||||
for _, record := range records {
|
||||
score := m.calculateMRUScore(record, currentTimestamp)
|
||||
scoredRecords = append(scoredRecords, scoredRecord{
|
||||
record: record,
|
||||
score: score,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort by score descending
|
||||
sort.Slice(scoredRecords, func(i, j int) bool {
|
||||
return scoredRecords[i].score > scoredRecords[j].score
|
||||
})
|
||||
|
||||
// Apply limit
|
||||
if limit > 0 && len(scoredRecords) > limit {
|
||||
scoredRecords = scoredRecords[:limit]
|
||||
}
|
||||
|
||||
items := make([]MRUItem, 0, len(scoredRecords))
|
||||
for _, sr := range scoredRecords {
|
||||
record := sr.record
|
||||
// Deserialize icon
|
||||
var icon common.WoxImage
|
||||
if err := json.Unmarshal([]byte(record.Icon), &icon); err != nil {
|
||||
|
@ -120,7 +150,7 @@ func (m *MRUManager) RemoveMRUItem(ctx context.Context, pluginID, title, subTitl
|
|||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to remove MRU item: %w", result.Error)
|
||||
}
|
||||
|
||||
|
||||
util.GetLogger().Debug(ctx, fmt.Sprintf("removed MRU item: %s", hash))
|
||||
return nil
|
||||
}
|
||||
|
@ -151,3 +181,45 @@ func (m *MRUManager) GetMRUCount(ctx context.Context) (int64, error) {
|
|||
err := m.db.Model(&database.MRURecord{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// calculateMRUScore calculates a smart score for MRU items based on usage patterns
|
||||
// This algorithm is inspired by calculateResultScore in plugin/manager.go
|
||||
func (m *MRUManager) calculateMRUScore(record database.MRURecord, currentTimestamp int64) int64 {
|
||||
var score int64 = 0
|
||||
|
||||
// Base score from use count (logarithmic scaling to prevent dominance)
|
||||
// Use count of 3-10: score 10-30, 11-50: score 35-70, 51+: score 75+
|
||||
useCountScore := int64(math.Log(float64(record.UseCount)) * 15)
|
||||
score += useCountScore
|
||||
|
||||
// Time-based scoring using fibonacci sequence (similar to calculateResultScore)
|
||||
// More recent usage gets higher weight
|
||||
hours := (currentTimestamp - record.LastUsed) / 1000 / 60 / 60
|
||||
if hours < 24*7 { // Within 7 days
|
||||
fibonacciIndex := int(math.Ceil(float64(hours) / 24))
|
||||
if fibonacciIndex > 7 {
|
||||
fibonacciIndex = 7
|
||||
}
|
||||
if fibonacciIndex < 1 {
|
||||
fibonacciIndex = 1
|
||||
}
|
||||
fibonacci := []int64{5, 8, 13, 21, 34, 55, 89}
|
||||
score += fibonacci[7-fibonacciIndex]
|
||||
} else if hours < 24*30 { // Within 30 days but older than 7 days
|
||||
score += 3 // Small bonus for recent but not very recent usage
|
||||
}
|
||||
// Items older than 30 days get no time bonus
|
||||
|
||||
// Frequency bonus: items used more frequently get higher scores
|
||||
// Calculate average usage frequency (uses per day since creation)
|
||||
daysSinceCreation := (currentTimestamp - record.CreatedAt.Unix()*1000) / 1000 / 60 / 60 / 24
|
||||
if daysSinceCreation > 0 {
|
||||
frequencyScore := int64(float64(record.UseCount) / float64(daysSinceCreation) * 10)
|
||||
if frequencyScore > 50 { // Cap frequency bonus
|
||||
frequencyScore = 50
|
||||
}
|
||||
score += frequencyScore
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
|
|
@ -64,7 +64,10 @@ class WoxImageView extends StatelessWidget {
|
|||
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_SVG.code) {
|
||||
imageWidget = SvgPicture.string(woxImage.imageData, width: width, height: height);
|
||||
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_EMOJI.code) {
|
||||
imageWidget = Text(woxImage.imageData, style: TextStyle(fontSize: width));
|
||||
imageWidget = Padding(
|
||||
padding: const EdgeInsets.only(left: 2, right: 2),
|
||||
child: Text(woxImage.imageData, style: const TextStyle(fontSize: 25)),
|
||||
);
|
||||
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_LOTTIE.code) {
|
||||
final bytes = utf8.encode(woxImage.imageData);
|
||||
imageWidget = Lottie.memory(bytes, width: width, height: height);
|
||||
|
|
|
@ -37,6 +37,34 @@ class WoxListItemView extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
Widget buildQuickSelectNumber() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 5.0),
|
||||
child: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: fromCssColor(isActive ? woxTheme.resultItemActiveTailTextColor : woxTheme.resultItemTailTextColor),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(
|
||||
color: fromCssColor(isActive ? woxTheme.resultItemActiveTailTextColor : woxTheme.resultItemTailTextColor).withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
item.quickSelectNumber,
|
||||
style: TextStyle(
|
||||
color: fromCssColor(isActive ? woxTheme.resultItemActiveBackgroundColor : woxTheme.appBackgroundColor),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildTails() {
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: WoxSettingUtil.instance.currentSetting.appWidth / 2),
|
||||
|
@ -103,13 +131,13 @@ class WoxListItemView extends StatelessWidget {
|
|||
} else if (isHovered) {
|
||||
// Use a lighter version of the active background color for hover state
|
||||
if (listViewType == WoxListViewTypeEnum.WOX_LIST_VIEW_TYPE_ACTION.code) {
|
||||
return fromCssColor(woxTheme.actionItemActiveBackgroundColor).withOpacity(0.3);
|
||||
return fromCssColor(woxTheme.actionItemActiveBackgroundColor).withValues(alpha: 0.3);
|
||||
}
|
||||
if (listViewType == WoxListViewTypeEnum.WOX_LIST_VIEW_TYPE_CHAT.code) {
|
||||
return fromCssColor(woxTheme.resultItemActiveBackgroundColor).withOpacity(0.3);
|
||||
return fromCssColor(woxTheme.resultItemActiveBackgroundColor).withValues(alpha: 0.3);
|
||||
}
|
||||
if (listViewType == WoxListViewTypeEnum.WOX_LIST_VIEW_TYPE_RESULT.code) {
|
||||
return fromCssColor(woxTheme.resultItemActiveBackgroundColor).withOpacity(0.3);
|
||||
return fromCssColor(woxTheme.resultItemActiveBackgroundColor).withValues(alpha: 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,6 +217,8 @@ class WoxListItemView extends StatelessWidget {
|
|||
),
|
||||
// Tails
|
||||
if (item.tails.isNotEmpty) buildTails() else const SizedBox(),
|
||||
// Quick select number
|
||||
if (item.isShowQuickSelect && item.quickSelectNumber.isNotEmpty) buildQuickSelectNumber(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'dart:convert';
|
|||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
import 'package:uuid/v4.dart';
|
||||
|
@ -99,6 +100,12 @@ class WoxLauncherController extends GetxController {
|
|||
Timer cleanToolbarTimer = Timer(const Duration(), () => {});
|
||||
final cleanToolbarDelay = 1000;
|
||||
|
||||
// quick select related variables
|
||||
final isQuickSelectMode = false.obs;
|
||||
Timer? quickSelectTimer;
|
||||
final quickSelectDelay = 300; // delay to show number labels
|
||||
bool isQuickSelectKeyPressed = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
@ -271,6 +278,14 @@ class WoxLauncherController extends GetxController {
|
|||
}
|
||||
|
||||
hideActionPanel(traceId);
|
||||
|
||||
// Clean up quick select state
|
||||
if (isQuickSelectMode.value) {
|
||||
deactivateQuickSelectMode(traceId);
|
||||
}
|
||||
quickSelectTimer?.cancel();
|
||||
isQuickSelectKeyPressed = false;
|
||||
|
||||
await windowManager.hide();
|
||||
|
||||
await WoxApi.instance.onHide(currentQuery.value);
|
||||
|
@ -774,22 +789,16 @@ class WoxLauncherController extends GetxController {
|
|||
resizeHeight();
|
||||
}
|
||||
|
||||
// Save user's current selection before updateItems (which calls filterItems and resets index)
|
||||
var oldActionName = getCurrentActionName();
|
||||
|
||||
var actions = result.value.data.actions.map((e) => WoxListItem.fromResultAction(e)).toList();
|
||||
var oldActionIndex = actionListViewController.activeIndex.value;
|
||||
var oldActionCount = actionListViewController.items.length;
|
||||
actionListViewController.updateItems(traceId, actions);
|
||||
// update active index to default action
|
||||
// if action panel is visible, prefer to keep the active index
|
||||
if (!isShowActionPanel.value || oldActionIndex >= actions.length || oldActionCount != actions.length) {
|
||||
var defaultActionIndex = actions.indexWhere((element) => element.data.isDefault);
|
||||
if (defaultActionIndex != -1) {
|
||||
actionListViewController.updateActiveIndex(traceId, defaultActionIndex);
|
||||
} else {
|
||||
actionListViewController.updateActiveIndex(traceId, 0);
|
||||
}
|
||||
} else {
|
||||
// keep the active index, we need to this to trigger the onItemActive callback, so the toolbar info can be updated
|
||||
actionListViewController.updateActiveIndex(traceId, actionListViewController.activeIndex.value);
|
||||
|
||||
// Restore user's selected action after refresh
|
||||
var newActiveIndex = calculatePreservedActionIndex(oldActionName);
|
||||
if (actionListViewController.activeIndex.value != newActiveIndex) {
|
||||
actionListViewController.updateActiveIndex(traceId, newActiveIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -804,6 +813,8 @@ class WoxLauncherController extends GetxController {
|
|||
subTitle: result.value.data.subTitle,
|
||||
isGroup: result.value.data.isGroup,
|
||||
data: result.value.data,
|
||||
isShowQuickSelect: result.value.isShowQuickSelect,
|
||||
quickSelectNumber: result.value.quickSelectNumber,
|
||||
));
|
||||
|
||||
isRequesting.remove(result.value.data.id);
|
||||
|
@ -1113,4 +1124,125 @@ class WoxLauncherController extends GetxController {
|
|||
action: () {},
|
||||
);
|
||||
}
|
||||
|
||||
// Quick select related methods
|
||||
|
||||
/// Check if the quick select modifier key is pressed (Cmd on macOS, Alt on Windows/Linux)
|
||||
bool isQuickSelectModifierPressed() {
|
||||
if (Platform.isMacOS) {
|
||||
return HardwareKeyboard.instance.isMetaPressed;
|
||||
} else {
|
||||
return HardwareKeyboard.instance.isAltPressed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the quick select timer when modifier key is pressed
|
||||
void startQuickSelectTimer(String traceId) {
|
||||
if (isQuickSelectMode.value || resultListViewController.items.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.instance.debug(traceId, "Quick select: starting timer");
|
||||
isQuickSelectKeyPressed = true;
|
||||
|
||||
quickSelectTimer?.cancel();
|
||||
quickSelectTimer = Timer(Duration(milliseconds: quickSelectDelay), () {
|
||||
if (isQuickSelectKeyPressed && isQuickSelectModifierPressed()) {
|
||||
Logger.instance.debug(traceId, "Quick select: activating mode");
|
||||
activateQuickSelectMode(traceId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Stop the quick select timer when modifier key is released
|
||||
void stopQuickSelectTimer(String traceId) {
|
||||
Logger.instance.debug(traceId, "Quick select: stopping timer");
|
||||
isQuickSelectKeyPressed = false;
|
||||
quickSelectTimer?.cancel();
|
||||
|
||||
if (isQuickSelectMode.value) {
|
||||
deactivateQuickSelectMode(traceId);
|
||||
}
|
||||
}
|
||||
|
||||
/// Activate quick select mode and add number labels to results
|
||||
void activateQuickSelectMode(String traceId) {
|
||||
Logger.instance.debug(traceId, "Quick select: activating mode");
|
||||
isQuickSelectMode.value = true;
|
||||
updateQuickSelectNumbers(traceId);
|
||||
}
|
||||
|
||||
/// Deactivate quick select mode and remove number labels
|
||||
void deactivateQuickSelectMode(String traceId) {
|
||||
Logger.instance.debug(traceId, "Quick select: deactivating mode");
|
||||
isQuickSelectMode.value = false;
|
||||
updateQuickSelectNumbers(traceId);
|
||||
}
|
||||
|
||||
/// Update quick select numbers for all result items
|
||||
void updateQuickSelectNumbers(String traceId) {
|
||||
var items = resultListViewController.items;
|
||||
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
var item = items[i].value;
|
||||
|
||||
// Update quick select properties
|
||||
var updatedItem = item.copyWith(
|
||||
isShowQuickSelect: isQuickSelectMode.value && !item.isGroup && i < 9,
|
||||
quickSelectNumber: (isQuickSelectMode.value && !item.isGroup && i < 9) ? (i + 1).toString() : '',
|
||||
);
|
||||
|
||||
// Directly update the reactive item to trigger UI refresh
|
||||
items[i].value = updatedItem;
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle number key press in quick select mode
|
||||
bool handleQuickSelectNumberKey(String traceId, int number) {
|
||||
if (!isQuickSelectMode.value || number < 1 || number > 9) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var items = resultListViewController.items;
|
||||
var targetIndex = number - 1;
|
||||
|
||||
if (targetIndex < items.length && !items[targetIndex].value.isGroup) {
|
||||
Logger.instance.debug(traceId, "Quick select: selecting item $number");
|
||||
resultListViewController.updateActiveIndex(traceId, targetIndex);
|
||||
executeToolbarAction(traceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int calculatePreservedActionIndex(String? oldActionName) {
|
||||
var items = actionListViewController.items;
|
||||
|
||||
// If action panel is not visible, use default action
|
||||
if (!isShowActionPanel.value) {
|
||||
var defaultIndex = items.indexWhere((element) => element.value.data.isDefault);
|
||||
return defaultIndex != -1 ? defaultIndex : 0;
|
||||
}
|
||||
|
||||
// Try to find the same action by name
|
||||
if (oldActionName != null) {
|
||||
var sameActionIndex = items.indexWhere((element) => element.value.data.name == oldActionName);
|
||||
if (sameActionIndex != -1) {
|
||||
return sameActionIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default action
|
||||
var defaultIndex = items.indexWhere((element) => element.value.data.isDefault);
|
||||
return defaultIndex != -1 ? defaultIndex : 0;
|
||||
}
|
||||
|
||||
String? getCurrentActionName() {
|
||||
var oldActionIndex = actionListViewController.activeIndex.value;
|
||||
if (actionListViewController.items.isNotEmpty && oldActionIndex < actionListViewController.items.length) {
|
||||
return actionListViewController.items[oldActionIndex].value.data.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ class WoxListItem<T> {
|
|||
final bool isGroup;
|
||||
final String? hotkey;
|
||||
final T data; // extra data associated with the item
|
||||
final bool isShowQuickSelect;
|
||||
final String quickSelectNumber;
|
||||
|
||||
WoxListItem({
|
||||
required this.id,
|
||||
|
@ -22,6 +24,8 @@ class WoxListItem<T> {
|
|||
required this.isGroup,
|
||||
this.hotkey,
|
||||
required this.data,
|
||||
this.isShowQuickSelect = false,
|
||||
this.quickSelectNumber = '',
|
||||
});
|
||||
|
||||
WoxListItem<T> copyWith({
|
||||
|
@ -33,6 +37,8 @@ class WoxListItem<T> {
|
|||
bool? isGroup,
|
||||
String? hotkey,
|
||||
T? data,
|
||||
bool? isShowQuickSelect,
|
||||
String? quickSelectNumber,
|
||||
}) {
|
||||
return WoxListItem<T>(
|
||||
id: id ?? this.id,
|
||||
|
@ -43,6 +49,8 @@ class WoxListItem<T> {
|
|||
isGroup: isGroup ?? this.isGroup,
|
||||
hotkey: hotkey ?? this.hotkey,
|
||||
data: data ?? this.data,
|
||||
isShowQuickSelect: isShowQuickSelect ?? this.isShowQuickSelect,
|
||||
quickSelectNumber: quickSelectNumber ?? this.quickSelectNumber,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,43 @@ class WoxQueryBoxView extends GetView<WoxLauncherController> {
|
|||
callback();
|
||||
}
|
||||
|
||||
// Helper method to convert LogicalKeyboardKey to number for quick select
|
||||
int? getNumberFromKey(LogicalKeyboardKey key) {
|
||||
switch (key) {
|
||||
case LogicalKeyboardKey.digit1:
|
||||
return 1;
|
||||
case LogicalKeyboardKey.digit2:
|
||||
return 2;
|
||||
case LogicalKeyboardKey.digit3:
|
||||
return 3;
|
||||
case LogicalKeyboardKey.digit4:
|
||||
return 4;
|
||||
case LogicalKeyboardKey.digit5:
|
||||
return 5;
|
||||
case LogicalKeyboardKey.digit6:
|
||||
return 6;
|
||||
case LogicalKeyboardKey.digit7:
|
||||
return 7;
|
||||
case LogicalKeyboardKey.digit8:
|
||||
return 8;
|
||||
case LogicalKeyboardKey.digit9:
|
||||
return 9;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if only the quick select modifier key is pressed (no other keys)
|
||||
bool isQuickSelectModifierKeyOnly(KeyEvent event) {
|
||||
if (Platform.isMacOS) {
|
||||
// On macOS, check if only Cmd key is pressed
|
||||
return event.logicalKey == LogicalKeyboardKey.metaLeft || event.logicalKey == LogicalKeyboardKey.metaRight;
|
||||
} else {
|
||||
// On Windows/Linux, check if only Alt key is pressed
|
||||
return event.logicalKey == LogicalKeyboardKey.altLeft || event.logicalKey == LogicalKeyboardKey.altRight;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (LoggerSwitch.enablePaintLog) Logger.instance.debug(const UuidV4().generate(), "repaint: query box view");
|
||||
|
@ -42,6 +79,25 @@ class WoxQueryBoxView extends GetView<WoxLauncherController> {
|
|||
Positioned(
|
||||
child: Focus(
|
||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
||||
var traceId = const UuidV4().generate();
|
||||
|
||||
// Handle number keys in quick select mode first (higher priority)
|
||||
if (controller.isQuickSelectMode.value && event is KeyDownEvent) {
|
||||
var numberKey = getNumberFromKey(event.logicalKey);
|
||||
if (numberKey != null) {
|
||||
if (controller.handleQuickSelectNumberKey(traceId, numberKey)) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle quick select modifier key press/release
|
||||
if (event is KeyDownEvent && isQuickSelectModifierKeyOnly(event)) {
|
||||
controller.startQuickSelectTimer(traceId);
|
||||
} else {
|
||||
controller.stopQuickSelectTimer(traceId);
|
||||
}
|
||||
|
||||
var isAnyModifierPressed = WoxHotkey.isAnyModifierPressed();
|
||||
if (!isAnyModifierPressed) {
|
||||
if (event is KeyDownEvent) {
|
||||
|
|
Loading…
Reference in New Issue