diff --git a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc index 366a7865a6..89297d5940 100644 --- a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc +++ b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc @@ -26,7 +26,7 @@ Here is the current list of supported arguments. Build hints change over time, s |true/false defaults to true - indicates whether to include the release version in the build |android.onDeviceDebug -|Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable="true"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable="true"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it — release builds are unaffected. See the link:#_ondevice_debugging_android[On-Device Debugging (Android) chapter] for the full flow. +|Boolean true/false defaults to false. When `true`, the generated `AndroidManifest.xml` is marked `android:debuggable="true"`, R8/proguard is disabled, and the build is pinned to debug-only (`android.release` is forced off and `android.debug` is forced on) so a stray hint can't ship a release-signed APK that's `debuggable="true"`. Pair with the `cn1:android-on-device-debugging` Maven goal (or the bundled IntelliJ run configs) to install, launch, forward JDWP, and stream logcat through adb. Has no effect on builds that don't carry it — release builds are unaffected. See the <> for the full flow. |android.installLocation |Maps to android:installLocation manifest entry defaults to auto. Can also be set to internalOnly or preferExternal. @@ -452,7 +452,7 @@ Only supported for App Store builds. See https://www.codenameone.com/developer-g |Boolean true/false defaults to false as of 5.0. Enable legacy generation of splash screen images for use when launching the app. These have been replaced now by the new launch storyboards. |ios.onDeviceDebug -|Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the link:#_ondevice_debugging_ios[On-Device Debugging chapter] for the full flow. +|Boolean true/false defaults to false. When `true`, the iOS build links a small JDWP listener thread (`cn1_debugger`) into the binary and the ParparVM translator emits source-line and locals metadata so a desktop proxy can serve the running app to any JDWP-speaking debugger. Has no effect on release builds. See the <> for the full flow. |ios.onDeviceDebug.proxyHost |Hostname or IP address the device-side listener dials to reach the desktop proxy. Default `127.0.0.1` (correct for the native iOS simulator). For a physical device, set this to the developer laptop's LAN IP. Has no effect unless `ios.onDeviceDebug=true`. @@ -768,42 +768,14 @@ Pass `null` to `AndroidNativeUtil.setPermissionPromptCallback()` to restore the === On device debugging -Codename One supports debugging applications on devices by using the natively generated project. All paid subscription levels include the ability to check an #Include Source# flag in the settings that returns a native OS project. You can debug that project in the respective native IDE. - -In iOS this is strait forward, open the project with Xcode and run it optionally disabling bitcode. Unzip the.bz2 file and open the `.xcworkspace` file if it's available otherwise open the `.xcodeproj` file inside the `dist` directory. - -IMPORTANT: The `.xcworkspace` is no longer exclusive to CocoaPods-based builds. Use it whenever it's generated, whether the project uses CocoaPods, Swift Package Manager, or both. - -With Android Studio this is sometimes as easy task as it's possible to actually open the Gradle project in Android Studio and run it. For example, due to the fragile nature of the Gradle project this stopped working for some builds and has been "flaky." - -==== Android Studio debugging (easy way) - -By default you should be able to open the Gradle project in Android Studio and run it. To get this to work open the Android Studio #Setting# and select Gradle *2.11*. - -.Gradle settings UI in Android Studio (notice you need Gradle 2.11 and not 2.8 as pictured here) -image::img/gradle-settings.png[Gradle settings UI in Android Studio,scaledwidth=50%] - -If this works for you then you can ignore the section below. - -==== Android Studio debugging the hard way - -Sometimes the Gradle project might not work or this might fail with a change from Google. - -Here are steps that should work for everyone: - -. Check the include source flag in the IDE and send a build -. Download the `sources.zip` result from the build server -. Launch Android Studio and create a new project -. Make sure to use the same package and app name as you did in the Codename One project, select to not create an activity -. Unzip the `sources.zip` file and copy the `main` directory from its `src` directory to the Android Studio projects `src` directory make sure to overwrite files/directories. -. Copy its `libs` directory on top of the existing libs -. Copy the source Gradle dependencies content to the destination Gradle file -. Connect your device and press the Debug button for the IDE - -NOTE: You might need to copy more Gradle file meta-data such as multi-dexing etc. - -You might not need to repeat the whole thing with every build. For example: it might be practical to copy the `userSources.jar` from the libs directory to get the latest version of your code. You can copy the `src/main` directory to get the latest up-to-date Android port. +On-device debugging now has two dedicated chapters, one per platform: +* <> covers attaching a standard Java + debugger (IntelliJ, jdb, VS Code) to an iOS app running on a device or in the + native iOS simulator, as well as debugging the generated Xcode project natively. +* <> covers the equivalent + adb/JDWP-based flow for Android devices and emulators, including wireless + debugging and debugging the generated Gradle project from Android Studio. === Native interfaces diff --git a/docs/developer-guide/Game-Development.asciidoc b/docs/developer-guide/Game-Development.asciidoc index 09eb659319..89f11cf40d 100644 --- a/docs/developer-guide/Game-Development.asciidoc +++ b/docs/developer-guide/Game-Development.asciidoc @@ -47,13 +47,15 @@ into 3D: in a grid; flipping one animates its horizontal scale through zero and swaps the image at the thin point, and taps are hit-tested against sprite bounds through `GameInput`. A compact illustration of flat 2D play with no camera tricks at all. +Dissected in <>. * *`BoardGameSample`* -- *faux-3D* checkers on an isometric board. Every tile and piece is still a flat `Sprite`, but laying them out in an isometric (2:1 diamond) projection and raising the pieces off their tiles with a drop shadow gives a convincing 3D look without any GPU 3D, perspective camera or models. The screen pixel-to-board-cell mapping (and its inverse, for picking) is the same math behind any isometric strategy or tycoon game; it also implements the full move/jump/chain -/crown rules and a small AI so you can play it solo. +/crown rules and a small AI so you can play it solo. Dissected in +<>. The action samples are driven by the joystick and buttons in <>, so they're equally playable on a touch device and in @@ -303,6 +305,142 @@ faces both ways -- no mirrored art needed (sprites are drawn double-sided for exactly this reason). `play()`, `pause()`, `stop()`, `setLooping(boolean)` and `setCurrentFrame(int)` control playback. +=== Case study: The card game (flat 2D) + +image::img/game-cards.png[The Memory card game mid-flip: two matched pairs and one card face up,240] + +`CardGameSample` is Memory (Concentration): sixteen face-down cards in a four-by-four +grid, tap two and they stay up when they match, flip back when they don't. It's +the smallest complete example of turn-based 2D play with `com.codename1.gaming` +-- no joystick, no physics, no camera -- which makes the three techniques it does +use easy to see in isolation. + +*One sprite per card.* Each card is a `Sprite` positioned at its grid slot. There +are exactly two images at any moment: the shared card-back image and the card's +own face (all generated at runtime with `Image.createImage` and `Graphics` calls, +so the sample needs no assets). Changing what a card shows is just +`sprite.setImage(...)`. + +*A flip you can fake with scale.* A real card flip is a 3D rotation, but +squeezing the sprite's horizontal scale through zero and swapping the image at +the thin point reads exactly the same to the eye: + +[source,java] +---- +// in the card's per-frame animation; flip runs 0 -> 1 over ~0.4s +flip += dt * 5; +float sx = Math.abs(1f - 2f * (float) flip); // 1 -> 0 -> 1 +if (flip >= 0.5 && !showingFace) { + showingFace = true; + sprite.setImage(face); // swap sides while the card is edge-on +} +sprite.setScale(Math.max(0.05f, sx), 1f); +---- + +This is the same negative/positive scale trick the scroller uses to flip its +runner, driven over time to play as an animation. + +*Polled taps against sprite bounds.* There are no buttons to wire up; +`update(double)` polls the `GameInput` edge state and hit-tests the tap against +each card's bounding box: + +[source,java] +---- +protected void update(double dt) { + // ... advance flip animations, count down the mismatch timer ... + if (getInput().wasPointerPressed()) { + int px = getInput().getPointerX(); + int py = getInput().getPointerY(); + for (int i = 0; i < cards.length; i++) { + Rectangle b = cards[i].sprite.getBounds(); + if (b.contains(px, py)) { + flipUp(cards[i]); + break; + } + } + } +} +---- + +The rest of the sample is a small state machine living in plain fields: the +first and second face-up cards, a `mismatchTimer` that counts down in `update` +before flipping a failed pair back, and a moves counter surfaced through the +form title. Note the timer: in a game loop you don't schedule callbacks, you +subtract `dt` from a countdown field each frame and act when it crosses zero. + +=== Case study: Faux-3D checkers (isometric projection) + +image::img/game-board.png[Isometric checkers mid-game with a selected piece and a highlighted destination,240] + +`BoardGameSample` plays a full game of checkers -- moves, forced captures, chained +jumps, crowning, and a small AI opponent -- on a board that looks convincingly 3D -- +yet there is no perspective camera and no `Model` anywhere: every tile and piece +is a flat `Sprite`. The 3D impression comes entirely from *where* the flat art is +placed, a technique (isometric or "2.5D" projection) that powered decades of +strategy and tycoon games and still works just as well on a GPU sprite pipeline. + +*The projection.* Board cell `(r, c)` maps to screen pixels by laying the grid out +as 2:1 diamonds -- columns step right-and-down, rows step left-and-down: + +[source,java] +---- +private float tileCenterX(int r, int c) { + return originX + (c - r) * (tileW / 2f); +} + +private float tileCenterY(int r, int c) { + return originY + (c + r) * (tileH / 2f); // tileH = tileW / 2 +} +---- + +*Picking is the inverse.* Because the mapping is linear it inverts with two +divisions, turning a tap back into a board cell -- no per-tile hit testing +required: + +[source,java] +---- +private int[] pick(int px, int py) { + float a = (px - originX) / (tileW / 2f); // = c - r + float b = (py - originY) / (tileH / 2f); // = c + r + int c = Math.round((a + b) / 2f); + int r = Math.round((b - a) / 2f); + if (r < 0 || r >= N || c < 0 || c >= N) { + return null; // tap missed the board + } + return new int[]{r, c}; +} +---- + +*Depth from z-order.* Cells further down the screen must draw over the cells +behind them, and within one cell the stacking is tile, then shadow, then +selection/move markers, then the piece. Both rules collapse into a single +number -- `(r + c)` strides the cells from back to front, and a small offset +layers the sprites within a cell: + +[source,java] +---- +// shadow sits flat on the tile, the piece floats above it +addDynamic(shadow, cx, cy + tileH * 0.10f, (r + c) * 4 + 1, 0.5, 0.5); +addDynamic(piece, cx, cy - tileH * 0.30f, (r + c) * 4 + 3, 0.5, 0.62); +---- + +That pair of lines is also the whole "3D height" illusion: the piece is drawn +raised off its tile while its shadow stays put, and the eye reads the gap as +elevation. The piece art helps -- an elliptical top face over a darker side rim, +again just `fillArc` calls on a runtime-generated image. + +*Rebuild, don't mutate.* After every move the sample throws away all its dynamic +sprites (pieces, shadows, markers) and recreates them from the `board[][]` array +in one `syncPieces()` pass. With a few dozen sprites this costs nothing, and it +keeps the scene a pure function of the game state -- there is no way for the +display to drift out of sync with the rules, which eliminates the whole class of +"the board shows X but the game thinks Y" bugs. The game logic itself +(`destinations`, forced captures, chaining, crowning) never touches a sprite; it +reads and writes the `int[][]` board only. + +When fake 3D stops being enough -- you want the camera to move through the world +rather than look at it -- the next section covers the real thing. + === 3D and perspective `GameView` renders on the GPU through the <<3D Graphics and Shaders,`com.codename1.gpu`>> diff --git a/docs/developer-guide/Index.asciidoc b/docs/developer-guide/Index.asciidoc index 8f5133caaa..352c60ee5f 100644 --- a/docs/developer-guide/Index.asciidoc +++ b/docs/developer-guide/Index.asciidoc @@ -385,6 +385,7 @@ public void start() { <5> The `show()` method places the `Form` on the screen. Only one `Form` can be shown at a time +[[TitleAndLabelImage]] .Title and Label in the UI image::img/codenameone-hello-world-title-label.png[Title and Label in the UI,scaledwidth=50%] diff --git a/docs/developer-guide/On-Device-Debugging-Android.asciidoc b/docs/developer-guide/On-Device-Debugging-Android.asciidoc index ea933b9c2f..a217fc2ce4 100644 --- a/docs/developer-guide/On-Device-Debugging-Android.asciidoc +++ b/docs/developer-guide/On-Device-Debugging-Android.asciidoc @@ -269,6 +269,30 @@ Without a source path, breakpoints in framework classes still trigger and locals / fields still read — you'll just see "Sources not found" in the editor when stepping into framework code. +=== Debugging the generated Gradle project from Android Studio + +Everything above drives a Codename One-built APK through `adb`. The +alternative is to open the generated Android project itself in Android +Studio and use it like any native Android app — useful when you're +debugging native interface code, working on the Android port, or want +Android Studio's profiler and layout inspector: + +* *Local build*: `mvn cn1:buildAndroidGradleProject` writes a complete + Gradle project under `target/...-android-source/`. Open that directory + in Android Studio (current versions open it directly — no Gradle + version fiddling required), connect a device, and press *Debug*. +* *Cloud build*: check the *Include Source* flag in Codename One + Settings before sending the build, then download the sources result + from the build server and open it the same way. + +When iterating, regenerate the project rather than hand-merging files — +the generated project is a build artifact, not a source of truth. + +For native C/C++ added through the NDK, attach Android Studio's LLDB to +the process; as noted in <>, the LLDB and +JDWP attaches are independent and can run side by side against one +device. + === Troubleshooting ==== "No Android device is online" diff --git a/docs/developer-guide/On-Device-Debugging.asciidoc b/docs/developer-guide/On-Device-Debugging.asciidoc index f54cef47b4..ac8e9d309b 100644 --- a/docs/developer-guide/On-Device-Debugging.asciidoc +++ b/docs/developer-guide/On-Device-Debugging.asciidoc @@ -248,6 +248,34 @@ of two to three times slower than a release build — this is normal and matches the overhead of `-g`-style debugging on other native VMs. +=== Debugging the generated Xcode project natively + +The JDWP flow above keeps you at the Java level. For native-level work -- +stepping through Objective-C sources, debugging a native interface, chasing a +crash inside the VM, or profiling with Instruments -- debug the generated Xcode +project directly: + +* *Local build*: `mvn cn1:buildIosXcodeProject` writes the complete Xcode + project under `target/...-ios-source/`. +* *Cloud build*: check the *Include Source* flag in Codename One Settings before + sending the build, and download the sources result from the build server. + +Open the `.xcworkspace` file if one was generated, otherwise the `.xcodeproj`, +and run on a device or the native iOS simulator from Xcode as you would any iOS +app. + +IMPORTANT: The `.xcworkspace` is no longer exclusive to CocoaPods-based builds. +Use it whenever it's generated, whether the project uses CocoaPods, Swift +Package Manager, or both. + +Inside Xcode you get LLDB and Instruments against the real binary. Your Java +code appears as the C sources ParparVM generated from it -- a Java method like +`com.mycompany.MyClass.doStuff(int)` becomes a C function named along the lines +of `com_mycompany_MyClass_doStuff___int`, so symbolic breakpoints on the +translated code work too. The native-IDE path and the JDWP path are +complementary: use Xcode for native frames and profiling, the JDWP proxy for +source-level Java debugging. + === Troubleshooting ==== The proxy reports "device disconnected" before the breakpoint fires diff --git a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc index 4af76abbaf..630d5fdd36 100644 --- a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc +++ b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc @@ -1,3 +1,4 @@ +[[working-with-codename-one-sources]] == Working with Codename One sources The Codename One SDK is published as a Maven multi-module project. Building the diff --git a/docs/developer-guide/img/game-board.png b/docs/developer-guide/img/game-board.png new file mode 100644 index 0000000000..36df4549d0 Binary files /dev/null and b/docs/developer-guide/img/game-board.png differ diff --git a/docs/developer-guide/img/game-cards.png b/docs/developer-guide/img/game-cards.png new file mode 100644 index 0000000000..a6d5579ce2 Binary files /dev/null and b/docs/developer-guide/img/game-cards.png differ diff --git a/docs/developer-guide/img/gradle-settings.png b/docs/developer-guide/img/gradle-settings.png deleted file mode 100644 index 293d30c89e..0000000000 Binary files a/docs/developer-guide/img/gradle-settings.png and /dev/null differ diff --git a/docs/developer-guide/io.asciidoc b/docs/developer-guide/io.asciidoc index 94958e3ab1..492f33de1a 100644 --- a/docs/developer-guide/io.asciidoc +++ b/docs/developer-guide/io.asciidoc @@ -876,7 +876,7 @@ Notice that a static version of the method is used! The callback object is an in ===== Writing JSON — `JSONWriter` For typed DTO serialization use the `@Mapped` annotation framework -(see `<>`) plus `Mappers.toJson(...)`. For ad-hoc +(see `<>`) plus `Mappers.toJson(...)`. For ad-hoc maps / lists where a build-time `Mapper` would be overkill, the `com.codename1.io.JSONWriter` class is the complement of `JSONParser`: @@ -1415,7 +1415,7 @@ branch on `data.get("root") instanceof List`. `Rest.fetchAsJsonMap` returns a generic `Map` that the caller casts and inspects key by key. Once your DTOs are `@Mapped`-annotated -(see `<>`), `fetchAsMapped` returns the typed object +(see `<>`), `fetchAsMapped` returns the typed object directly: [source,java]