(loadFrom "nat-archive-tool" "src/nat/Lib.kiss") // TODO store a map of Entry IDs -> EntrySprites. // TODO handleChanges() will need to kill every changed Entry's sprite and make a new one // TODO make the EntrySprite constructor assign the entry a serialized position component // maybe by writing a Map positions component so there can be multiple? (method &override :Void create [] (super.create) (FlxG.plugins.add (new FlxMouseControl)) (set FlxG.sound.muteKeys null) // TODO find a better way to pass the archiveDir to a HaxeFlixel game (let [archiveDir (or (Sys.getEnv "NAT_DIR") (throw "NAT_DIR environment variable must be set")) archive (new Archive archiveDir)] (set this.archive archive) (set controller (new ArchiveController archive this))) (prop &mut :FlxGroup uiGroup (new FlxGroup)) (add uiGroup) (prop :FlxTypedGroup entryGroup (new FlxTypedGroup)) (add entryGroup) (prop uiCamera (new FlxCamera 0 0 FlxG.width FlxG.height)) (set uiCamera.bgColor FlxColor.TRANSPARENT) (FlxG.cameras.add uiCamera false) (set uiGroup.cameras [uiCamera]) (prop mouseDragCamera (new FlxCamera 0 0 FlxG.width FlxG.height)) (set mouseDragCamera.bgColor FlxColor.TRANSPARENT) (FlxG.cameras.add mouseDragCamera false) (prop mouseDragSprite (new FlxSprite 0 0)) (prop &mut :FlxPoint mouseDown null) (mouseDragSprite.makeGraphic FlxG.width FlxG.height FlxColor.TRANSPARENT) (set mouseDragSprite.cameras [mouseDragCamera]) (add mouseDragSprite) (FlxMouseEventManager.add mouseDragSprite // mouseDown ->s { (set mouseDown (FlxG.mouse.getScreenPosition)) (controller.SelectEntries []) (entryGroup.forEach ->entrySprite (when (.containsPoint (entrySprite.getScreenBounds) (FlxG.mouse.getScreenPosition)) (controller.SelectEntry entrySprite.e))) } // mouseUp ->s { (set mouseDown null) (mouseDragSprite.makeGraphic FlxG.width FlxG.height FlxColor.TRANSPARENT true) } // mouseOver ->s {} // mouseOut ->s {} true true false) (FlxMouseEventManager.setMouseMoveCallback mouseDragSprite ->s (when mouseDown (unless FlxMouseControl.isDragging // draw the selection rectangle (mouseDragSprite.makeGraphic FlxG.width FlxG.height FlxColor.TRANSPARENT true) (let [curPos (FlxG.mouse.getScreenPosition) x1 (min curPos.x mouseDown.x) y1 (min curPos.y mouseDown.y) x2 (max curPos.x mouseDown.x) y2 (max curPos.y mouseDown.y) selectWidth (- x2 x1) selectHeight (- y2 y1) rectangle (new FlxRect x1 y1 selectWidth selectHeight)] (mouseDragSprite.drawRect x1 y1 selectWidth selectHeight FlxColor.TRANSPARENT (object color FlxColor.LIME)) // Handle entry selection (entryGroup.forEach ->entrySprite (let [overlaps (rectangle.overlaps (entrySprite.getScreenBounds))] (when !(= overlaps (controller.isSelected entrySprite.e)) (controller.ToggleSelectEntry entrySprite.e)))))))) // make text-only sprites for entries that have no images: (archive.addSystem (new TextSpriteSystem)) // make interactible sprites for entries that have images // TODO allow configuring the tags at runtime and erasing/re-creating sprites later // TODO allow using other position keys and erasing/re-creating sprites later (prop &mut :EntrySpriteSystem spriteSystem) (set spriteSystem (new EntrySpriteSystem "!done" "Playground-MAIN" this controller)) (archive.addSystem spriteSystem) (archive.processSystems)) (method &override :Void update [:Float elapsed] (super.update elapsed) (when FlxG.keys.justPressed.ESCAPE (Sys.exit 0)) // Press ENTER to type a command to run (when (and !textInput FlxG.keys.justPressed.ENTER) (typeCommand)) (when (and textInput !textInput.alive) (set textInput null)) // Press ESCAPE to clear the UI and cancel any input (when FlxG.keys.justPressed.ESCAPE (clearUI)) // Scroll the UI with the mouse: (var UI_SCROLL_FACTOR 2) (+= uiCamera.y (* FlxG.mouse.wheel UI_SCROLL_FACTOR)) // TODO allow changing the a scroll factor // Control the UI camera with WASD, and the playground camera with arrow keys: (var KEYBOARD_SCROLL_SPEED 200) (FlxG.camera.updateKeyControl elapsed KEYBOARD_SCROLL_SPEED ->{FlxG.keys.pressed.LEFT} ->{FlxG.keys.pressed.RIGHT} ->{FlxG.keys.pressed.UP} ->{FlxG.keys.pressed.DOWN}) (FlxG.camera.updateMouseBorderControl elapsed KEYBOARD_SCROLL_SPEED 0.15) (FlxG.camera.updateScrollWheelZoom elapsed 1) // don't move the ui camera before ui has been placed -- new UI elements could appear offscreen (when (> uiGroup.length 0) (unless (and textInput textInput.hasFocus) (uiCamera.updateKeyControl elapsed KEYBOARD_SCROLL_SPEED // TODO support dvorak ->{FlxG.keys.pressed.A} ->{FlxG.keys.pressed.D} ->{FlxG.keys.pressed.W} ->{FlxG.keys.pressed.S})))) (method :Void typeCommand [] (enterText "command to run:" ->commandName (controller.tryRunCommand commandName) Math.POSITIVE_INFINITY)) (prop &mut :ArchiveController controller) (prop &mut :Archive archive) (prop &mut :FlxText textInputLabel null) (prop &mut :FlxInputText textInput null) (method :Void enterText [prompt resolve maxLength] (set textInputLabel (new FlxText 0 0 300 prompt)) (showUI textInputLabel) (set textInput (new FlxInputText 0 0 300 "")) (set textInput.hasFocus true) (set textInput.callback ->:Void [text action] // Super weird that this check is necessary (when textInput (case [text action] ([text FlxInputText.ENTER_ACTION] (set textInput.callback null) (hideUI textInput) // This part is hacky... (set lastUI textInputLabel) (hideUI textInputLabel) (resolve text)) //([_ FlxInputText.]) (otherwise {})))) (showUI textInput)) (method :Void enterNumber [prompt resolve min max &opt inStepsOf] (enterText prompt ->:Void [numberStr] (let [number (try (Std.parseFloat numberStr) (catch [e] (reportError "Not a number: $numberStr") (return)))] (resolve number)) Math.POSITIVE_INFINITY)) (method :Void chooseEntry [prompt :Archive archive resolve] (resolve null)) (method :Void chooseEntries [prompt archive resolve min max] (_chooseEntries prompt archive resolve min max [])) // TODO is it possible to resolve with less than max? (method :Void _chooseEntries [prompt archive resolve min max :Array collectedEntries] (let [&mut :Void->Void chooseNextEntry null _chooseNextEntry ->:Void {(chooseEntry prompt archive ->:Void e {(collectedEntries.push e) // If the maximum is reached, return it (if (= max collectedEntries.length) (resolve collectedEntries) // Otherwise, recurse (chooseNextEntry))})}] (set chooseNextEntry _chooseNextEntry) (_chooseNextEntry))) (method handleChanges [:Archive archive :ChangeSet changeSet] (doFor e changeSet // process the WikipediaImageSystem and run spriteSystem process on newly created entries that get one (archive.processSystems) // Do a second loop through the systems, so Playground systems that trigger Core systems have their effects processed (archive.processSystems) // Entries whose data changed to remove them from the sprite pool will already have been removed // by refreshEntry() (when (spriteSystem.entries.exists e.id) // refresh the sprites for entries that changed data but still should have sprites (when (spriteSystem.sprites.exists e.id) (spriteSystem.onRemoveEntry archive e)) (spriteSystem.processEntry archive e)))) (prop &mut :Int uiY 0) (prop &mut :FlxSprite lastUI null) (method :Void showUI [:FlxSprite ui] (set ui.y uiY) (uiGroup.add ui) (set lastUI ui) (+= uiY ui.height) (when (> uiY FlxG.height) (+= uiCamera.scroll.y ui.height))) (method :Void hideUI [:FlxSprite ui] (uiGroup.remove ui) (ui.kill) (when (= lastUI ui) (-= uiY ui.height))) (method :Void displayMessage [:String message] (let [messageText (new FlxText 0 0 0 (message.replace "\n" "|"))] (showUI messageText))) (method :Void clearUI [] (when textInput (set textInput.callback null)) (uiGroup.kill) (set uiGroup (new FlxGroup)) (set uiGroup.cameras [uiCamera]) (add uiGroup) (set uiY 0) (set uiCamera.scroll.x 0) (set uiCamera.scroll.y 0)) (method :Void reportError [:String error] (let [text (new FlxText 0 0 0 (error.replace "\n" "|"))] (text.setFormat null 8 FlxColor.RED) (showUI text))) (method :Void onSelectionChanged [:Array selectedEntries :Array lastSelectedEntries] (doFor e (selectedEntries.concat lastSelectedEntries) (whenLet [sprite (dictGet spriteSystem.sprites e.id)] (sprite.updateColor))))