Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions src/platform/javascript/BrowserUiFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { SettingsSerializer } from '@src/generated/SettingsSerializer';
import { WebPlatform } from '@src/platform/javascript/WebPlatform';
import { AlphaTabError, AlphaTabErrorType } from '@src/AlphaTabError';
import { AlphaSynthAudioWorkletOutput } from '@src/platform/javascript/AlphaSynthAudioWorkletOutput';
import { ScalableHtmlElementContainer } from './ScalableHtmlElementContainer';

/**
* @target web
Expand Down Expand Up @@ -577,9 +578,13 @@ export class BrowserUiFacade implements IUiFacade<unknown> {
cursorWrapper.classList.add('at-cursors');
let selectionWrapper: HTMLElement = document.createElement('div');
selectionWrapper.classList.add('at-selection');
let barCursor: HTMLElement = document.createElement('div');

const barCursorContainer = this.createScalingElement();
const beatCursorContainer = this.createScalingElement();

let barCursor: HTMLElement = barCursorContainer.element;
barCursor.classList.add('at-cursor-bar');
let beatCursor: HTMLElement = document.createElement('div');
let beatCursor: HTMLElement = beatCursorContainer.element;
beatCursor.classList.add('at-cursor-beat');
// required css styles
element.style.position = 'relative';
Expand All @@ -596,16 +601,18 @@ export class BrowserUiFacade implements IUiFacade<unknown> {
barCursor.style.left = '0';
barCursor.style.top = '0';
barCursor.style.willChange = 'transform';
barCursor.style.width = '1px';
barCursor.style.height = '1px';
barCursorContainer.width = 1;
barCursorContainer.height = 1;
barCursorContainer.setBounds(0, 0, 1, 1);

beatCursor.style.position = 'absolute';
beatCursor.style.transition = 'all 0s linear';
beatCursor.style.left = '0';
beatCursor.style.top = '0';
beatCursor.style.willChange = 'transform';
beatCursor.style.width = '3px';
beatCursor.style.height = '1px';
beatCursorContainer.width = 3;
beatCursorContainer.height = 1;
beatCursorContainer.setBounds(0, 0, 1, 1);

// add cursors to UI
element.insertBefore(cursorWrapper, element.firstChild);
Expand All @@ -614,8 +621,8 @@ export class BrowserUiFacade implements IUiFacade<unknown> {
cursorWrapper.appendChild(beatCursor);
return new Cursors(
new HtmlElementContainer(cursorWrapper),
new HtmlElementContainer(barCursor),
new HtmlElementContainer(beatCursor),
barCursorContainer,
beatCursorContainer,
new HtmlElementContainer(selectionWrapper)
);
}
Expand Down Expand Up @@ -676,11 +683,24 @@ export class BrowserUiFacade implements IUiFacade<unknown> {
}

public createSelectionElement(): IContainer | null {
let element: HTMLElement = document.createElement('div');
return this.createScalingElement();
}

public createScalingElement(): ScalableHtmlElementContainer {
const element = document.createElement('div');
element.style.position = 'absolute';
element.style.width = '1px';
element.style.height = '1px';
return new HtmlElementContainer(element);

// to typical browser zoom levels are:
// Chromium: 25,33,50,67,75,80,90, 100, 110, 125, 150, 175, 200, 250, 300, 400, 500
// Firefox: 30, 50, 67, 80, 90, 100, 110, 120, 133, 150, 170, 200, 240, 300, 400, 500

// with having a 100x100 scaling container we should be able to provide appropriate scaling

const container = new ScalableHtmlElementContainer(element, 100, 100);
container.width = 1;
container.height = 1;
container.setBounds(0, 0, 1, 1);
return container;
}

public scrollToY(element: IContainer, scrollTargetY: number, speed: number): void {
Expand Down
18 changes: 9 additions & 9 deletions src/platform/javascript/HtmlElementContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,27 +141,27 @@ export class HtmlElementContainer implements IContainer {
this.setBounds(x, NaN, NaN, NaN);
}

private _lastBounds: Bounds = new Bounds();
protected lastBounds: Bounds = new Bounds();

public setBounds(x: number, y: number, w: number, h: number) {
if (isNaN(x)) {
x = this._lastBounds.x;
x = this.lastBounds.x;
}
if (isNaN(y)) {
y = this._lastBounds.y;
y = this.lastBounds.y;
}
if (isNaN(w)) {
w = this._lastBounds.w;
w = this.lastBounds.w;
}
if (isNaN(h)) {
h = this._lastBounds.h;
h = this.lastBounds.h;
}
this.element.style.transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
this.element.style.transformOrigin = 'top left';
this._lastBounds.x = x;
this._lastBounds.y = y;
this._lastBounds.w = w;
this._lastBounds.h = h;
this.lastBounds.x = x;
this.lastBounds.y = y;
this.lastBounds.w = w;
this.lastBounds.h = h;
}

/**
Expand Down
72 changes: 72 additions & 0 deletions src/platform/javascript/ScalableHtmlElementContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { HtmlElementContainer } from './HtmlElementContainer';

/**
* An IContainer implementation which can be used for cursors and select ranges
* where browser scaling is relevant.
*
* The problem is that with having 1x1 pixel elements which are sized then to the actual size with a
* scale transform this cannot be combined properly with a browser zoom.
*
* The browser will apply first the browser zoom to the 1x1px element and then apply the scale leaving it always
* at full scale instead of a 50% browser zoom.
*
* This is solved in this container by scaling the element first up to a higher degree (as specified)
* so that the browser can do a scaling according to typical zoom levels and then the scaling will work.
* @target web
*/
export class ScalableHtmlElementContainer extends HtmlElementContainer {
private _xscale: number;
private _yscale: number;

public constructor(element: HTMLElement, xscale: number, yscale: number) {
super(element);
this._xscale = xscale;
this._yscale = yscale;
}

public override get width(): number {
return this.element.offsetWidth / this._xscale;
}

public override set width(value: number) {
this.element.style.width = value * this._xscale + 'px';
}

public override get height(): number {
return this.element.offsetHeight / this._yscale;
}

public override set height(value: number) {
if (value >= 0) {
this.element.style.height = value * this._yscale + 'px';
} else {
this.element.style.height = '100%';
}
}

public override setBounds(x: number, y: number, w: number, h: number) {
if (isNaN(x)) {
x = this.lastBounds.x;
}
if (isNaN(y)) {
y = this.lastBounds.y;
}
if (isNaN(w)) {
w = this.lastBounds.w;
} else {
w = w / this._xscale;
}
if (isNaN(h)) {
h = this.lastBounds.h;
} else {
h = h / this._yscale;
}

this.element.style.transform = `translate(${x}px, ${y}px) scale(${w}, ${h})`;
this.element.style.transformOrigin = 'top left';
this.lastBounds.x = x;
this.lastBounds.y = y;
this.lastBounds.w = w;
this.lastBounds.h = h;
}
}