Skip to content

Commit

Permalink
Fullscreen mode (#29)
Browse files Browse the repository at this point in the history
* fullscreen mode

* +
  • Loading branch information
lifeart authored Nov 13, 2024
1 parent 5c5dc7b commit 3dfdac2
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 43 deletions.
56 changes: 52 additions & 4 deletions demo/index.js

Large diffs are not rendered by default.

60 changes: 54 additions & 6 deletions dist/index.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions dist/types/plugins/text.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export interface IText extends IShapeBase {
}
export declare class TextToolPlugin extends BasePlugin<IText> implements ToolPlugin<IText> {
name: keyof ShapeMap;
private activePopup;
handleKeyDown: (_e: KeyboardEvent) => undefined;
move(shape: IText, dx: number, dy: number): IText;
onActivate(): void;
onDeactivate(): void;
Expand All @@ -16,5 +18,7 @@ export declare class TextToolPlugin extends BasePlugin<IText> implements ToolPlu
onPointerDown(event: PointerEvent): void;
onPointerMove(event: PointerEvent): void;
normalize(shape: IText, canvasWidth: number, canvasHeight: number): IText;
private destroyPopup;
private createTextInputPopup;
onPointerUp(event: PointerEvent): void;
}
4 changes: 4 additions & 0 deletions dist/types/ui.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
import { AnnotationTool } from "./core";
export declare const playerControlsDefaultStyle = "position: relative; top: 0px; left: 0px; z-index: 2;";
export declare const uiContainerDefaultStyle = "position: absolute; top: -40px; left: 0px; z-index: 2; display: block;";
export declare const playerControlsFullScreenStyle = "position: absolute;left: 0px;bottom: 5px;width: 100%;z-index: 2;";
export declare const uiContainerFullScreenStyle = "position: absolute; top: 0; left: 0px; z-index: 2; display: block;";
export declare function initUI(this: AnnotationTool): void;
2 changes: 2 additions & 0 deletions dist/types/ui/toggle-fullscreen-button.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import type { AnnotationTool } from "../core";
export declare function createFullscreenButton(tool: AnnotationTool): HTMLButtonElement;
69 changes: 62 additions & 7 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IShape, ShapeMap, Tool, plugins, PluginInstances } from "./plugins";
import { ToolPlugin } from "./plugins/base";
import { detectFrameRate } from "./utils/detect-framerate";
import { VideoFrameBuffer } from "./plugins/utils/video-frame-buffer";
import { playerControlsDefaultStyle, playerControlsFullScreenStyle, uiContainerDefaultStyle, uiContainerFullScreenStyle } from "./ui";

const pixelRatio = window.devicePixelRatio || 1;

Expand Down Expand Up @@ -424,23 +425,77 @@ export class AnnotationTool extends AnnotationToolBase<IShape> {
const rawWidth = parseInt(style.width, 10);
const video = this.videoElement as HTMLVideoElement;
const trueAspectRatio = video.videoWidth / video.videoHeight;

if (isNaN(rawWidth) || !video.videoWidth || !video.videoHeight) {
this.isCanvasInitialized = false;
this.setCanvasSettings();
return false;
this.isCanvasInitialized = false;
this.setCanvasSettings();
return false;
}
const width = Math.min(rawWidth, video.videoWidth);
const height = Math.floor(width / trueAspectRatio);
video.style.width = `${width}px`;
video.style.height = `${height}px`;

// Get the container dimensions
const container = video.parentElement;
const isFullscreen = !!document.fullscreenElement;
let width = Math.min(rawWidth, video.videoWidth);
let height = Math.floor(width / trueAspectRatio);

if (isFullscreen && container) {
// Calculate dimensions maintaining aspect ratio in fullscreen
const CONTROLS_HEIGHT = 50;
const TOOLS_HEIGHT = 40;
const containerWidth = window.innerWidth;
const containerHeight = window.innerHeight - (CONTROLS_HEIGHT + TOOLS_HEIGHT);
const containerRatio = containerWidth / containerHeight;

if (containerRatio > trueAspectRatio) {
// Container is wider than video
height = containerHeight;
width = height * trueAspectRatio;
} else {
// Container is taller than video
width = containerWidth;
height = width / trueAspectRatio;
}

// Ensure video is centered and sized correctly
video.style.width = `${width}px`;
video.style.height = `${height}px`;
video.style.marginTop = `${TOOLS_HEIGHT}px`;
video.style.marginBottom = `${CONTROLS_HEIGHT}px`;
} else {
// Normal mode sizing
video.style.width = `${width}px`;
video.style.height = `${height}px`;
video.style.marginTop = '';
video.style.marginBottom = '';
}

if (isFullscreen) {
this.playerControlsContainer.style.cssText = playerControlsFullScreenStyle;
this.uiContainer.style.cssText = uiContainerFullScreenStyle;
} else {
this.playerControlsContainer.style.cssText = playerControlsDefaultStyle;
this.uiContainer.style.cssText = uiContainerDefaultStyle;
}

// Update canvas dimensions
this.isCanvasInitialized = video.videoWidth > 0 && video.videoHeight > 0;
this.canvas.width = width * this.pixelRatio;
this.canvas.height = height * this.pixelRatio;
this.canvas.style.width = `${width}px`;
this.canvas.style.height = `${height}px`;

// Match canvas position to video
this.canvas.style.position = 'absolute';
this.canvas.style.top = video.style.marginTop || '0';
this.canvas.style.left = '0';

// Store enforced size for other calculations
this.enforcedCanvasSize = { width, height };

// Set up the canvas context
this.ctx.scale(this.pixelRatio, this.pixelRatio);
this.setCanvasSettings();

return true;
}
setCanvasSize() {
Expand Down
172 changes: 160 additions & 12 deletions src/plugins/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export class TextToolPlugin
implements ToolPlugin<IText>
{
name = "text" as keyof ShapeMap;
private activePopup: HTMLDivElement | null = null;
handleKeyDown = (_e: KeyboardEvent) => void(0);

move(shape: IText, dx: number, dy: number) {
shape.x += dx;
shape.y += dy;
Expand All @@ -23,6 +26,7 @@ export class TextToolPlugin
this.isDrawing = true;
}
onDeactivate() {
this.destroyPopup();
this.annotationTool.canvas.style.cursor = "default";
this.isDrawing = false;
}
Expand Down Expand Up @@ -71,19 +75,163 @@ export class TextToolPlugin
y: shape.y / canvasHeight,
};
}
private destroyPopup() {
if (this.activePopup) {
this.annotationTool.canvas.parentElement?.removeChild(this.activePopup);
this.activePopup = null;
// Remove any lingering event listeners
document.removeEventListener('keydown', this.handleKeyDown);
}
}
private createTextInputPopup(x: number, y: number) {
// Destroy any existing popup first
this.destroyPopup();

// Create popup container
const popup = document.createElement('div');
this.activePopup = popup;
popup.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
z-index: 1000;
min-width: 280px;
`;

// Create text input
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Enter text to draw';
input.style.cssText = `
display: block;
width: 100%;
padding: 8px 12px;
margin-bottom: 16px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
line-height: 20px;
box-sizing: border-box;
outline: none;
transition: border-color 0.2s;
`;
input.addEventListener('focus', () => {
input.style.borderColor = '#007bff';
});
input.addEventListener('blur', () => {
input.style.borderColor = '#ddd';
});

// Create button container
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = `
display: flex;
gap: 12px;
justify-content: flex-end;
`;

// Common button styles
const buttonStyles = `
height: 36px;
min-width: 80px;
padding: 0 16px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: opacity 0.2s;
display: inline-flex;
align-items: center;
justify-content: center;
`;

// Create Cancel button
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.style.cssText = `
${buttonStyles}
background: #f0f0f0;
color: #333;
`;
cancelButton.addEventListener('mouseover', () => {
cancelButton.style.opacity = '0.8';
});
cancelButton.addEventListener('mouseout', () => {
cancelButton.style.opacity = '1';
});

// Create OK button
const okButton = document.createElement('button');
okButton.textContent = 'OK';
okButton.style.cssText = `
${buttonStyles}
background: #007bff;
color: white;
`;
okButton.addEventListener('mouseover', () => {
okButton.style.opacity = '0.8';
});
okButton.addEventListener('mouseout', () => {
okButton.style.opacity = '1';
});

const closePopup = () => {
this.destroyPopup();
};

const handleSave = () => {
const inputText = input.value.trim();
if (inputText) {
this.save({
type: "text",
x,
y,
text: inputText,
strokeStyle: this.ctx.strokeStyle,
fillStyle: this.ctx.fillStyle,
lineWidth: this.ctx.lineWidth,
});
this.annotationTool.currentTool = null;
}
closePopup();
};

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
closePopup();
} else if (e.key === 'Enter') {
handleSave();
}
};
this.handleKeyDown = handleKeyDown as (e: KeyboardEvent) => undefined;

okButton.onclick = handleSave;
cancelButton.onclick = closePopup;
input.onkeyup = handleKeyDown;

// Add event listener for ESC key
document.addEventListener('keydown', handleKeyDown);

buttonContainer.appendChild(cancelButton);
buttonContainer.appendChild(okButton);
popup.appendChild(input);
popup.appendChild(buttonContainer);

// Insert popup after canvas in its container
this.annotationTool.canvas.parentElement?.appendChild(popup);

requestAnimationFrame(() => {
input.focus();
});
}
onPointerUp(event: PointerEvent) {
const { x, y } = this.annotationTool.getRelativeCoords(event);
const inputText = prompt("Enter the text to be drawn:");
if (inputText !== null) {
this.save({
type: "text",
x,
y,
text: inputText,
strokeStyle: this.ctx.strokeStyle,
fillStyle: this.ctx.fillStyle,
lineWidth: this.ctx.lineWidth,
});
}
this.createTextInputPopup(x, y);
}
}
31 changes: 17 additions & 14 deletions src/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { onDocumentKeydown } from "./events/document-keydown";
import { onDocumentPaste } from "./events/document-paste";
import { createColorPicker } from "./ui/color-picker";
import { createStrokeWidthSlider } from "./ui/stroke-width-slider";
import { createFullscreenButton } from "./ui/toggle-fullscreen-button";

type StylePojo = Omit<Partial<CSSStyleDeclaration>, 'length' | 'parentRule' | typeof Symbol.iterator>;

Expand All @@ -22,26 +23,21 @@ const addStyle = (node: HTMLElement, style: StylePojo) => {
}
const defaultColor = "#F3CE32";

export const playerControlsDefaultStyle = `position: relative; top: 0px; left: 0px; z-index: 2;`;
export const uiContainerDefaultStyle = `position: absolute; top: -40px; left: 0px; z-index: 2; display: block;`;

export const playerControlsFullScreenStyle = 'position: absolute;left: 0px;bottom: 5px;width: 100%;z-index: 2;';
export const uiContainerFullScreenStyle = 'position: absolute; top: 0; left: 0px; z-index: 2; display: block;';

export function initUI(this: AnnotationTool) {
// Create the container for the UI elements
const uiContainer = document.createElement("div");
addStyle(uiContainer, {
position: 'absolute',
top: '-40px',
left: '0',
zIndex: '2',
});
uiContainer.style.position = "absolute";
uiContainer.style.top = "-40px";
uiContainer.style.left = "0";
uiContainer.style.zIndex = "2";
uiContainer.style.cssText = uiContainerDefaultStyle;

this.canvas.parentNode?.insertBefore(uiContainer, this.canvas);

const playerControls = document.createElement("div");
playerControls.style.position = "relative";
playerControls.style.top = "0";
playerControls.style.left = "0";
playerControls.style.zIndex = "2";
playerControls.style.cssText = playerControlsDefaultStyle;
// add player controls right after canvas
this.canvas.parentNode?.insertBefore(playerControls, this.canvas.nextSibling);

Expand Down Expand Up @@ -138,5 +134,12 @@ export function initUI(this: AnnotationTool) {
// fine
}
})

// Add fullscreen button to player controls
const fullscreenButton = createFullscreenButton(this);
fullscreenButton.style.position = 'absolute';
fullscreenButton.style.right = '40px';
fullscreenButton.style.bottom = '10px';
playerControls.appendChild(fullscreenButton);
}
}
Loading

0 comments on commit 3dfdac2

Please sign in to comment.