From 6284fa35c0bd58c6d7a38c6bcb1e802ec17f1781 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Sat, 5 Nov 2022 11:11:07 +0000
Subject: [PATCH] feat: logic frequency control using ui

---
 CHANGELOG.md                                  |  2 +-
 examples/web/index.ts                         | 12 ++++++++---
 examples/web/react/app.tsx                    | 21 ++++++++++++++-----
 .../button-increment/button-increment.tsx     | 19 +++++++++++------
 4 files changed, 39 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c3e07b9..52fd01b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
-*
+* Logic frequency control using on click UI and keyboard
 
 ### Changed
 
diff --git a/examples/web/index.ts b/examples/web/index.ts
index c055b7b6..c9222e5f 100644
--- a/examples/web/index.ts
+++ b/examples/web/index.ts
@@ -21,7 +21,7 @@ const LOGIC_HZ = 4194304;
 const VISUAL_HZ = 59.7275;
 const IDLE_HZ = 10;
 
-const FREQUENCY_DELTA = 350000;
+const FREQUENCY_DELTA = 400000;
 
 const SAMPLE_RATE = 2;
 
@@ -344,11 +344,11 @@ class GameboyEmulator extends EmulatorBase implements Emulator {
 
             switch (event.key) {
                 case "+":
-                    this.logicFrequency += FREQUENCY_DELTA;
+                    this.frequency += FREQUENCY_DELTA;
                     break;
 
                 case "-":
-                    this.logicFrequency -= FREQUENCY_DELTA;
+                    this.frequency -= FREQUENCY_DELTA;
                     break;
             }
         });
@@ -430,6 +430,12 @@ class GameboyEmulator extends EmulatorBase implements Emulator {
         return this.logicFrequency;
     }
 
+    set frequency(value: number) {
+        value = Math.max(value, 0);
+        this.logicFrequency = value;
+        this.trigger("frequency", value);
+    }
+
     get framerate(): number {
         return this.fps;
     }
diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx
index 57d3508f..b3c190b0 100644
--- a/examples/web/react/app.tsx
+++ b/examples/web/react/app.tsx
@@ -28,7 +28,7 @@ import {
 
 import "./app.css";
 
-export type Callback<T> = (owner: T, params?: Record<string, any>) => void;
+export type Callback<T> = (owner: T, params?: any) => void;
 
 /**
  * Abstract class that implements the basic functionality
@@ -54,7 +54,7 @@ export class Observable {
         this.events[event] = callbacks;
     }
 
-    trigger(event: string, params?: Record<string, any>) {
+    trigger(event: string, params?: any) {
         const callbacks = this.events[event] ?? [];
         callbacks.forEach((c) => c(this, params));
     }
@@ -123,7 +123,7 @@ export interface Emulator extends ObservableI {
      * The name of the current execution engine being used
      * by the emulator.
      */
-    get engine(): string;
+    get engine(): string | null;
 
     /**
      * The pixel format of the emulator's display
@@ -152,6 +152,7 @@ export interface Emulator extends ObservableI {
      * should impact other elements of the emulator.
      */
     get frequency(): number;
+    set frequency(value: number);
 
     /**
      * The current logic framerate of the running emulator.
@@ -410,6 +411,14 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
             `${emulator.device} running in engine "${engine}" from now on!`
         );
     };
+    const onFrequencyChange = (value: number) => {
+        emulator.frequency = value * 1000 * 1000;
+    };
+    const onFrequencyReady = (handler: (value: number) => void) => {
+        emulator.bind("frequency", (emulator: Emulator, frequency: number) => {
+            handler(frequency / 1000000);
+        });
+    };
     const onMinimize = () => {
         setFullscreen(!fullscreen);
     };
@@ -558,11 +567,13 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => {
                                 name={"CPU Frequency"}
                                 valueNode={
                                     <ButtonIncrement
-                                        value={4.19}
-                                        delta={0.1}
+                                        value={emulator.frequency / 1000 / 1000}
+                                        delta={0.4}
                                         min={0}
                                         suffix={"MHz"}
                                         decimalPlaces={2}
+                                        onChange={onFrequencyChange}
+                                        onReady={onFrequencyReady}
                                     />
                                 }
                             />
diff --git a/examples/web/react/components/button-increment/button-increment.tsx b/examples/web/react/components/button-increment/button-increment.tsx
index 62a9071a..f8060938 100644
--- a/examples/web/react/components/button-increment/button-increment.tsx
+++ b/examples/web/react/components/button-increment/button-increment.tsx
@@ -1,4 +1,4 @@
-import React, { FC, useState } from "react";
+import React, { FC, useEffect, useState } from "react";
 import Button from "../button/button";
 
 import "./button-increment.css";
@@ -16,6 +16,7 @@ type ButtonIncrementProps = {
     onClick?: () => void;
     onBeforeChange?: (value: number) => boolean;
     onChange?: (value: number) => void;
+    onReady?: (setValue: (value: number) => void) => void;
 };
 
 export const ButtonIncrement: FC<ButtonIncrementProps> = ({
@@ -30,25 +31,31 @@ export const ButtonIncrement: FC<ButtonIncrementProps> = ({
     style = ["simple", "border"],
     onClick,
     onBeforeChange,
-    onChange
+    onChange,
+    onReady
 }) => {
     const [valueState, setValue] = useState(value);
     const classes = () => ["button-increment", size, ...style].join(" ");
+    useEffect(() => {
+        onReady && onReady((value) => setValue(value));
+    }, []);
     const _onMinusClick = () => {
-        const valueNew = valueState - delta;
+        let valueNew = valueState - delta;
         if (onBeforeChange) {
             if (!onBeforeChange(valueNew)) return;
         }
-        if (min !== undefined && valueNew < min) return;
+        if (min !== undefined) valueNew = Math.max(min, valueNew);
+        if (valueNew === valueState) return;
         setValue(valueNew);
         if (onChange) onChange(valueNew);
     };
     const _onPlusClick = () => {
-        const valueNew = valueState + delta;
+        let valueNew = valueState + delta;
         if (onBeforeChange) {
             if (!onBeforeChange(valueNew)) return;
         }
-        if (max !== undefined && valueNew > max) return;
+        if (max !== undefined) valueNew = Math.min(max, valueNew);
+        if (valueNew === valueState) return;
         setValue(valueNew);
         if (onChange) onChange(valueNew);
     };
-- 
GitLab