feat(settings): implement NumberInput component
@@ -12,6 +12,13 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "NDS39";
|
||||||
|
src: url("/assets/fonts/nds-39px.ttf") format("truetype");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
/* NOTE: woff2 version of this font doesn't work */
|
/* NOTE: woff2 version of this font doesn't work */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Pokemon DP Pro";
|
font-family: "Pokemon DP Pro";
|
||||||
|
|||||||
BIN
app/assets/fonts/nds-39px.ttf
Normal file
190
app/components/Settings/BottomScreen/NumberInput.vue
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const APP_COLOR_TO_FONT_COLOR: Record<string, string> = {
|
||||||
|
"#61829a": "#fbfbfb", // cyan
|
||||||
|
"#ba4900": "#fbe3d3", // maroon
|
||||||
|
"#fb0018": "#fbcbd3", // red
|
||||||
|
"#fb8afb": "#fbebfb", // pink
|
||||||
|
"#fb9200": "#fbf3e3", // orange
|
||||||
|
"#f3e300": "#fbfbdb", // yellow
|
||||||
|
"#aafb00": "#fbfbfb", // lime
|
||||||
|
"#00fb00": "#fbfbfb", // green
|
||||||
|
"#00a238": "#fbfbfb", // dark green
|
||||||
|
"#49db8a": "#ebfbf3", // duck
|
||||||
|
"#30baf3": "#fbfbfb", // light blue
|
||||||
|
"#0059f3": "#dbebfb", // blue
|
||||||
|
"#000092": "#ebebfb", // dark blue
|
||||||
|
"#8a00d3": "#f3dbfb", // purple
|
||||||
|
"#d300eb": "#fbfbfb", // magenta
|
||||||
|
"#fb0092": "#ebe3f3", // fuschia
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = defineModel<number>({ required: true });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
x: number;
|
||||||
|
title: string;
|
||||||
|
digits?: 2 | 4;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
selected?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
digits: 2,
|
||||||
|
min: 0,
|
||||||
|
max: 99,
|
||||||
|
disabled: false,
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
select: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const app = useAppStore();
|
||||||
|
const { assets } = useAssets();
|
||||||
|
const { onRender, onClick } = useScreen();
|
||||||
|
|
||||||
|
const Y = 30;
|
||||||
|
const SQUARE_HEIGHT = 49;
|
||||||
|
const ARROW_IMAGE_HEIGHT = assets.settings.bottomScreen.numberInputUp.height;
|
||||||
|
|
||||||
|
const upImage = computed(() => {
|
||||||
|
if (props.digits === 2) {
|
||||||
|
return props.disabled
|
||||||
|
? assets.settings.bottomScreen.numberInputUpDisabled
|
||||||
|
: assets.settings.bottomScreen.numberInputUp;
|
||||||
|
} else {
|
||||||
|
return props.disabled
|
||||||
|
? assets.settings.bottomScreen.numberInputUpXlDisabled
|
||||||
|
: assets.settings.bottomScreen.numberInputUpXl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const downImage = computed(() => {
|
||||||
|
if (props.digits === 2) {
|
||||||
|
return props.disabled
|
||||||
|
? assets.settings.bottomScreen.numberInputDownDisabled
|
||||||
|
: assets.settings.bottomScreen.numberInputDown;
|
||||||
|
} else {
|
||||||
|
return props.disabled
|
||||||
|
? assets.settings.bottomScreen.numberInputDownXlDisabled
|
||||||
|
: assets.settings.bottomScreen.numberInputDownXl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const squareWidth = computed(() => upImage.value.width);
|
||||||
|
|
||||||
|
const increase = () => {
|
||||||
|
const newValue = value.value + 1;
|
||||||
|
value.value = newValue > props.max ? props.min : newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const decrease = () => {
|
||||||
|
const newValue = value.value - 1;
|
||||||
|
value.value = newValue < props.min ? props.max : newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
onRender((ctx) => {
|
||||||
|
// arrow up
|
||||||
|
ctx.drawImage(upImage.value, props.x, Y);
|
||||||
|
|
||||||
|
// outline
|
||||||
|
ctx.fillStyle = "#515151";
|
||||||
|
ctx.fillRect(
|
||||||
|
props.x,
|
||||||
|
Y + ARROW_IMAGE_HEIGHT,
|
||||||
|
squareWidth.value,
|
||||||
|
SQUARE_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
// background
|
||||||
|
ctx.fillStyle = props.selected ? app.color.hex : "#797979";
|
||||||
|
ctx.fillRect(
|
||||||
|
props.x + 1,
|
||||||
|
Y + ARROW_IMAGE_HEIGHT + 1,
|
||||||
|
squareWidth.value - 2,
|
||||||
|
SQUARE_HEIGHT - 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
// value
|
||||||
|
ctx.font = "39px NDS39";
|
||||||
|
ctx.letterSpacing = "2px";
|
||||||
|
ctx.textBaseline = "top";
|
||||||
|
ctx.fillStyle = props.selected
|
||||||
|
? APP_COLOR_TO_FONT_COLOR[app.color.hex]!
|
||||||
|
: "#fbfbfb";
|
||||||
|
ctx.fillText(
|
||||||
|
value.value.toString().padStart(props.digits, "0"),
|
||||||
|
props.x + 3,
|
||||||
|
Y + ARROW_IMAGE_HEIGHT + Math.floor((SQUARE_HEIGHT - 39) / 2) + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
// arrow down
|
||||||
|
ctx.drawImage(
|
||||||
|
downImage.value,
|
||||||
|
props.x,
|
||||||
|
Y + ARROW_IMAGE_HEIGHT + SQUARE_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
// title
|
||||||
|
ctx.font = "10px NDS10";
|
||||||
|
ctx.fillStyle = "#000000";
|
||||||
|
ctx.letterSpacing = "0px";
|
||||||
|
fillTextCentered(
|
||||||
|
ctx,
|
||||||
|
props.title,
|
||||||
|
props.x + 1,
|
||||||
|
// TODO: -10 is needed because fillTextCentered isn't using top baseline
|
||||||
|
// i will change that in the future (maybe)
|
||||||
|
Y + ARROW_IMAGE_HEIGHT * 2 + SQUARE_HEIGHT - 6,
|
||||||
|
downImage.value.width,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
useKeyDown((key) => {
|
||||||
|
if (!props.selected || props.disabled) return;
|
||||||
|
switch (key) {
|
||||||
|
case "NDS_UP":
|
||||||
|
increase();
|
||||||
|
break;
|
||||||
|
case "NDS_DOWN":
|
||||||
|
decrease();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onClick((x, y) => {
|
||||||
|
if (props.disabled) return;
|
||||||
|
if (
|
||||||
|
rectContains([props.x, Y, upImage.value.width, ARROW_IMAGE_HEIGHT], [x, y])
|
||||||
|
) {
|
||||||
|
increase();
|
||||||
|
emit("select");
|
||||||
|
} else if (
|
||||||
|
rectContains(
|
||||||
|
[props.x, Y + ARROW_IMAGE_HEIGHT, SQUARE_HEIGHT + 2, SQUARE_HEIGHT + 2],
|
||||||
|
[x, y],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
emit("select");
|
||||||
|
} else if (
|
||||||
|
rectContains(
|
||||||
|
[
|
||||||
|
props.x,
|
||||||
|
Y + ARROW_IMAGE_HEIGHT + SQUARE_HEIGHT + 2,
|
||||||
|
downImage.value.width,
|
||||||
|
ARROW_IMAGE_HEIGHT,
|
||||||
|
],
|
||||||
|
[x, y],
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
decrease();
|
||||||
|
emit("select");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
defineOptions({ render: () => null });
|
||||||
|
</script>
|
||||||
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/settings/bottom-screen/number-input-down-xl.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/settings/bottom-screen/number-input-down.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/settings/bottom-screen/number-input-up-xl.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/settings/bottom-screen/number-input-up.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |