From f2e869e283f6e51196ec6359166976701696368c Mon Sep 17 00:00:00 2001 From: Pihkaal Date: Sat, 13 Dec 2025 19:02:52 +0100 Subject: [PATCH] feat(home): implement background, clock and calendar --- public/images/home/top-screen/background.webp | Bin 0 -> 98 bytes .../home/top-screen/calendar/calendar.webp | Bin 0 -> 302 bytes .../top-screen/calendar/day-selector.webp | Bin 0 -> 90 bytes .../home/top-screen/calendar/last-row.webp | Bin 0 -> 122 bytes public/images/home/top-screen/clock.webp | Bin 0 -> 398 bytes src/screens/home/top/calendar.ts | 84 +++++++++++++++++ src/screens/home/top/clock.ts | 88 ++++++++++++++++++ src/screens/home/top/index.ts | 14 +++ src/screens/home/top/statusBar.ts | 26 ++++-- 9 files changed, 204 insertions(+), 8 deletions(-) create mode 100644 public/images/home/top-screen/background.webp create mode 100644 public/images/home/top-screen/calendar/calendar.webp create mode 100644 public/images/home/top-screen/calendar/day-selector.webp create mode 100644 public/images/home/top-screen/calendar/last-row.webp create mode 100644 public/images/home/top-screen/clock.webp create mode 100644 src/screens/home/top/calendar.ts create mode 100644 src/screens/home/top/clock.ts diff --git a/public/images/home/top-screen/background.webp b/public/images/home/top-screen/background.webp new file mode 100644 index 0000000000000000000000000000000000000000..ceecb61b4d3ab3c2b2ac81cc129b58a868c21313 GIT binary patch literal 98 zcmV-o0G@weQWMPk9k z-*RUb&42tW07U`FKuYSP#7d~@xhtao3fi_|+U^1f2ms3dNmlmXKh1H!|J->1xbyyV E$3e<0+W-In literal 0 HcmV?d00001 diff --git a/public/images/home/top-screen/calendar/calendar.webp b/public/images/home/top-screen/calendar/calendar.webp new file mode 100644 index 0000000000000000000000000000000000000000..e1814924e4e9890f9fe8dc33aac61e57e168132a GIT binary patch literal 302 zcmV+}0nz?aNk&E{0RRA3MM6+kP&iB)0RR9mbbuTXUtlBv5}3vW7Bb6dwDQ0kW&)6* zfmahrS8a=-!Hfhz0&m#MF`_=O^1%5I008`dJUX4W+S*9CJTW_D1Jr8U-ejSAR(L`Q zuOnH2?)wOM6q=}5B$8Kt8*tbTo;QuZ*NFZzaN9_cyT{D%1aJ4WG(E42XQlD=Z@Aq& zD>HA~dRi9E^&sM09H;mKjtOT`f1CMW>XZO_m$4rDAYcJfgr){zP94*f^ zWTIvjg^4mK5n%yrN67TCjw$l2hd`7%?v2=t;^!se0E=rpN{}F%%RyaEL1G|`V2CmS z6Xb%JYwzm?@Z9^v{=n&jQ+xr}8)s4f&apk6pOt???-Nh{&$Zku$y^-%ZV86>s!B^-!q>90GUG|ZvX%Q literal 0 HcmV?d00001 diff --git a/public/images/home/top-screen/calendar/last-row.webp b/public/images/home/top-screen/calendar/last-row.webp new file mode 100644 index 0000000000000000000000000000000000000000..2197dd87ccb13f695915788b41d97c46252b7e10 GIT binary patch literal 122 zcmV-=0EPcjNk&F;00012MM6+kP&iCw0000lbU*|EKR_fvV)Xw(E0G7@AQP|qgObo9+6i#W0kmI`#RDzyna$8~4|P1s720 zBKprjlB6_j)kJ~FE%t=jWF~~HpxN)iHGS(=;A++TTb4S)DrhzPbCO!aDrhysL6Izz ztCmQpWG~V_5S#-8t*OAA{(Il);3F#yfA2dN{Jbx$@bkV159Vwp#1aqw(QWl?lqH=c z>Dj7PcLAL}ILN`LZn!r-4XI_5&T(zobSG($&T)nAq^NMi`hsq&hgLlO+TB4S5Vk$u5aHtAEDdd@Ort49&c(MLgUD>`%;$#&SbMc%Xec&64q~RNvC4_bMKw slT6uE3DB(B6c0rSn_2TY3fxDZN`ZYUb@gdL@Ukv9>f!Y^o8i|On?Iw$uK)l5 literal 0 HcmV?d00001 diff --git a/src/screens/home/top/calendar.ts b/src/screens/home/top/calendar.ts new file mode 100644 index 0000000..41ad799 --- /dev/null +++ b/src/screens/home/top/calendar.ts @@ -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, + ); + } +} diff --git a/src/screens/home/top/clock.ts b/src/screens/home/top/clock.ts new file mode 100644 index 0000000..8bfab51 --- /dev/null +++ b/src/screens/home/top/clock.ts @@ -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); + } +} diff --git a/src/screens/home/top/index.ts b/src/screens/home/top/index.ts index 435026b..a273015 100644 --- a/src/screens/home/top/index.ts +++ b/src/screens/home/top/index.ts @@ -1,9 +1,23 @@ +import { ImageLoader } from "../../../utils/loadImages"; import { StatusBar } from "./statusBar"; +import { Clock } from "./clock"; +import { Calendar } from "./calendar"; export class HomeTopScreen { + private backgroundImage = new ImageLoader({ + background: "/images/home/top-screen/background.webp", + }); private statusBar = new StatusBar(); + private clock = new Clock(); + private calendar = new Calendar(); 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); } } diff --git a/src/screens/home/top/statusBar.ts b/src/screens/home/top/statusBar.ts index 877b62a..d4b4891 100644 --- a/src/screens/home/top/statusBar.ts +++ b/src/screens/home/top/statusBar.ts @@ -11,38 +11,48 @@ export class StatusBar { public render(ctx: CanvasRenderingContext2D): void { if (!this.images.isReady) return; - // background + const TEXT_Y = 11; + + // Draw status bar background ctx.drawImage(this.images.require("statusBar"), 0, 0); - // username + // Set text style ctx.fillStyle = "#ffffff"; 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 text = value.toFixed().padStart(2, "0"); const { actualBoundingBoxRight: width } = ctx.measureText(text); 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(); + // Draw time (hours:minutes) fillNumberCell(now.getHours(), 9, 1); if (Math.floor(now.getMilliseconds() / 500) % 2 === 0) { - ctx.fillText(":", 159, 11); + ctx.fillText(":", 159, TEXT_Y); } fillNumberCell(now.getMinutes(), 10, -1); + // Draw date (day/month) fillNumberCell(now.getDate(), 11, 1); - ctx.fillText("/", 190, 11); + ctx.fillText("/", 190, TEXT_Y); fillNumberCell(now.getMonth() + 1, 12, -1); - // icons + // Draw icons ctx.drawImage(this.images.require("gbaDisplay"), 210, 2); ctx.drawImage(this.images.require("startupMode"), 226, 2); ctx.drawImage(this.images.require("battery"), 242, 4); } + + public update(): void { + // Placeholder for future animation updates if needed + } }