Skip to content

Commit 6f0b2a2

Browse files
committed
Refactor overlay components and improve tooltip functionality
- Removed the Windows timer overlay implementation as it is no longer needed. - Simplified tooltip code by eliminating unused font size functions and streamlining the Show function. - Added tests for image overlay title extraction and overlay bounds calculations. - Introduced runtime text overlay with enhanced features including auto-close functionality and copy button. - Implemented tests for runtime text overlay behaviors, including size adjustments and tooltip anchoring. - Added tests for timer overlay size calculations to ensure proper expansion and text measurement.
1 parent 8455985 commit 6f0b2a2

61 files changed

Lines changed: 1861 additions & 9290 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

wox.core/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"wox/util/clipboard"
3232
"wox/util/imagecache"
3333
"wox/util/mainthread"
34+
"wox/util/overlay"
3435
"wox/util/permission"
3536
"wox/util/selection"
3637

@@ -377,6 +378,7 @@ func run() {
377378
}
378379
coreServices := ui.NewCoreServices()
379380
embeddedGoUIApp = golauncher.New(util.IsDev(), coreServices)
381+
overlay.SetWindowManager(embeddedGoUIApp.WindowManager())
380382
coreServices.AttachView(embeddedGoUIApp)
381383
// Wire text-field copy/cut/paste to the cross-platform clipboard backend.
382384
golauncher.SetClipboardProvider(golauncher.NewUtilClipboardProvider())

wox.core/plugin/system/ai_command.go

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
)
2727

2828
var aiCommandIcon = common.PluginAICommandIcon
29+
var aiCommandTitleIcon, _ = aiCommandIcon.ToImage()
2930

3031
var (
3132
// Native overlays require the app main thread. Keeping the calls replaceable
@@ -373,37 +374,15 @@ func buildAICommandLoadingOptions(name string, position mouse.Point, message str
373374
Anchor: overlay.AnchorTopLeft,
374375
OffsetX: position.X + aiCommandLoadingOverlayOffsetX,
375376
OffsetY: position.Y + aiCommandLoadingOverlayOffsetY,
376-
Width: estimateAICommandLoadingOverlayWidth(message),
377+
MinWidth: aiCommandLoadingOverlayMinWidth,
378+
MaxWidth: aiCommandLoadingOverlayMaxWidth,
377379
},
378380
Message: message,
379381
Loading: true,
380-
FontSize: 12,
381382
IconSize: aiCommandLoadingOverlayIconSize,
382383
}
383384
}
384385

385-
func estimateAICommandLoadingOverlayWidth(message string) float64 {
386-
// Bug fix: the old fixed width was sized for "AI". Localized labels such as
387-
// "Thinking..." need enough room after the spinner and padding are reserved,
388-
// otherwise the native text view wraps into unreadable fragments.
389-
textWidth := 0.0
390-
for _, r := range message {
391-
if r <= 0x7f {
392-
textWidth += 7
393-
} else {
394-
textWidth += 12
395-
}
396-
}
397-
width := 12 + float64(aiCommandLoadingOverlayIconSize) + 8 + textWidth + 12
398-
if width < aiCommandLoadingOverlayMinWidth {
399-
return aiCommandLoadingOverlayMinWidth
400-
}
401-
if width > aiCommandLoadingOverlayMaxWidth {
402-
return aiCommandLoadingOverlayMaxWidth
403-
}
404-
return width
405-
}
406-
407386
func (c *Plugin) showAICommandLoadingOverlay(ctx context.Context, name string) bool {
408387
position, ok := mouse.CurrentPosition()
409388
if !ok {
@@ -430,23 +409,26 @@ func (c *Plugin) currentAICommandOverlayPosition(ctx context.Context) mouse.Poin
430409
return position
431410
}
432411

433-
func (c *Plugin) showAICommandResultOverlay(ctx context.Context, name string, position *mouse.Point, streamResult common.ChatStreamData) {
412+
func (c *Plugin) showAICommandResultOverlay(ctx context.Context, name string, title string, position *mouse.Point, streamResult common.ChatStreamData) {
434413
message := formatAICommandResultOverlayMessage(ctx, streamResult)
435414
copyText := strings.TrimSpace(streamResult.Data)
436415
opts := textoverlay.Options{
437416
Window: overlay.WindowOptions{
438417
ID: name,
439418
Topmost: true,
419+
TakeFocus: true,
420+
Width: aiCommandResultOverlayMaxWidth,
440421
MinWidth: aiCommandResultOverlayMinWidth,
441422
MaxWidth: aiCommandResultOverlayMaxWidth,
442423
MaxHeight: aiCommandResultOverlayMaxHeight,
443424
Movable: true,
444425
CloseOnEscape: true,
445426
},
427+
Title: title,
428+
TitleIcon: aiCommandTitleIcon,
446429
Closable: true,
447430
Message: message,
448431
Loading: streamResult.Status == common.ChatStreamStatusStreaming && copyText == "",
449-
FontSize: 12,
450432
IconSize: aiCommandLoadingOverlayIconSize,
451433
FollowScroll: true,
452434
}
@@ -649,7 +631,7 @@ func (c *Plugin) buildAICommandActions(ctx context.Context, command commandSetti
649631
util.Go(ctx, "ai command run and show", func() {
650632
overlayName := fmt.Sprintf("ai_command_run_and_show_result_%s", actionContext.ResultId)
651633
position := c.currentAICommandOverlayPosition(ctx)
652-
c.showAICommandResultOverlay(ctx, overlayName, &position, common.ChatStreamData{Status: common.ChatStreamStatusStreaming})
634+
c.showAICommandResultOverlay(ctx, overlayName, command.Name, &position, common.ChatStreamData{Status: common.ChatStreamStatusStreaming})
653635
lastOverlayUpdateAt := int64(0)
654636
lastOverlayMessage := ""
655637

@@ -664,7 +646,7 @@ func (c *Plugin) buildAICommandActions(ctx context.Context, command commandSetti
664646
lastOverlayUpdateAt = now
665647
lastOverlayMessage = message
666648
}
667-
c.showAICommandResultOverlay(ctx, overlayName, nil, streamResult)
649+
c.showAICommandResultOverlay(ctx, overlayName, command.Name, nil, streamResult)
668650
},
669651
})
670652
if final.Err != nil {

wox.core/plugin/system/dictation/dictation.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1637,7 +1637,6 @@ func (p *DictationPlugin) showActionResultOverlay(ctx context.Context, text stri
16371637
Window: window,
16381638
Closable: true,
16391639
Message: text,
1640-
FontSize: 14,
16411640
FollowScroll: true,
16421641
ShowCopyButton: true,
16431642
CopyButtonTooltip: i18n.GetI18nManager().TranslateWox(ctx, "plugin_dictation_action_copy"),

wox.core/plugin/system/explorer/explorer.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,10 +1106,8 @@ func (c *ExplorerPlugin) startOverlayListener(ctx context.Context) {
11061106
return
11071107
}
11081108
messageKey := "plugin_explorer_hint_message_dialog"
1109-
fontSize := 10.0
11101109
if util.IsMacOS() {
11111110
messageKey = "plugin_explorer_hint_message_dialog_macos"
1112-
fontSize = 12
11131111
}
11141112

11151113
title := window.GetWindowNameByPid(pid)
@@ -1125,8 +1123,7 @@ func (c *ExplorerPlugin) startOverlayListener(ctx context.Context) {
11251123
StickyWindowId: dialogWindowId,
11261124
MaxWidth: 500,
11271125
},
1128-
Message: c.api.GetTranslation(localCtx, messageKey),
1129-
FontSize: fontSize,
1126+
Message: c.api.GetTranslation(localCtx, messageKey),
11301127
OnClick: func() bool {
11311128
clickCtx := context.WithValue(ctx, util.ContextKeyTraceId, uuid.NewString())
11321129
clickCtx = util.WithCoreSessionContext(clickCtx)

wox.core/plugin/system/screenshot.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,6 @@ func (p *ScreenshotPlugin) pinScreenshotToScreen(ctx context.Context, screenshot
843843
Width: width,
844844
Height: height,
845845
Movable: true,
846-
Shadow: true,
847846
CloseOnEscape: true,
848847
// Bug fix: Windows native overlays normally position screen overlays relative to the
849848
// primary work area. Screenshot selections are already desktop-absolute, so pinning must

wox.core/plugin/system/sys/sys.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,11 +479,10 @@ func (r *SysPlugin) buildDevCommands() []SysCommand {
479479
Icon: common.CPUProfileIcon,
480480
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
481481
img, _ := common.WoxIcon.ToImage()
482-
notifier.Notify(img, `This is a very long notification message to test the notification system in Wox.
483-
If you see this message, the notification system is working properly.
484-
You can customize the duration, appearance, and behavior of notifications as needed.
485-
Enjoy using Wox!
486-
`+time.Now().String())
482+
notifier.Notify(img, "This is a very long notification message to test the notification system in Wox.\n"+
483+
"If you see this message, the notification system is working properly.\n"+
484+
"You can customize the duration, appearance, and behavior of notifications as needed.\n"+
485+
"Enjoy using Wox!\n"+time.Now().String())
487486
},
488487
},
489488

wox.core/plugin/system/timer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -567,9 +567,9 @@ func (t *TimerPlugin) tick(ctx context.Context) {
567567
toRemove = append(toRemove, resultID)
568568
return true
569569
}
570-
if !t.pushTimerResultUpdate(ctx, resultID, timerID) {
571-
toRemove = append(toRemove, resultID)
572-
}
570+
// The first tick can race with the query result reaching the UI. Keep the
571+
// active timer tracked so a transient miss is retried on the next tick.
572+
t.pushTimerResultUpdate(ctx, resultID, timerID)
573573
return true
574574
})
575575
for _, resultID := range toRemove {

wox.core/plugin/system/timer_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,24 @@ import (
44
"context"
55
"testing"
66
"time"
7+
"wox/plugin"
8+
"wox/util"
79
)
810

11+
type timerUpdateTestAPI struct {
12+
plugin.API
13+
updates int
14+
}
15+
16+
func (a *timerUpdateTestAPI) IsVisible(context.Context) bool {
17+
return true
18+
}
19+
20+
func (a *timerUpdateTestAPI) UpdateResult(context.Context, plugin.UpdatableResult) bool {
21+
a.updates++
22+
return a.updates > 1
23+
}
24+
925
func TestParseTimerQuery(t *testing.T) {
1026
tests := []struct {
1127
input string
@@ -154,3 +170,23 @@ func TestTimerPersistedRoundTrip(t *testing.T) {
154170
t.Fatalf("deadline=%v want %v", restored.Deadline, original.Deadline)
155171
}
156172
}
173+
174+
func TestTimerTickRetriesTransientResultMiss(t *testing.T) {
175+
api := &timerUpdateTestAPI{}
176+
timer := &TimerPlugin{
177+
api: api,
178+
timers: map[string]*timerEntry{"t1": {ID: "t1", DurationLabel: "1m", Deadline: time.Now().Add(time.Minute)}},
179+
trackedResults: util.NewHashMap[string, string](),
180+
}
181+
timer.trackedResults.Store("t1", "t1")
182+
183+
timer.tick(context.Background())
184+
timer.tick(context.Background())
185+
186+
if api.updates != 2 {
187+
t.Fatalf("updates=%d want 2", api.updates)
188+
}
189+
if _, ok := timer.trackedResults.Load("t1"); !ok {
190+
t.Fatal("active timer should remain tracked after a transient update miss")
191+
}
192+
}

wox.core/plugin/system/window_manager/window_groups.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const (
3131
const (
3232
windowGroupLaunchPlaceholderPrefix = "window_group_launch_"
3333
windowGroupLaunchPlaceholderCornerRadius = 8.0
34-
windowGroupLaunchPlaceholderFontSize = 18.0
3534
)
3635

3736
const (
@@ -469,7 +468,6 @@ func (p *WindowManagerPlugin) showWindowGroupLaunchPlaceholder(ctx context.Conte
469468
Message: message,
470469
Loading: true,
471470
CenterContent: true,
472-
FontSize: windowGroupLaunchPlaceholderFontSize,
473471
})
474472
p.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("window manager showed launch placeholder: group=%s app=%s identity=%s overlay=%s rect=%+v", group.Id, placement.AppName, placement.Identity, name, placement.Rect))
475473
return name

wox.core/ui/launcher/app.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ func New(isDev bool, services contract.Services) *App {
227227
return app
228228
}
229229

230+
// WindowManager returns the process-local registry shared by launcher-owned secondary surfaces.
231+
func (a *App) WindowManager() *woxui.WindowManager {
232+
return a.windows
233+
}
234+
230235
// newApp builds isolated launcher state while sharing only process-wide window and message infrastructure.
231236
func newApp(isDev bool, services contract.Services, windows *woxui.WindowManager, instances *appInstanceRegistry, primary *App, isPrimary bool, instanceName string, windowID woxui.WindowID) *App {
232237
sessionID := newID()

0 commit comments

Comments
 (0)