Loading libs/WindowManager/Shell/res/values/dimen.xml +9 −0 Original line number Diff line number Diff line Loading @@ -618,6 +618,15 @@ <!-- The vertical inset to apply to the app chip's ripple drawable --> <dimen name="desktop_mode_header_app_chip_ripple_inset_vertical">4dp</dimen> <!-- The corner radius of the windowing actions pill buttons's ripple drawable --> <dimen name="desktop_mode_handle_menu_windowing_action_ripple_radius">24dp</dimen> <!-- The horizontal/vertical inset to apply to the ripple drawable effect of windowing actions pill central buttons --> <dimen name="desktop_mode_handle_menu_windowing_action_ripple_inset_base">2dp</dimen> <!-- The horizontal/vertical vertical inset to apply to the ripple drawable effect of windowing actions pill edge buttons --> <dimen name="desktop_mode_handle_menu_windowing_action_ripple_inset_shift">4dp</dimen> <!-- The corner radius of the minimize button's ripple drawable --> <dimen name="desktop_mode_header_minimize_ripple_radius">18dp</dimen> <!-- The vertical inset to apply to the minimize button's ripple drawable --> Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +74 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit import androidx.core.view.isGone import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.bubbles.ContextUtils.isRtl import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper Loading @@ -60,6 +61,8 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewCo import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.calculateMenuPosition import com.android.wm.shell.windowdecor.common.DrawableInsets import com.android.wm.shell.windowdecor.common.createRippleDrawable import com.android.wm.shell.windowdecor.extension.isFullscreen import com.android.wm.shell.windowdecor.extension.isMultiWindow import com.android.wm.shell.windowdecor.extension.isPinned Loading @@ -71,6 +74,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * Handle menu opened when the appropriate button is clicked on. * Loading Loading @@ -467,6 +471,33 @@ class HandleMenu( val rootView = LayoutInflater.from(context) .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View private val windowingButtonRippleRadius = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_handle_menu_windowing_action_ripple_radius) private val windowingButtonDrawableInsets = DrawableInsets( vertical = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base), horizontal = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base) ) private val windowingButtonDrawableInsetsLeft = DrawableInsets( vertical = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base), horizontalLeft = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_shift), ) private val windowingButtonDrawableInsetsRight = DrawableInsets( vertical = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base), horizontalRight = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_shift) ) // App Info Pill. private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill) private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>( Loading Loading @@ -708,6 +739,49 @@ class HandleMenu( desktopBtn.isSelected = taskInfo.isFreeform desktopBtn.isEnabled = !taskInfo.isFreeform desktopBtn.imageTintList = style.windowingButtonColor val startInsets = if (context.isRtl) { windowingButtonDrawableInsetsRight } else { windowingButtonDrawableInsetsLeft } val endInsets = if (context.isRtl) { windowingButtonDrawableInsetsLeft } else { windowingButtonDrawableInsetsRight } fullscreenBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = startInsets ) } splitscreenBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = windowingButtonDrawableInsets ) } floatingBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = windowingButtonDrawableInsets ) } desktopBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = endInsets ) } } private fun bindMoreActionsPill(style: MenuStyle) { Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.windowdecor.common import android.annotation.ColorInt import android.graphics.Color import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RoundRectShape import com.android.wm.shell.windowdecor.common.OPACITY_11 import com.android.wm.shell.windowdecor.common.OPACITY_15 import android.content.res.ColorStateList /** * Represents drawable insets, specifying the number of pixels to inset a drawable from its bounds. */ data class DrawableInsets(val l: Int, val t: Int, val r: Int, val b: Int) { constructor(vertical: Int = 0, horizontal: Int = 0) : this(horizontal, vertical, horizontal, vertical) constructor(vertical: Int = 0, horizontalLeft: Int = 0, horizontalRight: Int = 0) : this(horizontalLeft, vertical, horizontalRight, vertical) } /** * Replaces the alpha component of a color with the given alpha value. */ @ColorInt fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int { return Color.argb( alpha, Color.red(color), Color.green(color), Color.blue(color) ) } /** * Creates a RippleDrawable with specified color, corner radius, and insets. */ fun createRippleDrawable( @ColorInt color: Int, cornerRadius: Int, drawableInsets: DrawableInsets, ): RippleDrawable { return RippleDrawable( ColorStateList( arrayOf( intArrayOf(android.R.attr.state_hovered), intArrayOf(android.R.attr.state_pressed), intArrayOf(), ), intArrayOf( replaceColorAlpha(color, OPACITY_11), replaceColorAlpha(color, OPACITY_15), Color.TRANSPARENT, ) ), null /* content */, LayerDrawable(arrayOf( ShapeDrawable().apply { shape = RoundRectShape( FloatArray(8) { cornerRadius.toFloat() }, null /* inset */, null /* innerRadii */ ) paint.color = Color.WHITE } )).apply { require(numberOfLayers == 1) { "Must only contain one layer" } setLayerInset(0 /* index */, drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b) } ) } libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +2 −51 Original line number Diff line number Diff line Loading @@ -61,6 +61,8 @@ import com.android.wm.shell.windowdecor.common.OPACITY_15 import com.android.wm.shell.windowdecor.common.OPACITY_55 import com.android.wm.shell.windowdecor.common.OPACITY_65 import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.common.DrawableInsets import com.android.wm.shell.windowdecor.common.createRippleDrawable import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance Loading Loading @@ -635,61 +637,10 @@ class AppHeaderViewHolder( ) } @ColorInt private fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int { return Color.argb( alpha, Color.red(color), Color.green(color), Color.blue(color) ) } private fun createRippleDrawable( @ColorInt color: Int, cornerRadius: Int, drawableInsets: DrawableInsets, ): RippleDrawable { return RippleDrawable( ColorStateList( arrayOf( intArrayOf(android.R.attr.state_hovered), intArrayOf(android.R.attr.state_pressed), intArrayOf(), ), intArrayOf( replaceColorAlpha(color, OPACITY_11), replaceColorAlpha(color, OPACITY_15), Color.TRANSPARENT ) ), null /* content */, LayerDrawable(arrayOf( ShapeDrawable().apply { shape = RoundRectShape( FloatArray(8) { cornerRadius.toFloat() }, null /* inset */, null /* innerRadii */ ) paint.color = Color.WHITE } )).apply { require(numberOfLayers == 1) { "Must only contain one layer" } setLayerInset(0 /* index */, drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b) } ) } private enum class SizeToggleDirection { MAXIMIZE, RESTORE } private data class DrawableInsets(val l: Int, val t: Int, val r: Int, val b: Int) { constructor(vertical: Int = 0, horizontal: Int = 0) : this(horizontal, vertical, horizontal, vertical) } private data class Header( val type: Type, val appTheme: Theme, Loading Loading
libs/WindowManager/Shell/res/values/dimen.xml +9 −0 Original line number Diff line number Diff line Loading @@ -618,6 +618,15 @@ <!-- The vertical inset to apply to the app chip's ripple drawable --> <dimen name="desktop_mode_header_app_chip_ripple_inset_vertical">4dp</dimen> <!-- The corner radius of the windowing actions pill buttons's ripple drawable --> <dimen name="desktop_mode_handle_menu_windowing_action_ripple_radius">24dp</dimen> <!-- The horizontal/vertical inset to apply to the ripple drawable effect of windowing actions pill central buttons --> <dimen name="desktop_mode_handle_menu_windowing_action_ripple_inset_base">2dp</dimen> <!-- The horizontal/vertical vertical inset to apply to the ripple drawable effect of windowing actions pill edge buttons --> <dimen name="desktop_mode_handle_menu_windowing_action_ripple_inset_shift">4dp</dimen> <!-- The corner radius of the minimize button's ripple drawable --> <dimen name="desktop_mode_header_minimize_ripple_radius">18dp</dimen> <!-- The vertical inset to apply to the minimize button's ripple drawable --> Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +74 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit import androidx.core.view.isGone import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.bubbles.ContextUtils.isRtl import com.android.wm.shell.shared.annotations.ShellBackgroundThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper Loading @@ -60,6 +61,8 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewCo import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.calculateMenuPosition import com.android.wm.shell.windowdecor.common.DrawableInsets import com.android.wm.shell.windowdecor.common.createRippleDrawable import com.android.wm.shell.windowdecor.extension.isFullscreen import com.android.wm.shell.windowdecor.extension.isMultiWindow import com.android.wm.shell.windowdecor.extension.isPinned Loading @@ -71,6 +74,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** * Handle menu opened when the appropriate button is clicked on. * Loading Loading @@ -467,6 +471,33 @@ class HandleMenu( val rootView = LayoutInflater.from(context) .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View private val windowingButtonRippleRadius = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_handle_menu_windowing_action_ripple_radius) private val windowingButtonDrawableInsets = DrawableInsets( vertical = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base), horizontal = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base) ) private val windowingButtonDrawableInsetsLeft = DrawableInsets( vertical = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base), horizontalLeft = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_shift), ) private val windowingButtonDrawableInsetsRight = DrawableInsets( vertical = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_base), horizontalRight = context.resources .getDimensionPixelSize( R.dimen.desktop_mode_handle_menu_windowing_action_ripple_inset_shift) ) // App Info Pill. private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill) private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>( Loading Loading @@ -708,6 +739,49 @@ class HandleMenu( desktopBtn.isSelected = taskInfo.isFreeform desktopBtn.isEnabled = !taskInfo.isFreeform desktopBtn.imageTintList = style.windowingButtonColor val startInsets = if (context.isRtl) { windowingButtonDrawableInsetsRight } else { windowingButtonDrawableInsetsLeft } val endInsets = if (context.isRtl) { windowingButtonDrawableInsetsLeft } else { windowingButtonDrawableInsetsRight } fullscreenBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = startInsets ) } splitscreenBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = windowingButtonDrawableInsets ) } floatingBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = windowingButtonDrawableInsets ) } desktopBtn.apply { background = createRippleDrawable( color = style.textColor, cornerRadius = windowingButtonRippleRadius, drawableInsets = endInsets ) } } private fun bindMoreActionsPill(style: MenuStyle) { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.windowdecor.common import android.annotation.ColorInt import android.graphics.Color import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RoundRectShape import com.android.wm.shell.windowdecor.common.OPACITY_11 import com.android.wm.shell.windowdecor.common.OPACITY_15 import android.content.res.ColorStateList /** * Represents drawable insets, specifying the number of pixels to inset a drawable from its bounds. */ data class DrawableInsets(val l: Int, val t: Int, val r: Int, val b: Int) { constructor(vertical: Int = 0, horizontal: Int = 0) : this(horizontal, vertical, horizontal, vertical) constructor(vertical: Int = 0, horizontalLeft: Int = 0, horizontalRight: Int = 0) : this(horizontalLeft, vertical, horizontalRight, vertical) } /** * Replaces the alpha component of a color with the given alpha value. */ @ColorInt fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int { return Color.argb( alpha, Color.red(color), Color.green(color), Color.blue(color) ) } /** * Creates a RippleDrawable with specified color, corner radius, and insets. */ fun createRippleDrawable( @ColorInt color: Int, cornerRadius: Int, drawableInsets: DrawableInsets, ): RippleDrawable { return RippleDrawable( ColorStateList( arrayOf( intArrayOf(android.R.attr.state_hovered), intArrayOf(android.R.attr.state_pressed), intArrayOf(), ), intArrayOf( replaceColorAlpha(color, OPACITY_11), replaceColorAlpha(color, OPACITY_15), Color.TRANSPARENT, ) ), null /* content */, LayerDrawable(arrayOf( ShapeDrawable().apply { shape = RoundRectShape( FloatArray(8) { cornerRadius.toFloat() }, null /* inset */, null /* innerRadii */ ) paint.color = Color.WHITE } )).apply { require(numberOfLayers == 1) { "Must only contain one layer" } setLayerInset(0 /* index */, drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b) } ) }
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +2 −51 Original line number Diff line number Diff line Loading @@ -61,6 +61,8 @@ import com.android.wm.shell.windowdecor.common.OPACITY_15 import com.android.wm.shell.windowdecor.common.OPACITY_55 import com.android.wm.shell.windowdecor.common.OPACITY_65 import com.android.wm.shell.windowdecor.common.Theme import com.android.wm.shell.windowdecor.common.DrawableInsets import com.android.wm.shell.windowdecor.common.createRippleDrawable import com.android.wm.shell.windowdecor.extension.isLightCaptionBarAppearance import com.android.wm.shell.windowdecor.extension.isTransparentCaptionBarAppearance Loading Loading @@ -635,61 +637,10 @@ class AppHeaderViewHolder( ) } @ColorInt private fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int { return Color.argb( alpha, Color.red(color), Color.green(color), Color.blue(color) ) } private fun createRippleDrawable( @ColorInt color: Int, cornerRadius: Int, drawableInsets: DrawableInsets, ): RippleDrawable { return RippleDrawable( ColorStateList( arrayOf( intArrayOf(android.R.attr.state_hovered), intArrayOf(android.R.attr.state_pressed), intArrayOf(), ), intArrayOf( replaceColorAlpha(color, OPACITY_11), replaceColorAlpha(color, OPACITY_15), Color.TRANSPARENT ) ), null /* content */, LayerDrawable(arrayOf( ShapeDrawable().apply { shape = RoundRectShape( FloatArray(8) { cornerRadius.toFloat() }, null /* inset */, null /* innerRadii */ ) paint.color = Color.WHITE } )).apply { require(numberOfLayers == 1) { "Must only contain one layer" } setLayerInset(0 /* index */, drawableInsets.l, drawableInsets.t, drawableInsets.r, drawableInsets.b) } ) } private enum class SizeToggleDirection { MAXIMIZE, RESTORE } private data class DrawableInsets(val l: Int, val t: Int, val r: Int, val b: Int) { constructor(vertical: Int = 0, horizontal: Int = 0) : this(horizontal, vertical, horizontal, vertical) } private data class Header( val type: Type, val appTheme: Theme, Loading