@@ -2,6 +2,7 @@ import { FloatingButtonWindow } from './FloatingButtonWindow'
22import { FloatingButtonConfig , FloatingButtonState , DEFAULT_FLOATING_BUTTON_CONFIG } from './types'
33import {
44 buildFloatingWidgetSnapshot ,
5+ getPeekedCollapsedBounds ,
56 getWidgetSizeForSnapshot ,
67 repositionWidgetForResize ,
78 snapWidgetBoundsToEdge ,
@@ -23,6 +24,9 @@ const EMPTY_SNAPSHOT: FloatingWidgetSnapshot = {
2324
2425const WIDGET_LAYOUT_ANIMATION_DURATION_MS = 360
2526const WIDGET_LAYOUT_ANIMATION_INTERVAL_MS = 16
27+ const COLLAPSE_REVEAL_LOCK_MS = WIDGET_LAYOUT_ANIMATION_DURATION_MS + 120
28+ const COLLAPSED_WIDGET_INACTIVE_OPACITY = 0.5
29+ const ACTIVE_WIDGET_OPACITY = 1
2630
2731type DragRuntimeState = {
2832 startX : number
@@ -39,7 +43,10 @@ export class FloatingButtonPresenter {
3943 private configPresenter : IConfigPresenter
4044 private snapshot : FloatingWidgetSnapshot = { ...EMPTY_SNAPSHOT }
4145 private layoutAnimationTimer : ReturnType < typeof setInterval > | null = null
46+ private collapseRevealTimer : ReturnType < typeof setTimeout > | null = null
4247 private isDragging = false
48+ private isHovered = false
49+ private collapseRevealLock = false
4350 private pendingLayoutSync = false
4451
4552 constructor ( configPresenter : IConfigPresenter ) {
@@ -80,6 +87,8 @@ export class FloatingButtonPresenter {
8087 this . config . enabled = false
8188 this . snapshot = { ...EMPTY_SNAPSHOT }
8289 this . isDragging = false
90+ this . isHovered = false
91+ this . clearCollapseRevealLock ( )
8392 this . pendingLayoutSync = false
8493 this . stopLayoutAnimation ( )
8594
@@ -88,6 +97,7 @@ export class FloatingButtonPresenter {
8897 ipcMain . removeHandler ( FLOATING_BUTTON_EVENTS . THEME_REQUEST )
8998 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . CLICKED )
9099 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . RIGHT_CLICKED )
100+ ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . HOVER_STATE_CHANGED )
91101 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . TOGGLE_EXPANDED )
92102 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . SET_EXPANDED )
93103 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . OPEN_SESSION )
@@ -111,10 +121,10 @@ export class FloatingButtonPresenter {
111121 this . config . enabled = true
112122
113123 if ( this . floatingWindow ) {
114- this . floatingWindow . show ( )
115124 await this . refreshWidgetState ( )
116125 this . refreshLanguage ( )
117126 await this . refreshTheme ( )
127+ this . floatingWindow . show ( )
118128 return
119129 }
120130
@@ -190,10 +200,10 @@ export class FloatingButtonPresenter {
190200 await this . floatingWindow . create ( )
191201 }
192202
193- this . floatingWindow . show ( )
194203 await this . refreshWidgetState ( )
195204 this . refreshLanguage ( )
196205 await this . refreshTheme ( )
206+ this . floatingWindow . show ( )
197207 }
198208
199209 private registerIpcHandlers ( ) : void {
@@ -202,6 +212,7 @@ export class FloatingButtonPresenter {
202212 ipcMain . removeHandler ( FLOATING_BUTTON_EVENTS . THEME_REQUEST )
203213 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . CLICKED )
204214 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . RIGHT_CLICKED )
215+ ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . HOVER_STATE_CHANGED )
205216 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . TOGGLE_EXPANDED )
206217 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . SET_EXPANDED )
207218 ipcMain . removeAllListeners ( FLOATING_BUTTON_EVENTS . OPEN_SESSION )
@@ -234,6 +245,10 @@ export class FloatingButtonPresenter {
234245 this . showContextMenu ( )
235246 } )
236247
248+ ipcMain . on ( FLOATING_BUTTON_EVENTS . HOVER_STATE_CHANGED , ( _event , hovering : boolean ) => {
249+ this . setHovering ( Boolean ( hovering ) )
250+ } )
251+
237252 ipcMain . on ( FLOATING_BUTTON_EVENTS . TOGGLE_EXPANDED , ( ) => {
238253 this . toggleExpanded ( )
239254 } )
@@ -257,9 +272,11 @@ export class FloatingButtonPresenter {
257272 }
258273
259274 this . stopLayoutAnimation ( )
275+ this . clearCollapseRevealLock ( )
276+ this . isDragging = true
260277 const stableBounds = this . getSnapshotBounds ( bounds )
261278 this . floatingWindow . setBounds ( stableBounds )
262- this . isDragging = true
279+ this . floatingWindow . setOpacity ( this . resolveWindowOpacity ( ) )
263280
264281 dragState = {
265282 startX : x ,
@@ -313,7 +330,12 @@ export class FloatingButtonPresenter {
313330 this . floatingWindow . setBounds ( snapped )
314331 this . isDragging = false
315332 dragState = null
333+ this . floatingWindow . setOpacity ( this . resolveWindowOpacity ( ) )
334+ const hadPendingLayoutSync = this . pendingLayoutSync
316335 this . flushPendingLayoutSync ( )
336+ if ( ! hadPendingLayoutSync ) {
337+ this . applyWindowLayout ( )
338+ }
317339 } )
318340 }
319341
@@ -322,10 +344,20 @@ export class FloatingButtonPresenter {
322344 return
323345 }
324346
347+ const wasExpanded = this . snapshot . expanded
348+ if ( expanded ) {
349+ this . clearCollapseRevealLock ( )
350+ }
351+
325352 this . snapshot = {
326353 ...this . snapshot ,
327354 expanded
328355 }
356+
357+ if ( wasExpanded && ! expanded ) {
358+ this . engageCollapseRevealLock ( )
359+ }
360+
329361 this . applyWindowLayout ( true )
330362 this . pushSnapshotToRenderer ( )
331363 }
@@ -334,6 +366,20 @@ export class FloatingButtonPresenter {
334366 this . setExpanded ( ! this . snapshot . expanded )
335367 }
336368
369+ private setHovering ( hovering : boolean ) : void {
370+ if ( this . isHovered === hovering ) {
371+ return
372+ }
373+
374+ this . isHovered = hovering
375+
376+ if ( ! this . snapshot . expanded && this . collapseRevealLock ) {
377+ return
378+ }
379+
380+ this . applyWindowLayout ( true )
381+ }
382+
337383 private applyWindowLayout ( animate = false ) : void {
338384 if ( ! this . floatingWindow ?. exists ( ) ) {
339385 return
@@ -350,6 +396,7 @@ export class FloatingButtonPresenter {
350396 }
351397
352398 const nextBounds = this . getSnapshotBounds ( bounds )
399+ this . floatingWindow . setOpacity ( this . resolveWindowOpacity ( ) )
353400
354401 if ( ! animate || this . areBoundsEqual ( bounds , nextBounds ) ) {
355402 this . stopLayoutAnimation ( )
@@ -411,6 +458,29 @@ export class FloatingButtonPresenter {
411458 }
412459 }
413460
461+ private clearCollapseRevealLock ( ) : void {
462+ this . collapseRevealLock = false
463+
464+ if ( this . collapseRevealTimer ) {
465+ clearTimeout ( this . collapseRevealTimer )
466+ this . collapseRevealTimer = null
467+ }
468+ }
469+
470+ private engageCollapseRevealLock ( ) : void {
471+ this . collapseRevealLock = true
472+
473+ if ( this . collapseRevealTimer ) {
474+ clearTimeout ( this . collapseRevealTimer )
475+ }
476+
477+ this . collapseRevealTimer = setTimeout ( ( ) => {
478+ this . collapseRevealTimer = null
479+ this . collapseRevealLock = false
480+ this . applyWindowLayout ( true )
481+ } , COLLAPSE_REVEAL_LOCK_MS )
482+ }
483+
414484 private flushPendingLayoutSync ( ) : void {
415485 if ( ! this . pendingLayoutSync ) {
416486 return
@@ -426,12 +496,32 @@ export class FloatingButtonPresenter {
426496 }
427497
428498 const currentDisplay = screen . getDisplayMatching ( bounds )
429- return repositionWidgetForResize (
499+ const resizedBounds = repositionWidgetForResize (
430500 bounds ,
431501 getWidgetSizeForSnapshot ( this . snapshot ) ,
432502 currentDisplay . workArea ,
433503 this . floatingWindow . getDockSide ( )
434504 )
505+
506+ if ( ! this . snapshot . expanded && ! this . shouldRevealCollapsedWidget ( ) ) {
507+ return getPeekedCollapsedBounds (
508+ resizedBounds ,
509+ currentDisplay . workArea ,
510+ this . floatingWindow . getDockSide ( )
511+ )
512+ }
513+
514+ return resizedBounds
515+ }
516+
517+ private shouldRevealCollapsedWidget ( ) : boolean {
518+ return this . snapshot . expanded || this . isHovered || this . isDragging || this . collapseRevealLock
519+ }
520+
521+ private resolveWindowOpacity ( ) : number {
522+ return this . shouldRevealCollapsedWidget ( )
523+ ? ACTIVE_WIDGET_OPACITY
524+ : COLLAPSED_WIDGET_INACTIVE_OPACITY
435525 }
436526
437527 private easeInOutCubic ( progress : number ) : number {
0 commit comments