Skip to content

Commit 80fcc2e

Browse files
authored
Option to put close icons at the start of the tab label on macOS (eclipse-theia#15103)
Add a new window.tabCloseIconPlacement preference for whether to present the Close (X) icon in tab titles on the left or the right of the tab in conformity with the OS's native tab controls. Render the tab title with the icon on the left or right accordingly. Show the new preference in the Settings UI on macOS platform only. Default to the right as on other platforms to maintain current appearance. Fixes eclipse-theia/theia-ide#460 Signed-off-by: Christian W. Damus <[email protected]>
1 parent ec9a140 commit 80fcc2e

File tree

3 files changed

+58
-17
lines changed

3 files changed

+58
-17
lines changed

packages/core/src/browser/core-preferences.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ export const corePreferenceSchema: PreferenceSchema = {
121121
scope: 'application',
122122
markdownDescription: nls.localizeByDefault('Separator used by {0}.', '`#window.title#`')
123123
},
124+
'window.tabCloseIconPlacement': {
125+
type: 'string',
126+
enum: ['end', 'start'],
127+
enumDescriptions: [
128+
nls.localize('theia/core/window/tabCloseIconPlacement/end', 'Place the close icon at the end of the label. In left-to-right languages, this is the right side of the tab.'),
129+
nls.localize('theia/core/window/tabCloseIconPlacement/start', 'Place the close icon at the start of the label. In left-to-right languages, this is the left side of the tab.'),
130+
],
131+
default: 'end',
132+
scope: 'application',
133+
description: nls.localize('theia/core/window/tabCloseIconPlacement/description', 'Place the close icons on tab titles at the start or end of the tab. The default is end on all platforms.'),
134+
included: isOSX
135+
},
124136
'window.secondaryWindowPlacement': {
125137
type: 'string',
126138
enum: ['originalSize', 'halfWidth', 'fullSize'],
@@ -305,6 +317,7 @@ export interface CoreConfiguration {
305317
'window.menuBarVisibility': 'classic' | 'visible' | 'hidden' | 'compact';
306318
'window.title': string;
307319
'window.titleSeparator': string;
320+
'window.tabCloseIconPlacement': 'end' | 'start';
308321
'workbench.list.openMode': 'singleClick' | 'doubleClick';
309322
'workbench.commandPalette.history': number;
310323
'workbench.editor.highlightModifiedTabs': boolean;

packages/core/src/browser/shell/tab-bars.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ export class TabBarRenderer extends TabBar.Renderer {
119119
}
120120
}));
121121
}
122+
if (this.corePreferences) {
123+
this.toDispose.push(
124+
this.corePreferences.onPreferenceChanged(event => {
125+
if (event.preferenceName === 'window.tabCloseIconPlacement' && this._tabBar) {
126+
this._tabBar.update();
127+
}
128+
})
129+
);
130+
}
122131
}
123132

124133
dispose(): void {
@@ -160,22 +169,42 @@ export class TabBarRenderer extends TabBar.Renderer {
160169
* @returns {VirtualElement} The virtual element of the rendered tab.
161170
*/
162171
override renderTab(data: SideBarRenderData, isInSidePanel?: boolean, isPartOfHiddenTabBar?: boolean): VirtualElement {
172+
// Putting the close icon at the start is only pertinent to the horizontal orientation
173+
const isHorizontal = this.tabBar?.orientation === 'horizontal';
174+
const tabCloseIconStart = isHorizontal && this.corePreferences?.['window.tabCloseIconPlacement'] === 'start';
175+
163176
const title = data.title;
164177
const id = this.createTabId(title, isPartOfHiddenTabBar);
165178
const key = this.createTabKey(data);
166179
const style = this.createTabStyle(data);
167-
const className = this.createTabClass(data);
180+
const className = `${this.createTabClass(data)}${tabCloseIconStart ? ' closeIcon-start' : ''}`;
168181
const dataset = this.createTabDataset(data);
169182
const closeIconTitle = data.title.className.includes(PINNED_CLASS)
170183
? nls.localizeByDefault('Unpin')
171184
: nls.localizeByDefault('Close');
172185

173-
const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic')
186+
const hover = isHorizontal && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic'
174187
? { title: title.caption }
175188
: {
176189
onmouseenter: this.handleMouseEnterEvent
177190
};
178191

192+
const tabLabel = h.div(
193+
{ className: 'theia-tab-icon-label' },
194+
this.renderIcon(data, isInSidePanel),
195+
this.renderLabel(data, isInSidePanel),
196+
this.renderTailDecorations(data, isInSidePanel),
197+
this.renderBadge(data, isInSidePanel),
198+
this.renderLock(data, isInSidePanel)
199+
);
200+
const tabCloseIcon = h.div({
201+
className: 'lm-TabBar-tabCloseIcon action-label',
202+
title: closeIconTitle,
203+
onclick: this.handleCloseClickEvent,
204+
});
205+
206+
const tabContents = tabCloseIconStart ? [tabCloseIcon, tabLabel] : [tabLabel, tabCloseIcon];
207+
179208
return h.li(
180209
{
181210
...hover,
@@ -187,19 +216,7 @@ export class TabBarRenderer extends TabBar.Renderer {
187216
e.preventDefault();
188217
}
189218
},
190-
h.div(
191-
{ className: 'theia-tab-icon-label' },
192-
this.renderIcon(data, isInSidePanel),
193-
this.renderLabel(data, isInSidePanel),
194-
this.renderTailDecorations(data, isInSidePanel),
195-
this.renderBadge(data, isInSidePanel),
196-
this.renderLock(data, isInSidePanel)
197-
),
198-
h.div({
199-
className: 'lm-TabBar-tabCloseIcon action-label',
200-
title: closeIconTitle,
201-
onclick: this.handleCloseClickEvent
202-
})
219+
...tabContents
203220
);
204221
}
205222

packages/core/src/browser/style/tabs.css

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@
237237
-ms-user-select: none;
238238
}
239239

240+
.lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable.closeIcon-start > .lm-TabBar-tabCloseIcon,
241+
.lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned.closeIcon-start > .lm-TabBar-tabCloseIcon {
242+
margin-left: inherit;
243+
margin-right: 4px;
244+
}
245+
240246
.lm-TabBar.theia-app-centers.dynamic-tabs .lm-TabBar-tab.lm-mod-closable>.lm-TabBar-tabCloseIcon,
241247
.lm-TabBar.theia-app-centers.dynamic-tabs .lm-TabBar-tab.theia-mod-pinned>.lm-TabBar-tabCloseIcon {
242248
/* hide close icon for dynamic tabs strategy*/
@@ -254,11 +260,16 @@
254260
background-color: rgba(50%, 50%, 50%, 0.2);
255261
}
256262

257-
.lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable,
258-
.lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned {
263+
.lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable:not(.closeIcon-start),
264+
.lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned:not(.closeIcon-start) {
259265
padding-right: 4px;
260266
}
261267

268+
.lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable.closeIcon-start,
269+
.lm-TabBar.theia-app-centers .lm-TabBar-tab.theia-mod-pinned.closeIcon-start {
270+
padding-left: 4px;
271+
}
272+
262273
.lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable:not(.theia-mod-dirty):hover>.lm-TabBar-tabCloseIcon:before,
263274
.lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable:not(.theia-mod-dirty).lm-TabBar-tab.lm-mod-current>.lm-TabBar-tabCloseIcon:before,
264275
.lm-TabBar.theia-app-centers .lm-TabBar-tab.lm-mod-closable.theia-mod-dirty>.lm-TabBar-tabCloseIcon:hover:before {

0 commit comments

Comments
 (0)