Skip to content

Commit

Permalink
Add preference for visualPreview on hover (#12648)
Browse files Browse the repository at this point in the history
* Add preference for visualPreview on hover

Made window.tabbar.enhancedPreview an enum preference with these options:
- classic: a simple unstyled preview, containing the name
- enhanced: a styled preview containing title and caption (#12350)
- visual: the enhanced preview +  visual preview of the view
Extended the hover service so that it supports the visual preview.
Added the `PreviewableWidget` interface.
Widgets, implementing this interface, can be previewed once they were loaded.
The loaded flag is set to true when a widget is set to active.
Widgets implementing the interface can specify how the preview should look like.
The default is simply the node of the widget.
Webviews are currently not previewable, as they raise some challenges.
For example, scripts in the webviews would need to be handled/blocked.
Therefore, an approach for webviews should be tackled in a follow up.

Fixes #12646

Contributed on behalf of STMicroelectronics

* Address review feedback

Adjust changelog and preference description

Contributed on behalf of STMicroelectronics

* Scale previews to 16:9 aspect ratio

This way the size of the previews is more uniform.
If the view is wider than 16:9 the scaling is according to the width.
If the view is longer  than 16:9 the scaling is according to the height.
The other dimension is then cropped.

Contributed on behalf of STMicroelectronics

* Fix small issue with hover-title and -caption css

Before the two fields ignored the max-width.

Contributed on behalf of STMicroelectronics

* Fix preview width to 300px

Contributed on behalf of STMicroelectronics

* Rename div's css class

Contributed on behalf of STMicroelectronics
  • Loading branch information
sgraband authored Jul 27, 2023
1 parent 9f177b5 commit 002fd17
Show file tree
Hide file tree
Showing 10 changed files with 364 additions and 126 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@
- [cli] added `check:theia-extensions` which checks the uniqueness of Theia extension versions [#12596](https://github.com/eclipse-theia/theia/pull/12596) - Contributed on behalf of STMicroelectronics
- [vscode] Support AuthenticationForceNewSessionOptions and detail message [#12752](https://github.com/eclipse-theia/theia/pull/12752) - Contributed on behalf of STMicroelectronics
- [vscode] Add support for the TaskPresentationOptions close property [#12749](https://github.com/eclipse-theia/theia/pull/12749) - Contributed on behalf of STMicroelectronics
- [core] add support to render a visual preview of a tab while hovering [#12648](https://github.com/eclipse-theia/theia/pull/12648) - Contributed on behalf of STMicroelectronics

<a name="breaking_changes_1.40.0">[Breaking Changes:](#breaking_changes_1.40.0)</a>

- [workspace] split `CommonWorkspaceUtils` into `WorkspaceFileService` and `UntitledWorkspaceService` [#12420](https://github.com/eclipse-theia/theia/pull/12420)
- [preferences] changed the `window.tabbar.enhancedPreview` preference from boolean to enum: [#12648](https://github.com/eclipse-theia/theia/pull/12648) - Contributed on behalf of STMicroelectronics
- `classic`: Display a simple preview of the tab with basic information.
- `enhanced`: Display an enhanced preview of the tab with additional information. (The behavior introduced in [#12350](https://github.com/eclipse-theia/theia/pull/12350))
- `visual`: Display a visual preview of the tab. (The preview support was added with this PR)


## v1.39.0 - 06/29/2023

Expand Down
14 changes: 10 additions & 4 deletions packages/core/src/browser/core-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,15 @@ export const corePreferenceSchema: PreferenceSchema = {
markdownDescription: nls.localizeByDefault('Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`.')
},
'window.tabbar.enhancedPreview': {
type: 'boolean',
default: false,
description: nls.localize('theia/core/enhancedPreview', 'Controls whether more information about the tab should be displayed in horizontal tab bars.')
type: 'string',
enum: ['classic', 'enhanced', 'visual'],
markdownEnumDescriptions: [
nls.localize('theia/core/enhancedPreview/classic', 'Display a simple preview of the tab with basic information.'),
nls.localize('theia/core/enhancedPreview/enhanced', 'Display an enhanced preview of the tab with additional information.'),
nls.localize('theia/core/enhancedPreview/visual', 'Display a visual preview of the tab.'),
],
default: 'classic',
description: nls.localize('theia/core/enhancedPreview', 'Controls what information about the tab should be displayed in horizontal tab bars, when hovering.')
},
'window.menuBarVisibility': {
type: 'string',
Expand Down Expand Up @@ -263,7 +269,7 @@ export interface CoreConfiguration {
'breadcrumbs.enabled': boolean;
'files.encoding': string;
'keyboard.dispatch': 'code' | 'keyCode';
'window.tabbar.enhancedPreview': boolean;
'window.tabbar.enhancedPreview': 'classic' | 'enhanced' | 'visual';
'window.menuBarVisibility': 'classic' | 'visible' | 'hidden' | 'compact';
'window.title': string;
'window.titleSeparator': string;
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/browser/hover-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export interface HoverRequest {
* Used to style certain boxes different e.g. for the extended tab preview.
*/
cssClasses?: string[]
/**
* A function to render a visual preview on the hover.
* Function that takes the desired width and returns a HTMLElement to be rendered.
*/
visualPreview?: (width: number) => HTMLElement | undefined;
}

@injectable()
Expand Down Expand Up @@ -106,25 +111,38 @@ export class HoverService {

protected async renderHover(request: HoverRequest): Promise<void> {
const host = this.hoverHost;
let firstChild: HTMLElement | undefined;
const { target, content, position, cssClasses } = request;
if (cssClasses) {
host.classList.add(...cssClasses);
}
this.hoverTarget = target;
if (content instanceof HTMLElement) {
host.appendChild(content);
firstChild = content;
} else if (typeof content === 'string') {
host.textContent = content;
} else {
const renderedContent = this.markdownRenderer.render(content);
this.disposeOnHide.push(renderedContent);
host.appendChild(renderedContent.element);
firstChild = renderedContent.element;
}
// browsers might insert linebreaks when the hover appears at the edge of the window
// resetting the position prevents that
host.style.left = '0px';
host.style.top = '0px';
document.body.append(host);

if (request.visualPreview) {
// If just a string is being rendered use the size of the outer box
const width = firstChild ? firstChild.offsetWidth : this.hoverHost.offsetWidth;
const visualPreview = request.visualPreview(width);
if (visualPreview) {
host.appendChild(visualPreview);
}
}

await animationFrame(); // Allow the browser to size the host
const updatedPosition = this.setHostPosition(target, host, position);

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { nls } from '../../common/nls';
import { SecondaryWindowHandler } from '../secondary-window-handler';
import URI from '../../common/uri';
import { OpenerService } from '../opener-service';
import { PreviewableWidget } from '../widgets/previewable-widget';

/** The class name added to ApplicationShell instances. */
const APPLICATION_SHELL_CLASS = 'theia-ApplicationShell';
Expand Down Expand Up @@ -1186,6 +1187,9 @@ export class ApplicationShell extends Widget {
newValue['onCloseRequest'](msg);
};
this.toDisposeOnActiveChanged.push(Disposable.create(() => newValue['onCloseRequest'] = onCloseRequest));
if (PreviewableWidget.is(newValue)) {
newValue.loaded = true;
}
}
this.onDidChangeActiveWidgetEmitter.fire(args);
}
Expand Down
60 changes: 58 additions & 2 deletions packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { HoverService } from '../hover-service';
import { Root, createRoot } from 'react-dom/client';
import { SelectComponent } from '../widgets/select-component';
import { createElement } from 'react';
import { PreviewableWidget } from '../widgets/previewable-widget';

/** The class name added to hidden content nodes, which are required to render vertical side bars. */
const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
Expand Down Expand Up @@ -165,7 +166,9 @@ export class TabBarRenderer extends TabBar.Renderer {
? nls.localizeByDefault('Unpin')
: nls.localizeByDefault('Close');

const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && !this.corePreferences?.['window.tabbar.enhancedPreview']) ? { title: title.caption } : {
const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic')
? { title: title.caption }
: {
onmouseenter: this.handleMouseEnterEvent
};

Expand Down Expand Up @@ -510,6 +513,58 @@ export class TabBarRenderer extends TabBar.Renderer {
return hoverBox;
};

protected renderVisualPreview(desiredWidth: number, title: Title<Widget>): HTMLElement | undefined {
const widget = title.owner;
// Check that the widget is not currently shown, is a PreviewableWidget and it was already loaded before
if (this.tabBar && this.tabBar.currentTitle !== title && PreviewableWidget.isPreviewable(widget)) {
const html = document.getElementById(widget.id);
if (html) {
const previewNode: Node | undefined = widget.getPreviewNode();
if (previewNode) {
const clonedNode = previewNode.cloneNode(true);
const visualPreviewDiv = document.createElement('div');
visualPreviewDiv.classList.add('enhanced-preview-container');
// Add the clonedNode and get it from the children to have a HTMLElement instead of a Node
visualPreviewDiv.append(clonedNode);
const visualPreview = visualPreviewDiv.children.item(visualPreviewDiv.children.length - 1);
if (visualPreview instanceof HTMLElement) {
visualPreview.classList.remove('p-mod-hidden');
visualPreview.classList.add('enhanced-preview');
visualPreview.id = `preview:${widget.id}`;

// Use the current visible editor as a fallback if not available
const height: number = visualPreview.style.height === '' ? this.tabBar.currentTitle!.owner.node.offsetHeight : parseFloat(visualPreview.style.height);
const width: number = visualPreview.style.width === '' ? this.tabBar.currentTitle!.owner.node.offsetWidth : parseFloat(visualPreview.style.width);
const desiredRatio = 9 / 16;
const desiredHeight = desiredWidth * desiredRatio;
const ratio = height / width;
visualPreviewDiv.style.width = `${desiredWidth}px`;
visualPreviewDiv.style.height = `${desiredHeight}px`;

// If the view is wider than the desiredRatio scale the width and crop the height. If the view is longer its the other way around.
const scale = ratio < desiredRatio ? (desiredHeight / height) : (desiredWidth / width);
visualPreview.style.transform = `scale(${scale},${scale})`;
visualPreview.style.removeProperty('top');
visualPreview.style.removeProperty('left');

// Copy canvases (They are cloned empty)
const originalCanvases = html.getElementsByTagName('canvas');
const previewCanvases = visualPreview.getElementsByTagName('canvas');
// If this is not given, something went wrong during the cloning
if (originalCanvases.length === previewCanvases.length) {
for (let i = 0; i < originalCanvases.length; i++) {
previewCanvases[i].getContext('2d')?.drawImage(originalCanvases[i], 0, 0);
}
}

return visualPreviewDiv;
}
}
}
}
return undefined;
}

protected handleMouseEnterEvent = (event: MouseEvent) => {
if (this.tabBar && this.hoverService && event.currentTarget instanceof HTMLElement) {
const id = event.currentTarget.id;
Expand All @@ -520,7 +575,8 @@ export class TabBarRenderer extends TabBar.Renderer {
content: this.renderEnhancedPreview(title),
target: event.currentTarget,
position: 'bottom',
cssClasses: ['extended-tab-preview']
cssClasses: ['extended-tab-preview'],
visualPreview: this.corePreferences?.['window.tabbar.enhancedPreview'] === 'visual' ? width => this.renderVisualPreview(width, title) : undefined
});
} else {
this.hoverService.requestHover({
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/browser/style/hover-service.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

:root {
--theia-hover-max-width: 500px;
--theia-hover-preview-width: 300px;
}

.theia-hover {
Expand Down Expand Up @@ -96,4 +97,5 @@

.theia-hover.extended-tab-preview {
border-radius: 10px;
width: var(--theia-hover-preview-width);
}
Loading

0 comments on commit 002fd17

Please sign in to comment.