@@ -649,40 +649,55 @@ fn draw_buttons(
649649
650650/// Pick the (background, icon, scale) visual triple for a button
651651/// based on its interaction state. Matches the canonical
652- /// `WindowControls.svelte` in `sdk/ui-kit` field-for-field:
652+ /// `WindowControls.svelte` shipped in `app-settings` (which is
653+ /// the visible reference the user compares against side-by-side
654+ /// with the compositor-rendered header) **field-for-field**:
653655///
654656/// ```css
655- /// .control-btn { opacity: 0.7 }
656- /// .control-btn:hover { opacity: 1; transform: scale(1.1);
657- /// background: color-mix(fg 10%, transparent ); }
658- /// .control-btn:active { transform: scale(0.9) }
659- /// .close-btn:hover { background: var(--destructive);
660- /// color: #ffffff }
657+ /// .control-btn { opacity: 0.7;
658+ /// transition: opacity var(--duration-fast)
659+ /// var(--easing-default ); }
660+ /// .control-btn:hover { opacity: 1; } /* NOTHING ELSE */
661+ /// .close-btn:hover { background: var(--destructive);
662+ /// color: #ffffff; }
661663/// ```
662664///
663- /// The `opacity` is applied to both bg alpha AND the icon colour
664- /// so the whole button dims together. When the parent window is
665- /// inactive we dim further (`BUTTON_IDLE_OPACITY_INACTIVE`) to make
666- /// focus state obvious — WindowControls.svelte doesn't handle this
667- /// case because it's rendered inside the app's own window where
668- /// focus state isn't ambiguous.
665+ /// Notably, the canonical decoration buttons have:
666+ /// * NO hover-bg tint on non-close buttons (only opacity changes).
667+ /// * NO scale on hover.
668+ /// * NO scale on press.
669+ ///
670+ /// An older variant of `WindowControls.svelte` in `desktop-shell`
671+ /// has scale(1.1) on hover, scale(0.9) on press, and a 10 %
672+ /// foreground bg-tint on hover — those were a brief experiment
673+ /// that never made it back into the canonical version. The
674+ /// compositor used to mirror that experimental variant; this
675+ /// function now matches the pared-back canonical look so the
676+ /// app-settings decorations and the compositor-rendered Kitty
677+ /// decorations animate identically.
678+ ///
679+ /// Inactive-window dimming (`BUTTON_IDLE_OPACITY_INACTIVE`) stays
680+ /// — that's a compositor-specific extension because
681+ /// WindowControls.svelte never lives outside its own focused
682+ /// app window.
669683fn button_visual (
670684 button : HeaderButton ,
671685 state : & HeaderVisualState ,
672686 theme : & LunarisTheme ,
673687) -> ( Rgba , Rgba , f32 ) {
674688 let is_close = button == HeaderButton :: Close ;
675689
676- let ( hovered, pressed ) = match state. interaction {
690+ let ( hovered, _pressed ) = match state. interaction {
677691 ButtonInteraction :: Hover ( b) if b == button => ( true , false ) ,
678692 ButtonInteraction :: Pressed ( b) if b == button => ( true , true ) ,
679693 _ => ( false , false ) ,
680694 } ;
681695
682- // Effective "button opacity" — WindowControls.svelte:
683- // idle → 0.7 (or lower when window unfocused)
684- // hover → 1.0 (on focused window)
685- // hover on unfocused window → bump back to focused-idle
696+ // Effective "button opacity":
697+ // activated + idle → 0.7 (matches WindowControls.svelte)
698+ // activated + hover → 1.0
699+ // inactive + idle → 0.4 (compositor-only extension)
700+ // inactive + hover → 0.7
686701 let button_opacity = if hovered {
687702 if state. activated { 1.0 } else { BUTTON_IDLE_OPACITY }
688703 } else if state. activated {
@@ -691,26 +706,14 @@ fn button_visual(
691706 BUTTON_IDLE_OPACITY_INACTIVE
692707 } ;
693708
694- // Background — pre-opacity. Close-hover has special treatment.
695- let bg_raw = if hovered {
696- if is_close {
697- // `.close-btn:hover { background: var(--destructive) }`
698- // — FULL OPAQUE destructive (no alpha mix). This is a
699- // clear "you are about to close" signal, not a soft
700- // tint. The old renderer mixed at 80 % alpha which
701- // came from a stale version of WindowHeader.svelte;
702- // the canonical WindowControls is full-opaque.
703- theme. error
704- } else {
705- // `.control-btn:hover { background:
706- // color-mix(in srgb, var(--foreground) 10%, transparent) }`
707- mix ( theme. fg_primary , TRANSPARENT , 0.10 )
708- }
709+ // Background — pre-opacity. Non-close hover stays transparent
710+ // (no tint). Close-hover lights up the full destructive
711+ // colour, no alpha attenuation other than `button_opacity`.
712+ let bg_raw = if hovered && is_close {
713+ theme. error
709714 } else {
710715 TRANSPARENT
711716 } ;
712- // Apply button_opacity to the BG alpha so the whole button
713- // dims uniformly with its icon.
714717 let mut bg = bg_raw;
715718 bg[ 3 ] *= button_opacity;
716719
@@ -719,19 +722,16 @@ fn button_visual(
719722 let icon_raw = if hovered && is_close {
720723 [ 1.0 , 1.0 , 1.0 , 1.0 ]
721724 } else {
722- // Use fg_primary at full RGB; opacity handles the dimming.
723725 theme. fg_primary
724726 } ;
725727 let mut icon_color = icon_raw;
726728 icon_color[ 3 ] *= button_opacity;
727729
728- let scale = if pressed {
729- 0.9
730- } else if hovered {
731- 1.1
732- } else {
733- 1.0
734- } ;
730+ // Scale = 1.0 always. The canonical `WindowControls.svelte`
731+ // does NOT animate scale; only opacity. Keep this as a triple
732+ // return so callers stay shape-stable if a future variant
733+ // wants to bring scale back.
734+ let scale = 1.0 ;
735735
736736 ( bg, icon_color, scale)
737737}
@@ -974,7 +974,9 @@ mod tests {
974974 assert ! ( ( bg[ 3 ] - 1.0 ) . abs( ) < 0.001 , "A should be full, got {}" , bg[ 3 ] ) ;
975975 // Icon pure white at full alpha on activated window.
976976 assert_eq ! ( icon, [ 1.0 , 1.0 , 1.0 , 1.0 ] ) ;
977- assert ! ( ( scale - 1.1 ) . abs( ) < 0.001 ) ;
977+ // Canonical app-settings WindowControls has NO scale on
978+ // hover or active. Stay at 1.0.
979+ assert ! ( ( scale - 1.0 ) . abs( ) < 0.001 , "no hover scale, got {}" , scale) ;
978980 }
979981
980982 #[ test]
@@ -997,14 +999,44 @@ mod tests {
997999 }
9981000
9991001 #[ test]
1000- fn button_visual_pressed_shrinks ( ) {
1001- let state = HeaderVisualState {
1002+ fn button_visual_no_scale_animation ( ) {
1003+ // Canonical WindowControls has no transform animation —
1004+ // verify all states stay at scale 1.0 so the compositor
1005+ // doesn't reintroduce the bouncy scale-on-press feel.
1006+ let theme = LunarisTheme :: lunaris_dark ( ) ;
1007+
1008+ let idle = stub_state ( 800 , true ) ;
1009+ let ( _, _, s_idle) = button_visual ( HeaderButton :: Minimize , & idle, & theme) ;
1010+ assert ! ( ( s_idle - 1.0 ) . abs( ) < 0.001 ) ;
1011+
1012+ let hover = HeaderVisualState {
1013+ interaction : ButtonInteraction :: Hover ( HeaderButton :: Minimize ) ,
1014+ ..idle. clone ( )
1015+ } ;
1016+ let ( _, _, s_hover) = button_visual ( HeaderButton :: Minimize , & hover, & theme) ;
1017+ assert ! ( ( s_hover - 1.0 ) . abs( ) < 0.001 ) ;
1018+
1019+ let pressed = HeaderVisualState {
10021020 interaction : ButtonInteraction :: Pressed ( HeaderButton :: Minimize ) ,
1021+ ..idle
1022+ } ;
1023+ let ( _, _, s_press) = button_visual ( HeaderButton :: Minimize , & pressed, & theme) ;
1024+ assert ! ( ( s_press - 1.0 ) . abs( ) < 0.001 ) ;
1025+ }
1026+
1027+ #[ test]
1028+ fn button_visual_no_hover_bg_for_non_close ( ) {
1029+ // Non-close hover keeps bg transparent — only opacity
1030+ // changes. This is the visible difference vs the older
1031+ // desktop-shell variant that mixed a 10 % foreground tint.
1032+ let theme = LunarisTheme :: lunaris_dark ( ) ;
1033+ let hover = HeaderVisualState {
1034+ interaction : ButtonInteraction :: Hover ( HeaderButton :: Minimize ) ,
10031035 ..stub_state ( 800 , true )
10041036 } ;
1005- let theme = LunarisTheme :: panda ( ) ;
1006- let ( _ , _ , scale ) = button_visual ( HeaderButton :: Minimize , & state , & theme ) ;
1007- assert ! ( ( scale - 0.9 ) . abs ( ) < 0.001 ) ;
1037+ let ( bg , _ , _ ) = button_visual ( HeaderButton :: Minimize , & hover , & theme ) ;
1038+ // Pre-multiplied alpha == 0 → background fully transparent.
1039+ assert ! ( bg [ 3 ] < 1e-5 , "non-close hover should not paint bg, got alpha {}" , bg [ 3 ] ) ;
10081040 }
10091041
10101042 #[ test]
0 commit comments