Skip to content

Commit

Permalink
Port Tgui: Events & Colors in typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
Drulikar committed May 19, 2024
1 parent 915d476 commit 935de53
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 89 deletions.
84 changes: 0 additions & 84 deletions tgui/packages/common/color.js

This file was deleted.

49 changes: 49 additions & 0 deletions tgui/packages/common/color.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Color } from './color';

describe('Color', () => {
it('should create a color with default values', () => {
const color = new Color();
expect(color.r).toBe(0);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
expect(color.a).toBe(1);
});

it('should create a color from hex', () => {
const color = Color.fromHex('#ff0000');
expect(color.r).toBe(255);
expect(color.g).toBe(0);
expect(color.b).toBe(0);
});

it('should darken a color', () => {
const color = new Color(100, 100, 100).darken(50);
expect(color.r).toBe(50);
expect(color.g).toBe(50);
expect(color.b).toBe(50);
});

it('should lighten a color', () => {
const color = new Color(100, 100, 100).lighten(50);
expect(color.r).toBe(150);
expect(color.g).toBe(150);
expect(color.b).toBe(150);
});

it('should interpolate between two colors', () => {
const color1 = new Color(0, 0, 0);
const color2 = new Color(100, 100, 100);
const color = Color.lerp(color1, color2, 0.5);
expect(color.r).toBe(50);
expect(color.g).toBe(50);
expect(color.b).toBe(50);
});

it('should lookup a color in an array', () => {
const colors = [new Color(0, 0, 0), new Color(100, 100, 100)];
const color = Color.lookup(0.5, colors);
expect(color.r).toBe(50);
expect(color.g).toBe(50);
expect(color.b).toBe(50);
});
});
94 changes: 94 additions & 0 deletions tgui/packages/common/color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* @file
* @copyright 2020 Aleksej Komarov
* @license MIT
*/

const EPSILON = 0.0001;

export class Color {
r: number;
g: number;
b: number;
a: number;

constructor(r = 0, g = 0, b = 0, a = 1) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}

toString(): string {
// Alpha component needs to permit fractional values, so cannot use |
let alpha = this.a;
if (typeof alpha === 'string') {
alpha = parseFloat(this.a as any);
}
if (isNaN(alpha)) {
alpha = 1;
}
return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${alpha})`;
}

/** Darkens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value. */
darken(percent: number): Color {
percent /= 100;
return new Color(
this.r - this.r * percent,
this.g - this.g * percent,
this.b - this.b * percent,
this.a,
);
}

/** Brightens a color by a given percent. Returns a color, which can have toString called to get it's rgba() css value. */
lighten(percent: number): Color {
// No point in rewriting code we already have.
return this.darken(-percent);
}

/**
* Creates a color from the CSS hex color notation.
*/
static fromHex(hex: string): Color {
return new Color(
parseInt(hex.slice(1, 3), 16),
parseInt(hex.slice(3, 5), 16),
parseInt(hex.slice(5, 7), 16),
);
}

/**
* Linear interpolation of two colors.
*/
static lerp(c1: Color, c2: Color, n: number): Color {
return new Color(
(c2.r - c1.r) * n + c1.r,
(c2.g - c1.g) * n + c1.g,
(c2.b - c1.b) * n + c1.b,
(c2.a - c1.a) * n + c1.a,
);
}

/**
* Loops up the color in the provided list of colors
* with linear interpolation.
*/
static lookup(value: number, colors: Color[]): Color {
const len = colors.length;
if (len < 2) {
throw new Error('Needs at least two colors!');
}
const scaled = value * (len - 1);
if (value < EPSILON) {
return colors[0];
}
if (value >= 1 - EPSILON) {
return colors[len - 1];
}
const ratio = scaled % 1;
const index = scaled | 0;
return this.lerp(colors[index], colors[index + 1], ratio);
}
}
34 changes: 34 additions & 0 deletions tgui/packages/common/events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { EventEmitter } from './events';

describe('EventEmitter', () => {
it('should add and trigger an event listener', () => {
const emitter = new EventEmitter();
const mockListener = jest.fn();
emitter.on('test', mockListener);
emitter.emit('test', 'payload');
expect(mockListener).toHaveBeenCalledWith('payload');
});

it('should remove an event listener', () => {
const emitter = new EventEmitter();
const mockListener = jest.fn();
emitter.on('test', mockListener);
emitter.off('test', mockListener);
emitter.emit('test', 'payload');
expect(mockListener).not.toHaveBeenCalled();
});

it('should not fail when emitting an event with no listeners', () => {
const emitter = new EventEmitter();
expect(() => emitter.emit('test', 'payload')).not.toThrow();
});

it('should clear all event listeners', () => {
const emitter = new EventEmitter();
const mockListener = jest.fn();
emitter.on('test', mockListener);
emitter.clear();
emitter.emit('test', 'payload');
expect(mockListener).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
* @license MIT
*/

type Fn = (...args: any[]) => void;

export class EventEmitter {
private listeners: Record<string, Fn[]>;

constructor() {
this.listeners = {};
}

on(name, listener) {
on(name: string, listener: Fn): void {
this.listeners[name] = this.listeners[name] || [];
this.listeners[name].push(listener);
}

off(name, listener) {
off(name: string, listener: Fn): void {
const listeners = this.listeners[name];
if (!listeners) {
throw new Error(`There is no listeners for "${name}"`);
Expand All @@ -24,7 +28,7 @@ export class EventEmitter {
});
}

emit(name, ...params) {
emit(name: string, ...params: any[]): void {
const listeners = this.listeners[name];
if (!listeners) {
return;
Expand All @@ -35,7 +39,7 @@ export class EventEmitter {
}
}

clear() {
clear(): void {
this.listeners = {};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const PingIndicator = (props) => {
new Color(220, 40, 40),
new Color(220, 200, 40),
new Color(60, 220, 40),
]);
]).toString();
const roundtrip = ping.roundtrip ? toFixed(ping.roundtrip) : '--';
return (
<Button
Expand Down

0 comments on commit 935de53

Please sign in to comment.