feat(home): implement background, clock and calendar
This commit is contained in:
BIN
public/images/home/top-screen/background.webp
Normal file
BIN
public/images/home/top-screen/background.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 B |
BIN
public/images/home/top-screen/calendar/calendar.webp
Normal file
BIN
public/images/home/top-screen/calendar/calendar.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 302 B |
BIN
public/images/home/top-screen/calendar/day-selector.webp
Normal file
BIN
public/images/home/top-screen/calendar/day-selector.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 B |
BIN
public/images/home/top-screen/calendar/last-row.webp
Normal file
BIN
public/images/home/top-screen/calendar/last-row.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 122 B |
BIN
public/images/home/top-screen/clock.webp
Normal file
BIN
public/images/home/top-screen/clock.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 398 B |
84
src/screens/home/top/calendar.ts
Normal file
84
src/screens/home/top/calendar.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { ImageLoader } from "../../../utils/loadImages";
|
||||||
|
|
||||||
|
export class Calendar {
|
||||||
|
private images = new ImageLoader({
|
||||||
|
calendar: "/images/home/top-screen/calendar/calendar.webp",
|
||||||
|
lastRow: "/images/home/top-screen/calendar/last-row.webp",
|
||||||
|
daySelector: "/images/home/top-screen/calendar/day-selector.webp",
|
||||||
|
});
|
||||||
|
|
||||||
|
public render(ctx: CanvasRenderingContext2D): void {
|
||||||
|
if (!this.images.isReady) return;
|
||||||
|
|
||||||
|
const CALENDAR_COLS = 7;
|
||||||
|
const CALENDAR_ROWS = 5;
|
||||||
|
const CALENDAR_LEFT = 128;
|
||||||
|
const CALENDAR_TOP = 64;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = now.getMonth();
|
||||||
|
|
||||||
|
const firstDay = new Date(year, month, 1).getDay();
|
||||||
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||||
|
|
||||||
|
ctx.drawImage(
|
||||||
|
this.images.require("calendar"),
|
||||||
|
CALENDAR_LEFT - 3,
|
||||||
|
CALENDAR_TOP - 33,
|
||||||
|
);
|
||||||
|
|
||||||
|
const extraRow = CALENDAR_COLS * CALENDAR_ROWS - daysInMonth - firstDay < 0;
|
||||||
|
if (extraRow) {
|
||||||
|
ctx.drawImage(
|
||||||
|
this.images.require("lastRow"),
|
||||||
|
CALENDAR_LEFT - 3,
|
||||||
|
CALENDAR_TOP + 79,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillStyle = "#343434";
|
||||||
|
ctx.font = "7px NDS7";
|
||||||
|
|
||||||
|
for (let col = 0; col < CALENDAR_ROWS + (extraRow ? 1 : 0); col += 1) {
|
||||||
|
for (let row = 0; row < CALENDAR_COLS; row += 1) {
|
||||||
|
const cellIndex = col * CALENDAR_COLS + row;
|
||||||
|
const day = cellIndex - firstDay + 1;
|
||||||
|
|
||||||
|
if (day > 0 && day <= daysInMonth) {
|
||||||
|
const dayText = day.toString();
|
||||||
|
const { actualBoundingBoxRight: width } = ctx.measureText(dayText);
|
||||||
|
|
||||||
|
const cellLeft = CALENDAR_LEFT + row * 16;
|
||||||
|
const cellTop = CALENDAR_TOP + col * 16;
|
||||||
|
|
||||||
|
if (now.getDate() === day) {
|
||||||
|
ctx.drawImage(
|
||||||
|
this.images.require("daySelector"),
|
||||||
|
cellLeft,
|
||||||
|
cellTop,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillText(
|
||||||
|
dayText,
|
||||||
|
cellLeft + Math.floor((15 - width) / 2),
|
||||||
|
cellTop + 11,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.font = "10px NDS10";
|
||||||
|
|
||||||
|
const timeText = `${month}/${year}`;
|
||||||
|
const { actualBoundingBoxRight: width } = ctx.measureText(timeText);
|
||||||
|
|
||||||
|
ctx.fillText(
|
||||||
|
timeText,
|
||||||
|
CALENDAR_LEFT + Math.floor((111 - width) / 2),
|
||||||
|
CALENDAR_TOP - 20,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/screens/home/top/clock.ts
Normal file
88
src/screens/home/top/clock.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { ImageLoader } from "../../../utils/loadImages";
|
||||||
|
|
||||||
|
const CENTER_X = 63;
|
||||||
|
const CENTER_Y = 95;
|
||||||
|
|
||||||
|
export class Clock {
|
||||||
|
private images = new ImageLoader({
|
||||||
|
clock: "/images/home/top-screen/clock.webp",
|
||||||
|
});
|
||||||
|
|
||||||
|
private drawLine(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
x0: number,
|
||||||
|
y0: number,
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
width: number,
|
||||||
|
) {
|
||||||
|
const dx = Math.abs(x1 - x0);
|
||||||
|
const dy = Math.abs(y1 - y0);
|
||||||
|
const sx = x0 < x1 ? 1 : -1;
|
||||||
|
const sy = y0 < y1 ? 1 : -1;
|
||||||
|
let err = dx - dy;
|
||||||
|
|
||||||
|
const drawThickPixel = (x: number, y: number) => {
|
||||||
|
const isVertical = dy > dx;
|
||||||
|
|
||||||
|
if (width === 1) {
|
||||||
|
ctx.fillRect(x, y, 1, 1);
|
||||||
|
} else if (isVertical) {
|
||||||
|
const offset = Math.floor((width - 1) / 2);
|
||||||
|
ctx.fillRect(x - offset, y, width, 1);
|
||||||
|
} else {
|
||||||
|
const offset = Math.floor((width - 1) / 2);
|
||||||
|
ctx.fillRect(x, y - offset, 1, width);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
drawThickPixel(x0, y0);
|
||||||
|
|
||||||
|
if (x0 === x1 && y0 === y1) break;
|
||||||
|
|
||||||
|
const e2 = 2 * err;
|
||||||
|
if (e2 > -dy) {
|
||||||
|
err -= dy;
|
||||||
|
x0 += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
y0 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(ctx: CanvasRenderingContext2D): void {
|
||||||
|
if (!this.images.isReady) return;
|
||||||
|
|
||||||
|
ctx.drawImage(this.images.require("clock"), 13, 45);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const renderHand = (
|
||||||
|
value: number,
|
||||||
|
color: string,
|
||||||
|
length: number,
|
||||||
|
width: number,
|
||||||
|
) => {
|
||||||
|
const angle = value * Math.PI * 2 - Math.PI / 2;
|
||||||
|
const endX = Math.round(CENTER_X + Math.cos(angle) * length);
|
||||||
|
const endY = Math.round(CENTER_Y + Math.sin(angle) * length);
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
this.drawLine(ctx, CENTER_X, CENTER_Y, endX, endY, width);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderHand(now.getMinutes() / 60, "#797979", 30, 2);
|
||||||
|
renderHand(
|
||||||
|
now.getHours() / 12 + now.getMinutes() / 60 / 12,
|
||||||
|
"#797979",
|
||||||
|
23,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
renderHand(now.getSeconds() / 60, "#49db8a", 35, 2);
|
||||||
|
|
||||||
|
ctx.fillStyle = "#494949";
|
||||||
|
ctx.fillRect(CENTER_X - 2, CENTER_Y - 2, 5, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,23 @@
|
|||||||
|
import { ImageLoader } from "../../../utils/loadImages";
|
||||||
import { StatusBar } from "./statusBar";
|
import { StatusBar } from "./statusBar";
|
||||||
|
import { Clock } from "./clock";
|
||||||
|
import { Calendar } from "./calendar";
|
||||||
|
|
||||||
export class HomeTopScreen {
|
export class HomeTopScreen {
|
||||||
|
private backgroundImage = new ImageLoader({
|
||||||
|
background: "/images/home/top-screen/background.webp",
|
||||||
|
});
|
||||||
private statusBar = new StatusBar();
|
private statusBar = new StatusBar();
|
||||||
|
private clock = new Clock();
|
||||||
|
private calendar = new Calendar();
|
||||||
|
|
||||||
render(ctx: CanvasRenderingContext2D): void {
|
render(ctx: CanvasRenderingContext2D): void {
|
||||||
|
if (!this.backgroundImage.isReady) return;
|
||||||
|
|
||||||
|
ctx.drawImage(this.backgroundImage.require("background"), 0, 0);
|
||||||
|
|
||||||
|
this.clock.render(ctx);
|
||||||
|
this.calendar.render(ctx);
|
||||||
this.statusBar.render(ctx);
|
this.statusBar.render(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,38 +11,48 @@ export class StatusBar {
|
|||||||
public render(ctx: CanvasRenderingContext2D): void {
|
public render(ctx: CanvasRenderingContext2D): void {
|
||||||
if (!this.images.isReady) return;
|
if (!this.images.isReady) return;
|
||||||
|
|
||||||
// background
|
const TEXT_Y = 11;
|
||||||
|
|
||||||
|
// Draw status bar background
|
||||||
ctx.drawImage(this.images.require("statusBar"), 0, 0);
|
ctx.drawImage(this.images.require("statusBar"), 0, 0);
|
||||||
|
|
||||||
// username
|
// Set text style
|
||||||
ctx.fillStyle = "#ffffff";
|
ctx.fillStyle = "#ffffff";
|
||||||
ctx.font = "7px NDS7";
|
ctx.font = "7px NDS7";
|
||||||
ctx.fillText("pihkaal", 3, 11);
|
|
||||||
|
|
||||||
// time and date
|
// Draw username
|
||||||
|
ctx.fillText("pihkaal", 3, TEXT_Y);
|
||||||
|
|
||||||
|
// Helper function to draw centered numbers in cells
|
||||||
const fillNumberCell = (value: number, cellX: number, offset: number) => {
|
const fillNumberCell = (value: number, cellX: number, offset: number) => {
|
||||||
const text = value.toFixed().padStart(2, "0");
|
const text = value.toFixed().padStart(2, "0");
|
||||||
const { actualBoundingBoxRight: width } = ctx.measureText(text);
|
const { actualBoundingBoxRight: width } = ctx.measureText(text);
|
||||||
|
|
||||||
const x = cellX * 16;
|
const x = cellX * 16;
|
||||||
ctx.fillText(text, Math.floor(x + offset + (16 - width) / 2), 11);
|
ctx.fillText(text, Math.floor(x + offset + (16 - width) / 2), TEXT_Y);
|
||||||
};
|
};
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
|
// Draw time (hours:minutes)
|
||||||
fillNumberCell(now.getHours(), 9, 1);
|
fillNumberCell(now.getHours(), 9, 1);
|
||||||
if (Math.floor(now.getMilliseconds() / 500) % 2 === 0) {
|
if (Math.floor(now.getMilliseconds() / 500) % 2 === 0) {
|
||||||
ctx.fillText(":", 159, 11);
|
ctx.fillText(":", 159, TEXT_Y);
|
||||||
}
|
}
|
||||||
fillNumberCell(now.getMinutes(), 10, -1);
|
fillNumberCell(now.getMinutes(), 10, -1);
|
||||||
|
|
||||||
|
// Draw date (day/month)
|
||||||
fillNumberCell(now.getDate(), 11, 1);
|
fillNumberCell(now.getDate(), 11, 1);
|
||||||
ctx.fillText("/", 190, 11);
|
ctx.fillText("/", 190, TEXT_Y);
|
||||||
fillNumberCell(now.getMonth() + 1, 12, -1);
|
fillNumberCell(now.getMonth() + 1, 12, -1);
|
||||||
|
|
||||||
// icons
|
// Draw icons
|
||||||
ctx.drawImage(this.images.require("gbaDisplay"), 210, 2);
|
ctx.drawImage(this.images.require("gbaDisplay"), 210, 2);
|
||||||
ctx.drawImage(this.images.require("startupMode"), 226, 2);
|
ctx.drawImage(this.images.require("startupMode"), 226, 2);
|
||||||
ctx.drawImage(this.images.require("battery"), 242, 4);
|
ctx.drawImage(this.images.require("battery"), 242, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public update(): void {
|
||||||
|
// Placeholder for future animation updates if needed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user