@geastack/apple 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +54 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +3076 -0
- package/generated/AVFoundation.d.ts +112 -0
- package/generated/ActivityKit.d.ts +7 -0
- package/generated/AppKit.d.ts +268 -0
- package/generated/CoreGraphics.d.ts +18 -0
- package/generated/CoreLocation.d.ts +28 -0
- package/generated/CoreMedia.d.ts +8 -0
- package/generated/Dispatch.d.ts +2 -0
- package/generated/Foundation.d.ts +13 -0
- package/generated/MapKit.d.ts +28 -0
- package/generated/Metal.d.ts +91 -0
- package/generated/MetalKit.d.ts +22 -0
- package/generated/Photos.d.ts +5 -0
- package/generated/QuartzCore.d.ts +12 -0
- package/generated/SwiftData.d.ts +11 -0
- package/generated/UIKit.d.ts +187 -0
- package/package.json +123 -0
- package/runtime/AVFoundation.js +136 -0
- package/runtime/ActivityKit.js +18 -0
- package/runtime/AppKit.js +313 -0
- package/runtime/CoreGraphics.js +15 -0
- package/runtime/CoreLocation.js +30 -0
- package/runtime/CoreMedia.js +7 -0
- package/runtime/Dispatch.js +11 -0
- package/runtime/Foundation.js +21 -0
- package/runtime/MapKit.js +22 -0
- package/runtime/Metal.js +126 -0
- package/runtime/MetalKit.js +15 -0
- package/runtime/Photos.js +9 -0
- package/runtime/QuartzCore.js +9 -0
- package/runtime/SwiftData.js +29 -0
- package/runtime/UIKit.js +242 -0
- package/runtime/empty.js +1 -0
- package/targets/ios/Info.plist.in +25 -0
- package/targets/ios/README.md +46 -0
- package/targets/ios/build-ios.sh +395 -0
- package/targets/ios/detect-development-team.mjs +109 -0
- package/targets/ios/generate-resident-entry.mjs +67 -0
- package/targets/ios/generate-resident-registry.mjs +162 -0
- package/targets/ios/generate-xcode-project.mjs +520 -0
- package/targets/ios/main/canvas_view.h +7 -0
- package/targets/ios/main/canvas_view.mm +94 -0
- package/targets/ios/main/font_registry.h +17 -0
- package/targets/ios/main/font_registry.mm +129 -0
- package/targets/ios/main/image_bridge.h +13 -0
- package/targets/ios/main/image_bridge.mm +53 -0
- package/targets/ios/main/ios_apps.c +28 -0
- package/targets/ios/main/ios_camera.mm +446 -0
- package/targets/ios/main/ios_display.mm +344 -0
- package/targets/ios/main/ios_main.mm +811 -0
- package/targets/ios/main/ios_memory.cpp +55 -0
- package/targets/ios/main/ios_renderer.h +20 -0
- package/targets/ios/main/ios_renderer.mm +27 -0
- package/targets/ios/main/ios_root_background.cpp +20 -0
- package/targets/ios/main/ios_root_background.h +11 -0
- package/targets/ios/main/ios_sensors.mm +244 -0
- package/targets/ios/main/ios_timers.mm +53 -0
- package/targets/ios/main/renderer/ios_renderer_internal.h +65 -0
- package/targets/ios/main/renderer/ios_renderer_support.mm +111 -0
- package/targets/ios/main/renderer/native_button.mm +179 -0
- package/targets/ios/main/renderer/native_label.mm +161 -0
- package/targets/ios/main/renderer/native_scroll_container.mm +279 -0
- package/targets/ios/main/renderer/view_reconciler.mm +274 -0
- package/targets/macos/Info.plist.in +26 -0
- package/targets/macos/README.md +131 -0
- package/targets/macos/build-child-lifecycle.sh +119 -0
- package/targets/macos/build-macos.sh +2156 -0
- package/targets/macos/check-module-graph-freshness.mjs +82 -0
- package/targets/macos/compiler-input-fingerprint.mjs +32 -0
- package/targets/macos/finalize-apple-native-output.mjs +77 -0
- package/targets/macos/generate-resident-entry.mjs +80 -0
- package/targets/macos/generate-resident-registry.mjs +141 -0
- package/targets/macos/heavy-build-lock.sh +176 -0
- package/targets/macos/main/canvas_view.h +9 -0
- package/targets/macos/main/canvas_view.mm +176 -0
- package/targets/macos/main/color_convert.h +18 -0
- package/targets/macos/main/color_convert.mm +39 -0
- package/targets/macos/main/font_registry.h +35 -0
- package/targets/macos/main/font_registry.mm +350 -0
- package/targets/macos/main/image_bridge.h +16 -0
- package/targets/macos/main/image_bridge.mm +52 -0
- package/targets/macos/main/macos_app_platform.mm +95 -0
- package/targets/macos/main/macos_apps.c +31 -0
- package/targets/macos/main/macos_device_control.mm +56 -0
- package/targets/macos/main/macos_display.mm +484 -0
- package/targets/macos/main/macos_host_device_control.mm +95 -0
- package/targets/macos/main/macos_main.mm +839 -0
- package/targets/macos/main/macos_memory.cpp +31 -0
- package/targets/macos/main/macos_native_shell.h +31 -0
- package/targets/macos/main/macos_native_shell.mm +396 -0
- package/targets/macos/main/macos_network.mm +253 -0
- package/targets/macos/main/macos_renderer.h +39 -0
- package/targets/macos/main/macos_renderer.mm +1969 -0
- package/targets/macos/main/macos_sensors.cpp +148 -0
- package/targets/macos/main/macos_smoke_app.cpp +65 -0
- package/targets/macos/main/macos_timers.mm +52 -0
- package/targets/macos/main/press_bridge.h +21 -0
- package/targets/macos/main/press_bridge.mm +72 -0
- package/targets/macos/main/thermalright_hid_display.h +14 -0
- package/targets/macos/main/thermalright_hid_display.mm +995 -0
- package/targets/macos/native-artifact-provenance.mjs +421 -0
- package/targets/macos/order-compile-edges.mjs +64 -0
- package/targets/macos/preflight-hard-muted-artifact.mjs +93 -0
- package/targets/macos/prune-generated-modules.mjs +147 -0
- package/targets/macos/sync-resources.mjs +153 -0
- package/targets/shared/analyze-audio-capture.mjs +131 -0
- package/targets/shared/apple_audio.mm +1194 -0
- package/targets/shared/apple_audio_selftest.mm +602 -0
- package/targets/shared/apple_audio_testing.h +34 -0
- package/targets/shared/build-audio-selftest.sh +50 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#import <AppKit/AppKit.h>
|
|
2
|
+
|
|
3
|
+
#include "apps.h"
|
|
4
|
+
|
|
5
|
+
#include <string>
|
|
6
|
+
|
|
7
|
+
// macOS app-launcher platform. When the JSX-level `Apps.launch('foo')` runs,
|
|
8
|
+
// AppManager::launch routes through this platform. We don't have in-process
|
|
9
|
+
// app swapping yet (that'd need every app's __gea_top_level bundled into one
|
|
10
|
+
// binary with a registry — like ESP32's resident-app system), so we cheat:
|
|
11
|
+
// find dist/<id>/*.app next to the currently running bundle, open it with
|
|
12
|
+
// NSWorkspace, and terminate self. Result is "click a tile → next app's
|
|
13
|
+
// window appears, launcher window goes away" — same UX as the watch, just
|
|
14
|
+
// realized via separate Mac processes.
|
|
15
|
+
//
|
|
16
|
+
// If the target .app hasn't been built yet, we log a message and stay in
|
|
17
|
+
// the launcher. The user runs `targets/macos/build-macos.sh <id>` first.
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
class MacosAppLauncherPlatform final : public gea::framework::apps::AppLauncherPlatform {
|
|
22
|
+
public:
|
|
23
|
+
explicit MacosAppLauncherPlatform(std::string currentId) : currentId_(std::move(currentId)) {}
|
|
24
|
+
|
|
25
|
+
const char *currentInstalledAppId() override { return currentId_.c_str(); }
|
|
26
|
+
bool runningAppIsLauncher(const char *launcherAppId) override
|
|
27
|
+
{
|
|
28
|
+
if (!launcherAppId) return false;
|
|
29
|
+
return currentId_ == launcherAppId;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
bool launchInstalledApp(const char *appId) override
|
|
33
|
+
{
|
|
34
|
+
if (!appId || !appId[0]) return false;
|
|
35
|
+
NSString *target = [NSString stringWithUTF8String:appId];
|
|
36
|
+
NSString *bundle = findBundleForAppId(target);
|
|
37
|
+
if (!bundle) {
|
|
38
|
+
NSLog(@"[gea-launch] no built .app for '%@' under dist/. Run "
|
|
39
|
+
@"targets/macos/build-macos.sh %@ first.",
|
|
40
|
+
target, target);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
NSURL *url = [NSURL fileURLWithPath:bundle];
|
|
44
|
+
NSWorkspaceOpenConfiguration *cfg = [NSWorkspaceOpenConfiguration configuration];
|
|
45
|
+
cfg.activates = YES;
|
|
46
|
+
cfg.createsNewApplicationInstance = YES; // don't reuse a running instance of the same app
|
|
47
|
+
[[NSWorkspace sharedWorkspace] openApplicationAtURL:url
|
|
48
|
+
configuration:cfg
|
|
49
|
+
completionHandler:^(NSRunningApplication *app, NSError *err) {
|
|
50
|
+
(void)app;
|
|
51
|
+
if (err) NSLog(@"[gea-launch] open failed: %@", err);
|
|
52
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
53
|
+
[NSApp terminate:nil];
|
|
54
|
+
});
|
|
55
|
+
}];
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private:
|
|
60
|
+
std::string currentId_;
|
|
61
|
+
|
|
62
|
+
static NSString *findBundleForAppId(NSString *appId)
|
|
63
|
+
{
|
|
64
|
+
// Default bundle: .../dist/<currentId>/<Name>.app. Tagged builds use
|
|
65
|
+
// .../dist/.namespaces/<tag>/<currentId>/<Name>.app. In both layouts,
|
|
66
|
+
// two parent traversals land on the root whose children are app ids, so a
|
|
67
|
+
// tagged launcher opens apps built in the same output namespace.
|
|
68
|
+
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
|
|
69
|
+
NSString *distDir = [[bundlePath stringByDeletingLastPathComponent]
|
|
70
|
+
stringByDeletingLastPathComponent];
|
|
71
|
+
NSString *appDir = [distDir stringByAppendingPathComponent:appId];
|
|
72
|
+
NSError *err = nil;
|
|
73
|
+
NSArray<NSString *> *entries =
|
|
74
|
+
[[NSFileManager defaultManager] contentsOfDirectoryAtPath:appDir error:&err];
|
|
75
|
+
if (!entries) return nil;
|
|
76
|
+
for (NSString *name in entries) {
|
|
77
|
+
if ([name hasSuffix:@".app"]) return [appDir stringByAppendingPathComponent:name];
|
|
78
|
+
}
|
|
79
|
+
return nil;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
} // namespace
|
|
84
|
+
|
|
85
|
+
namespace gea::macos {
|
|
86
|
+
|
|
87
|
+
void installAppLauncherPlatform(const char *currentAppId)
|
|
88
|
+
{
|
|
89
|
+
static MacosAppLauncherPlatform *instance = nullptr;
|
|
90
|
+
if (instance) return;
|
|
91
|
+
instance = new MacosAppLauncherPlatform(currentAppId ? currentAppId : "");
|
|
92
|
+
gea::framework::apps::AppManager::setPlatform(instance);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
} // namespace gea::macos
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#include <string.h>
|
|
2
|
+
|
|
3
|
+
/* The apps.h header in @geastack/core's include/ is C++-only. The framework
|
|
4
|
+
* itself calls the gea_embedded_apps_* C API via generated app code (and
|
|
5
|
+
* compiles it as C), so we declare the prototypes inline here. */
|
|
6
|
+
int gea_embedded_apps_launch(const char *app_id);
|
|
7
|
+
int gea_embedded_apps_return_to_launcher_on_reset(void);
|
|
8
|
+
void gea_embedded_apps_start_launcher_button_task(void);
|
|
9
|
+
const char *gea_embedded_apps_get_current_id(void);
|
|
10
|
+
|
|
11
|
+
static char s_current_app_id[64] = "";
|
|
12
|
+
|
|
13
|
+
int gea_embedded_apps_launch(const char *app_id)
|
|
14
|
+
{
|
|
15
|
+
if (!app_id || !app_id[0]) return 0;
|
|
16
|
+
strncpy(s_current_app_id, app_id, sizeof(s_current_app_id) - 1);
|
|
17
|
+
s_current_app_id[sizeof(s_current_app_id) - 1] = '\0';
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
int gea_embedded_apps_return_to_launcher_on_reset(void)
|
|
22
|
+
{
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void gea_embedded_apps_start_launcher_button_task(void) {}
|
|
27
|
+
|
|
28
|
+
const char *gea_embedded_apps_get_current_id(void)
|
|
29
|
+
{
|
|
30
|
+
return s_current_app_id;
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Native implementation of @geajs/apple/AppKit runDeviceCommand(command).
|
|
2
|
+
//
|
|
3
|
+
// A small escape hatch for companion apps that drive external tooling: runs a
|
|
4
|
+
// shell command via /bin/bash and returns its combined stdout+stderr as a
|
|
5
|
+
// string. The gea Companion uses it to talk to the watch over USB through
|
|
6
|
+
// scripts/esp32-device-control.py (GEADEV protocol). The apple-native bridge
|
|
7
|
+
// is C++/ObjC++, so we have full NSTask/Foundation access here even though the
|
|
8
|
+
// TS layer only sees a `string -> string` function.
|
|
9
|
+
//
|
|
10
|
+
// Same wiring pattern as installRootViewController/installToolbar in
|
|
11
|
+
// macos_main.mm: a thunk declared by the generated bridge header, implemented
|
|
12
|
+
// natively, compiled + linked by build-macos.sh.
|
|
13
|
+
|
|
14
|
+
#import <Foundation/Foundation.h>
|
|
15
|
+
|
|
16
|
+
#include <cstdio>
|
|
17
|
+
#include <string>
|
|
18
|
+
|
|
19
|
+
#if __has_include("gea/apple/native_bridge.h")
|
|
20
|
+
#include "gea/apple/native_bridge.h"
|
|
21
|
+
|
|
22
|
+
std::string gea::apple::AppKit::runDeviceCommand(std::string command)
|
|
23
|
+
{
|
|
24
|
+
@autoreleasepool {
|
|
25
|
+
NSString *cmd = [NSString stringWithUTF8String:command.c_str()];
|
|
26
|
+
NSTask *task = [[NSTask alloc] init];
|
|
27
|
+
// -lc: login shell so PATH includes the user's python3 (with pyserial).
|
|
28
|
+
task.launchPath = @"/bin/bash";
|
|
29
|
+
task.arguments = @[ @"-lc", cmd ];
|
|
30
|
+
NSPipe *pipe = [NSPipe pipe];
|
|
31
|
+
task.standardOutput = pipe;
|
|
32
|
+
task.standardError = pipe;
|
|
33
|
+
|
|
34
|
+
std::string result;
|
|
35
|
+
@try {
|
|
36
|
+
[task launch];
|
|
37
|
+
NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
|
|
38
|
+
[task waitUntilExit];
|
|
39
|
+
NSString *out = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
40
|
+
result = out ? std::string([out UTF8String]) : std::string();
|
|
41
|
+
} @catch (NSException *ex) {
|
|
42
|
+
result = std::string("EXC: ") + (ex.reason ? [ex.reason UTF8String] : "exception");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Append to a log so headless runs (locked Mac, no visible window) can
|
|
46
|
+
// still confirm the command ran and what the device returned.
|
|
47
|
+
FILE *f = std::fopen("/tmp/gea-companion-device.log", "a");
|
|
48
|
+
if (f) {
|
|
49
|
+
std::fprintf(f, "$ %s\n%s\n---\n", command.c_str(), result.c_str());
|
|
50
|
+
std::fclose(f);
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#endif // __has_include("gea/apple/native_bridge.h")
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
#import <Cocoa/Cocoa.h>
|
|
2
|
+
|
|
3
|
+
#include "display.h"
|
|
4
|
+
#include "canvas.h"
|
|
5
|
+
#include "host/display_orientation.h"
|
|
6
|
+
#include "pixel.h"
|
|
7
|
+
#include "thermalright_hid_display.h"
|
|
8
|
+
|
|
9
|
+
#include <algorithm>
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <cstdlib>
|
|
12
|
+
#include <cstring>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
// The default macOS target renders Gea UI through AppKit-native NSViews, so
|
|
16
|
+
// Display::* stays inert there. Thermalright HID display builds flip this file
|
|
17
|
+
// back into the embedded-style RGB565 framebuffer path and stream full-canvas
|
|
18
|
+
// JPEG frames to the USB panel from flush/flushRects.
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
|
|
22
|
+
gea::framework::graphics::Canvas g_canvas;
|
|
23
|
+
std::uint16_t *g_framebuffer = nullptr;
|
|
24
|
+
// Logical canvas size. Starts at the compile-time panel dims and follows the
|
|
25
|
+
// app's orientation: applyOrientation() below reallocates the surface (and
|
|
26
|
+
// resizes the window) to the rotated logical size, so window.innerWidth, the
|
|
27
|
+
// canvas surface, and the visible window always agree.
|
|
28
|
+
int g_canvas_w = gea::platform::display::kWidth;
|
|
29
|
+
int g_canvas_h = gea::platform::display::kHeight;
|
|
30
|
+
int g_brightness = 100;
|
|
31
|
+
std::uint8_t g_alpha = 255;
|
|
32
|
+
int g_flush_rows = 0;
|
|
33
|
+
int g_flush_depth = 0;
|
|
34
|
+
int g_flush_calls = 0;
|
|
35
|
+
int g_flush_pixels = 0;
|
|
36
|
+
|
|
37
|
+
void ensure_canvas()
|
|
38
|
+
{
|
|
39
|
+
if (g_framebuffer) return;
|
|
40
|
+
g_framebuffer = static_cast<std::uint16_t *>(std::calloc((size_t)g_canvas_w * (size_t)g_canvas_h, sizeof(std::uint16_t)));
|
|
41
|
+
g_canvas.bindPixels(g_framebuffer, g_canvas_w, g_canvas_h);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
bool framebuffer_target()
|
|
45
|
+
{
|
|
46
|
+
return gea::macos::thermalright::enabled();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void count_rect(int x0, int y0, int x1, int y1)
|
|
50
|
+
{
|
|
51
|
+
using gea::platform::display::kWidth;
|
|
52
|
+
using gea::platform::display::kHeight;
|
|
53
|
+
if (x0 < 0) x0 = 0;
|
|
54
|
+
if (y0 < 0) y0 = 0;
|
|
55
|
+
if (x1 >= kWidth) x1 = kWidth - 1;
|
|
56
|
+
if (y1 >= kHeight) y1 = kHeight - 1;
|
|
57
|
+
if (x0 > x1 || y0 > y1) return;
|
|
58
|
+
g_flush_pixels += (x1 - x0 + 1) * (y1 - y0 + 1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void submit_full_frame()
|
|
62
|
+
{
|
|
63
|
+
if (!framebuffer_target()) return;
|
|
64
|
+
ensure_canvas();
|
|
65
|
+
gea::macos::thermalright::submitRgb565(g_framebuffer,
|
|
66
|
+
gea::platform::display::kWidth,
|
|
67
|
+
gea::platform::display::kHeight);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} // namespace
|
|
71
|
+
|
|
72
|
+
namespace gea::platform::display {
|
|
73
|
+
|
|
74
|
+
bool Display::init() { ensure_canvas(); return true; }
|
|
75
|
+
bool Display::start() { return true; }
|
|
76
|
+
|
|
77
|
+
gea::framework::graphics::Canvas *Display::canvas()
|
|
78
|
+
{
|
|
79
|
+
ensure_canvas();
|
|
80
|
+
return &g_canvas;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void Display::clear()
|
|
84
|
+
{
|
|
85
|
+
if (!framebuffer_target()) return;
|
|
86
|
+
canvas()->clear(0);
|
|
87
|
+
flush();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
void Display::clearNoFlush()
|
|
91
|
+
{
|
|
92
|
+
if (!framebuffer_target()) return;
|
|
93
|
+
canvas()->clear(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void Display::print(const char *) {}
|
|
97
|
+
void Display::flush()
|
|
98
|
+
{
|
|
99
|
+
if (!framebuffer_target()) return;
|
|
100
|
+
ensure_canvas();
|
|
101
|
+
g_flush_calls++;
|
|
102
|
+
int x0 = 0;
|
|
103
|
+
int y0 = 0;
|
|
104
|
+
int x1 = -1;
|
|
105
|
+
int y1 = -1;
|
|
106
|
+
if (g_canvas.dirty(&x0, &y0, &x1, &y1)) count_rect(x0, y0, x1, y1);
|
|
107
|
+
submit_full_frame();
|
|
108
|
+
g_canvas.resetDirty();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
void Display::flushRects(const DisplayFlushRect *rects, int count, bool)
|
|
112
|
+
{
|
|
113
|
+
if (!framebuffer_target()) return;
|
|
114
|
+
if (!rects || count <= 0) return;
|
|
115
|
+
ensure_canvas();
|
|
116
|
+
g_flush_calls++;
|
|
117
|
+
for (int i = 0; i < count; i++) count_rect(rects[i].x0, rects[i].y0, rects[i].x1, rects[i].y1);
|
|
118
|
+
submit_full_frame();
|
|
119
|
+
g_canvas.resetDirty();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
void Display::flushRectsRasterized(const DisplayFlushRect *rects, int count, DisplayStreamRasterFn raster, void *user, bool allowPerChunkDrain)
|
|
123
|
+
{
|
|
124
|
+
(void)allowPerChunkDrain;
|
|
125
|
+
if (!framebuffer_target() || !rects || count <= 0 || !raster) return;
|
|
126
|
+
ensure_canvas();
|
|
127
|
+
for (int i = 0; i < count; i++) {
|
|
128
|
+
int x0 = std::max(0, rects[i].x0);
|
|
129
|
+
int y0 = std::max(0, rects[i].y0);
|
|
130
|
+
int x1 = std::min(kWidth - 1, rects[i].x1);
|
|
131
|
+
int y1 = std::min(kHeight - 1, rects[i].y1);
|
|
132
|
+
if (x0 > x1 || y0 > y1) continue;
|
|
133
|
+
const int w = x1 - x0 + 1;
|
|
134
|
+
const int h = y1 - y0 + 1;
|
|
135
|
+
std::vector<std::uint16_t> pixels(static_cast<std::size_t>(w) * static_cast<std::size_t>(h));
|
|
136
|
+
raster(pixels.data(), w, h, x0, y0, user);
|
|
137
|
+
for (int row = 0; row < h; row++) {
|
|
138
|
+
std::memcpy(g_framebuffer + static_cast<std::size_t>(y0 + row) * kWidth + x0,
|
|
139
|
+
pixels.data() + static_cast<std::size_t>(row) * w,
|
|
140
|
+
static_cast<std::size_t>(w) * sizeof(std::uint16_t));
|
|
141
|
+
}
|
|
142
|
+
g_canvas.markDirty(x0, y0, x1, y1);
|
|
143
|
+
}
|
|
144
|
+
flushRects(rects, count, allowPerChunkDrain);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
void Display::rebindCanvasToFramebuffer()
|
|
148
|
+
{
|
|
149
|
+
ensure_canvas();
|
|
150
|
+
g_canvas.bindPixels(g_framebuffer, g_canvas_w, g_canvas_h);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
bool Display::streamRect(int x, int y, int w, int h, DisplayStreamRasterFn raster, void *user)
|
|
154
|
+
{
|
|
155
|
+
if (!framebuffer_target()) return false;
|
|
156
|
+
ensure_canvas();
|
|
157
|
+
if (!raster || w <= 0 || h <= 0) return false;
|
|
158
|
+
int x0 = std::max(0, x);
|
|
159
|
+
int y0 = std::max(0, y);
|
|
160
|
+
int x1 = std::min(kWidth - 1, x + w - 1);
|
|
161
|
+
int y1 = std::min(kHeight - 1, y + h - 1);
|
|
162
|
+
if (x0 > x1 || y0 > y1) return true;
|
|
163
|
+
|
|
164
|
+
const int rw = x1 - x0 + 1;
|
|
165
|
+
const int rh = y1 - y0 + 1;
|
|
166
|
+
std::vector<std::uint16_t> pixels(static_cast<std::size_t>(rw) * static_cast<std::size_t>(rh));
|
|
167
|
+
raster(pixels.data(), rw, rh, x0, y0, user);
|
|
168
|
+
for (int row = 0; row < rh; row++) {
|
|
169
|
+
std::memcpy(g_framebuffer + static_cast<std::size_t>(y0 + row) * kWidth + x0,
|
|
170
|
+
pixels.data() + static_cast<std::size_t>(row) * rw,
|
|
171
|
+
static_cast<std::size_t>(rw) * sizeof(std::uint16_t));
|
|
172
|
+
}
|
|
173
|
+
g_canvas.markDirty(x0, y0, x1, y1);
|
|
174
|
+
flush();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
bool Display::present(const DisplayPresentCommand *commands, int command_count)
|
|
179
|
+
{
|
|
180
|
+
if (framebuffer_target()) {
|
|
181
|
+
ensure_canvas();
|
|
182
|
+
if (!commands || command_count <= 0) return false;
|
|
183
|
+
for (int i = 0; i < command_count; i++) {
|
|
184
|
+
const auto &command = commands[i];
|
|
185
|
+
switch (command.type) {
|
|
186
|
+
case DisplayPresentCommandType::Clear:
|
|
187
|
+
g_canvas.clear(command.clear.color);
|
|
188
|
+
break;
|
|
189
|
+
case DisplayPresentCommandType::FillRectRgb565:
|
|
190
|
+
g_canvas.setGlobalAlpha(command.fillRectRgb565.alpha);
|
|
191
|
+
g_canvas.fillRect(command.fillRectRgb565.x, command.fillRectRgb565.y,
|
|
192
|
+
command.fillRectRgb565.w, command.fillRectRgb565.h,
|
|
193
|
+
command.fillRectRgb565.color);
|
|
194
|
+
break;
|
|
195
|
+
case DisplayPresentCommandType::StrokeRectRgb565:
|
|
196
|
+
g_canvas.setGlobalAlpha(command.strokeRectRgb565.alpha);
|
|
197
|
+
g_canvas.strokeRect(command.strokeRectRgb565.x, command.strokeRectRgb565.y,
|
|
198
|
+
command.strokeRectRgb565.w, command.strokeRectRgb565.h,
|
|
199
|
+
command.strokeRectRgb565.color);
|
|
200
|
+
break;
|
|
201
|
+
case DisplayPresentCommandType::FillTriangleRgb565:
|
|
202
|
+
g_canvas.setGlobalAlpha(command.fillTriangleRgb565.alpha);
|
|
203
|
+
g_canvas.fillTriangle(command.fillTriangleRgb565.x0, command.fillTriangleRgb565.y0,
|
|
204
|
+
command.fillTriangleRgb565.x1, command.fillTriangleRgb565.y1,
|
|
205
|
+
command.fillTriangleRgb565.x2, command.fillTriangleRgb565.y2,
|
|
206
|
+
command.fillTriangleRgb565.color);
|
|
207
|
+
break;
|
|
208
|
+
case DisplayPresentCommandType::FillCircleRgb565:
|
|
209
|
+
g_canvas.setGlobalAlpha(command.fillCircleRgb565.alpha);
|
|
210
|
+
g_canvas.fillCircle(command.fillCircleRgb565.x, command.fillCircleRgb565.y,
|
|
211
|
+
command.fillCircleRgb565.radius,
|
|
212
|
+
command.fillCircleRgb565.color);
|
|
213
|
+
break;
|
|
214
|
+
case DisplayPresentCommandType::StrokeCircleRgb565:
|
|
215
|
+
g_canvas.setGlobalAlpha(command.strokeCircleRgb565.alpha);
|
|
216
|
+
g_canvas.strokeCircle(command.strokeCircleRgb565.x, command.strokeCircleRgb565.y,
|
|
217
|
+
command.strokeCircleRgb565.radius,
|
|
218
|
+
command.strokeCircleRgb565.color);
|
|
219
|
+
break;
|
|
220
|
+
case DisplayPresentCommandType::FillCirclesRgb565:
|
|
221
|
+
g_canvas.setGlobalAlpha(command.fillCirclesRgb565.alpha);
|
|
222
|
+
g_canvas.fillCirclesRgb565(command.fillCirclesRgb565.xs,
|
|
223
|
+
command.fillCirclesRgb565.ys,
|
|
224
|
+
command.fillCirclesRgb565.count,
|
|
225
|
+
command.fillCirclesRgb565.radius,
|
|
226
|
+
command.fillCirclesRgb565.colors);
|
|
227
|
+
break;
|
|
228
|
+
case DisplayPresentCommandType::FillTrianglesRgb565:
|
|
229
|
+
// Entries are depth-ordered by the canvas layer; draw in order
|
|
230
|
+
// (same replay as canvas_element's replayPresentBatchToCanvas).
|
|
231
|
+
g_canvas.setGlobalAlpha(command.fillTrianglesRgb565.alpha);
|
|
232
|
+
for (int t = 0; t < command.fillTrianglesRgb565.count; t++) {
|
|
233
|
+
const auto &tri = command.fillTrianglesRgb565.entries[t];
|
|
234
|
+
g_canvas.fillTriangle(tri.x0, tri.y0, tri.x1, tri.y1, tri.x2, tri.y2, tri.color);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case DisplayPresentCommandType::DrawImage:
|
|
238
|
+
g_canvas.setGlobalAlpha(command.drawImage.alpha);
|
|
239
|
+
g_canvas.drawImage(command.drawImage.pixels, command.drawImage.alphaPixels,
|
|
240
|
+
command.drawImage.srcWidth, command.drawImage.srcHeight,
|
|
241
|
+
command.drawImage.x, command.drawImage.y);
|
|
242
|
+
break;
|
|
243
|
+
case DisplayPresentCommandType::DrawImageScaled:
|
|
244
|
+
g_canvas.setGlobalAlpha(command.drawImageScaled.alpha);
|
|
245
|
+
g_canvas.drawImage(command.drawImageScaled.pixels, command.drawImageScaled.alphaPixels,
|
|
246
|
+
command.drawImageScaled.srcWidth, command.drawImageScaled.srcHeight,
|
|
247
|
+
command.drawImageScaled.x, command.drawImageScaled.y,
|
|
248
|
+
command.drawImageScaled.w, command.drawImageScaled.h);
|
|
249
|
+
break;
|
|
250
|
+
case DisplayPresentCommandType::DrawImageRotated90CW:
|
|
251
|
+
g_canvas.setGlobalAlpha(command.drawImageRotated90CW.alpha);
|
|
252
|
+
g_canvas.drawImageRotated90CW(command.drawImageRotated90CW.pixels,
|
|
253
|
+
command.drawImageRotated90CW.alphaPixels,
|
|
254
|
+
command.drawImageRotated90CW.srcWidth,
|
|
255
|
+
command.drawImageRotated90CW.srcHeight,
|
|
256
|
+
command.drawImageRotated90CW.x,
|
|
257
|
+
command.drawImageRotated90CW.y,
|
|
258
|
+
command.drawImageRotated90CW.w,
|
|
259
|
+
command.drawImageRotated90CW.h);
|
|
260
|
+
break;
|
|
261
|
+
case DisplayPresentCommandType::DrawImageTiledX:
|
|
262
|
+
g_canvas.setGlobalAlpha(command.drawImageTiledX.alpha);
|
|
263
|
+
g_canvas.drawImageTiledX(command.drawImageTiledX.pixels,
|
|
264
|
+
command.drawImageTiledX.alphaPixels,
|
|
265
|
+
command.drawImageTiledX.srcWidth,
|
|
266
|
+
command.drawImageTiledX.srcHeight,
|
|
267
|
+
command.drawImageTiledX.x,
|
|
268
|
+
command.drawImageTiledX.y,
|
|
269
|
+
command.drawImageTiledX.w);
|
|
270
|
+
break;
|
|
271
|
+
case DisplayPresentCommandType::FillText:
|
|
272
|
+
g_canvas.setGlobalAlpha(command.fillText.alpha);
|
|
273
|
+
if (command.fillText.fontFamilyId >= 0) {
|
|
274
|
+
g_canvas.drawTextFontFamily(command.fillText.text,
|
|
275
|
+
command.fillText.x,
|
|
276
|
+
command.fillText.y,
|
|
277
|
+
command.fillText.color,
|
|
278
|
+
command.fillText.fontFamilyId,
|
|
279
|
+
command.fillText.fontSizePx);
|
|
280
|
+
} else {
|
|
281
|
+
g_canvas.drawText(command.fillText.text,
|
|
282
|
+
command.fillText.x,
|
|
283
|
+
command.fillText.y,
|
|
284
|
+
command.fillText.color,
|
|
285
|
+
command.fillText.scale);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
g_canvas.setGlobalAlpha(255);
|
|
291
|
+
flush();
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
// Return false to signal "the present pipeline can't run here". macOS
|
|
295
|
+
// has no hardware-accelerated batched present (this is the ESP32 LCD
|
|
296
|
+
// command path); if we return true the framework's CanvasRenderingContext2D
|
|
297
|
+
// thinks the recorded fill/stroke commands were drawn and skips the
|
|
298
|
+
// fallback that replays them onto the canvas surface — animations
|
|
299
|
+
// freeze on frame 1 because the per-frame draws get silently dropped.
|
|
300
|
+
// Returning false makes endBatch() take the replayPresentBatchToCanvas
|
|
301
|
+
// path, so the canvas pixel buffer actually receives each frame.
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
void Display::setFlushConfig(int chunk_rows, int queue_depth) { g_flush_rows = chunk_rows; g_flush_depth = queue_depth; }
|
|
306
|
+
int Display::flushChunkRows() { return g_flush_rows; }
|
|
307
|
+
int Display::flushQueueDepth() { return g_flush_depth; }
|
|
308
|
+
int Display::flushBufferBytes() { return kWidth * g_flush_rows * 2 * g_flush_depth; }
|
|
309
|
+
|
|
310
|
+
void Display::pushClip(int x, int y, int w, int h) { if (framebuffer_target()) canvas()->pushClip(x, y, w, h); }
|
|
311
|
+
void Display::popClip() { if (framebuffer_target()) canvas()->popClip(); }
|
|
312
|
+
void Display::resetClip() { if (framebuffer_target()) canvas()->resetClip(); }
|
|
313
|
+
void Display::setAlpha(uint8_t a)
|
|
314
|
+
{
|
|
315
|
+
g_alpha = a;
|
|
316
|
+
if (framebuffer_target()) canvas()->setGlobalAlpha(a);
|
|
317
|
+
}
|
|
318
|
+
uint8_t Display::alpha() { return g_alpha; }
|
|
319
|
+
int Display::brightness() { return g_brightness; }
|
|
320
|
+
void Display::setBrightness(int b)
|
|
321
|
+
{
|
|
322
|
+
if (b < 0) b = 0;
|
|
323
|
+
if (b > 100) b = 100;
|
|
324
|
+
g_brightness = b;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Tearing sync (TE/VBlank): not implemented on this target.
|
|
328
|
+
void Display::setVSync(bool) {}
|
|
329
|
+
void Display::invalidate() {}
|
|
330
|
+
bool Display::vsyncEnabled() { return false; }
|
|
331
|
+
void Display::vsyncWaitForFrame() {}
|
|
332
|
+
|
|
333
|
+
void Display::clip(int *x0, int *y0, int *x1, int *y1)
|
|
334
|
+
{
|
|
335
|
+
if (x0) *x0 = 0;
|
|
336
|
+
if (y0) *y0 = 0;
|
|
337
|
+
if (x1) *x1 = g_canvas_w - 1;
|
|
338
|
+
if (y1) *y1 = g_canvas_h - 1;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
void Display::fillRect(int x, int y, int w, int h, uint16_t c) { if (framebuffer_target()) canvas()->fillRect(x, y, w, h, c); }
|
|
342
|
+
void Display::scrollRect(int x, int y, int w, int h, int dx, int dy) { if (framebuffer_target()) canvas()->scrollRect(x, y, w, h, dx, dy); }
|
|
343
|
+
void Display::resetScrollRegion() { if (framebuffer_target()) canvas()->setScrollRegion(0, 0, 0); }
|
|
344
|
+
void Display::strokeRect(int x, int y, int w, int h, uint16_t c) { if (framebuffer_target()) canvas()->strokeRect(x, y, w, h, c); }
|
|
345
|
+
void Display::fillCircle(int cx, int cy, int r, uint16_t c) { if (framebuffer_target()) canvas()->fillCircle(cx, cy, r, c); }
|
|
346
|
+
void Display::strokeCircle(int cx, int cy, int r, uint16_t c) { if (framebuffer_target()) canvas()->strokeCircle(cx, cy, r, c); }
|
|
347
|
+
void Display::drawLine(int x0, int y0, int x1, int y1, uint16_t c) { if (framebuffer_target()) canvas()->drawLine(x0, y0, x1, y1, c); }
|
|
348
|
+
void Display::drawArc(int cx, int cy, int r, int start_deg, int end_deg, uint16_t c) { if (framebuffer_target()) canvas()->drawArc(cx, cy, r, start_deg, end_deg, c); }
|
|
349
|
+
void Display::fillTriangle(int x0, int y0, int x1, int y1, int x2, int y2, uint16_t c) { if (framebuffer_target()) canvas()->fillTriangle(x0, y0, x1, y1, x2, y2, c); }
|
|
350
|
+
void Display::drawText(const char *text, int x, int y, uint16_t c, float scale) { if (framebuffer_target()) canvas()->drawText(text, x, y, c, scale); }
|
|
351
|
+
void Display::drawTextFont(const char *text, int x, int y, uint16_t c, int font_id)
|
|
352
|
+
{
|
|
353
|
+
if (!framebuffer_target()) return;
|
|
354
|
+
#ifdef GEA_EMBEDDED_HAS_GENERATED_FONTS
|
|
355
|
+
canvas()->drawTextFont(text, x, y, c, font_id);
|
|
356
|
+
#else
|
|
357
|
+
(void)font_id;
|
|
358
|
+
canvas()->drawText(text, x, y, c, 1.0f);
|
|
359
|
+
#endif
|
|
360
|
+
}
|
|
361
|
+
void Display::drawTextFontFamily(const char *text, int x, int y, uint16_t c, int family_id, int size_px) { if (framebuffer_target()) canvas()->drawTextFontFamily(text, x, y, c, family_id, size_px); }
|
|
362
|
+
void Display::setPixel(int x, int y, uint16_t c) { if (framebuffer_target()) canvas()->fillRect(x, y, 1, 1, c); }
|
|
363
|
+
void Display::fillRoundedRect(int x, int y, int w, int h, int tl, int tr, int br, int bl, uint16_t c) { if (framebuffer_target()) canvas()->fillRoundedRect(x, y, w, h, tl, tr, br, bl, c); }
|
|
364
|
+
void Display::fillRoundedRectBoxesRgb565(const int16_t *xs, const int16_t *ys, int count, int w, int h, int tl, int tr, int br, int bl, const uint16_t *colors)
|
|
365
|
+
{
|
|
366
|
+
if (framebuffer_target()) canvas()->fillRoundedRectBoxesRgb565(xs, ys, count, w, h, tl, tr, br, bl, colors);
|
|
367
|
+
}
|
|
368
|
+
void Display::strokeRoundedRect(int x, int y, int w, int h, int tl, int tr, int br, int bl, int lw, uint16_t c) { if (framebuffer_target()) canvas()->strokeRoundedRect(x, y, w, h, tl, tr, br, bl, lw, c); }
|
|
369
|
+
void Display::blitImage(const gea::framework::graphics::pixel::native_t *src, const uint8_t *alpha, int src_w, int src_h, int dx, int dy) { if (framebuffer_target()) canvas()->drawImage(src, alpha, src_w, src_h, dx, dy); }
|
|
370
|
+
void Display::blitImageScaled(const gea::framework::graphics::pixel::native_t *src, const uint8_t *alpha, int src_w, int src_h, int dx, int dy, int dst_w, int dst_h) { if (framebuffer_target()) canvas()->drawImage(src, alpha, src_w, src_h, dx, dy, dst_w, dst_h); }
|
|
371
|
+
void Display::setWorldOverlay(const uint16_t *, int, int, int, int) {}
|
|
372
|
+
void Display::setWorldScroll(int) {}
|
|
373
|
+
bool Display::framebufferIsPanelDirect() { return false; }
|
|
374
|
+
bool Display::panelScanoutSurface(uint16_t **out_buffer, int *out_width, int *out_height)
|
|
375
|
+
{
|
|
376
|
+
if (out_buffer) *out_buffer = nullptr;
|
|
377
|
+
if (out_width) *out_width = 0;
|
|
378
|
+
if (out_height) *out_height = 0;
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
bool Display::panelDirectTarget(int, int, int, int, uint16_t **, int *, int *, int *, int *, int *, int *, int *, int *) { return false; }
|
|
382
|
+
void Display::flipPanelToBack() {}
|
|
383
|
+
bool Display::copySnapshotRgb565(uint16_t *dst, int pixel_capacity, int *width, int *height, bool)
|
|
384
|
+
{
|
|
385
|
+
ensure_canvas();
|
|
386
|
+
const int pixels = g_canvas_w * g_canvas_h;
|
|
387
|
+
if (width) *width = g_canvas_w;
|
|
388
|
+
if (height) *height = g_canvas_h;
|
|
389
|
+
if (!dst || pixel_capacity < pixels) return false;
|
|
390
|
+
std::memcpy(dst, g_framebuffer, static_cast<std::size_t>(pixels) * sizeof(std::uint16_t));
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
int Display::countNonBlackPixels(bool)
|
|
394
|
+
{
|
|
395
|
+
ensure_canvas();
|
|
396
|
+
int count = 0;
|
|
397
|
+
for (int i = 0; i < g_canvas_w * g_canvas_h; i++) {
|
|
398
|
+
if (g_framebuffer[i] != 0) count++;
|
|
399
|
+
}
|
|
400
|
+
return count;
|
|
401
|
+
}
|
|
402
|
+
void Display::reserveInternal(std::size_t) {}
|
|
403
|
+
void Display::applyPendingInternalReserve() {}
|
|
404
|
+
void Display::flushStatsRead(std::int64_t *total_us, int *call_count, int *pixel_count)
|
|
405
|
+
{
|
|
406
|
+
if (total_us) *total_us = 0;
|
|
407
|
+
if (call_count) *call_count = g_flush_calls;
|
|
408
|
+
if (pixel_count) *pixel_count = g_flush_pixels;
|
|
409
|
+
}
|
|
410
|
+
DisplayFlushPerfStats Display::flushPerfStatsRead()
|
|
411
|
+
{
|
|
412
|
+
DisplayFlushPerfStats stats;
|
|
413
|
+
stats.callCount = g_flush_calls;
|
|
414
|
+
stats.pixelCount = g_flush_pixels;
|
|
415
|
+
return stats;
|
|
416
|
+
}
|
|
417
|
+
void Display::presentPathDebug(int *calls, int *direct, int *general, int *rejected, int *tileShapeFailKind, int *tileShapeFailType, int *tileShapeFailCount)
|
|
418
|
+
{
|
|
419
|
+
if (calls) *calls = 0;
|
|
420
|
+
if (direct) *direct = 0;
|
|
421
|
+
if (general) *general = 0;
|
|
422
|
+
if (rejected) *rejected = 0;
|
|
423
|
+
if (tileShapeFailKind) *tileShapeFailKind = 0;
|
|
424
|
+
if (tileShapeFailType) *tileShapeFailType = 0;
|
|
425
|
+
if (tileShapeFailCount) *tileShapeFailCount = 0;
|
|
426
|
+
}
|
|
427
|
+
void Display::landFrameDebug(int *total, int *align, int *raster, int *text, int *flip)
|
|
428
|
+
{
|
|
429
|
+
if (total) *total = 0;
|
|
430
|
+
if (align) *align = 0;
|
|
431
|
+
if (raster) *raster = 0;
|
|
432
|
+
if (text) *text = 0;
|
|
433
|
+
if (flip) *flip = 0;
|
|
434
|
+
}
|
|
435
|
+
void Display::landPanDebug(int *detect, int *kick, int *strips, int *wait, int *interior)
|
|
436
|
+
{
|
|
437
|
+
if (detect) *detect = 0;
|
|
438
|
+
if (kick) *kick = 0;
|
|
439
|
+
if (strips) *strips = 0;
|
|
440
|
+
if (wait) *wait = 0;
|
|
441
|
+
if (interior) *interior = 0;
|
|
442
|
+
}
|
|
443
|
+
void Display::flushStatsReset()
|
|
444
|
+
{
|
|
445
|
+
g_flush_calls = 0;
|
|
446
|
+
g_flush_pixels = 0;
|
|
447
|
+
}
|
|
448
|
+
const char *Display::flushStageName() { return "idle"; }
|
|
449
|
+
int Display::flushStageChunk() { return 0; }
|
|
450
|
+
DisplayFlushStageDetail Display::flushStageDetail() { return {}; }
|
|
451
|
+
|
|
452
|
+
// Rotation hook. Defining this (even as a no-op) advertises rotation support
|
|
453
|
+
// to DisplayOrientationState, which then flips the LOGICAL viewport on
|
|
454
|
+
// setOrientation('landscape') — so it must actually rotate: a no-op left the
|
|
455
|
+
// window, canvas surface, and AppKit layout at 410x502 while apps like maps
|
|
456
|
+
// sized their world to a 502x410 viewport that didn't exist (tiles right of
|
|
457
|
+
// x=410 fetched but never visible; the bottom 92px band never receiving the
|
|
458
|
+
// app's base fill, accumulating stale tile fragments on every pan). The Mac
|
|
459
|
+
// "panel" is a window, so rotating means resizing: reallocate the surface at
|
|
460
|
+
// the rotated logical size and match the window content to it.
|
|
461
|
+
void applyOrientation(gea::framework::display::DisplayOrientation)
|
|
462
|
+
{
|
|
463
|
+
// The Thermalright HID panel has a fixed scanout — never resize it.
|
|
464
|
+
if (framebuffer_target()) return;
|
|
465
|
+
namespace od = gea::framework::display::detail;
|
|
466
|
+
const int w = od::DisplayOrientationState::width();
|
|
467
|
+
const int h = od::DisplayOrientationState::height();
|
|
468
|
+
if (w <= 0 || h <= 0 || (w == g_canvas_w && h == g_canvas_h)) return;
|
|
469
|
+
g_canvas_w = w;
|
|
470
|
+
g_canvas_h = h;
|
|
471
|
+
std::free(g_framebuffer);
|
|
472
|
+
g_framebuffer = static_cast<std::uint16_t *>(std::calloc((size_t)w * (size_t)h, sizeof(std::uint16_t)));
|
|
473
|
+
g_canvas.bindPixels(g_framebuffer, w, h);
|
|
474
|
+
// Orientation is set from the app's top level inside Application::init,
|
|
475
|
+
// which runs on the main thread with the window already on screen.
|
|
476
|
+
NSWindow *window = NSApp.mainWindow ?: NSApp.windows.firstObject;
|
|
477
|
+
if (window) {
|
|
478
|
+
[window setContentSize:NSMakeSize(w, h)];
|
|
479
|
+
[window center];
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
bool autoRotateAllowed() { return false; }
|
|
483
|
+
|
|
484
|
+
} // namespace gea::platform::display
|