Skip to content

Commit 846a426

Browse files
committed
feat(harmony): add ViewAnnotationUtils and NativeMapView components
- Add ViewAnnotationUtils utility class for annotation operations - Add NativeMapView component for native view rendering support - Enhance InfoWindowManager with improved layout management - Update NativeViewAnnotationOverlay with better view lifecycle handling - Refactor light_harmony.cpp style handling
1 parent daa833b commit 846a426

8 files changed

Lines changed: 426 additions & 39 deletions

File tree

platform/harmony/maplibre_harmony/src/main/cpp/style/light_harmony.cpp

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ namespace harmony {
1414
// Static member initialization
1515
napi_ref LightHarmony::constructor = nullptr;
1616

17+
// Constructor data structure for passing to napi_new_instance
18+
struct LightConstructorData {
19+
mbgl::Map* map;
20+
mbgl::style::Light* light;
21+
};
22+
1723
LightHarmony::LightHarmony(mbgl::Map& coreMap, mbgl::style::Light& coreLight)
1824
: light(coreLight), map(&coreMap) {
1925
Logger::info("LightHarmony", "Light wrapper created");
@@ -30,7 +36,7 @@ void LightHarmony::Destructor(napi_env env, void* nativeObject, void* hint) {
3036

3137
napi_value LightHarmony::Init(napi_env env, napi_value exports) {
3238
Logger::info("LightHarmony", "Initializing Light NAPI class");
33-
39+
3440
napi_property_descriptor properties[] = {
3541
{ "getAnchor", nullptr, GetAnchor, nullptr, nullptr, nullptr, napi_default, nullptr },
3642
{ "setAnchor", nullptr, SetAnchor, nullptr, nullptr, nullptr, napi_default, nullptr },
@@ -47,33 +53,51 @@ napi_value LightHarmony::Init(napi_env env, napi_value exports) {
4753
{ "getIntensityTransition", nullptr, GetIntensityTransition, nullptr, nullptr, nullptr, napi_default, nullptr },
4854
{ "setIntensityTransition", nullptr, SetIntensityTransition, nullptr, nullptr, nullptr, napi_default, nullptr },
4955
};
50-
56+
5157
napi_value cons;
52-
napi_status status = napi_define_class(env, "Light", NAPI_AUTO_LENGTH,
58+
napi_status status = napi_define_class(env, "Light", NAPI_AUTO_LENGTH,
5359
[](napi_env env, napi_callback_info info) -> napi_value {
5460
napi_value jsThis;
55-
napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);
61+
size_t argc = 1;
62+
napi_value argv[1];
63+
napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr);
64+
65+
// Check if called as constructor (new Light(...))
66+
napi_value newTarget;
67+
napi_get_new_target(env, info, &newTarget);
68+
if (newTarget != nullptr) {
69+
// Constructor call - extract data and create wrapper
70+
if (argc > 0) {
71+
LightConstructorData* data = nullptr;
72+
napi_get_value_external(env, argv[0], (void**)&data);
73+
if (data) {
74+
LightHarmony* lightHarmony = new LightHarmony(*data->map, *data->light);
75+
napi_wrap(env, jsThis, lightHarmony, Destructor, nullptr, nullptr);
76+
Logger::info("LightHarmony", "Light wrapper created via constructor");
77+
}
78+
}
79+
}
5680
return jsThis;
57-
},
81+
},
5882
nullptr, sizeof(properties) / sizeof(properties[0]), properties, &cons);
59-
83+
6084
if (status != napi_ok) {
6185
Logger::error("LightHarmony", "Failed to define Light class");
6286
return nullptr;
6387
}
64-
88+
6589
status = napi_create_reference(env, cons, 1, &constructor);
6690
if (status != napi_ok) {
6791
Logger::error("LightHarmony", "Failed to create Light constructor reference");
6892
return nullptr;
6993
}
70-
94+
7195
status = napi_set_named_property(env, exports, "Light", cons);
7296
if (status != napi_ok) {
7397
Logger::error("LightHarmony", "Failed to export Light class");
7498
return nullptr;
7599
}
76-
100+
77101
Logger::info("LightHarmony", "Light NAPI class initialized successfully");
78102
return exports;
79103
}
@@ -85,32 +109,31 @@ napi_value LightHarmony::CreateLightPeer(napi_env env, mbgl::Map& map, mbgl::sty
85109
napi_get_undefined(env, &undefined);
86110
return undefined;
87111
}
88-
112+
89113
napi_value cons;
90114
napi_get_reference_value(env, constructor, &cons);
91-
92-
napi_value instance;
93-
napi_status status = napi_new_instance(env, cons, 0, nullptr, &instance);
115+
116+
// Create data structure with Map and Light pointers
117+
LightConstructorData data{&map, &coreLight};
118+
119+
napi_value dataExternal;
120+
napi_status status = napi_create_external(env, &data, nullptr, nullptr, &dataExternal);
94121
if (status != napi_ok) {
95-
Logger::error("LightHarmony", "Failed to create Light instance");
122+
Logger::error("LightHarmony", "Failed to create external data");
96123
napi_value undefined;
97124
napi_get_undefined(env, &undefined);
98125
return undefined;
99126
}
100-
101-
// Create native wrapper
102-
LightHarmony* lightHarmony = new LightHarmony(map, coreLight);
103-
104-
// Wrap native object
105-
status = napi_wrap(env, instance, lightHarmony, Destructor, nullptr, nullptr);
127+
128+
napi_value instance;
129+
status = napi_new_instance(env, cons, 1, &dataExternal, &instance);
106130
if (status != napi_ok) {
107-
Logger::error("LightHarmony", "Failed to wrap Light instance");
108-
delete lightHarmony;
131+
Logger::error("LightHarmony", "Failed to create Light instance");
109132
napi_value undefined;
110133
napi_get_undefined(env, &undefined);
111134
return undefined;
112135
}
113-
136+
114137
Logger::info("LightHarmony", "Light peer created successfully");
115138
return instance;
116139
}

platform/harmony/maplibre_harmony/src/main/ets/maps/MapLibreMap.ets

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ export class MapLibreMap {
223223
private pendingAnimationCallback: CameraAnimationCallback | null = null;
224224
private animationTimerId: number | null = null;
225225
private animationCancelled: boolean = false;
226+
// Camera move throttling - limits update frequency during gestures
227+
private lastCameraMoveTime: number = 0;
228+
private static readonly CAMERA_MOVE_THROTTLE_MS = 16; // ~60fps limit
226229

227230
constructor(nativeMapView: NativeMapView, options: MapLibreMapOptions) {
228231
this.nativeMapView = nativeMapView;
@@ -3098,7 +3101,12 @@ export class MapLibreMap {
30983101
console.warn('[MapLibreMap] _notifyCameraMove() called but map is destroyed, skipping');
30993102
return;
31003103
}
3101-
console.info('[MapLibreMap] _notifyCameraMove() called');
3104+
// Throttle updates to ~60fps to avoid overwhelming the UI thread during gestures
3105+
const now = Date.now();
3106+
if (now - this.lastCameraMoveTime < MapLibreMap.CAMERA_MOVE_THROTTLE_MS) {
3107+
return;
3108+
}
3109+
this.lastCameraMoveTime = now;
31023110
// Mirrors Android: update UI elements (InfoWindow, etc.) while the camera moves
31033111
this.onUpdateRegionChange();
31043112
this.cameraChangeDispatcher.notifyCameraMove();

platform/harmony/maplibre_harmony/src/main/ets/maps/annotations/InfoWindow.ets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ struct DefaultInfoWindowHost {
305305
this.context.onClose?.();
306306
},
307307
onSizeChanged: (width: number, height: number) => {
308-
this.notifySizeChanged(width, height);
308+
this.notifySizeChanged?.(width, height);
309309
}
310310
});
311311
}

platform/harmony/maplibre_harmony/src/main/ets/maps/annotations/InfoWindowManager.ets

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ interface InfoWindowUpdateOptions extends ViewAnnotationUpdateOptions {
2929
viewName: string;
3030
}
3131

32+
/**
33+
* Global reference to the current InfoWindowManager instance.
34+
* Used by NativeViewAnnotationOverlay to get InfoWindowBuilderContext for rendering.
35+
*/
36+
let currentInfoWindowManager: InfoWindowManager | null = null;
37+
38+
/**
39+
* Get the current InfoWindowManager instance.
40+
* Used for cross-component context access during rendering.
41+
*/
42+
export function getInfoWindowManagerInstance(): InfoWindowManager | null {
43+
return currentInfoWindowManager;
44+
}
45+
3246
/**
3347
* Options supplied when displaying an InfoWindow via the manager.
3448
*/
@@ -51,8 +65,8 @@ class InfoWindowContextManager {
5165
registerInfoWindowContext(annotationId: number, ctx: InfoWindowBuilderContext) {
5266
this.ctxMap.set(annotationId, ctx)
5367
}
54-
getInfoWindowContext(annotation: number): InfoWindowBuilderContext | null{
55-
return this.ctxMap.get(annotation)?? null
68+
public getInfoWindowContext(annotation: number): InfoWindowBuilderContext | null {
69+
return this.ctxMap.get(annotation) ?? null;
5670
}
5771
clear() {
5872
this.ctxMap.clear();
@@ -72,6 +86,28 @@ export class InfoWindowManager {
7286
private onCloseListener: OnInfoWindowCloseListener | null = null;
7387
private allowConcurrentMultiple: boolean = false;
7488
private viewAnnotationManager: ViewAnnotationManager | null = null;
89+
private ctxMap: Map<number, InfoWindowBuilderContext> = new Map();
90+
91+
constructor() {
92+
// Set as the current global instance for cross-component access
93+
currentInfoWindowManager = this;
94+
}
95+
96+
/**
97+
* Register an InfoWindowBuilderContext for an annotation ID.
98+
* Called by the rendering system to enable context lookup during view updates.
99+
*/
100+
registerInfoWindowContext(annotationId: number, ctx: InfoWindowBuilderContext): void {
101+
this.ctxMap.set(annotationId, ctx);
102+
}
103+
104+
/**
105+
* Get InfoWindowBuilderContext by annotation ID.
106+
* Used by NativeViewAnnotationOverlay during rendering.
107+
*/
108+
public getInfoWindowContext(annotationId: number): InfoWindowBuilderContext | null {
109+
return this.ctxMap.get(annotationId) ?? null;
110+
}
75111

76112
/**
77113
* Bind the ViewAnnotationManager used to create InfoWindow annotations.
@@ -225,6 +261,7 @@ export class InfoWindowManager {
225261
const layout = activeInfoWindow.getLayoutOptions();
226262

227263
const existingId = activeInfoWindow.getAnnotationId();
264+
let annotationId = existingId;
228265
if (existingId > 0) {
229266
try {
230267
const updateOptions = {
@@ -241,7 +278,6 @@ export class InfoWindowManager {
241278
} catch (error) {
242279
const reason = error instanceof Error ? error.message : `${error}`;
243280
console.error('[InfoWindowManager] updateViewAnnotation failed: ', reason);
244-
return null;
245281
}
246282
} else {
247283
const estimatedSize = activeInfoWindow.estimateSize();
@@ -260,7 +296,7 @@ export class InfoWindowManager {
260296
zIndex: layout.zIndex
261297
} as InfoWindowAddOptions;
262298
try {
263-
const annotationId = this.viewAnnotationManager.addViewAnnotation(addOptions);
299+
annotationId = this.viewAnnotationManager.addViewAnnotation(addOptions);
264300
if (annotationId <= 0) {
265301
console.error('[InfoWindowManager] Failed to add ViewAnnotation for InfoWindow');
266302
return null;
@@ -273,6 +309,10 @@ export class InfoWindowManager {
273309
}
274310
}
275311

312+
// Register the InfoWindowBuilderContext for rendering
313+
const builderContext = new InfoWindowBuilderContext(activeInfoWindow, infoWindow.getRenderContext());
314+
this.registerInfoWindowContext(annotationId, builderContext);
315+
276316
activeInfoWindow.setVisible(true);
277317
return activeInfoWindow;
278318
}
@@ -291,6 +331,8 @@ export class InfoWindowManager {
291331
if (this.viewAnnotationManager) {
292332
this.viewAnnotationManager.removeViewAnnotation(annotationId);
293333
}
334+
// Clean up the registered context
335+
this.ctxMap.delete(annotationId);
294336
}
295337

296338
infoWindow.resetAnnotation();

platform/harmony/maplibre_harmony/src/main/ets/maps/annotations/NativeViewAnnotationOverlay.ets

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { ViewAnnotationWrappedBuilder } from './ViewAnnotationTypes';
2-
import { ViewAnnotationBuilderContext, InfoWindowBuilderContext, INFO_WINDOW_VIEW_NAME } from './ViewAnnotationTypes';
2+
import {
3+
ViewAnnotationBuilderContext,
4+
InfoWindowBuilderContext,
5+
INFO_WINDOW_VIEW_NAME
6+
} from './ViewAnnotationTypes';
7+
import { getInfoWindowManagerInstance } from './InfoWindowManager';
38

49
export interface ViewAnnotationRenderStateParams {
510
id: number;
@@ -217,8 +222,15 @@ export struct NativeViewAnnotationOverlay {
217222
private createBuilderContext(state: ViewAnnotationRenderState): ViewAnnotationBuilderContext {
218223
let ctx: ViewAnnotationBuilderContext;
219224
if (state.viewName === INFO_WINDOW_VIEW_NAME) {
220-
// Create a basic ViewAnnotationBuilderContext for InfoWindows
221-
// The actual InfoWindowBuilderContext will be created by the InfoWindowManager
225+
// For InfoWindows, get the actual InfoWindowBuilderContext from InfoWindowManager
226+
const infoWindowManager = getInfoWindowManagerInstance();
227+
if (infoWindowManager) {
228+
const infoWindowCtx = infoWindowManager.getInfoWindowContext(state.id);
229+
if (infoWindowCtx) {
230+
return infoWindowCtx;
231+
}
232+
}
233+
// Fallback: create a basic context if InfoWindowManager is not available
222234
ctx = new ViewAnnotationBuilderContext();
223235
} else {
224236
ctx = new ViewAnnotationBuilderContext();

platform/harmony/maplibre_harmony/src/main/ets/maps/annotations/ViewAnnotationManager.ets

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,6 @@ export class ViewAnnotationManager {
412412
const positionX = frameLogicalX - logicalWidth * anchorU + frameOffsetX + centerOffsetX;
413413
const positionY = frameLogicalY - logicalHeight * anchorV + frameOffsetY + centerOffsetY;
414414

415-
console.info(
416-
`[ViewAnnotationManager] frame=${frame.id} raw=(${format(frame.x)}, ${format(frame.y)}) ` +
417-
`pixelRatio(raw=${format(frame.pixelRatio)}, resolved=${format(frameRatio)}) ` +
418-
`logical=(${format(frameLogicalX)}, ${format(frameLogicalY)}) size=(${format(logicalWidth)}, ${format(logicalHeight)}) ` +
419-
`offset=(${format(frameOffsetX)}, ${format(frameOffsetY)}) position=(${format(positionX)}, ${format(positionY)})`
420-
);
421-
422415
nextStates.push(new ViewAnnotationRenderState({
423416
id: frame.id,
424417
positionX,

0 commit comments

Comments
 (0)