(loadFrom "kiss-tools" "src/kiss_tools/RefactorUtil.kiss") // All windows share the same text size (var &mut textSize 16) (var :kiss.List windowStack []) (var &mut :flixel.FlxCamera defaultCamera null) (prop :FlxCamera controlCamera) (prop &mut keyboardEnabled true) // TODO tooltip support with left-click and right-click action // icons and explanations? (defNew [&opt :String _title :FlxColor bgColor :FlxColor _textColor :Float percentWidth :Float percentHeight :Bool _xButton :String _xKey :String _leftKey :String _rightKey :ShortcutAction _onClose] [:String title (or _title "") &mut :Float nextControlX 0 &mut :Float nextControlY 0 &mut :Int controlsPerColumn 0 :FlxColor titleColor (or _textColor FlxColor.WHITE) &mut :FlxColor textColor (or _textColor FlxColor.WHITE) :Bool xButton ?_xButton :String xKey _xKey :String leftKey _leftKey :String rightKey _rightKey :ShortcutAction onClose _onClose :FlxTypedGroup controls (new FlxTypedGroup) :FlxKeyShortcutHandler keyHandler (new FlxKeyShortcutHandler) // The xHandler exists so that when keyboard shortcuts are disabled, // UI key controls are still available. it also handles left and right. :FlxKeyShortcutHandler xHandler (new FlxKeyShortcutHandler)] (super 0 0) (when defaultCamera (set this.cameras [defaultCamera])) (makeGraphic (Std.int (* FlxG.width (or percentWidth 0.5))) (Std.int (* FlxG.height (or percentHeight 0.5))) (or bgColor FlxColor.BLACK)) (screenCenter) (set controlCamera (new FlxCamera (Std.int x) (Std.int y) (Std.int width) (Std.int height))) (set controlCamera.bgColor FlxColor.TRANSPARENT) // Top-left corner for controls is (0,0) because a camera displays them (set nextControlX 0) (set nextControlY 0) (let [textHeight .height (new FlxText 0 0 0 "a" textSize)] (set controlsPerColumn (Math.floor (/ height textHeight))) (-= controlsPerColumn 1) // Column at the bottom for left/right scroll arrows (when title (-= controlsPerColumn 1))) (when title (set titleText (makeText title null))) (set keyHandler.onBadKey ->:Void [key context] (unless (= key xKey) (#when debug (print "bad key $key in context $context")) // TODO do SOMETHING like visual/audio bell, cancel and restart )) (set keyHandler.onSelectItem ->:Void [:ShortcutAction a] { (a) (keyHandler.start) }) (set xHandler.cancelKey null) (set xHandler.onBadKey ->:Void [key context] 0) (set xHandler.onSelectItem ->:Void [:ShortcutAction a] { (a) (xHandler.start) }) (defAndCall method makeXControls (let [closeAction ->:Void {(hide)(when onClose (onClose))}] (when xButton (let [ftext (new FlxText width 0 0 "X" textSize)] (set ftext.cameras [controlCamera]) (-= ftext.x ftext.width) (set ftext.color textColor) (dictSet _colors ftext ftext.color) (dictSet _actions ftext ->:Void _ (closeAction)) (set xText ftext) (controls.add xText))) (when xKey (when (= keyHandler.cancelKey xKey) (set keyHandler.cancelKey null)) (xHandler.registerItem "{${xKey}}" closeAction true)))) // TODO show which shortcuts' prefixes are partially highlighted? ) (prop :FlxTypedGroup inputTexts (new FlxTypedGroup)) (method addControl [:FlxSprite control] (when (Std.isOfType control KissInputText) (inputTexts.add (cast control KissInputText))) (set control.cameras [controlCamera]) (set control.x nextControlX) (set control.y nextControlY) (controls.add control) (+= nextControlY control.height) // TODO controls that aren't the same height as text will be able to vertically overflow (let [columnControls (controls.members.slice (if title 1 0))] // Don't count special controls as part of any column: (doFor c [xText leftText rightText] (when c (columnControls.remove c))) (doFor c columnTexts (when c (columnControls.remove c))) (setNth columnWidths -1 (max (+ control.width textSize) (last columnWidths))) (when (and columnControls (= 0 (% columnControls.length controlsPerColumn))) (set nextControlY 0) (when title (+= nextControlY control.height)) (+= nextControlX (last columnWidths)) (columnWidths.push 0) (when (> (apply + columnWidths) width) (makeScrollArrows)))) control) (prop &mut :FlxText titleText) (prop &mut :FlxText leftText) (prop &mut :FlxText rightText) (prop &mut :Array columnTexts []) (prop &mut :FlxText xText) (method columnTextStr [:Int column] (if (= cameraColumn column) ">${column}<" "$column")) (method makeScrollArrows [] (unless hasScrollArrows // The left arrow control is not added until the window scrolls right (let [ftext (new FlxText 0 height 0 "<-" textSize)] (set ftext.cameras [controlCamera]) (-= ftext.y ftext.height) (set ftext.color textColor) (dictSet _colors ftext ftext.color) (dictSet _actions ftext ->:Void _ (scrollLeft)) (set leftText ftext)) (when leftKey (xHandler.registerItem "{${leftKey}}" scrollLeft true)) (let [ftext (new FlxText width height 0 "->" textSize)] (set ftext.cameras [controlCamera]) (-= ftext.x ftext.width) (-= ftext.y ftext.height) (set ftext.color textColor) (dictSet _colors ftext ftext.color) (controls.add ftext) (dictSet _actions ftext ->:Void _ (scrollRight)) (set rightText ftext)) (when rightKey (xHandler.registerItem "{${rightKey}}" scrollRight true)) (refreshColumnTexts) (set hasScrollArrows true)) // A column could be added while the same window is shown. (refreshColumnTexts)) (method refreshColumnTexts [] (doFor i (range columnWidths.length) (unless (> columnTexts.length i) (let [ftext (new FlxText (fHalf width) height 0 (columnTextStr i) textSize)] (set ftext.cameras [controlCamera]) (-= ftext.x (fHalf ftext.width)) (-= ftext.y ftext.height) (set ftext.color textColor) (dictSet _colors ftext ftext.color) (dictSet _actions ftext ->:Void _ (until (= cameraColumn i) (if (< cameraColumn i) (scrollRight) (scrollLeft)))) (controls.add ftext) (columnTexts.push ftext))) (let [ftext (nth columnTexts i)] (set ftext.text (columnTextStr i)) (set ftext.x (+ (fHalf width) controlCamera.scroll.x)) (-= ftext.x (* (- (fHalf columnWidths.length) i) textSize 3)) (when (= cameraColumn i) (-= ftext.x .width (new FlxText 0 0 0 ">" textSize)))))) (prop :Map _actions (new Map)) (prop :Map _colors (new Map)) (method makeText [:String text &opt :FlxColor color :Action onClick :Bool noShortcut] (let [ftext (new FlxText nextControlX nextControlY 0 text textSize)] (set ftext.color (or color textColor)) (dictSet _colors ftext ftext.color) (addControl ftext) (when onClick (dictSet _actions ftext onClick) // TODO right click? (unless noShortcut (keyHandler.registerItem text ->:Void (onClick ftext)))) ftext)) // TODO makeButton // TODO make inputText (prop &mut _shown false) (method isShown [] _shown) (prop &mut :kiss.List columnWidths [0.0]) (prop &mut cameraColumn 0) (prop &mut hasScrollArrows false) (method clearControls [] (set columnWidths [0.0]) (set columnTexts []) (set hasScrollArrows false) (_actions.clear) (controls.clear) (inputTexts.clear) (keyHandler.clear) (makeXControls) (set nextControlX 0) (set nextControlY 0) (set titleText (makeText title titleColor))) (method :Void show [&opt :Int _cameraColumn] (when (and _cameraColumn !(= cameraColumn _cameraColumn)) (assert (<= 0 _cameraColumn (- columnWidths.length 1)) "Tried to show out-of-bounds camera column ${_cameraColumn} of ${columnWidths.length}") (while (> cameraColumn _cameraColumn) (scrollLeft)) (while (< cameraColumn _cameraColumn) (scrollRight))) (unless _shown (FlxG.cameras.add controlCamera) (FlxG.state.add this) (FlxG.state.add controls) (windowStack.push this) (keyHandler.start) (xHandler.start) (set _shown true))) (method :Void hide [] (when _shown (FlxG.cameras.remove controlCamera false) (FlxG.state.remove this) (FlxG.state.remove controls) (windowStack.remove this) (keyHandler.cancel) (xHandler.cancel) (set _shown false))) (function getHighlighted [:FlxColor color &opt :Float amount] (unless amount (set amount 0.2)) (cond ((> color.lightness amount) (color.getDarkened amount)) (true (color.getLightened amount)))) (prop &mut otherIsSelected false) (method &override update [:Float elapsed] (super.update elapsed) (set otherIsSelected false) (when (= (last windowStack) this) (when keyboardEnabled (unless (apply or (for textBox inputTexts.members textBox.hasFocus)) (keyHandler.update))) (xHandler.update) // Handle mouse input (let [mousePos (FlxG.mouse.getScreenPosition controlCamera)] // Click and hover on clickable text entries (controls.forEach ->text (whenLet [onClick (dictGet _actions text)] (let [color (dictGet _colors text)] (if (and !otherIsSelected (.containsPoint (text.getScreenBounds controlCamera) mousePos)) { (when FlxG.mouse.justPressed (onClick text)) (set otherIsSelected true) (set text.color (getHighlighted color)) } { (set text.color color) })))) // Click on text boxes to focus them (inputTexts.forEach ->text (when FlxG.mouse.justPressed (when (.containsPoint (text.getScreenBounds controlCamera) mousePos) (inputTexts.forEach ->text (set text.hasFocus false)) (set otherIsSelected true) (set text.caretIndex (text.getCaretIndex controlCamera)) (set text.hasFocus true)))) // Click anywhere else to take focus away from text boxes (when (and !otherIsSelected FlxG.mouse.justPressed) (inputTexts.forEach ->text (set text.hasFocus false)))))) (function :SimpleWindow promptForChoice <>[T] [:String prompt :Array choices :T->Void onChoice &opt :FlxColor bgColor :FlxColor titleColor :FlxColor choiceColor :Float percentWidth :Float percentHeight :Bool xButton :String xKey :String leftKey :String rightKey :ShortcutAction onClose :Bool noShortcuts] (let [window (new SimpleWindow prompt bgColor titleColor percentWidth percentHeight xButton xKey leftKey rightKey onClose) choiceColor (or choiceColor titleColor FlxColor.WHITE)] (doFor choice choices (window.makeText (Std.string choice) choiceColor ->:Void s { (window.hide) (onChoice choice) } noShortcuts)) (window.show) window)) (method scrollLeft [] (when (> cameraColumn 0) (-= cameraColumn 1) (when (= cameraColumn 0) (controls.remove leftText)) (controls.add rightText) (let [scrollAmount (nth columnWidths cameraColumn)] (-= controlCamera.scroll.x scrollAmount) (when titleText (-= titleText.x scrollAmount)) (-= leftText.x scrollAmount) (-= rightText.x scrollAmount) (doFor columnText columnTexts (-= columnText.x scrollAmount)) (when xText (-= xText.x scrollAmount))) (refreshColumnTexts))) (method scrollRight [] (when (< cameraColumn (- columnWidths.length 1 )) (let [scrollAmount (nth columnWidths cameraColumn)] (+= controlCamera.scroll.x scrollAmount) (when titleText (+= titleText.x scrollAmount)) (+= leftText.x scrollAmount) (+= rightText.x scrollAmount) (doFor columnText columnTexts (+= columnText.x scrollAmount)) (when xText (+= xText.x scrollAmount))) (+= cameraColumn 1) (when (< (apply + (columnWidths.slice cameraColumn)) width) (controls.remove rightText)) (controls.add leftText) (refreshColumnTexts))) // Irreversibly disable the window's buttons (for when you're going to hide it in the next frame) (method clearActions [] (_actions.clear))