feat(settings): implement NumberInput component
This commit is contained in:
190
app/components/Settings/BottomScreen/NumberInput.vue
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>
|
||||
Reference in New Issue
Block a user