Skip to content

Commit fa4ecc8

Browse files
committed
feat: enhance plugin settings and UI components with new label and text measurement utilities
1 parent 0a1c2f0 commit fa4ecc8

15 files changed

+233
-146
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,34 @@
88
- Add "App font family" setting to choose system font for Wox interface #4335
99
- [`Plugin Setting`] Add image emoji selector for plugin table image fields
1010
- [`Plugin Setting`] Add `maxHeight` property support in plugin table setting value #4339
11+
- [`Plugin Store`] Add filter functionality and upgrade indicators for plugins #4356
12+
- [`Browser Bookmarks`] Add Firefox support #4354
13+
- [`Web Search`] Allow user to select custom browser #3597
14+
- [`Setting`] Add log management features including clearing logs and changing log level
15+
- [`Explorer`] Add quick jump paths and enhance file dialog interactions
1116

1217
- Improve
1318
- [`Shell`] Enhance Shell plugin terminal preview to support search/full-screen/scroll-to-load functions
1419
- Improve query hotkey tooltips and add Wox Chrome extension link in settings #4333
1520
- Improve app process exit handling when shutting down Wox #4338
1621
- Improve the layout of the plugin settings page
22+
- [`Plugin Setting`] Improve focus management and validation
23+
- Improve preview functionality and local actions
24+
- Improve Windows Start Menu handling by dismissing it when Wox opens #4341
25+
- [`Explorer`] Improve overlay positioning and DPI handling
26+
- [`Calculator`] Improve history management and limit displayed history to top 100 entries #4340
27+
- [`Explorer`] Improve file path resolution for open/save dialogs on macOS
28+
- Improve listview rendering performance
1729

1830
- Fix
1931
- [`File Explorer Search`] Fix an issue that file explorer search plugin cannot navigate on open/save dialog
2032
- [`Clipboard`] Fix self-triggering in clipboard watch #4309
2133
- Fix an issue that Wox setting table values can't be saved sometimes
2234
- Fix query results not being cleared correctly when app visibility changes
2335
- Fix Base64 JPEG decode issue in image preview
36+
- [`Plugin Setting`] Fix an issue with handling null and empty JSON responses in plugin table settings
37+
- [`File`] Fix Windows Phone Link automatic downloads occurring when fetching file icons #4352
38+
- Fix image loading error handling in image view
2439

2540
## v2.0.0 - 2026-02-09
2641

wox.core/plugin/manager.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,8 @@ func (m *Manager) ParseMetadata(ctx context.Context, pluginDirectory string) (Me
481481
}
482482

483483
// ParseScriptMetadata parses metadata from script plugin file comments
484-
// Supports two formats:
484+
// Supports formats:
485485
// 1. JSON block format (preferred): # { ... } with complete plugin.json structure
486-
// 2. Legacy @wox.xxx format: individual @wox.id, @wox.name, etc. annotations
487486
func (m *Manager) ParseScriptMetadata(ctx context.Context, scriptPath string) (Metadata, error) {
488487
content, err := os.ReadFile(scriptPath)
489488
if err != nil {

wox.ui.flutter/wox/lib/components/plugin/wox_setting_plugin_head_view.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import 'package:flutter/material.dart';
22
import 'package:wox/entity/setting/wox_plugin_setting_head.dart';
33
import 'package:wox/utils/colors.dart';
4-
import 'package:wox/utils/consts.dart';
54

65
import 'wox_setting_plugin_item_view.dart';
76

87
class WoxSettingPluginHead extends WoxSettingPluginItem {
98
final PluginSettingValueHead item;
109

11-
const WoxSettingPluginHead({super.key, required this.item, required super.value, required super.onUpdate, super.labelWidth = SETTING_LABEL_DEAULT_WIDTH});
10+
const WoxSettingPluginHead({super.key, required this.item, required super.value, required super.onUpdate, required super.labelWidth});
1211

1312
@override
1413
Widget build(BuildContext context) {

wox.ui.flutter/wox/lib/components/plugin/wox_setting_plugin_label_view.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import 'package:flutter/material.dart';
22
import 'package:wox/entity/setting/wox_plugin_setting_label.dart';
33
import 'package:wox/entity/wox_plugin_setting.dart';
44
import 'package:wox/utils/colors.dart';
5-
import 'package:wox/utils/consts.dart';
65

76
import 'wox_setting_plugin_item_view.dart';
87

98
class WoxSettingPluginLabel extends WoxSettingPluginItem {
109
final PluginSettingValueLabel item;
1110

12-
const WoxSettingPluginLabel({super.key, required this.item, required super.value, required super.onUpdate, super.labelWidth = SETTING_LABEL_DEAULT_WIDTH});
11+
const WoxSettingPluginLabel({super.key, required this.item, required super.value, required super.onUpdate, required super.labelWidth});
1312

1413
PluginSettingValueStyle buildEffectiveStyle() {
1514
final style = PluginSettingValueStyle.fromJson(<String, dynamic>{});

wox.ui.flutter/wox/lib/components/plugin/wox_setting_plugin_newline_view.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'wox_setting_plugin_item_view.dart';
77
class WoxSettingPluginNewLine extends WoxSettingPluginItem {
88
final PluginSettingValueNewLine item;
99

10-
const WoxSettingPluginNewLine({super.key, required this.item, required super.value, required super.onUpdate, super.labelWidth = SETTING_LABEL_DEAULT_WIDTH});
10+
const WoxSettingPluginNewLine({super.key, required this.item, required super.value, required super.onUpdate, required super.labelWidth});
1111

1212
@override
1313
Widget build(BuildContext context) {

wox.ui.flutter/wox/lib/components/plugin/wox_setting_plugin_table_view.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class WoxSettingPluginTable extends WoxSettingPluginItem {
4242
required this.item,
4343
required super.value,
4444
required super.onUpdate,
45-
super.labelWidth = SETTING_LABEL_DEAULT_WIDTH,
45+
super.labelWidth = 160.0,
4646
this.tableWidth = 740.0,
4747
this.readonly = false,
4848
this.onUpdateValidate,

wox.ui.flutter/wox/lib/components/wox_form_action_view.dart

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
33
import 'package:wox/components/wox_button.dart';
44
import 'package:wox/components/wox_checkbox.dart';
55
import 'package:wox/components/wox_dropdown_button.dart';
6+
import 'package:wox/components/wox_platform_focus.dart';
67
import 'package:wox/components/wox_textfield.dart';
78
import 'package:wox/entity/setting/wox_plugin_setting_checkbox.dart';
89
import 'package:wox/entity/setting/wox_plugin_setting_head.dart';
@@ -12,6 +13,7 @@ import 'package:wox/entity/setting/wox_plugin_setting_textbox.dart';
1213
import 'package:wox/entity/wox_plugin_setting.dart';
1314
import 'package:wox/entity/wox_query.dart';
1415
import 'package:wox/utils/colors.dart';
16+
import 'package:wox/utils/wox_text_measure_util.dart';
1517

1618
/// A form panel for collecting form action values
1719
class WoxFormActionView extends StatefulWidget {
@@ -34,24 +36,23 @@ class _WoxFormActionViewState extends State<WoxFormActionView> {
3436
late Map<String, String> _values;
3537
final Map<String, TextEditingController> _textControllers = {};
3638
double _maxLabelWidth = 60;
39+
bool _formInitialized = false;
3740

38-
double _measureLabelWidth(String text) {
41+
double _measureLabelWidth(BuildContext context, String text) {
3942
final trimmed = text.trim();
4043
if (trimmed.isEmpty) {
4144
return 60;
4245
}
4346

44-
final painter = TextPainter(text: TextSpan(text: trimmed, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), textDirection: TextDirection.ltr, maxLines: 1)
45-
..layout();
46-
return painter.width + 8;
47+
return WoxTextMeasureUtil.measureTextWidth(context: context, text: trimmed, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)) + 8;
4748
}
4849

4950
@override
5051
void initState() {
5152
super.initState();
5253
_values = Map<String, String>.from(widget.initialValues);
5354

54-
// Find first focusable control, init text controllers, and calculate max label width
55+
// Find first focusable control and init text controllers.
5556
for (int i = 0; i < widget.action.form.length; i++) {
5657
final item = widget.action.form[i];
5758
if (item.type == "textbox") {
@@ -61,27 +62,13 @@ class _WoxFormActionViewState extends State<WoxFormActionView> {
6162

6263
final textbox = item.value as PluginSettingValueTextBox;
6364
_textControllers[textbox.key] = TextEditingController(text: _values[textbox.key] ?? textbox.defaultValue);
64-
final labelWidth = _measureLabelWidth(widget.translate(textbox.label));
65-
if (labelWidth > _maxLabelWidth) {
66-
_maxLabelWidth = labelWidth;
67-
}
6865
} else if (item.type == "select") {
6966
if (_firstFocusableIndex == -1) {
7067
_firstFocusableIndex = i;
7168
}
72-
final select = item.value as PluginSettingValueSelect;
73-
final labelWidth = _measureLabelWidth(widget.translate(select.label));
74-
if (labelWidth > _maxLabelWidth) {
75-
_maxLabelWidth = labelWidth;
76-
}
77-
} else if (item.type == "checkbox") {
78-
final checkbox = item.value as PluginSettingValueCheckBox;
79-
final labelWidth = _measureLabelWidth(widget.translate(checkbox.label));
80-
if (labelWidth > _maxLabelWidth) {
81-
_maxLabelWidth = labelWidth;
82-
}
8369
}
8470
}
71+
_formInitialized = true;
8572

8673
// Request focus on the first focusable control after build
8774
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -93,6 +80,39 @@ class _WoxFormActionViewState extends State<WoxFormActionView> {
9380
});
9481
}
9582

83+
@override
84+
void didChangeDependencies() {
85+
super.didChangeDependencies();
86+
if (!_formInitialized) {
87+
return;
88+
}
89+
90+
double maxLabelWidth = 60;
91+
for (int i = 0; i < widget.action.form.length; i++) {
92+
final item = widget.action.form[i];
93+
if (item.type == "textbox") {
94+
final textbox = item.value as PluginSettingValueTextBox;
95+
final measuredWidth = _measureLabelWidth(context, widget.translate(textbox.label));
96+
if (measuredWidth > maxLabelWidth) {
97+
maxLabelWidth = measuredWidth;
98+
}
99+
} else if (item.type == "select") {
100+
final select = item.value as PluginSettingValueSelect;
101+
final measuredWidth = _measureLabelWidth(context, widget.translate(select.label));
102+
if (measuredWidth > maxLabelWidth) {
103+
maxLabelWidth = measuredWidth;
104+
}
105+
} else if (item.type == "checkbox") {
106+
final checkbox = item.value as PluginSettingValueCheckBox;
107+
final measuredWidth = _measureLabelWidth(context, widget.translate(checkbox.label));
108+
if (measuredWidth > maxLabelWidth) {
109+
maxLabelWidth = measuredWidth;
110+
}
111+
}
112+
}
113+
_maxLabelWidth = maxLabelWidth;
114+
}
115+
96116
@override
97117
void dispose() {
98118
_firstFocusNode.dispose();
@@ -122,10 +142,17 @@ class _WoxFormActionViewState extends State<WoxFormActionView> {
122142
@override
123143
Widget build(BuildContext context) {
124144
return CallbackShortcuts(
125-
bindings: {const SingleActivator(LogicalKeyboardKey.enter, control: true): _handleSave, const SingleActivator(LogicalKeyboardKey.escape): widget.onCancel},
126-
child: Focus(
145+
bindings: {const SingleActivator(LogicalKeyboardKey.enter, control: true): _handleSave},
146+
child: WoxPlatformFocus(
127147
focusNode: _formFocusNode,
128148
autofocus: true,
149+
onKeyEvent: (node, event) {
150+
if (event is KeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) {
151+
widget.onCancel();
152+
return KeyEventResult.handled;
153+
}
154+
return KeyEventResult.ignored;
155+
},
129156
child: Column(
130157
crossAxisAlignment: CrossAxisAlignment.start,
131158
mainAxisSize: MainAxisSize.min,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:wox/components/wox_tooltip.dart';
3+
import 'package:wox/utils/colors.dart';
4+
import 'package:wox/utils/wox_text_measure_util.dart';
5+
6+
class WoxLabel extends StatelessWidget {
7+
final String label;
8+
final double width;
9+
final TextStyle? style;
10+
final TextAlign textAlign;
11+
final bool enableTooltipOnOverflow;
12+
13+
const WoxLabel({super.key, required this.label, required this.width, this.style, this.textAlign = TextAlign.right, this.enableTooltipOnOverflow = true});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
final resolvedStyle = TextStyle(color: getThemeTextColor(), fontSize: 13).merge(style);
18+
final textWidget = Text(label, textAlign: textAlign, style: resolvedStyle, maxLines: 1, overflow: TextOverflow.ellipsis);
19+
20+
return SizedBox(
21+
width: width,
22+
child: LayoutBuilder(
23+
builder: (context, constraints) {
24+
final maxWidth = constraints.maxWidth;
25+
final hasLabel = label.trim().isNotEmpty;
26+
final shouldShowTooltip =
27+
enableTooltipOnOverflow && hasLabel && maxWidth > 0 && WoxTextMeasureUtil.isTextOverflow(context: context, text: label, style: resolvedStyle, maxWidth: maxWidth);
28+
final child = SizedBox(width: double.infinity, child: textWidget);
29+
30+
if (!shouldShowTooltip) {
31+
return child;
32+
}
33+
34+
return WoxTooltip(message: label, child: child);
35+
},
36+
),
37+
);
38+
}
39+
}
Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:flutter/material.dart';
2-
import 'package:wox/components/wox_tooltip.dart';
3-
import 'package:wox/utils/colors.dart';
2+
import 'package:wox/components/wox_label.dart';
43

54
class WoxSettingFormField extends StatelessWidget {
65
final String label;
@@ -16,8 +15,8 @@ class WoxSettingFormField extends StatelessWidget {
1615
super.key,
1716
required this.label,
1817
required this.child,
18+
required this.labelWidth,
1919
this.tips,
20-
this.labelWidth = 160,
2120
this.labelGap = 20,
2221
this.bottomSpacing = 20,
2322
this.tipsTopSpacing = 2,
@@ -33,7 +32,7 @@ class WoxSettingFormField extends StatelessWidget {
3332
Row(
3433
crossAxisAlignment: rowCrossAxisAlignment,
3534
children: [
36-
Padding(padding: EdgeInsets.only(right: labelGap), child: SizedBox(width: labelWidth, child: _SettingLabelWithTooltip(label: label))),
35+
Padding(padding: EdgeInsets.only(right: labelGap), child: WoxLabel(label: label, width: labelWidth)),
3736
Flexible(child: Align(alignment: Alignment.centerLeft, child: child)),
3837
],
3938
),
@@ -47,45 +46,3 @@ class WoxSettingFormField extends StatelessWidget {
4746
);
4847
}
4948
}
50-
51-
class _SettingLabelWithTooltip extends StatelessWidget {
52-
final String label;
53-
54-
const _SettingLabelWithTooltip({required this.label});
55-
56-
bool _isTextOverflow({required BuildContext context, required String text, required TextStyle style, required double maxWidth}) {
57-
// Merge with DefaultTextStyle to match how the Text widget actually renders
58-
// (Text widget inherits letterSpacing, fontFamily, etc. from the theme)
59-
final resolvedStyle = DefaultTextStyle.of(context).style.merge(style);
60-
final textPainter = TextPainter(
61-
text: TextSpan(text: text, style: resolvedStyle),
62-
textDirection: Directionality.of(context),
63-
textScaler: MediaQuery.textScalerOf(context),
64-
locale: Localizations.maybeLocaleOf(context),
65-
maxLines: 1,
66-
)..layout(minWidth: 0, maxWidth: double.infinity);
67-
68-
return textPainter.width > maxWidth;
69-
}
70-
71-
@override
72-
Widget build(BuildContext context) {
73-
final textStyle = TextStyle(color: getThemeTextColor(), fontSize: 13);
74-
final textWidget = Text(label, textAlign: TextAlign.right, style: textStyle, maxLines: 1, overflow: TextOverflow.ellipsis);
75-
76-
return LayoutBuilder(
77-
builder: (context, constraints) {
78-
final maxWidth = constraints.maxWidth;
79-
final hasLabel = label.trim().isNotEmpty;
80-
final shouldShowTooltip = hasLabel && maxWidth > 0 && _isTextOverflow(context: context, text: label, style: textStyle, maxWidth: maxWidth);
81-
final child = SizedBox(width: double.infinity, child: textWidget);
82-
83-
if (!shouldShowTooltip) {
84-
return child;
85-
}
86-
87-
return WoxTooltip(message: label, child: child);
88-
},
89-
);
90-
}
91-
}

0 commit comments

Comments
 (0)