+ * This interface provides a consistent abstraction layer for map operations in both JavaFX and Vaadin + * implementations, allowing developers to write framework-agnostic mapping code. + *
+ *+ * The interface provides access to various layers (UI, Vector, Control, GeoJSON) and map manipulation + * methods such as view setting, zooming, and retrieving map state information. + *
+ * + * @param+ * The web engine handles the communication between Java code and the Leaflet JavaScript library, + * enabling dynamic map operations and event handling. + *
+ * + * @return the web engine instance specific to the UI framework implementation + */ + JLWebEngine+ * This method sets up the necessary JavaScript infrastructure for map operations, + * including event handlers and communication bridges between Java and JavaScript layers. + *
+ *+ * Typically called internally during map initialization and should not be invoked manually. + *
+ */ + void addControllerToDocument(); + + /** + * Returns the internal registry of map layers by their class types. + *+ * This method provides access to the underlying layer management system, mapping + * layer interface classes to their concrete implementations. + *
+ *+ * Note: This is primarily for internal use. Access layers through + * the dedicated getter methods instead: {@link #getUiLayer()}, {@link #getVectorLayer()}, + * {@link #getControlLayer()}, {@link #getGeoJsonLayer()}. + *
+ * + * @return a map of layer classes to their instances + */ + HashMap+ * The UI layer handles user interface elements that appear above the map content, + * including markers with custom icons, popups with HTML content, and image overlays. + *
+ * + * @return the UI layer interface for adding and managing user interface elements + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default LeafletUILayerInt getUiLayer() { + checkMapState(); + return getLayerInternal(LeafletUILayerInt.class); + } + + /** + * Provides access to the vector layer for managing geometric shapes and paths. + *+ * The vector layer handles geometric elements such as circles, polygons, polylines, + * and other vectorial shapes that can be styled and made interactive. + *
+ * + * @return the vector layer interface for adding and managing geometric shapes + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default LeafletVectorLayerInt getVectorLayer() { + checkMapState(); + return getLayerInternal(LeafletVectorLayerInt.class); + } + + /** + * Provides access to the control layer for map navigation and view management. + *+ * The control layer handles map manipulation operations such as zooming, panning, + * setting bounds, and controlling the map's viewport and navigation state. + *
+ * + * @return the control layer interface for map navigation and view control + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default LeafletControlLayerInt getControlLayer() { + checkMapState(); + return getLayerInternal(LeafletControlLayerInt.class); + } + + /** + * Provides access to the GeoJSON layer for managing geographic data layers. + *+ * The GeoJSON layer handles the loading, styling, and interaction with GeoJSON + * geographic data, supporting both simple displays and advanced features like + * custom styling functions and data filtering. + *
+ * + * @return the GeoJSON layer interface for managing geographic data + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default LeafletGeoJsonLayerInt getGeoJsonLayer() { + checkMapState(); + return getLayerInternal(LeafletGeoJsonLayerInt.class); + } + + + /** + * Smoothly pans the map view to the specified geographical coordinates. + *+ * This method performs an animated transition to center the map on the given location + * while maintaining the current zoom level. The pan operation uses the default + * animation duration and easing settings. + *
+ * + * @param latLng the target geographical coordinates to pan to + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default void setView(JLLatLng latLng) { + checkMapState(); + getJLEngine() + .executeScript(String.format("this.map.panTo([%f, %f]);", + latLng.getLat(), latLng.getLng())); + } + + /** + * Smoothly pans the map view to the specified geographical coordinates with custom animation duration. + *+ * This method performs an animated transition to center the map on the given location + * while maintaining the current zoom level, using the specified animation duration. + *
+ * + * @param latLng the target geographical coordinates to pan to + * @param duration the animation duration in milliseconds + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default void setView(JLLatLng latLng, int duration) { + checkMapState(); + getJLEngine() + .executeScript(String.format("this.map.panTo([%f, %f], %d);", + latLng.getLat(), latLng.getLng(), duration)); + } + + /** + * Retrieves the current zoom level of the map. + *+ * Zoom levels typically range from 0 (world view) to 18+ (building level detail), + * depending on the tile provider. Each zoom level represents a doubling of the scale. + *
+ * + * @return the current zoom level as an integer + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default int getZoom() { + checkMapState(); + Object result = getJLEngine() + .executeScript("this.map.getZoom();"); + return Integer.parseInt(result.toString()); + } + + /** + * Sets the zoom level of the map with animation. + *+ * This method smoothly animates the map to the specified zoom level. + * Zoom levels typically range from 0 (world view) to 18+ (building level detail). + * Values outside the supported range will be clamped to valid bounds. + *
+ * + * @param zoomLevel the target zoom level (typically 0-19) + * @throws JLMapNotReadyException if the map is not properly initialized + */ + default void setZoom(int zoomLevel) { + checkMapState(); + getJLEngine() + .executeScript(String.format("this.map.setZoom(%d);", zoomLevel)); + } + + /** + * Retrieves the current center coordinates of the map view. + *+ * This method returns the geographical coordinates (latitude and longitude) + * that correspond to the center point of the current map viewport. + *
+ * + * @return the center coordinates as a {@link JLLatLng} object + * @throws JLMapNotReadyException if the map is not properly initialized + * @throws NumberFormatException if the coordinate parsing fails + */ + default JLLatLng getCenter() { + checkMapState(); + Object result = getJLEngine() + .executeScript("this.map.getCenter();"); + String[] coords = result.toString().split(","); + double lat = Double.parseDouble(coords[0].trim()); + double lng = Double.parseDouble(coords[1].trim()); + return new JLLatLng(lat, lng); + } + + /** + * Checks if the map is ready for operations. + * + * @throws JLMapNotReadyException if the map is not ready + */ + default void checkMapState() { + if (getJLEngine() == null) { + throw new JLMapNotReadyException("Map engine is not initialized"); + } + } + + private @Nullable+ * The context menu provides a platform-independent way to add interactive menus + * to map elements. It uses mediators to handle platform-specific implementations + * while maintaining a consistent API across different UI frameworks (JavaFX, Vaadin). + *
+ *{@code + * JLMarker marker = JLMarker.builder() + * .latLng(new JLLatLng(51.5, -0.09)) + * .build(); + * + * JLContextMenu+ * + * @parammenu = marker.getContextMenu(); + * menu.addItem("Edit", "edit-icon") + * .addItem("Delete", "delete-icon") + * .addItem("Info", "info-icon") + * .setOnMenuItemListener(item -> { + * switch (item.getId()) { + * case "Edit" -> editMarker(marker); + * case "Delete" -> deleteMarker(marker); + * case "Info" -> showMarkerInfo(marker); + * } + * }); + * }
+ * Creates a new menu item with the given text and an auto-generated ID. + * The ID will be derived from the text by removing spaces and converting + * to lowercase for consistent identification. + *
+ * + * @param text the display text for the menu item + * @return this context menu instance for method chaining + * @throws IllegalArgumentException if text is null or empty + */ + @NonNull + public JLContextMenu+ * Creates a new menu item with the given ID and display text. + * If a menu item with the same ID already exists, it will be replaced. + *
+ * + * @param id unique identifier for the menu item + * @param text display text for the menu item + * @return this context menu instance for method chaining + * @throws IllegalArgumentException if id or text is null or empty + */ + @NonNull + public JLContextMenu+ * Creates a new menu item with all primary properties specified. + * If a menu item with the same ID already exists, it will be replaced. + *
+ * + * @param id unique identifier for the menu item + * @param text display text for the menu item + * @param icon icon or image reference for the menu item + * @return this context menu instance for method chaining + * @throws IllegalArgumentException if id or text is null or empty + */ + @NonNull + public JLContextMenu+ * Allows adding fully configured menu items with custom properties. + * If a menu item with the same ID already exists, it will be replaced. + *
+ * + * @param item the menu item to add + * @return this context menu instance for method chaining + * @throws IllegalArgumentException if item is null + */ + @NonNull + public JLContextMenu+ * If no menu item exists with the given ID, this method has no effect. + *
+ * + * @param id the ID of the menu item to remove + * @return this context menu instance for method chaining + */ + @NonNull + public JLContextMenu+ * After calling this method, the context menu will be empty and + * will not be displayed until new items are added. + *
+ * + * @return this context menu instance for method chaining + */ + @NonNull + public JLContextMenu+ * Returns the menu item with the specified ID, or null if no such + * item exists in the context menu. + *
+ * + * @param id the ID of the menu item to retrieve + * @return the menu item with the specified ID, or null if not found + */ + public JLMenuItem getItem(@NonNull String id) { + return menuItems.get(id); + } + + /** + * Updates an existing menu item with new properties. + *+ * Finds the menu item with the matching ID and replaces it with the + * updated version. If no item exists with the given ID, the new item + * will be added to the menu. + *
+ * + * @param updatedItem the updated menu item + * @return this context menu instance for method chaining + * @throws IllegalArgumentException if updatedItem is null + */ + @NonNull + public JLContextMenu+ * The returned collection reflects the current state of the menu items + * and will be updated as items are added or removed. However, the + * collection itself cannot be modified directly. + *
+ * + * @return an unmodifiable collection of all menu items + */ + @NonNull + public Collection+ * Filters the menu items to include only those that are marked as visible. + * This is useful for determining what items should actually be displayed + * to the user. + *
+ * + * @return an unmodifiable collection of visible menu items + */ + @NonNull + public Collection+ * Returns true if there is at least one menu item that is marked as visible. + * This is useful for determining whether the context menu should be displayed. + *
+ * + * @return true if there are visible menu items, false otherwise + */ + public boolean hasVisibleItems() { + return menuItems.values().stream().anyMatch(JLMenuItem::isVisible); + } + + /** + * Returns the number of menu items in the context menu. + *+ * Includes both visible and hidden items in the count. + *
+ * + * @return the total number of menu items + */ + public int getItemCount() { + return menuItems.size(); + } + + /** + * Returns the number of visible menu items in the context menu. + *+ * Counts only the items that are marked as visible. + *
+ * + * @return the number of visible menu items + */ + public int getVisibleItemCount() { + return (int) menuItems.values().stream().filter(JLMenuItem::isVisible).count(); + } + + /** + * Sets the listener for menu item selection events. + *+ * The listener will be called whenever a user selects a menu item from + * the context menu. Only one listener can be active at a time; setting + * a new listener will replace the previous one. + *
+ * + * @param listener the listener for menu item selection events, or null to remove + * @return this context menu instance for method chaining + */ + @NonNull + public JLContextMenu+ * This method is called internally when a menu item is selected. + * It delegates to the registered menu item listener if one exists. + *
+ *+ * Internal API: This method is intended for use by + * the context menu mediator implementations and should not be called + * directly by application code. + *
+ * + * @param selectedItem the menu item that was selected + */ + public void handleMenuItemSelection(@NonNull JLMenuItem selectedItem) { + if (onMenuItemListener != null) { + onMenuItemListener.onMenuItemSelected(selectedItem); + } + } + + /** + * Handles context menu lifecycle events. + *+ * This method is called when the context menu is opened or closed, + * allowing the owner object to respond to these events through its + * OnJLActionListener. + *
+ *+ * Internal API: This method is intended for use by + * the context menu mediator implementations and should not be called + * directly by application code. + *
+ * + * @param event the context menu event (open/close) + */ + public void handleContextMenuEvent(@NonNull Event event) { + OnJLActionListener+ * This method is called internally whenever menu items are added, removed, + * or modified. It can be used by mediator implementations to refresh + * the display or update platform-specific menu representations. + *
+ *+ * Internal API: This method is intended for use by + * the context menu mediator implementations and should not be called + * directly by application code. + *
+ */ + protected void notifyMenuUpdated() { + // This method can be overridden by mediator implementations + // to respond to menu changes + } +} diff --git a/jlmap-api/src/main/java/io/github/makbn/jlmap/element/menu/JLContextMenuMediator.java b/jlmap-api/src/main/java/io/github/makbn/jlmap/element/menu/JLContextMenuMediator.java new file mode 100644 index 0000000..f0ff798 --- /dev/null +++ b/jlmap-api/src/main/java/io/github/makbn/jlmap/element/menu/JLContextMenuMediator.java @@ -0,0 +1,125 @@ +package io.github.makbn.jlmap.element.menu; + +import io.github.makbn.jlmap.JLMap; +import io.github.makbn.jlmap.model.JLObject; +import lombok.NonNull; + +/** + * Platform-independent mediator interface for context menu implementations. + *+ * This interface abstracts the platform-specific context menu implementations + * (JavaFX, Vaadin, etc.) from the common JL context menu API. Each UI framework + * should provide its own implementation of this mediator to handle platform-specific + * menu rendering, event handling, and user interactions. + *
+ *+ * Concrete mediators must: + *
+ *+ * Implementations should be discoverable via Java's ServiceLoader mechanism + * by providing a service configuration file in META-INF/services/. + *
+ *+ * File: META-INF/services/io.github.makbn.jlmap.element.menu.JLContextMenuMediator + * Content: io.github.makbn.jlmap.vaadin.menu.VaadinContextMenuMediator + *+ * + * @author Matt Akbarian (@makbn) + * @since 2.0.0 + */ +public interface JLContextMenuMediator { + + + /** + * Shows the context menu for the specified JL object at the given coordinates. + *
+ * This method is typically called in response to user gestures like right-click + * or long-press. The mediator should display the context menu using platform-specific + * popup or menu components at the specified screen coordinates. + *
+ *+ * Coordinate System: Coordinates are typically relative to + * the map view or screen, depending on the platform implementation. + *
+ * + * @param+ * This method is called when the context menu should be dismissed, either + * programmatically or in response to user actions like clicking outside + * the menu area. + *
+ * + * @param+ * This method allows the service locator to determine which mediator + * should handle a particular JL object. Mediators may support all object + * types or be specialized for specific object categories. + *
+ *+ * This method is useful for debugging, logging, and development tools + * that need to identify which mediator is being used. + *
+ *+ * Names should include the target platform or framework: + *
+ *+ * This interface enables map elements (markers, polygons, polylines, etc.) to have + * context menus that can be displayed when users right-click or long-press on them. + * The context menu is implemented using platform-specific mediators to avoid + * dependency on Leaflet's internal context menu implementation. + *
+ *{@code + * JLMarker marker = JLMarker.builder() + * .latLng(new JLLatLng(51.5, -0.09)) + * .build(); + * + * JLContextMenu contextMenu = marker.getContextMenu(); + * contextMenu.addItem("Edit", "edit-icon") + * .addItem("Delete", "delete-icon") + * .setOnMenuItemListener(item -> { + * switch (item.getId()) { + * case "Edit" -> editMarker(marker); + * case "Delete" -> marker.remove(); + * } + * }); + * }+ * + * @param
+ * Returns the context menu associated with this object. If no context menu + * has been created yet, a new one will be instantiated. The context menu + * allows adding/removing menu items and setting up event listeners. + *
+ *+ * Note: The context menu is created lazily when first accessed. + * This ensures optimal memory usage and performance for objects that may not + * require context menu functionality. + *
+ * + * @return the context menu instance for this object, never null + */ + @Nullable + JLContextMenu+ * Returns true if a context menu exists and contains at least one menu item + * that should be displayed to the user. This is useful for determining + * whether to show context menu indicators or enable right-click functionality. + *
+ *+ * Objects should return false if: + *
+ *+ * Returns true if the context menu will be displayed when users perform + * context menu gestures on this object. A disabled context menu will not + * appear regardless of whether it exists and contains menu items. + *
+ * + * @return true if the context menu is enabled, false otherwise + */ + boolean isContextMenuEnabled(); + + /** + * Enables or disables the context menu for this object. + *+ * Controls whether the context menu should be displayed when users perform + * context menu gestures (right-click, long-press, etc.) on this object. + * When disabled, the context menu will not appear even if it exists and + * contains menu items. + *
+ *+ * Default State: Context menus are enabled by default when created. + *
+ *+ * Each menu item has a unique identifier, display text, optional icon, and can be + * enabled/disabled or shown/hidden. Menu items are immutable once created, promoting + * consistent state management and thread safety. + *
+ *{@code + * JLMenuItem editItem = JLMenuItem.builder() + * .id("edit") + * .text("Edit Properties") + * .icon("edit-icon.png") + * .enabled(true) + * .visible(true) + * .build(); + * + * JLMenuItem deleteItem = JLMenuItem.builder() + * .id("delete") + * .text("Delete Item") + * .icon("delete-icon.png") + * .enabled(canDelete) + * .visible(hasDeletePermission) + * .build(); + * }+ * + * @author Matt Akbarian (@makbn) + * @since 2.0.0 + */ +@Value +@Builder(toBuilder = true) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class JLMenuItem { + + /** + * Unique identifier for this menu item. + *
+ * The ID is used to identify the menu item when handling selection events + * and for programmatic access. It should be unique within the context menu + * and remain constant throughout the item's lifecycle. + *
+ */ + @NonNull + String id; + + /** + * Display text shown to the user. + *+ * This is the human-readable text that appears in the context menu. + * It should be descriptive and indicate the action that will be performed + * when the menu item is selected. + *
+ */ + @NonNull + String text; + + /** + * Optional icon or image reference for this menu item. + *+ * The icon can be a file path, URL, CSS class name, or any other identifier + * that the underlying UI framework can interpret as an icon. The specific + * format depends on the platform implementation (JavaFX, Vaadin, etc.). + *
+ *+ * Disabled items typically appear grayed out and do not respond to + * click events. They remain visible but cannot be activated. + *
+ *+ * Default: true (enabled) + *
+ */ + @Builder.Default + boolean enabled = true; + + /** + * Whether this menu item is visible in the context menu. + *+ * Hidden items do not appear in the menu at all. This is useful for + * conditionally showing menu items based on user permissions, application + * state, or other dynamic factors. + *
+ *+ * Default: true (visible) + *
+ */ + @Builder.Default + boolean visible = true; + + /** + * Creates a simple menu item with just ID and text. + *+ * This is a convenience factory method for creating basic menu items + * without icons or custom enabled/visible states. + *
+ * + * @param id unique identifier for the menu item + * @param text display text for the menu item + * @return a new menu item instance + */ + public static JLMenuItem of(@NonNull String id, @NonNull String text) { + return JLMenuItem.builder() + .id(id) + .text(text) + .build(); + } + + /** + * Creates a menu item with ID, text, and icon. + *+ * This is a convenience factory method for creating menu items with + * the most commonly used properties. + *
+ * + * @param id unique identifier for the menu item + * @param text display text for the menu item + * @param icon icon or image reference for the menu item + * @return a new menu item instance + */ + public static JLMenuItem of(@NonNull String id, @NonNull String text, String icon) { + return JLMenuItem.builder() + .id(id) + .text(text) + .icon(icon) + .build(); + } +} diff --git a/jlmap-api/src/main/java/io/github/makbn/jlmap/element/menu/OnJLContextMenuItemListener.java b/jlmap-api/src/main/java/io/github/makbn/jlmap/element/menu/OnJLContextMenuItemListener.java new file mode 100644 index 0000000..e81cd9b --- /dev/null +++ b/jlmap-api/src/main/java/io/github/makbn/jlmap/element/menu/OnJLContextMenuItemListener.java @@ -0,0 +1,37 @@ +package io.github.makbn.jlmap.element.menu; + +/** + * Listener interface for handling context menu item selection events. + *+ * This listener is triggered when a user selects a specific menu item from a context menu. + * It provides the selected menu item to the implementing code for further processing. + *
+ *{@code + * JLContextMenu contextMenu = marker.getContextMenu(); + * contextMenu.setOnMenuItemListener(selectedItem -> { + * System.out.println("Selected: " + selectedItem.getText()); + * if (selectedItem.getId().equals("delete")) { + * marker.remove(); + * } + * }); + * }+ * + * @author Matt Akbarian (@makbn) + * @since 2.0.0 + */ +@FunctionalInterface +public interface OnJLContextMenuItemListener { + + /** + * Called when a menu item is selected from the context menu. + *
+ * This method is invoked whenever a user clicks on a menu item in the context menu. + * The selected menu item is passed as a parameter, allowing the listener to + * determine which action was chosen and respond accordingly. + *
+ * + * @param selectedItem the menu item that was selected by the user + */ + void onMenuItemSelected(JLMenuItem selectedItem); +} \ No newline at end of file diff --git a/jlmap-api/src/main/java/io/github/makbn/jlmap/engine/JLClientToServerTransporter.java b/jlmap-api/src/main/java/io/github/makbn/jlmap/engine/JLClientToServerTransporter.java new file mode 100644 index 0000000..9b61a84 --- /dev/null +++ b/jlmap-api/src/main/java/io/github/makbn/jlmap/engine/JLClientToServerTransporter.java @@ -0,0 +1,45 @@ +package io.github.makbn.jlmap.engine; + +import io.github.makbn.jlmap.model.JLObject; + +/** + * Generic bridge for JavaScript-to-Java direct method calls on JLObjects. + * This allows JavaScript to call Java methods directly on specific object instances. + * + * @author Matt Akbarian (@makbn) + */ +public interface JLClientToServerTransporter { + + /** + * Calls a method on a specific JLObject instance. + * + * @param objectId The unique identifier of the JLObject + * @param methodName The name of the method to call + * @param args Arguments to pass to the method (JSON serialized) + * @return The result of the method call (JSON serialized), or null for void methods + */ + String callObjectMethod(String objectId, String methodName, String... args); + + /** + * Registers a JLObject so it can be called from JavaScript. + * + * @param objectId Unique identifier for the object + * @param object The JLObject instance + */ + void registerObject(String objectId, JLObject> object); + + /** + * Unregisters a JLObject. + * + * @param objectId The unique identifier of the object to remove + */ + void unregisterObject(String objectId); + + /** + * Gets the JavaScript code to inject that enables calling Java methods. + * This should be executed once to set up the bridge. + * + * @return JavaScript code for the bridge + */ + String getJavaScriptBridge(); +} \ No newline at end of file diff --git a/jlmap-api/src/main/java/io/github/makbn/jlmap/engine/JLClientToServerTransporterBase.java b/jlmap-api/src/main/java/io/github/makbn/jlmap/engine/JLClientToServerTransporterBase.java new file mode 100644 index 0000000..1f14a52 --- /dev/null +++ b/jlmap-api/src/main/java/io/github/makbn/jlmap/engine/JLClientToServerTransporterBase.java @@ -0,0 +1,151 @@ +package io.github.makbn.jlmap.engine; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.makbn.jlmap.model.JLGeoJson; +import io.github.makbn.jlmap.model.JLObject; +import io.github.makbn.jlmap.model.JLOptions; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +/** + * Base implementation of the JLObjectBridge that handles method calling logic. + * Platform-specific implementations extend this to provide the JavaScript integration. + * + * @author Matt Akbarian (@makbn) + */ +@Slf4j +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public abstract class JLClientToServerTransporterBase