waffensachkunde

Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.

/ app src shared fensterlage.ts

4,1 KB Rohdatei
app/src/shared/fensterlage.ts — 116 Zeilen
1 /**
2 * Größe und Lage des Hauptfensters – reine Logik, ohne Electron.
3 *
4 * **Wozu.** Das Fenster startete bis 0.22.0 immer mit derselben Größe an der
5 * Systemposition. Wer maximiert arbeitet oder es schmal neben eine
6 * Bildschirmlupe oder ein zweites Programm stellt, richtete das bei jedem
7 * Start neu ein — und das sind gerade die Nutzer, für die diese Anwendung
8 * gebaut ist.
9 *
10 * **Warum die Prüfung hier liegt und nicht bei der Fenstererzeugung.** An ihr
11 * hängt der einzige gefährliche Fall: eine gespeicherte Lage auf einem
12 * Bildschirm, den es nicht mehr gibt. Das Fenster ginge dann außerhalb jeder
13 * sichtbaren Fläche auf — vorhanden, aber unerreichbar, und ohne Fenster gibt
14 * es auch keinen Weg, die Einstellung zurückzusetzen. Als reine Funktion
15 * lässt sich das prüfen, ohne einen zweiten Monitor abzustecken.
16 */
17
18 /** Fenstergröße und -lage, wie sie gespeichert wird. */
19 export interface Fensterlage {
20 readonly breite: number;
21 readonly hoehe: number;
22 readonly x?: number;
23 readonly y?: number;
24 readonly maximiert?: boolean;
25 }
26
27 /** Ein Rechteck einer verfügbaren Bildschirmfläche. */
28 export interface Flaeche {
29 readonly x: number;
30 readonly y: number;
31 readonly width: number;
32 readonly height: number;
33 }
34
35 /** Vorgabe, wenn nichts gespeichert ist oder das Gespeicherte nicht taugt. */
36 export const FENSTER_VORGABE = Object.freeze({ breite: 1180, hoehe: 820 });
37
38 /** Untergrenzen; dieselben wie `minWidth`/`minHeight` am Fenster. */
39 export const FENSTER_MINDESTBREITE = 360;
40 export const FENSTER_MINDESTHOEHE = 560;
41
42 /** So viel vom Fenster muss auf einer Fläche liegen, damit es greifbar ist. */
43 const SICHTBAR_MINDESTENS = 80;
44
45 function zahl(wert: unknown): number | null {
46 return typeof wert === 'number' && Number.isFinite(wert) ? Math.round(wert) : null;
47 }
48
49 /**
50 * Nimmt eine gespeicherte Lage entgegen und gibt zurück, was davon taugt.
51 *
52 * `null` heißt: nichts Brauchbares, es gilt die Vorgabe.
53 */
54 export function fensterlageBereinigen(wert: unknown): Fensterlage | null {
55 if (typeof wert !== 'object' || wert === null) {
56 return null;
57 }
58 const roh = wert as Record<string, unknown>;
59
60 const breite = zahl(roh['breite']);
61 const hoehe = zahl(roh['hoehe']);
62 if (breite === null || hoehe === null) {
63 return null;
64 }
65
66 const x = zahl(roh['x']);
67 const y = zahl(roh['y']);
68
69 return {
70 /* Nach unten gekappt, nicht nach oben: Ein zu großes Fenster schiebt das
71 Betriebssystem selbst zurecht, ein zu kleines wäre unbedienbar. */
72 breite: Math.max(FENSTER_MINDESTBREITE, breite),
73 hoehe: Math.max(FENSTER_MINDESTHOEHE, hoehe),
74 ...(x === null ? {} : { x }),
75 ...(y === null ? {} : { y }),
76 ...(roh['maximiert'] === true ? { maximiert: true } : {}),
77 };
78 }
79
80 /**
81 * Ob die gespeicherte Lage auf einer der vorhandenen Flächen liegt.
82 *
83 * Verlangt wird kein vollständiges Enthaltensein: Ein Fenster, das ein Stück
84 * über den Rand ragt, ist gewollt und alltäglich. Verlangt wird, dass ein
85 * greifbarer Teil sichtbar bleibt — sonst ließe es sich nicht einmal mehr
86 * anfassen, um es zurückzuholen.
87 */
88 export function lageIstErreichbar(lage: Fensterlage, flaechen: readonly Flaeche[]): boolean {
89 const { x, y } = lage;
90 if (x === undefined || y === undefined) {
91 /* Ohne Position entscheidet das Betriebssystem – das ist immer gültig. */
92 return true;
93 }
94
95 return flaechen.some((flaeche) => {
96 const ueberlappungX =
97 Math.min(x + lage.breite, flaeche.x + flaeche.width) - Math.max(x, flaeche.x);
98 const ueberlappungY =
99 Math.min(y + lage.hoehe, flaeche.y + flaeche.height) - Math.max(y, flaeche.y);
100 return ueberlappungX >= SICHTBAR_MINDESTENS && ueberlappungY >= SICHTBAR_MINDESTENS;
101 });
102 }
103
104 /**
105 * Die Lage, mit der das Fenster aufgehen soll.
106 *
107 * Fällt auf die Vorgabe zurück, wenn nichts gespeichert ist oder die
108 * gespeicherte Lage auf keiner vorhandenen Fläche mehr liegt.
109 */
110 export function startlage(gespeichert: unknown, flaechen: readonly Flaeche[]): Fensterlage {
111 const lage = fensterlageBereinigen(gespeichert);
112 if (lage === null || !lageIstErreichbar(lage, flaechen)) {
113 return { ...FENSTER_VORGABE };
114 }
115 return lage;
116 }