2025-02-09 23:19:41 +01:00
/ * *
* V - Shell ( Vertical Workspaces )
* appDisplay . js
*
* @ author GdH < G - dH @ github . com >
* @ copyright 2022 - 2024
* @ license GPL - 3.0
*
* /
'use strict' ;
import Clutter from 'gi://Clutter' ;
import Gio from 'gi://Gio' ;
import GLib from 'gi://GLib' ;
import GObject from 'gi://GObject' ;
import Graphene from 'gi://Graphene' ;
import Meta from 'gi://Meta' ;
import Pango from 'gi://Pango' ;
import Shell from 'gi://Shell' ;
import St from 'gi://St' ;
import * as Main from 'resource:///org/gnome/shell/ui/main.js' ;
import * as AppDisplay from 'resource:///org/gnome/shell/ui/appDisplay.js' ;
import * as DND from 'resource:///org/gnome/shell/ui/dnd.js' ;
import * as PageIndicators from 'resource:///org/gnome/shell/ui/pageIndicators.js' ;
import { IconSize } from './iconGrid.js' ;
let Me ;
let opt ;
// gettext
let _ ;
let _appDisplay ;
let _timeouts ;
const APP _ICON _TITLE _EXPAND _TIME = 200 ;
const APP _ICON _TITLE _COLLAPSE _TIME = 100 ;
const shellVersion46 = ! Clutter . Container ; // Container has been removed in 46
function _getCategories ( info ) {
let categoriesStr = info . get _categories ( ) ;
if ( ! categoriesStr )
return [ ] ;
return categoriesStr . split ( ';' ) ;
}
function _listsIntersect ( a , b ) {
for ( let itemA of a ) {
if ( b . includes ( itemA ) )
return true ;
}
return false ;
}
export const AppDisplayModule = class {
constructor ( me ) {
Me = me ;
opt = Me . opt ;
_ = Me . gettext ;
_appDisplay = Main . overview . _overview . controls . _appDisplay ;
this . _firstActivation = true ;
this . moduleEnabled = false ;
this . _overrides = null ;
this . _appSystemStateConId = 0 ;
this . _appGridLayoutConId = 0 ;
this . _origAppViewItemAcceptDrop = null ;
this . _updateFolderIcons = 0 ;
2025-02-12 16:21:01 +01:00
// By default appDisplay.name (which can be used to address styling) is not set
// In GS 46+ we need to adapt the appDisplay style even if the appDisplay module is disabled,
// to allow the use of wallpaper in the overview
if ( shellVersion46 )
Main . overview . _overview . controls . _appDisplay . name = 'app-display' ;
2025-02-09 23:19:41 +01:00
}
cleanGlobals ( ) {
Me = null ;
opt = null ;
_ = null ;
_appDisplay = null ;
2025-02-12 16:21:01 +01:00
Main . overview . _overview . controls . _appDisplay . name = null ;
2025-02-09 23:19:41 +01:00
}
update ( reset ) {
this . _removeTimeouts ( ) ;
this . moduleEnabled = opt . get ( 'appDisplayModule' ) ;
const conflict = false ;
reset = reset || ! this . moduleEnabled || conflict ;
// don't touch the original code if module disabled
if ( reset && ! this . _firstActivation ) {
this . _disableModule ( ) ;
this . moduleEnabled = false ;
} else if ( ! reset ) {
this . _firstActivation = false ;
this . _activateModule ( ) ;
}
if ( reset && this . _firstActivation ) {
this . moduleEnabled = false ;
console . debug ( ' AppDisplayModule - Keeping untouched' ) ;
}
}
_activateModule ( ) {
Me . Modules . iconGridModule . update ( ) ;
if ( ! this . _overrides )
this . _overrides = new Me . Util . Overrides ( ) ;
_timeouts = { } ;
this . _applyOverrides ( ) ;
this . _updateAppDisplay ( ) ;
console . debug ( ' AppDisplayModule - Activated' ) ;
}
_disableModule ( ) {
Me . Modules . iconGridModule . update ( true ) ;
if ( this . _overrides )
this . _overrides . removeAll ( ) ;
this . _overrides = null ;
const reset = true ;
this . _updateAppDisplay ( reset ) ;
this . _restoreOverviewGroup ( ) ;
console . debug ( ' AppDisplayModule - Disabled' ) ;
}
_removeTimeouts ( ) {
if ( _timeouts ) {
Object . values ( _timeouts ) . forEach ( t => {
if ( t )
GLib . source _remove ( t ) ;
} ) ;
_timeouts = null ;
}
}
_applyOverrides ( ) {
// Common/appDisplay
// this._overrides.addOverride('BaseAppViewCommon', AppDisplay.BaseAppView.prototype, BaseAppViewCommon);
// instead of overriding inaccessible BaseAppView class, we override its subclasses - AppDisplay and FolderView
this . _overrides . addOverride ( 'BaseAppViewCommonApp' , AppDisplay . AppDisplay . prototype , BaseAppViewCommon ) ;
this . _overrides . addOverride ( 'AppDisplay' , AppDisplay . AppDisplay . prototype , AppDisplayCommon ) ;
this . _overrides . addOverride ( 'AppViewItem' , AppDisplay . AppViewItem . prototype , AppViewItemCommon ) ;
this . _overrides . addOverride ( 'AppGridCommon' , AppDisplay . AppGrid . prototype , AppGridCommon ) ;
this . _overrides . addOverride ( 'AppIcon' , AppDisplay . AppIcon . prototype , AppIcon ) ;
if ( opt . ORIENTATION ) {
this . _overrides . removeOverride ( 'AppGridLayoutHorizontal' ) ;
this . _overrides . addOverride ( 'AppGridLayoutVertical' , _appDisplay . _appGridLayout , BaseAppViewGridLayoutVertical ) ;
} else {
this . _overrides . removeOverride ( 'AppGridLayoutVertical' ) ;
this . _overrides . addOverride ( 'AppGridLayoutHorizontal' , _appDisplay . _appGridLayout , BaseAppViewGridLayoutHorizontal ) ;
}
// Custom folders
this . _overrides . addOverride ( 'BaseAppViewCommonFolder' , AppDisplay . FolderView . prototype , BaseAppViewCommon ) ;
this . _overrides . addOverride ( 'FolderView' , AppDisplay . FolderView . prototype , FolderView ) ;
this . _overrides . addOverride ( 'AppFolderDialog' , AppDisplay . AppFolderDialog . prototype , AppFolderDialog ) ;
this . _overrides . addOverride ( 'FolderIcon' , AppDisplay . FolderIcon . prototype , FolderIcon ) ;
// Prevent changing grid page size when showing/hiding _pageIndicators
this . _overrides . addOverride ( 'PageIndicators' , PageIndicators . PageIndicators . prototype , PageIndicatorsCommon ) ;
}
_updateAppDisplay ( reset ) {
const orientation = reset ? Clutter . Orientation . HORIZONTAL : opt . ORIENTATION ;
BaseAppViewCommon . _adaptForOrientation . bind ( _appDisplay ) ( orientation ) ;
this . _updateFavoritesConnection ( reset ) ;
_appDisplay . visible = true ;
if ( reset ) {
_appDisplay . _grid . layoutManager . fixedIconSize = - 1 ;
_appDisplay . _grid . layoutManager . allow _incomplete _pages = true ;
_appDisplay . _grid . _currentMode = - 1 ;
_appDisplay . _grid . setGridModes ( ) ;
_appDisplay . _grid . set _style ( '' ) ;
_appDisplay . _prevPageArrow . set _scale ( 1 , 1 ) ;
_appDisplay . _nextPageArrow . set _scale ( 1 , 1 ) ;
if ( this . _appGridLayoutConId ) {
global . settings . disconnect ( this . _appGridLayoutConId ) ;
this . _appGridLayoutConId = 0 ;
}
this . _repopulateAppDisplay ( reset ) ;
} else {
_appDisplay . _grid . _currentMode = - 1 ;
// update grid on layout reset
if ( ! this . _appGridLayoutConId )
this . _appGridLayoutConId = global . settings . connect ( 'changed::app-picker-layout' , this . _updateLayout . bind ( this ) ) ;
// avoid resetting appDisplay before startup animation
// x11 shell restart skips startup animation
if ( ! Main . layoutManager . _startingUp ) {
this . _repopulateAppDisplay ( ) ;
} else if ( Main . layoutManager . _startingUp && Meta . is _restart ( ) ) {
_timeouts . three = GLib . idle _add ( GLib . PRIORITY _LOW , ( ) => {
this . _repopulateAppDisplay ( ) ;
_timeouts . three = 0 ;
return GLib . SOURCE _REMOVE ;
} ) ;
}
}
}
_updateFavoritesConnection ( reset ) {
if ( ! reset ) {
if ( ! this . _appSystemStateConId && opt . APP _GRID _INCLUDE _DASH >= 3 ) {
this . _appSystemStateConId = Shell . AppSystem . get _default ( ) . connect (
'app-state-changed' ,
( ) => {
this . _updateFolderIcons = true ;
_appDisplay . _redisplay ( ) ;
}
) ;
}
} else if ( this . _appSystemStateConId ) {
Shell . AppSystem . get _default ( ) . disconnect ( this . _appSystemStateConId ) ;
this . _appSystemStateConId = 0 ;
}
}
_restoreOverviewGroup ( ) {
Main . overview . dash . showAppsButton . checked = false ;
Main . layoutManager . overviewGroup . opacity = 255 ;
Main . layoutManager . overviewGroup . scale _x = 1 ;
Main . layoutManager . overviewGroup . scale _y = 1 ;
Main . layoutManager . overviewGroup . hide ( ) ;
_appDisplay . translation _x = 0 ;
_appDisplay . translation _y = 0 ;
_appDisplay . visible = true ;
_appDisplay . opacity = 255 ;
}
_updateLayout ( settings , key ) {
// Reset the app grid only if the user layout has been completely removed
if ( ! settings . get _value ( key ) . deep _unpack ( ) . length ) {
this . _repopulateAppDisplay ( ) ;
}
}
_repopulateAppDisplay ( reset = false , callback ) {
// Remove all icons so they can be re-created with the current configuration
// Updating appGrid content while rebasing extensions when session is locked makes no sense (relevant for GS version < 46)
if ( ! Main . sessionMode . isLocked )
AppDisplayCommon . removeAllItems . bind ( _appDisplay ) ( ) ;
// appDisplay disabled
if ( reset ) {
_appDisplay . _redisplay ( ) ;
return ;
}
_appDisplay . _readyToRedisplay = true ;
_appDisplay . _redisplay ( ) ;
// Setting OffscreenRedirect should improve performance when opacity transitions are used
_appDisplay . offscreen _redirect = Clutter . OffscreenRedirect . ALWAYS ;
if ( opt . APP _GRID _PERFORMANCE )
this . _realizeAppDisplay ( callback ) ;
else if ( callback )
callback ( ) ;
}
_realizeAppDisplay ( callback ) {
// Workaround - silently realize appDisplay
// The realization takes some time and affects animations during the first use
// If we do it invisibly before the user needs the app grid, it can improve the user's experience
_appDisplay . opacity = 1 ;
this . _exposeAppGrid ( ) ;
_appDisplay . _redisplay ( ) ;
this . _exposeAppFolders ( ) ;
// Let the main loop process our changes before we continue
_timeouts . updateAppGrid = GLib . idle _add ( GLib . PRIORITY _LOW , ( ) => {
this . _restoreAppGrid ( ) ;
Me . _resetInProgress = false ;
if ( callback )
callback ( ) ;
_timeouts . updateAppGrid = 0 ;
return GLib . SOURCE _REMOVE ;
} ) ;
}
_exposeAppGrid ( ) {
const overviewGroup = Main . layoutManager . overviewGroup ;
if ( ! overviewGroup . visible ) {
// scale down the overviewGroup so it don't cover uiGroup
overviewGroup . scale _y = 0.001 ;
// make it invisible to the eye, but visible for the renderer
overviewGroup . opacity = 1 ;
// if overview is hidden, show it
overviewGroup . visible = true ;
}
}
_restoreAppGrid ( ) {
if ( opt . APP _GRID _PERFORMANCE )
this . _hideAppFolders ( ) ;
const overviewGroup = Main . layoutManager . overviewGroup ;
if ( ! Main . overview . _shown )
overviewGroup . hide ( ) ;
overviewGroup . scale _y = 1 ;
overviewGroup . opacity = 255 ;
_appDisplay . opacity = 0 ;
_appDisplay . visible = false ;
}
_exposeAppFolders ( ) {
_appDisplay . _folderIcons . forEach ( d => {
d . _ensureFolderDialog ( ) ;
d . _dialog . scale _y = 0.0001 ;
d . _dialog . show ( ) ;
d . _dialog . _updateFolderSize ( ) ;
} ) ;
}
_hideAppFolders ( ) {
_appDisplay . _folderIcons . forEach ( d => {
if ( d . _dialog ) {
d . _dialog . hide ( ) ;
d . _dialog . scale _y = 1 ;
}
} ) ;
}
} ;
function _getViewFromIcon ( icon ) {
icon = icon . _sourceItem ? icon . _sourceItem : icon ;
for ( let parent = icon . get _parent ( ) ; parent ; parent = parent . get _parent ( ) ) {
if ( parent instanceof AppDisplay . AppDisplay || parent instanceof AppDisplay . FolderView ) {
return parent ;
}
}
return null ;
}
const AppDisplayCommon = {
_ensureDefaultFolders ( ) {
// disable creation of default folders if user deleted them
} ,
removeAllItems ( ) {
this . _orderedItems . slice ( ) . forEach ( item => {
if ( item . _dialog )
Main . layoutManager . overviewGroup . remove _child ( item . _dialog ) ;
this . _removeItem ( item ) ;
item . destroy ( ) ;
} ) ;
this . _folderIcons = [ ] ;
} ,
// apps load adapted for custom sorting and including dash items
2025-02-12 16:21:01 +01:00
_loadApps ( results ) {
2025-02-09 23:19:41 +01:00
let appIcons = [ ] ;
const runningApps = Shell . AppSystem . get _default ( ) . get _running ( ) . map ( a => a . id ) ;
this . _appInfoList = Shell . AppSystem . get _default ( ) . get _installed ( ) . filter ( appInfo => {
try {
appInfo . get _id ( ) ; // catch invalid file encodings
} catch ( e ) {
return false ;
}
const appIsRunning = runningApps . includes ( appInfo . get _id ( ) ) ;
const appIsFavorite = this . _appFavorites . isFavorite ( appInfo . get _id ( ) ) ;
const excludeApp = ( opt . APP _GRID _EXCLUDE _RUNNING && appIsRunning ) || ( opt . APP _GRID _EXCLUDE _FAVORITES && appIsFavorite ) ;
return this . _parentalControlsManager . shouldShowApp ( appInfo ) && ! excludeApp ;
} ) ;
let apps = this . _appInfoList . map ( app => app . get _id ( ) ) ;
let appSys = Shell . AppSystem . get _default ( ) ;
const appsInsideFolders = new Set ( ) ;
this . _folderIcons = [ ] ;
2025-02-12 16:21:01 +01:00
if ( ! ( opt . APP _GRID _USAGE || results ) ) {
2025-02-09 23:19:41 +01:00
let folders = this . _folderSettings . get _strv ( 'folder-children' ) ;
folders . forEach ( id => {
let path = ` ${ this . _folderSettings . path } folders/ ${ id } / ` ;
let icon = this . _items . get ( id ) ;
if ( ! icon ) {
icon = new AppDisplay . FolderIcon ( id , path , this ) ;
icon . connect ( 'apps-changed' , ( ) => {
this . _redisplay ( ) ;
this . _savePages ( ) ;
} ) ;
icon . connect ( 'notify::pressed' , ( ) => {
if ( icon . pressed )
this . updateDragFocus ( icon ) ;
} ) ;
} else if ( this . _updateFolderIcons && opt . APP _GRID _EXCLUDE _RUNNING ) {
// if any app changed its running state, update folder icon
icon . icon . update ( ) ;
}
// remove empty folder icons
if ( ! icon . visible ) {
icon . destroy ( ) ;
return ;
}
appIcons . push ( icon ) ;
this . _folderIcons . push ( icon ) ;
icon . getAppIds ( ) . forEach ( appId => appsInsideFolders . add ( appId ) ) ;
} ) ;
}
// reset request to update active icon
this . _updateFolderIcons = false ;
// Allow dragging of the icon only if the Dash would accept a drop to
// change favorite-apps. There are no other possible drop targets from
// the app picker, so there's no other need for a drag to start,
// at least on single-monitor setups.
// This also disables drag-to-launch on multi-monitor setups,
// but we hope that is not used much.
const isDraggable =
global . settings . is _writable ( 'favorite-apps' ) ||
global . settings . is _writable ( 'app-picker-layout' ) ;
apps . forEach ( appId => {
if ( ! opt . APP _GRID _USAGE && appsInsideFolders . has ( appId ) )
return ;
let icon = this . _items . get ( appId ) ;
if ( ! icon ) {
let app = appSys . lookup _app ( appId ) ;
icon = new AppDisplay . AppIcon ( app , { isDraggable } ) ;
icon . connect ( 'notify::pressed' , ( ) => {
if ( icon . pressed )
this . updateDragFocus ( icon ) ;
} ) ;
}
appIcons . push ( icon ) ;
} ) ;
// At last, if there's a placeholder available, add it
if ( this . _placeholder )
appIcons . push ( this . _placeholder ) ;
return appIcons ;
} ,
_onDragBegin ( overview , source ) {
// let sourceId;
// support active preview icons
if ( source . _sourceItem ) {
// sourceId = source._sourceFolder._id;
source = source . _sourceItem ;
} / * else {
sourceId = source . id ;
} * /
// Prevent switching page when an item on another page is selected
// by removing the focus from all icons
// This is an upstream bug
// this.selectApp(sourceId);
this . grab _key _focus ( ) ;
this . _dragMonitor = {
dragMotion : this . _onDragMotion . bind ( this ) ,
dragDrop : this . _onDragDrop . bind ( this ) ,
} ;
DND . addDragMonitor ( this . _dragMonitor ) ;
this . _appGridLayout . showPageIndicators ( ) ;
this . _dragFocus = null ;
this . _swipeTracker . enabled = false ;
// When dragging from a folder dialog, the dragged app icon doesn't
// exist in AppDisplay. We work around that by adding a placeholder
// icon that is either destroyed on cancel, or becomes the effective
// new icon when dropped.
if ( /* AppDisplay.*/ _getViewFromIcon ( source ) instanceof AppDisplay . FolderView ||
( opt . APP _GRID _EXCLUDE _FAVORITES && this . _appFavorites . isFavorite ( source . id ) ) )
this . _ensurePlaceholder ( source ) ;
} ,
_ensurePlaceholder ( source ) {
if ( this . _placeholder )
return ;
if ( source . _sourceItem )
source = source . _sourceItem ;
const appSys = Shell . AppSystem . get _default ( ) ;
const app = appSys . lookup _app ( source . id ) ;
const isDraggable =
global . settings . is _writable ( 'favorite-apps' ) ||
global . settings . is _writable ( 'app-picker-layout' ) ;
this . _placeholder = new AppDisplay . AppIcon ( app , { isDraggable } ) ;
this . _placeholder . connect ( 'notify::pressed' , ( ) => {
if ( this . _placeholder ? . pressed )
this . updateDragFocus ( this . _placeholder ) ;
} ) ;
this . _placeholder . scaleAndFade ( ) ;
this . _redisplay ( ) ;
} ,
// accept source from active folder preview
acceptDrop ( source ) {
if ( opt . APP _GRID _USAGE )
return false ;
if ( source . _sourceItem )
source = source . _sourceItem ;
if ( ! this . _acceptDropCommon ( source ) )
return false ;
this . _savePages ( ) ;
const view = /* AppDisplay.*/ _getViewFromIcon ( source ) ;
if ( view instanceof AppDisplay . FolderView )
view . removeApp ( source . app ) ;
if ( this . _currentDialog )
this . _currentDialog . popdown ( ) ;
if ( opt . APP _GRID _EXCLUDE _FAVORITES && this . _appFavorites . isFavorite ( source . id ) )
this . _appFavorites . removeFavorite ( source . id ) ;
return true ;
} ,
_savePages ( ) {
// Skip saving pages when search app grid mode is active
// and the grid is showing search results
if ( Main . overview . _overview . controls . _origAppGridContent )
return ;
const pages = [ ] ;
for ( let i = 0 ; i < this . _grid . nPages ; i ++ ) {
const pageItems =
this . _grid . getItemsAtPage ( i ) . filter ( c => c . visible ) ;
const pageData = { } ;
pageItems . forEach ( ( item , index ) => {
pageData [ item . id ] = {
position : GLib . Variant . new _int32 ( index ) ,
} ;
} ) ;
pages . push ( pageData ) ;
}
this . _pageManager . pages = pages ;
} ,
} ;
const BaseAppViewCommon = {
after _ _init ( ) {
// Only folders can run this init
this . _isFolder = true ;
this . _adaptForOrientation ( opt . ORIENTATION , true ) ;
// Because the original class prototype is not exported, we need to inject every instance
const overrides = new Me . Util . Overrides ( ) ;
if ( opt . ORIENTATION ) {
overrides . addOverride ( 'FolderGridLayoutVertical' , this . _appGridLayout , BaseAppViewGridLayoutVertical ) ;
this . _pageIndicators . set _style ( 'margin-right: 12px;' ) ;
} else {
overrides . addOverride ( 'FolderGridLayoutHorizontal' , this . _appGridLayout , BaseAppViewGridLayoutHorizontal ) ;
this . _pageIndicators . set _style ( 'margin-bottom: 12px;' ) ;
}
} ,
_adaptForOrientation ( orientation , folder ) {
const vertical = ! ! orientation ;
this . _grid . layoutManager . fixedIconSize = folder ? opt . APP _GRID _FOLDER _ICON _SIZE : opt . APP _GRID _ICON _SIZE ;
this . _grid . layoutManager . _orientation = orientation ;
this . _orientation = orientation ;
this . _swipeTracker . orientation = orientation ;
this . _swipeTracker . _reset ( ) ;
2025-02-12 16:21:01 +01:00
if ( this . _scrollView . get _vadjustment ) {
this . _adjustment = vertical
? this . _scrollView . get _vadjustment ( )
: this . _scrollView . get _hadjustment ( ) ;
} else {
this . _adjustment = vertical
? this . _scrollView . get _vscroll _bar ( ) . adjustment
: this . _scrollView . get _hscroll _bar ( ) . adjustment ;
}
2025-02-09 23:19:41 +01:00
this . _prevPageArrow . pivot _point = new Graphene . Point ( { x : 0.5 , y : 0.5 } ) ;
this . _prevPageArrow . rotation _angle _z = vertical ? 90 : 0 ;
this . _nextPageArrow . pivot _point = new Graphene . Point ( { x : 0.5 , y : 0.5 } ) ;
this . _nextPageArrow . rotation _angle _z = vertical ? 90 : 0 ;
const pageIndicators = this . _pageIndicators ;
pageIndicators . vertical = vertical ;
this . _box . vertical = ! vertical ;
pageIndicators . x _expand = ! vertical ;
pageIndicators . y _align = vertical ? Clutter . ActorAlign . CENTER : Clutter . ActorAlign . START ;
pageIndicators . x _align = vertical ? Clutter . ActorAlign . START : Clutter . ActorAlign . CENTER ;
this . _grid . layoutManager . allow _incomplete _pages = folder ? false : opt . APP _GRID _ALLOW _INCOMPLETE _PAGES ;
const spacing = folder ? opt . APP _GRID _FOLDER _SPACING : opt . APP _GRID _SPACING ;
this . _grid . set _style ( ` column-spacing: ${ spacing } px; row-spacing: ${ spacing } px; ` ) ;
if ( vertical ) {
this . _scrollView . set _policy ( St . PolicyType . NEVER , St . PolicyType . EXTERNAL ) ;
if ( ! this . _scrollConId ) {
this . _scrollConId = this . _adjustment . connect ( 'notify::value' , adj => {
const value = adj . value / adj . page _size ;
this . _pageIndicators . setCurrentPosition ( value ) ;
} ) ;
}
pageIndicators . remove _style _class _name ( 'page-indicators-horizontal' ) ;
pageIndicators . add _style _class _name ( 'page-indicators-vertical' ) ;
this . _prevPageIndicator . add _style _class _name ( 'prev-page-indicator' ) ;
this . _nextPageIndicator . add _style _class _name ( 'next-page-indicator' ) ;
this . _nextPageArrow . translationY = 0 ;
this . _prevPageArrow . translationY = 0 ;
this . _nextPageIndicator . translationX = 0 ;
this . _prevPageIndicator . translationX = 0 ;
} else {
this . _scrollView . set _policy ( St . PolicyType . EXTERNAL , St . PolicyType . NEVER ) ;
if ( this . _scrollConId ) {
this . _adjustment . disconnect ( this . _scrollConId ) ;
this . _scrollConId = 0 ;
}
pageIndicators . remove _style _class _name ( 'page-indicators-vertical' ) ;
pageIndicators . add _style _class _name ( 'page-indicators-horizontal' ) ;
this . _prevPageIndicator . remove _style _class _name ( 'prev-page-indicator' ) ;
this . _nextPageIndicator . remove _style _class _name ( 'next-page-indicator' ) ;
this . _nextPageArrow . translationX = 0 ;
this . _prevPageArrow . translationX = 0 ;
this . _nextPageIndicator . translationY = 0 ;
this . _prevPageIndicator . translationY = 0 ;
}
const scale = opt . APP _GRID _SHOW _PAGE _ARROWS ? 1 : 0 ;
this . _prevPageArrow . set _scale ( scale , scale ) ;
this . _nextPageArrow . set _scale ( scale , scale ) ;
} ,
_sortItemsByName ( items ) {
items . sort ( ( a , b ) => a . name . toLowerCase ( ) . localeCompare ( b . name . toLowerCase ( ) ) ) ;
} ,
_updateItemPositions ( icons , allowIncompletePages = false ) {
// Avoid recursion when relocating icons
this . _grid . layoutManager . _skipRelocateSurplusItems = true ;
const { itemsPerPage } = this . _grid ;
icons . slice ( ) . forEach ( ( icon , index ) => {
const [ currentPage , currentPosition ] = this . _grid . layoutManager . getItemPosition ( icon ) ;
let page , position ;
if ( allowIncompletePages ) {
[ page , position ] = this . _getItemPosition ( icon ) ;
} else {
page = Math . floor ( index / itemsPerPage ) ;
position = index % itemsPerPage ;
}
if ( currentPage !== page || currentPosition !== position ) {
this . _moveItem ( icon , page , position ) ;
}
} ) ;
this . _grid . layoutManager . _skipRelocateSurplusItems = false ;
// Disable animating the icons to their new positions
// since it can cause glitches when the app grid search mode is active
// and many icons are repositioning at once
this . _grid . layoutManager . _shouldEaseItems = false ;
} ,
2025-02-12 16:21:01 +01:00
// Adds sorting options / support app search provider
_redisplay ( results ) {
2025-02-09 23:19:41 +01:00
// different options for main app grid and app folders
const thisIsFolder = this instanceof AppDisplay . FolderView ;
const thisIsAppDisplay = ! thisIsFolder ;
// When an app was dragged from a folder and dropped to the main grid
// folders (if exist) need to be redisplayed even if we temporary block it for the appDisplay
this . _folderIcons ? . forEach ( icon => {
icon . view . _redisplay ( ) ;
} ) ;
// Avoid unwanted updates
if ( thisIsAppDisplay && ! this . _readyToRedisplay )
return ;
const oldApps = this . _orderedItems . slice ( ) ;
const oldAppIds = oldApps . map ( icon => icon . id ) ;
2025-02-12 16:21:01 +01:00
const newApps = this . _loadApps ( results ) ;
2025-02-09 23:19:41 +01:00
const newAppIds = newApps . map ( icon => icon . id ) ;
const addedApps = newApps . filter ( icon => ! oldAppIds . includes ( icon . id ) ) ;
const removedApps = oldApps . filter ( icon => ! newAppIds . includes ( icon . id ) ) ;
// Don't update folder without dialog if its content didn't change
if ( ! addedApps . length && ! removedApps . length && thisIsFolder && ! this . get _parent ( ) )
return ;
// Remove old app icons
removedApps . forEach ( icon => {
this . _removeItem ( icon ) ;
icon . destroy ( ) ;
} ) ;
// For the main app grid only
let allowIncompletePages = thisIsAppDisplay && opt . APP _GRID _ALLOW _INCOMPLETE _PAGES ;
const customOrder = ! ( ( opt . APP _GRID _ORDER && thisIsAppDisplay ) || ( opt . APP _FOLDER _ORDER && thisIsFolder ) ) ;
2025-02-12 16:21:01 +01:00
if ( results ) {
newApps . sort ( ( a , b ) => results . indexOf ( a . app ? . id ) - results . indexOf ( b . app ? . id ) ) ;
} else if ( ! customOrder ) {
2025-02-09 23:19:41 +01:00
allowIncompletePages = false ;
// Sort by name
this . _sortItemsByName ( newApps ) ;
// Sort by usage
if ( ( opt . APP _GRID _USAGE && thisIsAppDisplay ) ||
( opt . APP _FOLDER _USAGE && thisIsFolder ) ) {
newApps . sort ( ( a , b ) => Shell . AppUsage . get _default ( ) . compare ( a . app ? . id , b . app ? . id ) ) ;
}
// Sort favorites first
if ( ! opt . APP _GRID _EXCLUDE _FAVORITES && opt . APP _GRID _DASH _FIRST ) {
const fav = Object . keys ( this . _appFavorites . _favorites ) ;
newApps . sort ( ( a , b ) => {
let aFav = fav . indexOf ( a . id ) ;
if ( aFav < 0 )
aFav = 999 ;
let bFav = fav . indexOf ( b . id ) ;
if ( bFav < 0 )
bFav = 999 ;
return bFav < aFav ;
} ) ;
}
// Sort running first
if ( ! opt . APP _GRID _EXCLUDE _RUNNING && opt . APP _GRID _DASH _FIRST ) {
newApps . sort ( ( a , b ) => a . app ? . get _state ( ) !== Shell . AppState . RUNNING && b . app ? . get _state ( ) === Shell . AppState . RUNNING ) ;
}
// Sort folders first
if ( thisIsAppDisplay && opt . APP _GRID _FOLDERS _FIRST )
newApps . sort ( ( a , b ) => b . _folder && ! a . _folder ) ;
// Sort folders last
else if ( thisIsAppDisplay && opt . APP _GRID _FOLDERS _LAST )
newApps . sort ( ( a , b ) => a . _folder && ! b . _folder ) ;
} else {
// Sort items according to the custom order stored in pageManager
newApps . sort ( this . _compareItems . bind ( this ) ) ;
}
// Add new app icons to the grid
newApps . forEach ( icon => {
const [ page , position ] = this . _grid . getItemPosition ( icon ) ;
if ( page === - 1 && position === - 1 )
this . _addItem ( icon , - 1 , - 1 ) ;
} ) ;
// When a placeholder icon was added to the custom sorted grid during DND from a folder
// update its initial position on the page
2025-02-12 16:21:01 +01:00
if ( customOrder && ! results )
2025-02-09 23:19:41 +01:00
newApps . sort ( this . _compareItems . bind ( this ) ) ;
this . _orderedItems = newApps ;
// Update icon positions if needed
this . _updateItemPositions ( this . _orderedItems , allowIncompletePages ) ;
// Relocate items with invalid positions
if ( thisIsAppDisplay ) {
const nPages = this . _grid . layoutManager . nPages ;
for ( let pageIndex = 0 ; pageIndex < nPages ; pageIndex ++ )
this . _grid . layoutManager . _relocateSurplusItems ( pageIndex ) ;
}
this . emit ( 'view-loaded' ) ;
} ,
_canAccept ( source ) {
return source instanceof AppDisplay . AppViewItem ;
} ,
// this method is replacing BaseAppVew.acceptDrop which can't be overridden directly
_acceptDropCommon ( source ) {
const dropTarget = this . _dropTarget ;
delete this . _dropTarget ;
if ( ! this . _canAccept ( source ) )
return false ;
if ( dropTarget === this . _prevPageIndicator ||
dropTarget === this . _nextPageIndicator ) {
let increment ;
increment = dropTarget === this . _prevPageIndicator ? - 1 : 1 ;
const { currentPage , nPages } = this . _grid ;
const page = Math . min ( currentPage + increment , nPages ) ;
const position = page < nPages ? - 1 : 0 ;
this . _moveItem ( source , page , position ) ;
this . goToPage ( page ) ;
} else if ( this . _delayedMoveData ) {
// Dropped before the icon was moved
const { page , position } = this . _delayedMoveData ;
try {
this . _moveItem ( source , page , position ) ;
} catch ( e ) {
console . warn ( ` Warning: ${ e } ` ) ;
}
this . _removeDelayedMove ( ) ;
}
return true ;
} ,
// support active preview icons
_onDragMotion ( dragEvent ) {
if ( ! ( dragEvent . source instanceof AppDisplay . AppViewItem ) )
return DND . DragMotionResult . CONTINUE ;
if ( dragEvent . source . _sourceItem )
dragEvent . source = dragEvent . source . _sourceItem ;
const appIcon = dragEvent . source ;
if ( appIcon instanceof AppDisplay . AppViewItem ) {
if ( ! this . _dragMaybeSwitchPageImmediately ( dragEvent ) ) {
// Two ways of switching pages during DND:
// 1) When "bumping" the cursor against the monitor edge, we switch
// page immediately.
// 2) When hovering over the next-page indicator for a certain time,
// we also switch page.
const { targetActor } = dragEvent ;
if ( targetActor === this . _prevPageIndicator ||
targetActor === this . _nextPageIndicator )
this . _maybeSetupDragPageSwitchInitialTimeout ( dragEvent ) ;
else
this . _resetDragPageSwitch ( ) ;
}
}
const thisIsFolder = this instanceof AppDisplay . FolderView ;
const thisIsAppDisplay = ! thisIsFolder ;
// Prevent reorganizing the main app grid icons when an app folder is open and when sorting is not custom
// For some reason in V-Shell the drag motion events propagate from folder to main grid, which is not a problem in default code - so test the open dialog
if ( ! this . _currentDialog && ( ! opt . APP _GRID _ORDER && thisIsAppDisplay ) || ( ! opt . APP _FOLDER _ORDER && thisIsFolder ) )
this . _maybeMoveItem ( dragEvent ) ;
return DND . DragMotionResult . CONTINUE ;
} ,
} ;
const BaseAppViewGridLayoutHorizontal = {
_getIndicatorsWidth ( box ) {
const [ width , height ] = box . get _size ( ) ;
const arrows = [
this . _nextPageArrow ,
this . _previousPageArrow ,
] ;
let minArrowsWidth ;
minArrowsWidth = arrows . reduce (
( previousWidth , accessory ) => {
const [ min ] = accessory . get _preferred _width ( height ) ;
return Math . max ( previousWidth , min ) ;
} , 0 ) ;
minArrowsWidth = opt . APP _GRID _SHOW _PAGE _ARROWS ? minArrowsWidth : 0 ;
const indicatorWidth = ! this . _grid . _isFolder
? minArrowsWidth + ( ( width - minArrowsWidth ) * ( 1 - opt . APP _GRID _PAGE _WIDTH _SCALE ) ) / 2
: minArrowsWidth + 6 ;
return Math . round ( indicatorWidth ) ;
} ,
vfunc _allocate ( container , box ) {
const ltr = container . get _text _direction ( ) !== Clutter . TextDirection . RTL ;
const indicatorsWidth = this . _getIndicatorsWidth ( box ) ;
const pageIndicatorsHeight = 20 ; // _appDisplay._pageIndicators.height is unstable, 20 is determined by the style
const availHeight = box . get _height ( ) - pageIndicatorsHeight ;
const vPadding = Math . round ( ( availHeight - availHeight * opt . APP _GRID _PAGE _HEIGHT _SCALE ) / 2 ) ;
this . _grid . indicatorsPadding = new Clutter . Margin ( {
left : indicatorsWidth ,
right : indicatorsWidth ,
top : vPadding + pageIndicatorsHeight ,
bottom : vPadding ,
} ) ;
this . _scrollView . allocate ( box ) ;
const leftBox = box . copy ( ) ;
leftBox . x2 = leftBox . x1 + indicatorsWidth ;
const rightBox = box . copy ( ) ;
rightBox . x1 = rightBox . x2 - indicatorsWidth ;
this . _previousPageIndicator . allocate ( ltr ? leftBox : rightBox ) ;
this . _previousPageArrow . allocate _align _fill ( ltr ? leftBox : rightBox ,
0.5 , 0.5 , false , false ) ;
this . _nextPageIndicator . allocate ( ltr ? rightBox : leftBox ) ;
this . _nextPageArrow . allocate _align _fill ( ltr ? rightBox : leftBox ,
0.5 , 0.5 , false , false ) ;
this . _pageWidth = box . get _width ( ) ;
// Center page arrow buttons
this . _previousPageArrow . translationY = pageIndicatorsHeight / 2 ;
this . _nextPageArrow . translationY = pageIndicatorsHeight / 2 ;
// Reset page indicators vertical position
this . _nextPageIndicator . translationY = 0 ;
this . _previousPageIndicator . translationY = 0 ;
} ,
} ;
const BaseAppViewGridLayoutVertical = {
_getIndicatorsHeight ( box ) {
const [ width , height ] = box . get _size ( ) ;
const arrows = [
this . _nextPageArrow ,
this . _previousPageArrow ,
] ;
let minArrowsHeight ;
minArrowsHeight = arrows . reduce (
( previousHeight , accessory ) => {
const [ min ] = accessory . get _preferred _height ( width ) ;
return Math . max ( previousHeight , min ) ;
} , 0 ) ;
minArrowsHeight = opt . APP _GRID _SHOW _PAGE _ARROWS ? minArrowsHeight : 0 ;
const indicatorHeight = ! this . _grid . _isFolder
? minArrowsHeight + ( ( height - minArrowsHeight ) * ( 1 - opt . APP _GRID _PAGE _HEIGHT _SCALE ) ) / 2
: minArrowsHeight + 6 ;
return Math . round ( indicatorHeight ) ;
} ,
_syncPageIndicators ( ) {
if ( ! this . _container )
return ;
const { value } = this . _pageIndicatorsAdjustment ;
const { top , bottom } = this . _grid . indicatorsPadding ;
const topIndicatorOffset = - top * ( 1 - value ) ;
const bottomIndicatorOffset = bottom * ( 1 - value ) ;
this . _previousPageIndicator . translationY =
topIndicatorOffset ;
this . _nextPageIndicator . translationY =
bottomIndicatorOffset ;
const leftArrowOffset = - top * value ;
const rightArrowOffset = bottom * value ;
this . _previousPageArrow . translationY =
leftArrowOffset ;
this . _nextPageArrow . translationY =
rightArrowOffset ;
// Page icons
this . _translatePreviousPageIcons ( value ) ;
this . _translateNextPageIcons ( value ) ;
if ( this . _grid . nPages > 0 ) {
this . _grid . getItemsAtPage ( this . _currentPage ) . forEach ( icon => {
icon . translationY = 0 ;
} ) ;
}
} ,
_translatePreviousPageIcons ( value ) {
if ( this . _currentPage === 0 )
return ;
const pageHeight = this . _grid . layoutManager . _pageHeight ;
const previousPage = this . _currentPage - 1 ;
const icons = this . _grid . getItemsAtPage ( previousPage ) . filter ( i => i . visible ) ;
if ( icons . length === 0 )
return ;
const { top } = this . _grid . indicatorsPadding ;
const { rowSpacing } = this . _grid . layoutManager ;
const endIcon = icons [ icons . length - 1 ] ;
let iconOffset ;
const currentPageOffset = pageHeight * this . _currentPage ;
iconOffset = currentPageOffset - endIcon . allocation . y1 - endIcon . width + top - rowSpacing ;
for ( const icon of icons )
icon . translationY = iconOffset * value ;
} ,
_translateNextPageIcons ( value ) {
if ( this . _currentPage >= this . _grid . nPages - 1 )
return ;
const nextPage = this . _currentPage + 1 ;
const icons = this . _grid . getItemsAtPage ( nextPage ) . filter ( i => i . visible ) ;
if ( icons . length === 0 )
return ;
const { bottom } = this . _grid . indicatorsPadding ;
const { rowSpacing } = this . _grid . layoutManager ;
let iconOffset ;
const pageOffset = this . _pageHeight * nextPage ;
iconOffset = pageOffset - icons [ 0 ] . allocation . y1 - bottom + rowSpacing ;
for ( const icon of icons )
icon . translationY = iconOffset * value ;
} ,
vfunc _allocate ( container , box ) {
const indicatorsHeight = this . _getIndicatorsHeight ( box ) ;
const pageIndicatorsWidth = 20 ; // _appDisplay._pageIndicators.width is not stable, 20 is determined by the style
const availWidth = box . get _width ( ) - pageIndicatorsWidth ;
const hPadding = Math . round ( ( availWidth - availWidth * opt . APP _GRID _PAGE _WIDTH _SCALE ) / 2 ) ;
this . _grid . indicatorsPadding = new Clutter . Margin ( {
top : indicatorsHeight ,
bottom : indicatorsHeight ,
left : hPadding + pageIndicatorsWidth ,
right : hPadding ,
} ) ;
this . _scrollView . allocate ( box ) ;
const topBox = box . copy ( ) ;
topBox . y2 = topBox . y1 + indicatorsHeight ;
const bottomBox = box . copy ( ) ;
bottomBox . y1 = bottomBox . y2 - indicatorsHeight ;
this . _previousPageIndicator . allocate ( topBox ) ;
this . _previousPageArrow . allocate _align _fill ( topBox ,
0.5 , 0.5 , false , false ) ;
this . _nextPageIndicator . allocate ( bottomBox ) ;
this . _nextPageArrow . allocate _align _fill ( bottomBox ,
0.5 , 0.5 , false , false ) ;
this . _pageHeight = box . get _height ( ) ;
// Center page arrow buttons
this . _previousPageArrow . translationX = pageIndicatorsWidth / 2 ;
this . _nextPageArrow . translationX = pageIndicatorsWidth / 2 ;
// Reset page indicators vertical position
this . _nextPageIndicator . translationX = 0 ;
this . _previousPageIndicator . translationX = 0 ;
} ,
} ;
const AppGridCommon = {
_updatePadding ( ) {
const { rowSpacing , columnSpacing } = this . layoutManager ;
const padding = this . _indicatorsPadding . copy ( ) ;
padding . left += rowSpacing ;
padding . right += rowSpacing ;
padding . top += columnSpacing ;
padding . bottom += columnSpacing ;
this . layoutManager . pagePadding = padding ;
} ,
} ;
const FolderIcon = {
after _ _init ( ) {
2025-02-12 16:21:01 +01:00
this . button _mask = St . ButtonMask . ONE | St . ButtonMask . TWO | St . ButtonMask . THREE ;
2025-02-09 23:19:41 +01:00
} ,
open ( ) {
// Prevent switching page when an item on another page is selected
GLib . idle _add ( GLib . PRIORITY _DEFAULT , ( ) => {
// Select folder icon to prevent switching page to the one with currently selected icon
this . _parentView . _selectAppInternal ( this . _id ) ;
// Remove key focus from the selected icon to prevent switching page after dropping the removed folder icon on another page of the main grid
this . _parentView . grab _key _focus ( ) ;
this . _ensureFolderDialog ( ) ;
this . _dialog . popup ( ) ;
} ) ;
} ,
vfunc _clicked ( ) {
this . open ( ) ;
} ,
_canAccept ( source ) {
if ( ! ( source instanceof AppDisplay . AppIcon ) )
return false ;
const view = _getViewFromIcon ( source ) ;
if ( ! view /* || !(view instanceof AppDisplay.AppDisplay)*/ )
return false ;
// Disable this test to allow the user to cancel the current DND by dropping the icon on its original source
/ * i f ( t h i s . _ f o l d e r . g e t _ s t r v ( ' a p p s ' ) . i n c l u d e s ( s o u r c e . i d ) )
return false ; * /
return true ;
} ,
acceptDrop ( source ) {
if ( source . _sourceItem )
source = source . _sourceItem ;
const accepted = AppViewItemCommon . acceptDrop . bind ( this ) ( source ) ;
if ( ! accepted )
return false ;
// If the icon is already in the folder (user dropped it back on the same folder), skip re-adding it
if ( this . _folder . get _strv ( 'apps' ) . includes ( source . id ) )
return true ;
this . _onDragEnd ( ) ;
this . view . addApp ( source . app ) ;
return true ;
} ,
} ;
const FolderView = {
_createGrid ( ) {
let grid = new FolderGrid ( ) ;
grid . _view = this ;
return grid ;
} ,
createFolderIcon ( size ) {
const layout = new Clutter . GridLayout ( {
row _homogeneous : true ,
column _homogeneous : true ,
} ) ;
let icon = new St . Widget ( {
layout _manager : layout ,
x _align : Clutter . ActorAlign . CENTER ,
style : ` width: ${ size } px; height: ${ size } px; ` ,
} ) ;
const numItems = this . _orderedItems . length ;
// decide what number of icons switch to 3x3 grid
// APP_GRID_FOLDER_ICON_GRID: 3 -> more than 4
// : 4 -> more than 8
const threshold = opt . APP _GRID _FOLDER _ICON _GRID % 3 ? 8 : 4 ;
const gridSize = opt . APP _GRID _FOLDER _ICON _GRID > 2 && numItems > threshold ? 3 : 2 ;
const FOLDER _SUBICON _FRACTION = gridSize === 2 ? 0.4 : 0.27 ;
let subSize = Math . floor ( FOLDER _SUBICON _FRACTION * size ) ;
let rtl = icon . get _text _direction ( ) === Clutter . TextDirection . RTL ;
for ( let i = 0 ; i < gridSize * gridSize ; i ++ ) {
const style = ` width: ${ subSize } px; height: ${ subSize } px; ` ;
let bin = new St . Bin ( { style , reactive : true } ) ;
bin . pivot _point = new Graphene . Point ( { x : 0.5 , y : 0.5 } ) ;
if ( i < numItems ) {
if ( ! opt . APP _GRID _ACTIVE _PREVIEW ) {
bin . child = this . _orderedItems [ i ] . app . create _icon _texture ( subSize ) ;
} else {
const app = this . _orderedItems [ i ] . app ;
const child = new AppDisplay . AppIcon ( app , {
setSizeManually : true ,
showLabel : false ,
} ) ;
child . _sourceItem = this . _orderedItems [ i ] ;
child . _sourceFolder = this ;
child . icon . style _class = '' ;
child . set _style _class _name ( '' ) ;
child . icon . set _style ( 'margin: 0; padding: 0;' ) ;
child . _dot . set _style ( 'margin-bottom: 1px;' ) ;
child . icon . setIconSize ( subSize ) ;
child . _canAccept = ( ) => false ;
bin . child = child ;
bin . connect ( 'enter-event' , ( ) => {
bin . ease ( {
duration : 100 ,
translation _y : - 3 ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
} ) ;
bin . connect ( 'leave-event' , ( ) => {
bin . ease ( {
duration : 100 ,
translation _y : 0 ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
} ) ;
}
}
layout . attach ( bin , rtl ? ( i + 1 ) % gridSize : i % gridSize , Math . floor ( i / gridSize ) , 1 , 1 ) ;
}
return icon ;
} ,
_loadApps ( ) {
this . _apps = [ ] ;
const excludedApps = this . _folder . get _strv ( 'excluded-apps' ) ;
const appSys = Shell . AppSystem . get _default ( ) ;
const addAppId = appId => {
if ( excludedApps . includes ( appId ) )
return ;
if ( opt . APP _GRID _EXCLUDE _FAVORITES && this . _appFavorites . isFavorite ( appId ) )
return ;
const app = appSys . lookup _app ( appId ) ;
if ( ! app )
return ;
if ( opt . APP _GRID _EXCLUDE _RUNNING ) {
const runningApps = Shell . AppSystem . get _default ( ) . get _running ( ) . map ( a => a . id ) ;
if ( runningApps . includes ( appId ) )
return ;
}
if ( ! this . _parentalControlsManager . shouldShowApp ( app . get _app _info ( ) ) )
return ;
if ( this . _apps . indexOf ( app ) !== - 1 )
return ;
this . _apps . push ( app ) ;
} ;
const folderApps = this . _folder . get _strv ( 'apps' ) ;
folderApps . forEach ( addAppId ) ;
const folderCategories = this . _folder . get _strv ( 'categories' ) ;
const appInfos = this . _parentView . getAppInfos ( ) ;
appInfos . forEach ( appInfo => {
let appCategories = /* AppDisplay.*/ _getCategories ( appInfo ) ;
if ( ! _listsIntersect ( folderCategories , appCategories ) )
return ;
addAppId ( appInfo . get _id ( ) ) ;
} ) ;
let items = [ ] ;
this . _apps . forEach ( app => {
let icon = this . _items . get ( app . get _id ( ) ) ;
if ( ! icon )
icon = new AppDisplay . AppIcon ( app ) ;
items . push ( icon ) ;
} ) ;
return items ;
} ,
acceptDrop ( source ) {
/ * i f ( ! B a s e A p p V i e w C o m m o n . a c c e p t D r o p . b i n d ( t h i s ) ( s o u r c e ) )
return false ; * /
if ( opt . APP _FOLDER _ORDER )
return false ;
if ( source . _sourceItem )
source = source . _sourceItem ;
if ( ! this . _acceptDropCommon ( source ) )
return false ;
const folderApps = this . _orderedItems . map ( item => item . id ) ;
this . _folder . set _strv ( 'apps' , folderApps ) ;
return true ;
} ,
} ;
const FolderGrid = GObject . registerClass ( {
// Registered name should be unique
GTypeName : ` FolderGrid ${ Math . floor ( Math . random ( ) * 1000 ) } ` ,
} , class FolderGrid extends AppDisplay . AppGrid {
_init ( ) {
super . _init ( {
allow _incomplete _pages : false ,
// For adaptive size (0), set the numbers high enough to fit all the icons
// to avoid splitting the icons to pages upon creating the grid
columns _per _page : 20 ,
rows _per _page : 20 ,
page _halign : Clutter . ActorAlign . CENTER ,
page _valign : Clutter . ActorAlign . CENTER ,
} ) ;
this . layoutManager . _isFolder = true ;
this . _isFolder = true ;
const spacing = opt . APP _GRID _FOLDER _SPACING ;
this . set _style ( ` column-spacing: ${ spacing } px; row-spacing: ${ spacing } px; ` ) ;
this . layoutManager . fixedIconSize = opt . APP _GRID _FOLDER _ICON _SIZE ;
this . setGridModes ( [
{
columns : 20 ,
rows : 20 ,
} ,
] ) ;
}
_updatePadding ( ) {
const { scaleFactor } = St . ThemeContext . get _for _stage ( global . stage ) ;
const padding = this . _indicatorsPadding . copy ( ) ;
const pageIndicatorSize = opt . ORIENTATION
? this . _view . _pageIndicators . get _preferred _width ( 1000 ) [ 1 ] / scaleFactor
: this . _view . _pageIndicators . get _preferred _height ( 1000 ) [ 1 ] / scaleFactor ;
Math . round ( Math . min ( ... this . _view . _pageIndicators . get _size ( ) ) ) ; // / scaleFactor);// ~28;
padding . left = opt . ORIENTATION ? pageIndicatorSize : 0 ;
padding . right = 0 ;
padding . top = opt . ORIENTATION ? 0 : pageIndicatorSize ;
padding . bottom = 0 ;
this . layoutManager . pagePadding = padding ;
}
} ) ;
const FOLDER _DIALOG _ANIMATION _TIME = 200 ; // AppDisplay.FOLDER_DIALOG_ANIMATION_TIME
const AppFolderDialog = {
// injection to _init()
after _ _init ( ) {
// GS 46 changed the aligning to CENTER which restricts max folder dialog size
this . _viewBox . set ( {
x _align : Clutter . ActorAlign . FILL ,
y _align : Clutter . ActorAlign . FILL ,
} ) ;
// delegate this dialog to the FolderIcon._view
// so its _createFolderIcon function can update the dialog if folder content changed
this . _view . _dialog = this ;
// right click into the folder popup should close it
this . child . reactive = true ;
const clickAction = new Clutter . ClickAction ( ) ;
clickAction . connect ( 'clicked' , act => {
if ( act . get _button ( ) === Clutter . BUTTON _PRIMARY )
return Clutter . EVENT _STOP ;
const [ x , y ] = clickAction . get _coords ( ) ;
const actor = global . stage . get _actor _at _pos ( Clutter . PickMode . ALL , x , y ) ;
// if it's not entry for editing folder title
if ( actor !== this . _entry )
this . popdown ( ) ;
return Clutter . EVENT _STOP ;
} ) ;
this . child . add _action ( clickAction ) ;
2025-02-12 16:21:01 +01:00
// Redundant, added just because of extensions.gnome.org rules
this . connect ( 'destroy' , this . _removePopdownTimeout . bind ( this ) ) ;
this . _viewBox . add _style _class _name ( 'app-folder-dialog-translucent' ) ;
2025-02-09 23:19:41 +01:00
} ,
after _ _addFolderNameEntry ( ) {
// edit-folder-button class has been replaced with icon-button class which is not transparent in 46
this . _editButton . add _style _class _name ( 'edit-folder-button' ) ;
// Edit button
this . _removeButton = new St . Button ( {
style _class : 'icon-button edit-folder-button' ,
button _mask : St . ButtonMask . ONE ,
toggle _mode : false ,
reactive : true ,
can _focus : true ,
x _align : Clutter . ActorAlign . END ,
y _align : Clutter . ActorAlign . CENTER ,
child : new St . Icon ( {
icon _name : 'user-trash-symbolic' ,
icon _size : 16 ,
} ) ,
} ) ;
this . _removeButton . connect ( 'clicked' , ( ) => {
if ( Date . now ( ) - this . _removeButton . _lastClick < Clutter . Settings . get _default ( ) . double _click _time ) {
// Close dialog to avoid crashes
this . _isOpen = false ;
this . _grabHelper . ungrab ( { actor : this } ) ;
this . emit ( 'open-state-changed' , false ) ;
this . hide ( ) ;
this . _popdownCallbacks . forEach ( func => func ( ) ) ;
this . _popdownCallbacks = [ ] ;
_appDisplay . ease ( {
opacity : 255 ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
// Reset all keys to delete the relocatable schema
this . _view . _deletingFolder = true ; // Upstream property
let keys = this . _folder . settings _schema . list _keys ( ) ;
for ( const key of keys )
this . _folder . reset ( key ) ;
let settings = new Gio . Settings ( { schema _id : 'org.gnome.desktop.app-folders' } ) ;
let folders = settings . get _strv ( 'folder-children' ) ;
folders . splice ( folders . indexOf ( this . _view . _id ) , 1 ) ;
// remove all abandoned folders (usually my own garbage and unwanted default folders...)
/ * c o n s t a p p F o l d e r s = _ a p p D i s p l a y . _ f o l d e r I c o n s . m a p ( i c o n = > i c o n . _ i d ) ;
folders . forEach ( folder => {
if ( ! appFolders . includes ( folder ) ) {
folders . splice ( folders . indexOf ( folder . _id ) , 1 ) ;
}
} ) ; * /
settings . set _strv ( 'folder-children' , folders ) ;
this . _view . _deletingFolder = false ;
return ;
}
this . _removeButton . _lastClick = Date . now ( ) ;
} ) ;
this . _entryBox . add _child ( this . _removeButton ) ;
this . _entryBox . set _child _at _index ( this . _removeButton , 0 ) ;
this . _closeButton = new St . Button ( {
style _class : 'icon-button edit-folder-button' ,
button _mask : St . ButtonMask . ONE ,
toggle _mode : false ,
reactive : true ,
can _focus : true ,
x _align : Clutter . ActorAlign . END ,
y _align : Clutter . ActorAlign . CENTER ,
child : new St . Icon ( {
icon _name : 'window-close-symbolic' ,
icon _size : 16 ,
} ) ,
} ) ;
this . _closeButton . connect ( 'clicked' , ( ) => {
this . popdown ( ) ;
} ) ;
this . _entryBox . add _child ( this . _closeButton ) ;
} ,
popup ( ) {
if ( this . _isOpen )
return ;
this . _isOpen = this . _grabHelper . grab ( {
actor : this ,
focus : this . _editButton ,
onUngrab : ( ) => this . popdown ( ) ,
} ) ;
if ( ! this . _isOpen )
return ;
this . get _parent ( ) . set _child _above _sibling ( this , null ) ;
// _zoomAndFadeIn() is called from the dialog's allocate()
this . _needsZoomAndFade = true ;
this . show ( ) ;
// force update folder size
this . _folderAreaBox = null ;
this . _updateFolderSize ( ) ;
this . emit ( 'open-state-changed' , true ) ;
} ,
_setupPopdownTimeout ( ) {
if ( this . _popdownTimeoutId > 0 )
return ;
// This timeout is handled in the original code and removed in _onDestroy()
// All dialogs are destroyed on extension disable()
this . _popdownTimeoutId =
GLib . timeout _add ( GLib . PRIORITY _DEFAULT , 500 , ( ) => {
this . _popdownTimeoutId = 0 ;
// Following line fixes upstream bug
// https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6164
this . _view . _onDragEnd ( ) ;
this . popdown ( ) ;
return GLib . SOURCE _REMOVE ;
} ) ;
} ,
2025-02-12 16:21:01 +01:00
_removePopdownTimeout ( ) {
if ( this . _popdownTimeoutId === 0 )
return ;
GLib . source _remove ( this . _popdownTimeoutId ) ;
this . _popdownTimeoutId = 0 ;
} ,
2025-02-09 23:19:41 +01:00
vfunc _allocate ( box ) {
this . _updateFolderSize ( ) ;
// super.allocate(box)
St . Bin . prototype . vfunc _allocate . bind ( this ) ( box ) ;
// Override any attempt to resize the folder dialog, that happens when some child gets wild
// Re-allocate the child only if necessary, because it terminates grid animations
if ( this . _width && this . _height && ( this . _width !== this . child . width || this . _height !== this . child . height ) )
this . _allocateChild ( ) ;
// We can only start zooming after receiving an allocation
if ( this . _needsZoomAndFade )
this . _zoomAndFadeIn ( ) ;
} ,
_allocateChild ( ) {
const childBox = new Clutter . ActorBox ( ) ;
childBox . set _size ( this . _width , this . _height ) ;
this . child . allocate ( childBox ) ;
} ,
// Note that the appDisplay may be off-screen so its coordinates may be shifted
// However, for _updateFolderSize() it doesn't matter
// and when _zoomAndFadeIn() is called, appDisplay is on the right place
_getFolderAreaBox ( ) {
const appDisplay = this . _source . _parentView ;
const folderAreaBox = appDisplay . get _allocation _box ( ) . copy ( ) ;
const searchEntryHeight = opt . SHOW _SEARCH _ENTRY ? Main . overview . _overview . controls . _searchEntryBin . height : 0 ;
folderAreaBox . y1 -= searchEntryHeight ;
// _zoomAndFadeIn() needs an absolute position within a multi-monitor workspace
const monitorGeometry = global . display . get _monitor _geometry ( global . display . get _primary _monitor ( ) ) ;
folderAreaBox . x1 += monitorGeometry . x ;
folderAreaBox . x2 += monitorGeometry . x ;
folderAreaBox . y1 += monitorGeometry . y ;
folderAreaBox . y2 += monitorGeometry . y ;
return folderAreaBox ;
} ,
_updateFolderSize ( ) {
const view = this . _view ;
const nItems = view . _orderedItems . length ;
const [ firstItem ] = view . _grid . layoutManager . _container ;
if ( ! firstItem )
return ;
const { scaleFactor } = St . ThemeContext . get _for _stage ( global . stage ) ;
const margin = 18 ; // see stylesheet .app-folder-dialog-container;
const folderAreaBox = this . _getFolderAreaBox ( ) ;
const maxDialogWidth = folderAreaBox . get _width ( ) / scaleFactor ;
const maxDialogHeight = folderAreaBox . get _height ( ) / scaleFactor ;
// We can't build folder if the available space is not available
if ( ! isFinite ( maxDialogWidth ) || ! isFinite ( maxDialogHeight ) || ! maxDialogWidth || ! maxDialogHeight )
return ;
// We don't need to recalculate grid if nothing changed
if (
this . _folderAreaBox ? . get _width ( ) === folderAreaBox . get _width ( ) &&
this . _folderAreaBox ? . get _height ( ) === folderAreaBox . get _height ( ) &&
nItems === this . _nItems
)
return ;
const layoutManager = view . _grid . layoutManager ;
const spacing = opt . APP _GRID _FOLDER _SPACING ;
const padding = 40 ;
const titleBoxHeight =
Math . round ( this . _entryBox . get _preferred _height ( - 1 ) [ 1 ] / scaleFactor ) ; // ~75
const minDialogWidth = Math . max ( 640 ,
Math . round ( this . _entryBox . get _preferred _width ( - 1 ) [ 1 ] / scaleFactor + 2 * margin ) ) ;
const navigationArrowsSize = // padding + one arrow width is sufficient for both arrows
Math . round ( view . _nextPageArrow . get _preferred _width ( - 1 ) [ 1 ] / scaleFactor ) ;
const pageIndicatorSize =
Math . round ( Math . min ( ... view . _pageIndicators . get _size ( ) ) / scaleFactor ) ; // ~28;
const horizontalNavigation = opt . ORIENTATION ? pageIndicatorSize : navigationArrowsSize ; // either add padding or arrows
const verticalNavigation = opt . ORIENTATION ? navigationArrowsSize : pageIndicatorSize ;
// Horizontal size
const baseWidth = horizontalNavigation + 3 * padding + 2 * margin ;
const maxGridPageWidth = maxDialogWidth - baseWidth ;
// Vertical size
const baseHeight = titleBoxHeight + verticalNavigation + 2 * padding + 2 * margin ;
const maxGridPageHeight = maxDialogHeight - baseHeight ;
// Will be updated to the actual value later
let itemPadding = 55 ;
const minItemSize = 48 + itemPadding ;
let columns = opt . APP _GRID _FOLDER _COLUMNS ;
let rows = opt . APP _GRID _FOLDER _ROWS ;
const maxColumns = columns ? columns : 100 ;
const maxRows = rows ? rows : 100 ;
// Find best icon size
let iconSize = opt . APP _GRID _FOLDER _ICON _SIZE < 0 ? opt . APP _GRID _FOLDER _ICON _SIZE _DEFAULT : opt . APP _GRID _FOLDER _ICON _SIZE ;
if ( opt . APP _GRID _FOLDER _ICON _SIZE === - 1 ) {
let maxIconSize ;
if ( columns ) {
const maxItemWidth = ( maxGridPageWidth - ( columns - 1 ) * opt . APP _GRID _FOLDER _SPACING ) / columns ;
maxIconSize = maxItemWidth - itemPadding ;
}
if ( rows ) {
const maxItemHeight = ( maxGridPageHeight - ( rows - 1 ) * spacing ) / rows ;
maxIconSize = Math . min ( maxItemHeight - itemPadding , maxIconSize ) ;
}
if ( maxIconSize ) {
// We only need sizes from the default to the smallest
let iconSizes = Object . values ( IconSize ) . sort ( ( a , b ) => b - a ) ;
iconSizes = iconSizes . slice ( iconSizes . indexOf ( iconSize ) ) ;
for ( const size of iconSizes ) {
iconSize = size ;
if ( iconSize <= maxIconSize )
break ;
}
}
}
if ( ( ! columns && ! rows ) || opt . APP _GRID _FOLDER _ICON _SIZE !== - 1 ) {
columns = Math . ceil ( Math . sqrt ( nItems ) ) ;
rows = columns ;
if ( columns * ( columns - 1 ) >= nItems ) {
rows = columns - 1 ;
} else if ( ( columns + 1 ) * ( columns - 1 ) >= nItems ) {
rows = columns - 1 ;
columns += 1 ;
}
} else if ( columns && ! rows ) {
rows = Math . ceil ( nItems / columns ) ;
} else if ( rows && ! columns ) {
columns = Math . ceil ( nItems / rows ) ;
}
columns = Math . clamp ( columns , 1 , maxColumns ) ;
columns = Math . min ( nItems , columns ) ;
rows = Math . clamp ( rows , 1 , maxRows ) ;
let itemSize = iconSize + itemPadding ;
// First run sets the grid before we can read the real icon size
// so we estimate the size from default properties
// and correct it in the second run
if ( this . realized ) {
firstItem . icon . setIconSize ( iconSize ) ;
// Item height is inconsistent because it depends on its label height
const [ , firstItemWidth ] = firstItem . get _preferred _width ( - 1 ) ;
const realSize = firstItemWidth / scaleFactor ;
itemSize = realSize ;
itemPadding = realSize - iconSize ;
}
const gridWidth = columns * ( itemSize + spacing ) ;
let width = gridWidth + baseWidth ;
const gridHeight = rows * ( itemSize + spacing ) ;
let height = gridHeight + baseHeight ;
// Folder must fit the appDisplay area plus searchEntryBin if visible
// reduce columns/rows if needed
while ( height > maxDialogHeight && rows > 1 ) {
height -= itemSize + spacing ;
rows -= 1 ;
}
while ( width > maxDialogWidth && columns > 1 ) {
width -= itemSize + spacing ;
columns -= 1 ;
}
// Try to compensate for the previous reduction if there is a space
while ( ( nItems > columns * rows ) && ( ( width + ( itemSize + spacing ) ) <= maxDialogWidth ) && ( columns < maxColumns ) ) {
width += itemSize + spacing ;
columns += 1 ;
}
// remove columns that cannot be displayed
if ( ( ( columns * minItemSize + ( columns - 1 ) * spacing ) ) > maxDialogWidth )
columns = Math . floor ( maxDialogWidth / ( minItemSize + spacing ) ) ;
while ( ( nItems > columns * rows ) && ( ( height + ( itemSize + spacing ) ) <= maxDialogHeight ) && ( rows < maxRows ) ) {
height += itemSize + spacing ;
rows += 1 ;
}
// remove rows that cannot be displayed
if ( ( ( ( rows * minItemSize + ( rows - 1 ) * spacing ) ) ) > maxDialogHeight )
rows = Math . floor ( maxDialogWidth / ( minItemSize + spacing ) ) ;
// remove size for rows that are empty
const rowsNeeded = Math . ceil ( nItems / columns ) ;
if ( rows > rowsNeeded ) {
height -= ( rows - rowsNeeded ) * ( itemSize + spacing ) ;
rows -= rows - rowsNeeded ;
}
// Remove space reserved for page controls and indicator if not used
if ( rows * columns >= nItems ) {
width -= horizontalNavigation ;
height -= verticalNavigation ;
}
width = Math . clamp ( width , minDialogWidth , maxDialogWidth ) ;
height = Math . min ( height , maxDialogHeight ) ;
layoutManager . columns _per _page = columns ;
layoutManager . rows _per _page = rows ;
layoutManager . fixedIconSize = iconSize ;
// Store data for further use
this . _width = width * scaleFactor ;
this . _height = height * scaleFactor ;
this . _folderAreaBox = folderAreaBox ;
this . _nItems = nItems ;
// Set fixed dialog size to prevent size instability
this . child . set _size ( this . _width , this . _height ) ;
this . _viewBox . set _style ( ` width: ${ this . _width - 2 * margin } px; height: ${ this . _height - 2 * margin } px; ` ) ;
this . _viewBox . set _size ( this . _width - 2 * margin , this . _height - 2 * margin ) ;
view . _redisplay ( ) ;
} ,
_zoomAndFadeIn ( ) {
let [ sourceX , sourceY ] =
this . _source . get _transformed _position ( ) ;
let [ dialogX , dialogY ] =
this . child . get _transformed _position ( ) ;
const sourceCenterX = sourceX + this . _source . width / 2 ;
const sourceCenterY = sourceY + this . _source . height / 2 ;
// this. covers the whole screen
let dialogTargetX = dialogX ;
let dialogTargetY = dialogY ;
const appDisplay = this . _source . _parentView ;
const folderAreaBox = this . _getFolderAreaBox ( ) ;
let folderAreaX = folderAreaBox . x1 ;
let folderAreaY = folderAreaBox . y1 ;
const folderAreaWidth = folderAreaBox . get _width ( ) ;
const folderAreaHeight = folderAreaBox . get _height ( ) ;
const folder = this . child ;
if ( opt . APP _GRID _FOLDER _CENTER ) {
dialogTargetX = folderAreaX + folderAreaWidth / 2 - folder . width / 2 ;
dialogTargetY = folderAreaY + ( folderAreaHeight / 2 - folder . height / 2 ) / 2 ;
} else {
const { pagePadding } = appDisplay . _grid . layoutManager ;
const hPadding = ( pagePadding . left + pagePadding . right ) / 2 ;
const vPadding = ( pagePadding . top + pagePadding . bottom ) / 2 ;
const minX = Math . min ( folderAreaX + hPadding , folderAreaX + ( folderAreaWidth - folder . width ) / 2 ) ;
const maxX = Math . max ( folderAreaX + folderAreaWidth - hPadding - folder . width , folderAreaX + folderAreaWidth / 2 - folder . width / 2 ) ;
const minY = Math . min ( folderAreaY + vPadding , folderAreaY + ( folderAreaHeight - folder . height ) / 2 ) ;
const maxY = Math . max ( folderAreaY + folderAreaHeight - vPadding - folder . height , folderAreaY + folderAreaHeight / 2 - folder . height / 2 ) ;
dialogTargetX = sourceCenterX - folder . width / 2 ;
dialogTargetX = Math . clamp ( dialogTargetX , minX , maxX ) ;
dialogTargetY = sourceCenterY - folder . height / 2 ;
dialogTargetY = Math . clamp ( dialogTargetY , minY , maxY ) ;
// keep the dialog in the appDisplay area
dialogTargetX = Math . clamp (
dialogTargetX ,
folderAreaX ,
folderAreaX + folderAreaWidth - folder . width
) ;
dialogTargetY = Math . clamp (
dialogTargetY ,
folderAreaY ,
folderAreaY + folderAreaHeight - folder . height
) ;
}
const dialogOffsetX = Math . round ( dialogTargetX - dialogX ) ;
const dialogOffsetY = Math . round ( dialogTargetY - dialogY ) ;
this . child . set ( {
translation _x : sourceX - dialogX ,
translation _y : sourceY - dialogY ,
scale _x : this . _source . width / this . child . width ,
scale _y : this . _source . height / this . child . height ,
opacity : 0 ,
} ) ;
2025-02-12 16:21:01 +01:00
// Add a short delay to account for the dialog update time
// and prevent incomplete animation that disrupts the user experience
const delay = 20 ;
2025-02-09 23:19:41 +01:00
this . child . ease ( {
2025-02-12 16:21:01 +01:00
delay ,
2025-02-09 23:19:41 +01:00
translation _x : dialogOffsetX ,
translation _y : dialogOffsetY ,
scale _x : 1 ,
scale _y : 1 ,
opacity : 255 ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
appDisplay . ease ( {
2025-02-12 16:21:01 +01:00
delay ,
2025-02-09 23:19:41 +01:00
opacity : 0 ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
if ( opt . SHOW _SEARCH _ENTRY ) {
Main . overview . searchEntry . ease ( {
opacity : 0 ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
}
this . _needsZoomAndFade = false ;
if ( this . _sourceMappedId === 0 ) {
this . _sourceMappedId = this . _source . connect (
'notify::mapped' , this . _zoomAndFadeOut . bind ( this ) ) ;
}
} ,
_zoomAndFadeOut ( ) {
if ( ! this . _isOpen )
return ;
if ( ! this . _source . mapped ) {
this . hide ( ) ;
return ;
}
// if the dialog was shown silently, skip animation
if ( this . scale _y < 1 ) {
this . _needsZoomAndFade = false ;
this . hide ( ) ;
this . _popdownCallbacks . forEach ( func => func ( ) ) ;
this . _popdownCallbacks = [ ] ;
return ;
}
let [ sourceX , sourceY ] =
this . _source . get _transformed _position ( ) ;
let [ dialogX , dialogY ] =
this . child . get _transformed _position ( ) ;
this . child . ease ( {
translation _x : sourceX - dialogX + this . child . translation _x ,
translation _y : sourceY - dialogY + this . child . translation _y ,
scale _x : this . _source . width / this . child . width ,
scale _y : this . _source . height / this . child . height ,
opacity : 0 ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _IN _QUAD ,
onComplete : ( ) => {
this . child . set ( {
translation _x : 0 ,
translation _y : 0 ,
scale _x : 1 ,
scale _y : 1 ,
opacity : 255 ,
} ) ;
this . hide ( ) ;
this . _popdownCallbacks . forEach ( func => func ( ) ) ;
this . _popdownCallbacks = [ ] ;
} ,
} ) ;
const appDisplay = this . _source . _parentView ;
appDisplay . ease ( {
opacity : 255 ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _IN _QUAD ,
} ) ;
if ( opt . SHOW _SEARCH _ENTRY ) {
Main . overview . searchEntry . ease ( {
opacity : 255 ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _IN _QUAD ,
} ) ;
}
this . _needsZoomAndFade = false ;
} ,
_setLighterBackground ( lighter ) {
let opacity = 255 ;
if ( this . _isOpen )
opacity = lighter ? 20 : 0 ;
_appDisplay . ease ( {
opacity ,
duration : FOLDER _DIALOG _ANIMATION _TIME ,
mode : Clutter . AnimationMode . EASE _OUT _QUAD ,
} ) ;
} ,
vfunc _key _press _event ( event ) {
if ( global . focus _manager . navigate _from _event ( event ) )
return Clutter . EVENT _STOP ;
return Clutter . EVENT _PROPAGATE ;
} ,
_showFolderLabel ( ) {
if ( this . _editButton . checked )
this . _editButton . checked = false ;
this . _maybeUpdateFolderName ( ) ;
this . _switchActor ( this . _entry , this . _folderNameLabel ) ;
// This line has been added in 47 to fix focus after editing the folder name
this . navigate _focus ( this , St . DirectionType . TAB _FORWARD , false ) ;
} ,
} ;
const AppIcon = {
after _ _init ( ) {
// update the app label behavior
this . _updateMultiline ( ) ;
} ,
// avoid accepting by placeholder when dragging active preview
// and also by icon if usage sorting is used
_canAccept ( source ) {
if ( source . _sourceItem )
source = source . _sourceItem ;
// Folders in folder are not supported
if ( ! ( _getViewFromIcon ( this ) instanceof AppDisplay . AppDisplay ) || ! this . opacity )
return false ;
const view = /* AppDisplay.*/ _getViewFromIcon ( source ) ;
return source !== this &&
( source instanceof this . constructor ) &&
// Include drops from folders
// (view instanceof AppDisplay.AppDisplay &&
( view &&
! opt . APP _GRID _USAGE ) ;
} ,
} ;
const AppViewItemCommon = {
_updateMultiline ( ) {
const { label } = this . icon ;
if ( label )
label . opacity = 255 ;
if ( ! this . _expandTitleOnHover || ! this . icon . label )
return ;
const { clutterText } = label ;
const isHighlighted = this . has _key _focus ( ) || this . hover || this . _forcedHighlight ;
if ( opt . APP _GRID _NAMES _MODE === 2 && this . _expandTitleOnHover ) { // !_expandTitleOnHover indicates search result icon
label . opacity = isHighlighted || ! this . app ? 255 : 0 ;
}
if ( isHighlighted )
this . get _parent ( ) ? . set _child _above _sibling ( this , null ) ;
if ( ! opt . APP _GRID _NAMES _MODE ) {
const layout = clutterText . get _layout ( ) ;
if ( ! layout . is _wrapped ( ) && ! layout . is _ellipsized ( ) )
return ;
}
label . remove _transition ( 'allocation' ) ;
const id = label . connect ( 'notify::allocation' , ( ) => {
label . restore _easing _state ( ) ;
label . disconnect ( id ) ;
} ) ;
const expand = opt . APP _GRID _NAMES _MODE === 1 || this . _forcedHighlight || this . hover || this . has _key _focus ( ) ;
label . save _easing _state ( ) ;
label . set _easing _duration ( expand
? APP _ICON _TITLE _EXPAND _TIME
: APP _ICON _TITLE _COLLAPSE _TIME ) ;
clutterText . set ( {
line _wrap : expand ,
line _wrap _mode : expand ? Pango . WrapMode . WORD _CHAR : Pango . WrapMode . NONE ,
ellipsize : expand ? Pango . EllipsizeMode . NONE : Pango . EllipsizeMode . END ,
} ) ;
} ,
// support active preview icons
acceptDrop ( source , _actor , x ) {
if ( opt . APP _GRID _USAGE )
return DND . DragMotionResult . NO _DROP ;
this . _setHoveringByDnd ( false ) ;
if ( ! this . _canAccept ( source ) )
return false ;
if ( this . _withinLeeways ( x ) )
return false ;
// added - remove app from the source folder after dnd to other folder
let view = /* AppDisplay.*/ _getViewFromIcon ( source ) ;
if ( view instanceof AppDisplay . FolderView )
view . removeApp ( source . app ) ;
return true ;
} ,
} ;
const PageIndicatorsCommon = {
after _setNPages ( ) {
this . visible = true ;
this . opacity = this . _nPages > 1 ? 255 : 0 ;
} ,
} ;