Skip to content

Commit

Permalink
it works, now time to fix animtab
Browse files Browse the repository at this point in the history
  • Loading branch information
saboooor committed Jun 13, 2024
1 parent 7d9b4c1 commit 3a59ced
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 108 deletions.
47 changes: 35 additions & 12 deletions src/components/util/HexUtils.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,66 @@
/**
* Typescript implementation of HexUtils Gradients from RoseGarden.
* https://github.com/Rosewood-Development/RoseGarden/blob/master/src/main/java/dev/rosewood/rosegarden/utils/HexUtils.java#L358
* Modified to work with custom gradient points.
*/
export class Gradient {
colors: number[][];
gradients: any[];
colors: { rgb: number[], pos: number }[];
gradients: TwoStopGradient[];
steps: number;
step: number;
gradient: number;

constructor(colors: number[][], numSteps: number) {
constructor(colors: Gradient['colors'], numSteps: number) {
this.colors = colors;
this.gradients = [];
this.steps = numSteps - 1;
this.step = 0;
this.gradient = 0;
if (colors[0].pos !== 0) colors.unshift({ rgb: colors[0].rgb, pos: 0 });
if (colors[colors.length - 1].pos !== 100) colors.push({ rgb: colors[colors.length - 1].rgb, pos: 100 });

const increment = this.steps / (colors.length - 1);
for (let i = 0; i < colors.length - 1; i++) {
let currentColor = colors[i];
let nextColor = colors[i + 1];
if (currentColor.pos > nextColor.pos) {
const newColor = currentColor;
currentColor = nextColor;
nextColor = newColor;
}

const lowerRange = Math.round(colors[i].pos / 100 * numSteps);
const upperRange = Math.round(colors[i + 1].pos / 100 * numSteps);
if (lowerRange === upperRange) continue;

this.gradients.push(
new TwoStopGradient(
colors[i],
colors[i + 1],
increment * i,
increment * (i + 1)),
currentColor.rgb,
nextColor.rgb,
lowerRange,
upperRange,
),
);
}
}

/* Gets the next color in the gradient sequence as an array of 3 numbers: [r, g, b] */
next() {
if (this.steps <= 1) { return this.colors[0]; }
if (this.steps <= 1) { return this.colors[0].rgb; }

const adjustedStep = Math.round(Math.abs(((2 * Math.asin(Math.sin(this.step * (Math.PI / (2 * this.steps))))) / Math.PI) * this.steps));
let color;
if (this.gradients.length < 2) {
color = this.gradients[0].colorAt(adjustedStep);
}
else {
const segment = this.steps / this.gradients.length;
const segment = this.gradients[this.gradient].upperRange - this.gradients[this.gradient].lowerRange;
const index = Math.min(Math.floor(adjustedStep / segment), this.gradients.length - 1);
color = this.gradients[index].colorAt(adjustedStep);
const gradient = this.gradients[index];
color = gradient.colorAt(adjustedStep);
if (adjustedStep >= gradient.upperRange) {
this.gradient++;
if (this.gradient > this.gradients.length) this.gradient = 0;
}
}

this.step++;
Expand All @@ -60,6 +82,7 @@ class TwoStopGradient {
}

colorAt(step: number) {
if (this.startColor === this.endColor) return this.startColor;
return [
this.calculateHexPiece(step, this.startColor[0], this.endColor[0]),
this.calculateHexPiece(step, this.startColor[1], this.endColor[1]),
Expand All @@ -75,7 +98,7 @@ class TwoStopGradient {
}

export class AnimatedGradient extends Gradient {
constructor(colors: number[][], numSteps: number, offset: number) {
constructor(colors: Gradient['colors'], numSteps: number, offset: number) {
super(colors, numSteps);
this.step = offset;
}
Expand Down
8 changes: 4 additions & 4 deletions src/components/util/PresetUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ export const v3formats = [
},
{
color: 'MiniMessage',
bold: '<bold>$t</bold>',
italic: '<italic>$t</italic>',
underline: '<underline>$t</underline>',
strikethrough: '<strikethrough>$t</strikethrough>',
bold: '<b>$t</b>',
italic: '<i>$t</i>',
underline: '<u>$t</u>',
strikethrough: '<s>$t</s>',
},
];

Expand Down
8 changes: 4 additions & 4 deletions src/components/util/RGBUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export function getRandomColor() {
}

export function getAnimFrames(store: typeof defaults) {
let colors = store.colors.map(color => convertToRGB(color.hex));
if (colors.length < 2) colors = [convertToRGB('#00FFE0'), convertToRGB('#EB00FF')];
const colors = store.colors.map(color => ({ rgb: convertToRGB(color.hex), pos: color.pos }));
if (colors.length < 2) return { OutputArray: [], frames: [] };

const text = store.text ?? 'birdflop';
let loopAmount;
Expand Down Expand Up @@ -154,8 +154,8 @@ export function generateOutput(
output += `<gradient:${colors.join(':')}>${text}</gradient>`;
}

const newColors = colors?.map(color => convertToRGB(color.hex));
while (newColors.length < 2) newColors.push(convertToRGB(getRandomColor()));
const newColors = colors?.map(color => ({ rgb: convertToRGB(color.hex), pos: color.pos }));
if (newColors.length < 2) return 'Error: Not enough colors.';

const gradient = new Gradient(newColors, text.length);

Expand Down
196 changes: 110 additions & 86 deletions src/routes/resources/animtab/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { $, component$, useStore, useTask$, useVisibleTask$ } from '@builder.io/qwik';
import { component$, useStore, useTask$, useVisibleTask$ } from '@builder.io/qwik';
import { routeLoader$, type DocumentHead } from '@builder.io/qwik-city';

import { defaults, loadPreset, presets, types, v3formats } from '~/components/util/PresetUtils';
import { AnimationOutput, convertToRGB, getAnimFrames, getBrightness, getRandomColor } from '~/components/util/RGBUtils';

import { Add, Remove, SettingsOutline, Text, TrashOutline } from 'qwik-ionicons';
import { Add, SettingsOutline, Text, TrashOutline } from 'qwik-ionicons';

import { Button, Header, NumberInput, Dropdown, TextArea, TextInput, Toggle, ColorPicker } from '@luminescent/ui';
import { Button, Header, NumberInput, Dropdown, TextArea, TextInput, Toggle, ColorPicker, NumberInputRaw } from '@luminescent/ui';
import { inlineTranslate, useSpeak } from 'qwik-speak';
import { getCookies, setCookies } from '~/components/util/SharedUtils';
import { getCookies, setCookies, sortColors } from '~/components/util/SharedUtils';
import { isBrowser } from '@builder.io/qwik/build';

const animTABDefaults = {
Expand Down Expand Up @@ -55,22 +55,6 @@ export default component$(() => {
frame: 0,
}, { deep: true });

const handleSwap = $((currentIndex: number, newIndex: number) => {
// check if the index is out of bounds
const colorsLength = store.colors.length;
if (newIndex < 0) {
newIndex = colorsLength - 1;
} else if (newIndex >= colorsLength) {
newIndex = 0;
}

const newColors = [...store.colors];
const currentColor = `${newColors[currentIndex].hex}`;
newColors[currentIndex].hex = newColors[newIndex].hex;
newColors[newIndex].hex = currentColor;
store.colors = newColors;
});

// eslint-disable-next-line qwik/no-use-visible-task
useVisibleTask$(async () => {
let speed = store.speed;
Expand Down Expand Up @@ -149,95 +133,128 @@ export default component$(() => {
</h1>

<div class="flex gap-2 my-4 items-center">
<Button square disabled={store.colors.length < 3} transparent aria-label="Remove Color" onClick$={() => {
const newColors = [...store.colors];
newColors.pop();
store.colors = newColors;
}}>
<Remove width="24" />
</Button>
<div class="w-full h-3 rounded-full items-center relative" id="colormap" style={`background: linear-gradient(to right, ${store.colors.map(c => c.hex).join(', ')});`}>
{store.colors.map((color, i) => <div class="absolute -mt-1 -ml-3" key={i} onMouseDown$={() => {
<div class="w-full h-3 rounded-full items-center relative" id="colormap"
style={`background: linear-gradient(to right, ${sortColors(store.colors).map(color => `${color.hex} ${color.pos}%`).join(', ')});`}
onClick$={(e, el) => {
if (e.target != el) return;
const rect = el.getBoundingClientRect();
const pos = (e.clientX - rect.left) / rect.width * 100;
const newColors = [...store.colors];
newColors.push({ hex: getRandomColor(), pos });
store.colors = newColors;
}}
onMouseEnter$={(e, el) => {
const abortController = new AbortController();
const colormap = document.getElementById('colormap')!;
document.addEventListener('mousemove', (e) => {
const rect = colormap.getBoundingClientRect();
const x = e.clientX - rect.left;
const width = rect.width;
const index = Math.round((store.colors.length - 1) * (x / width));
if (index != i) {
handleSwap(i, index);
abortController.abort();
el.addEventListener('mousemove', e => {
const addbutton = document.getElementById('add-button')!;
if (e.target != el) {
addbutton.classList.add('opacity-0');
return;
}
addbutton.classList.remove('opacity-0');
addbutton.style.left = `${(e.clientX - el.getBoundingClientRect().left) / el.getBoundingClientRect().width * 100}%`;
}, { signal: abortController.signal });
document.addEventListener('mouseup', () => {
el.addEventListener('mouseleave', () => {
const addbutton = document.getElementById('add-button')!;
addbutton.classList.add('opacity-0');
abortController.abort();
}, { signal: abortController.signal });
}} style={{
left: `${(100 / (store.colors.length - 1)) * i}%`,
}}
>
<div id="add-button" class={{
'absolute -mt-1 -ml-3 transition-opacity w-5 h-5 rounded-full shadow-md border border-gray-700 bg-gray-800 opacity-0 pointer-events-none': true,
}}>
<Add width="19" />
</div>
{store.colors.map((color, i) => <div class="absolute -mt-1 -ml-3" key={i}
onMouseDown$={() => {
const abortController = new AbortController();
const colormap = document.getElementById('colormap')!;
const rect = colormap.getBoundingClientRect();
document.addEventListener('mousemove', e => {
const newColors = [...store.colors];
newColors[i].pos = (e.clientX - rect.left) / rect.width * 100;
if (newColors[i].pos < 0) newColors[i].pos = 0;
if (newColors[i].pos > 100) newColors[i].pos = 100;
store.colors = newColors;
}, { signal: abortController.signal });
document.addEventListener('mouseup', () => {
abortController.abort();
}, { signal: abortController.signal });
}} style={{
left: `${color.pos}%`,
}}
>
<button key={`color${i + 1}`} id={`color${i + 1}`}
class={{
'transition-transform w-5 h-5 hover:scale-125 rounded-full shadow-md border': true,
'border-white': getBrightness(convertToRGB(color.hex)) < 126,
'border-black': getBrightness(convertToRGB(color.hex)) > 126,
}}
style={`background: ${color.hex};`}
onFocus$={() => {
onClick$={() => {
const abortController = new AbortController();
document.addEventListener('click', (e) => {
if (e.target instanceof HTMLElement && !e.target.closest(`#color${i + 1}`) && !e.target.closest(`#color${i + 1}-picker`)) {
if (e.target instanceof HTMLElement && !e.target.closest(`#color${i + 1}`) && !e.target.closest(`#color${i + 1}-popup`)) {
if (store.opened == i) store.opened = -1;
abortController.abort();
}
}, { signal: abortController.signal });
store.opened = i;
}}
/>
<div onMouseDown$={(e) => e.stopPropagation()}>
<ColorPicker
id={`color${i + 1}`}
value={color.hex}
class={{
'motion-safe:transition-all absolute top-full mt-2 gap-1 z-[1000]': true,
'opacity-0 scale-95 pointer-events-none': store.opened != i,
'left-0': i < store.colors.length / 2,
'right-0': i >= store.colors.length / 2,
}}
onInput$={newColor => {
const newColors = [...store.colors];
newColors[i].hex = newColor;
store.colors = newColors;
}}
horizontal
/>
{store.colors.length > 2 &&
<Button
class={{
'motion-safe:transition-all absolute top-full mt-2 gap-1 z-[1000]': true,
'opacity-0 scale-95 pointer-events-none': store.opened != i,
'left-0': i < store.colors.length / 2,
'right-0': i >= store.colors.length / 2,
}}
square size='sm' color="red" onClick$={() => {
<div id={`color${i + 1}-popup`} onMouseDown$={(e) => e.stopPropagation()}>
<div class={{
'flex flex-col gap-2 motion-safe:transition-all absolute top-full z-[1000] mt-2': true,
'opacity-0 scale-95 pointer-events-none': store.opened != i,
'left-0 items-start': color.pos < 50,
'right-0 items-end': color.pos >= 50,
}}>
<div class="flex gap-2">
{store.colors.length > 2 &&
<Button class={{ 'backdrop-blur-md': true }} square size='sm' color="red" onClick$={() => {
const newColors = [...store.colors];
newColors.splice(i, 1);
store.colors = newColors;
}}>
<TrashOutline width="20" />
</Button>
}
<div class="flex w-48">
<NumberInputRaw class={{ 'w-full': true }} input id={`color${i + 1}-pos`} value={Number(color.pos.toFixed(2))} min={0} max={100}
onInput$={(e, el) => {
const newColors = [...store.colors];
newColors[i].pos = Number(el.value);
store.colors = newColors;
}}
onDecrement$={() => {
const newColors = [...store.colors];
newColors[i].pos -= 1;
store.colors = newColors;
}}
onIncrement$={() => {
const newColors = [...store.colors];
newColors[i].pos += 1;
store.colors = newColors;
}}
/>
</div>
</div>
<ColorPicker
id={`color${i + 1}`}
value={color.hex}
onInput$={newColor => {
const newColors = [...store.colors];
newColors.splice(i, 1);
newColors[i].hex = newColor;
store.colors = newColors;
}
}>
<TrashOutline width="20" />
</Button>
}
}}
horizontal
/>
</div>
</div>
</div>,
)}
</div>
<Button square disabled={store.colors.length > store.text.length} transparent aria-label="Add Color" onClick$={() => {
const newColors = [...store.colors, { hex: getRandomColor(), pos: Math.floor(Math.random() * 100) }];
store.colors = newColors;
}}>
<Add width="24" class="fill-white" />
</Button>
</div>

<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-4">
Expand Down Expand Up @@ -275,9 +292,16 @@ export default component$(() => {
}}>
{t('animtab.speed@@Speed')}
</NumberInput>
<TextInput id="prefixsuffix" value={store.prefixsuffix} placeholder={'welcome to $t'} onInput$={(event: any) => { store.prefixsuffix = event.target!.value; }}>
Prefix/Suffix
</TextInput>
<NumberInput id="length" input disabled value={store.length * store.text.length} min={store.text.length} class={{ 'w-full': true }}
onIncrement$={() => {
store.length++;
}}
onDecrement$={() => {
if (store.length > 1) store.length--;
}}
>
{t('animtab.length@@Gradient Length')}
</NumberInput>
</div>

<div class="grid md:grid-cols-3 gap-2">
Expand Down Expand Up @@ -375,8 +399,8 @@ export default component$(() => {
]} value={Object.keys(presets).find((preset: any) => presets[preset as keyof typeof presets].toString() == store.colors.toString()) ?? 'custom'}>
{t('color.colorPreset@@Color Preset')}
</Dropdown>
<TextInput id="prefixsuffix" value={store.prefixsuffix} placeholder={'/nick $t'} onInput$={(event: any) => { store.prefixsuffix = event.target!.value; }}>
Prefix/Suffix
<TextInput id="prefixsuffix" value={store.prefixsuffix} placeholder={'welcome to $t'} onInput$={(event: any) => { store.prefixsuffix = event.target!.value; }}>
Prefix/Suffix
</TextInput>
</div>
<Toggle id="trimspaces" checked={store.trimspaces}
Expand Down
Loading

0 comments on commit 3a59ced

Please sign in to comment.