(loadFrom "nat-archive-tool" "src/nat/Lib.kiss") (var TEXT_SIZE 16) (prop &mut :EntrySpriteSystem spriteSystem) (method :PlaygroundSystem playgroundSystem [] (set spriteSystem (new EntrySpriteSystem this controller))) (prop :KeyShortcutHandler shortcutHandler (new FlxKeyShortcutHandler)) (method &override :Void create [] (super.create) (FlxG.plugins.add (new FlxMouseControl)) (set FlxG.sound.muteKeys null) (set FlxG.sound.volumeDownKeys null) (set FlxG.sound.volumeUpKeys 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)) (let [&mut clickedOnSomething false] (entryGroup.forEach ->:Void entrySprite (when (.containsPoint (entrySprite.getScreenBounds) (FlxG.mouse.getScreenPosition)) (when (hasComponent entrySprite.e Circle) (unless (entrySprite.pixelsOverlapPoint (FlxG.mouse.getWorldPosition)) (return))) (set clickedOnSomething true) (unless (controller.isSelected entrySprite.e) (controller.SelectEntry entrySprite.e)))) (unless clickedOnSomething (controller.SelectEntries []))) } // mouseUp ->s { (set mouseDown null) (mouseDragSprite.makeGraphic FlxG.width FlxG.height FlxColor.TRANSPARENT true) } // mouseOver ->s {} // mouseOut ->s {} // also send events to sprites that overlap the dragging background 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)] (when (< 0 (+ 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)) (unless (hasComponent entrySprite.e Circle) (controller.ToggleSelectEntry entrySprite.e))))))))) (FlxG.camera.calculateScrollBounds entryGroup SCROLL_BOUND_MARGIN)) (method :Void showPrefixMap [:Map> map] (clearUI) (doFor =>key thing map (displayMessage "$key - $thing"))) (method :Void hidePrefixMap [] (clearUI)) (prop &mut :Bool confirmQuit false) (prop &mut :KeyShortcutWindow shw) (method &override :Void update [:Float elapsed] (super.update elapsed) (when (and FlxG.keys.justPressed.V FlxG.keys.pressed.CONTROL) (when (and textInput textInput.hasFocus) (whenLet [text (Clipboard.generalClipboard.getData ClipboardFormats.TEXT_FORMAT)] (when (textInput.text.endsWith "v") (set textInput.text (substr textInput.text 0 -1))) (+= textInput.text text) (set textInput.caretIndex textInput.text.length)))) (when FlxG.keys.justPressed.ESCAPE (cond ((and textInput textInput.hasFocus) (set textInput.callback null) (hideUI textInput) // This part is hacky... (set lastUI textInputLabel) (hideUI textInputLabel)) ((and shw (shw.isShown)) (shw.hide)) (confirmQuit (Sys.exit 0)) (true (displayMessage "Really quit?") (set confirmQuit true)))) // Press ENTER to type a command to run (when (and !textInput FlxG.keys.justPressed.ENTER) (set confirmQuit false) (typeCommand)) (when (and textInput !textInput.alive) (set textInput null)) // 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 check keys that can be used in shortcuts outside this block: (unless (or (and shw (shw.isShown)) (and textInput textInput.hasFocus)) (when FlxG.keys.justPressed.SEMICOLON (set confirmQuit false) (set shw (new KeyShortcutWindow shortcutHandler ->[:Entry e] (readComponentOr e Name "unnamed entry"))) (set shw.cameras [uiCamera]) (shw.show) (return)) // +/- keys to change an entry's z (doFor e (controller.getSelectedEntries) (when FlxG.keys.justPressed.MINUS (withWritableComponents archive e [positions Positions] (-= .z (dictGet positions (spriteSystem.getPlaygroundKey)) 1))) (when FlxG.keys.justPressed.PLUS (withWritableComponents archive e [positions Positions] (+= .z (dictGet positions (spriteSystem.getPlaygroundKey)) 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 TEXT_SIZE)) (showUI textInputLabel) (set textInput (new FlxInputText 0 0 300 "" TEXT_SIZE)) (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))) (var SCROLL_BOUND_MARGIN 200) (method handleChanges [:Archive archive :ChangeSet changeSet] (when changeSet // process the WikipediaImageSystem and run spriteSystem process on newly created entries that get one (archive.processSystems this) // Do a second loop through the systems, so Playground systems that trigger Core systems have their effects processed (archive.processSystems this)) (doFor e changeSet // 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))) (FlxG.camera.calculateScrollBounds entryGroup SCROLL_BOUND_MARGIN)) (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" "|") TEXT_SIZE)] (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))))