;;-*-Lisp-*- (in-package goal) (bundles "ENGINE.CGO" "GAME.CGO") (require "engine/gfx/hw/display.gc") (require "engine/gfx/font.gc") ;; This file contains the UI and rendering for the debug menu, but not the actual menu layout and callbacks. ;; The "context" is the entire multi-level debug menu. There's a separate context for the main debug and the "popup" menu. ;; A "menu" is a listing of "items". ;; An item is a line in the menu. It can be a flag, function, or variable. (declare-type debug-menu basic) (declare-type debug-menu-item basic) ;; DECOMP BEGINS ;; this file is debug only (declare-file (debug)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; context, menu, and item ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; There is one for the normal menu, and one for the "popup" one that appears when you press L3/R3 ;; This stores a stack of open menus in sel-menu. ;; The 0th index is the selection in the root-menu. (deftype debug-menu-context (basic) ((is-active symbol) ;; should we draw? (sel-length int32) ;; depth of open menus (sel-menu debug-menu 8) ;; at each level, what is selected? (root-menu debug-menu) ;; the top level menu (joypad-func (function basic none)) ;; if not, #f, callback for getting joystick inputs (joypad-item basic) ;; object passed as arg to joypad-func (font font-context) ;; font rendering settings (is-hidden symbol) ;; set to #t to temporarily hide. ) (:methods (new (symbol type) _type_))) (defmethod new debug-menu-context ((allocation symbol) (type-to-make type)) "Create a new debug-menu-context" (let ((context (object-new allocation type-to-make (the-as int (-> type-to-make size))))) (set! (-> context is-active) #f) (set! (-> context is-hidden) #f) (set! (-> context sel-length) 0) (set! (-> context root-menu) #f) (set! (-> context joypad-func) #f) (set! (-> context joypad-item) #f) ;; og:preserve-this added pc-hack flag (set! (-> context font) (new 'debug 'font-context *font-default-matrix* 0 0 0.0 (font-color default) (font-flags shadow kerning pc-hack))) context)) ;; Parent type for entrees in the debug-menu tree. ;; This is used for both entries and menus. ;; Items will be periodically "refreshed" to update their color/status. ;; Updating every item on every frame would be slow, so you can set a nonzero value in refresh-delay ;; to only run the refresh every refresh-delay frames. (deftype debug-menu-node (basic) ((name string) (parent debug-menu) (refresh-delay int32) (refresh-ctr int32))) (defmethod print ((this debug-menu-node)) (format #t "#<~A ~A @ #x~X>" (-> this type) (-> this name) this) this) ;; Parent type for a menu (list of things) (deftype debug-menu (debug-menu-node) ((context debug-menu-context) (selected-item debug-menu-item) (pix-width int32) (pix-height int32) (items pair)) (:methods (new (symbol type debug-menu-context string) _type_))) (defmethod new debug-menu ((allocation symbol) (type-to-make type) (context debug-menu-context) (name string)) "Create a new debug-menu" (let ((menu (object-new allocation type-to-make (the-as int (-> type-to-make size))))) (set! (-> menu context) context) (set! (-> menu name) name) (set! (-> menu parent) #f) (set! (-> menu selected-item) #f) (set! (-> menu items) '()) menu)) ;; Parent type for an item (an individual, selectable entry within a menu) (deftype debug-menu-item (debug-menu-node) ((id int32))) (deftype debug-menu-item-submenu (debug-menu-item) ((submenu debug-menu)) (:methods (new (symbol type string debug-menu) _type_))) (defmethod new debug-menu-item-submenu ((allocation symbol) (type-to-make type) (name string) (menu debug-menu)) "Create an item that opens the given menu." (let ((item (object-new allocation type-to-make (the-as int (-> type-to-make size))))) (set! (-> item name) name) (set! (-> item parent) #f) (set! (-> item refresh-delay) 0) (set! (-> item refresh-ctr) (-> item refresh-delay)) (set! (-> item submenu) menu) ;; in this case, the submenu's parent is set to the item, not a menu. ;; it's possible that the type of parent here is just debug-menu-node, but this value is never used. (set! (-> item submenu parent) (the-as debug-menu item)) item)) ;;;;;;;;;;;;;;;;;;;;;;;; ;; Items ;;;;;;;;;;;;;;;;;;;;;;;; (defenum debug-menu-msg :type int32 (activate 1) (deactivate 2) (update 3) (press 4)) ;; An item that calls a function when you select it. (deftype debug-menu-item-function (debug-menu-item) ((activate-func (function object object)) (hilite-timer int8) ;; how much longer to stay highlighted for. ) (:methods (new (symbol type string object (function object object)) _type_))) (defmethod new debug-menu-item-function ((allocation symbol) (type-to-make type) (name string) (id object) (activate-func (function object object))) "Create an item for a function." (let ((item (object-new allocation type-to-make (the-as int (-> type-to-make size))))) (set! (-> item name) name) (set! (-> item parent) #f) (set! (-> item refresh-delay) 0) (set! (-> item refresh-ctr) (-> item refresh-delay)) (set! (-> item id) (the-as int id)) (set! (-> item activate-func) activate-func) (set! (-> item hilite-timer) 0) item)) ;; An item with on/off state. (deftype debug-menu-item-flag (debug-menu-item) ((activate-func (function object debug-menu-msg object)) (is-on object)) (:methods (new (symbol type string object (function object debug-menu-msg object)) _type_))) (defmethod new debug-menu-item-flag ((allocation symbol) (type-to-make type) (name string) (id object) (activate-func (function object debug-menu-msg object))) "Create a periodically refreshed flag item whose callback reads or toggles the value." (let ((item (object-new allocation type-to-make (the-as int (-> type-to-make size))))) (set! (-> item name) name) (set! (-> item parent) #f) (set! (-> item refresh-delay) 23) (set! (-> item refresh-ctr) (-> item refresh-delay)) (set! (-> item id) (the-as int id)) (set! (-> item activate-func) activate-func) (set! (-> item is-on) (the-as object #f)) item)) (deftype debug-menu-item-var (debug-menu-item) ((display-str string) (grabbed-joypad-p symbol) (float-p symbol) (range-p symbol) (show-len int32) (inc-delay int32) (inc-delay-ctr int32) (step-delay-ctr int32) (inc-dir int32) (fval float) (fundo-val float) (frange-min float) (frange-max float) (fstart-inc float) (fstep float) (fprecision int32) (factivate-func (function int debug-menu-msg float float float)) (ival int32 :overlay-at fval) (iundo-val int32 :overlay-at fundo-val) (irange-min int32 :overlay-at frange-min) (irange-max int32 :overlay-at frange-max) (istart-inc int32 :overlay-at fstart-inc) (istep int32 :overlay-at fstep) (ihex-p symbol) (iactivate-func (function int debug-menu-msg int int int) :overlay-at factivate-func) (ifloat-p symbol)) (:methods (new (symbol type string int int) _type_))) (defenum debug-menu-dest :type int32 (root 1) (open-menus 2) (current-selection 3) (activation 0)) (define-extern debug-menu-context-send-msg (function debug-menu-context debug-menu-msg debug-menu-dest debug-menu-context)) (define-extern debug-menu-item-send-msg (function debug-menu-item debug-menu-msg debug-menu-item)) ;;;;;;;;;;;;;;;;;;;;;;;; ;; Variable Menu Setup ;;;;;;;;;;;;;;;;;;;;;;;; (defun debug-menu-item-var-update-display-str ((item debug-menu-item-var)) "Update display-str to the current value of the variable" (cond ((-> item float-p) (format (clear (-> item display-str)) "~f" (-> item fval))) ((-> item ihex-p) (format (clear (-> item display-str)) "x~X" (-> item fval))) ((-> item ifloat-p) (cond ((and (< (the-as int (-> item fval)) 0) (< -100 (the-as int (-> item fval)))) (let ((abs-value (abs (the-as int (-> item fval))))) (format (clear (-> item display-str)) "-0.~1d" (/ (mod abs-value 100) 10)))) (else (let ((abs-value (abs (the-as int (-> item fval))))) (format (clear (-> item display-str)) "~2d.~1d" (/ (the-as int (-> item fval)) 100) (/ (mod abs-value 100) 10)))))) (else (format (clear (-> item display-str)) "~D" (-> item fval)))) item) (defun debug-menu-item-var-make-int ((item debug-menu-item-var) (callback (function int debug-menu-msg int int int)) (inc int) (has-range symbol) (range-min int) (range-max int) (hex symbol)) "Set up the given item as an integer variable" (set! (-> item float-p) #f) (set! (-> item range-p) has-range) (set! (-> item irange-min) range-min) (set! (-> item irange-max) range-max) (set! (-> item istart-inc) inc) (set! (-> item istep) inc) (set! (-> item ihex-p) hex) (set! (-> item iactivate-func) callback) (cond (has-range (set! (-> item fval) (the-as float range-min))) (else (set! (-> item fval) (the-as float 0)) 0)) ;; initialize with the callback. (if callback (set! (-> item ival) (callback (-> item id) (debug-menu-msg update) (-> item ival) (-> item ival)))) (debug-menu-item-var-update-display-str item) item) (defun debug-menu-item-var-make-float ((item debug-menu-item-var) (callback (function int debug-menu-msg float float float)) (inc float) (has-range symbol) (range-min float) (range-max float) (precision int)) "Set up the given item as a float variable" (set! (-> item float-p) #t) (set! (-> item range-p) has-range) (set! (-> item frange-min) range-min) (set! (-> item frange-max) range-max) (set! (-> item fstart-inc) inc) (set! (-> item fstep) inc) (set! (-> item fprecision) precision) (set! (-> item factivate-func) callback) (if has-range (set! (-> item fval) range-min) (set! (-> item fval) 0.0)) ;; The callback result is converted from int to float here even though the callback's ;; declared result is float. (if callback (set! (-> item fval) (the float (callback (-> item id) (debug-menu-msg update) (-> item fval) (-> item fval))))) (debug-menu-item-var-update-display-str item) item) (defmethod new debug-menu-item-var ((allocation symbol) (type-to-make type) (name string) (id int) (max-width int)) "Create a new item for modifying a variable. Will default to int." (let ((item (object-new allocation type-to-make (the-as int (-> type-to-make size))))) (let ((max-chars (/ max-width 8))) (set! (-> item name) name) (set! (-> item parent) #f) (set! (-> item refresh-delay) (#if PC_PORT 1 31)) (set! (-> item refresh-ctr) (-> item refresh-delay)) (set! (-> item id) id) (set! max-chars (if (< 3 max-chars) max-chars 3)) (set! (-> item show-len) max-chars)) (set! (-> item grabbed-joypad-p) #f) (set! (-> item ifloat-p) #f) (set! (-> item display-str) (new 'debug 'string 64 (the-as string #f))) (debug-menu-item-var-make-int item (the-as (function int debug-menu-msg int int int) #f) 1 #t 0 0 #f) item)) ;;;;;;;;;;;;;;;;;;;;;;;; ;; joypad grabbing ;;;;;;;;;;;;;;;;;;;;;;;; (defun debug-menu-context-grab-joypad ((ctxt debug-menu-context) (callback-arg basic) (callback-func (function basic none))) "Set up this context to be controlled from a joypad. If we are already, return #f, otherwise return #t" (cond ((-> ctxt joypad-func) #f) (else (set! (-> ctxt joypad-func) callback-func) (set! (-> ctxt joypad-item) callback-arg) #t))) (defun debug-menu-context-release-joypad ((ctxt debug-menu-context)) "Remove joypad control from this context" (set! (-> ctxt joypad-func) #f) (set! (-> ctxt joypad-item) #f) #f) ;;;;;;;;;;;;;;;;;;;;;;;; ;; menu building ;;;;;;;;;;;;;;;;;;;;;;;; (defun debug-menu-item-get-max-width ((item debug-menu-item) (menu debug-menu)) "Determine the width, in screen units" 0 (cond ((= (-> item type) debug-menu-item-submenu) (+ (the int (get-string-length (-> item name) (-> menu context font))) 16)) ((= (-> item type) debug-menu-item-var) (the int (get-string-length (-> (the-as debug-menu-item-var item) display-str) (-> menu context font)))) (else (+ (the int (get-string-length (-> item name) (-> menu context font))) 6)))) (defun debug-menu-context-default-selection ((ctxt debug-menu-context) (keep-current symbol)) "Set the menu to a default selection. If keep-current-selection is set to #t, this will only change the selection if nothing is selected yet." ;; sel-length = 0 means nothing is selected (when (or (zero? (-> ctxt sel-length)) (not keep-current)) (let ((menu (-> ctxt root-menu))) ;; check that we have a menu with items (when (and menu (not (null? (-> menu items)))) (let ((currently-active (-> ctxt is-active))) ;; if we're active, deactivate it (if currently-active (debug-menu-context-send-msg ctxt (debug-menu-msg deactivate) (debug-menu-dest activation))) ;; reset the selection stack down to a single thing, just the root menu. (set! (-> ctxt sel-length) 1) (set! (-> ctxt sel-menu 0) menu) ;; select the first thing within the root menu (set! (-> menu selected-item) (the-as debug-menu-item (car (-> menu items)))) ;; if we were active, activate again. (if currently-active (debug-menu-context-send-msg ctxt (debug-menu-msg activate) (debug-menu-dest activation))))))) ctxt) (defun debug-menu-rebuild ((menu debug-menu)) "Set the width and height of the background. If needed, completely reset the menu." (let ((max-width 0) (entry-count 0)) ;; loop over entries (let* ((iter (-> menu items)) (current-item (car iter))) (while (not (null? iter)) (+! entry-count 1) ;; link to parent (set! (-> (the-as debug-menu-item current-item) parent) menu) (set! max-width (max max-width (debug-menu-item-get-max-width (the-as debug-menu-item current-item) menu))) (set! iter (cdr iter)) (set! current-item (car iter)))) (set! (-> menu pix-width) (+ max-width 18)) (set! (-> menu pix-height) (+ (* entry-count 8) 6))) (let ((a0-2 (-> menu context))) ;; will only reset to default if nothing is selected. (debug-menu-context-default-selection a0-2 #t)) menu) (defun debug-menu-context-set-root-menu ((context debug-menu-context) (menu debug-menu)) "Set the root menu and reset everything." ;; deactivate, if we are active (let ((active (-> context is-active))) (if active (debug-menu-context-send-msg context (debug-menu-msg deactivate) (debug-menu-dest activation))) ;; the actual set (set! (-> context root-menu) menu) ;; reset (debug-menu-context-default-selection context #f) ;; activate if needed (if active (debug-menu-context-send-msg context (debug-menu-msg activate) (debug-menu-dest activation)))) context) (defun debug-menu-append-item ((menu debug-menu) (item debug-menu-node)) "Add an entry to the debug menu." (let* ((context (-> menu context)) (was-active (-> context is-active))) (if was-active (debug-menu-context-send-msg context (debug-menu-msg deactivate) (debug-menu-dest activation))) (set! (-> item parent) menu) ;; og:preserve-this was normal cons (set! (-> menu items) (the-as pair (append! (-> menu items) (dcons item '())))) (debug-menu-rebuild menu) (if was-active (debug-menu-context-send-msg context (debug-menu-msg activate) (debug-menu-dest activation)))) item) (defun debug-menu-remove-all-items ((menu debug-menu)) "Remove all the items from a menu" (let* ((context (-> menu context)) (was-active (-> context is-active))) (if was-active (debug-menu-context-send-msg context (debug-menu-msg deactivate) (debug-menu-dest activation))) (set! (-> menu items) '()) (set! (-> menu selected-item) #f) (debug-menu-rebuild menu) (if was-active (debug-menu-context-send-msg context (debug-menu-msg activate) (debug-menu-dest activation)))) menu) (defun debug-menu-func-decode ((value object)) "Get a function. The input can be a symbol or a function. Otherwise it will give you the nothing function." (let ((value-type (rtype-of value))) (the-as function (cond ((or (= value-type symbol) (= value-type type)) (the-as symbol (-> (the-as symbol value) value))) ((= value-type function) (the-as symbol value)) (else (the-as symbol nothing)))))) (defun-recursive debug-menu-make-from-template debug-menu-node ((context debug-menu-context) (template pair)) "Recursively build menus and items from a static template. Menu entries contain child templates; flag and function entries decode callbacks; variable entries select integer, fixed-point, hexadecimal, floating-point, or scaled floating-point setup." (local-vars (result basic)) (when (or (not template) (null? template)) (set! result #f) (goto cfg-41)) (let ((kind (car template)) (name (the-as string (car (cdr template))))) (cond ((= kind 'menu) (let ((submenu (new 'debug 'debug-menu context name))) (set! result (new 'debug 'debug-menu-item-submenu name submenu)) (let* ((children (cdr (cdr template))) (child-template (car children))) (while (not (null? children)) (let ((child (debug-menu-make-from-template context (the-as pair child-template)))) (if child (debug-menu-append-item submenu child))) (set! children (cdr children)) (set! child-template (car children)))))) ((= kind 'main-menu) (set! result (new 'debug 'debug-menu context name)) (let* ((children (cdr (cdr template))) (child-template (car children))) (while (not (null? children)) (let ((child (debug-menu-make-from-template context (the-as pair child-template)))) (if child (debug-menu-append-item (the-as debug-menu result) child))) (set! children (cdr children)) (set! child-template (car children)))) (debug-menu-context-set-root-menu context (the-as debug-menu result))) (else (set! result (cond ((= kind 'flag) (new 'debug 'debug-menu-item-flag name (car (cdr (cdr template))) (the-as (function object debug-menu-msg object) (debug-menu-func-decode (car (cdr (cdr (cdr template)))))))) ((or (= kind 0) (= kind 'function)) (new 'debug 'debug-menu-item-function name (car (cdr (cdr template))) (the-as (function object object) (debug-menu-func-decode (car (cdr (cdr (cdr template)))))))) ((= kind 'var) (new 'debug 'debug-menu-item-var name (the-as int (car (cdr (cdr template)))) (the-as int (car (cdr (cdr (cdr template))))))) ((or (= kind 'int-var) (= kind 'int-var-gat1) (= kind 'hex-var)) (set! result (new 'debug 'debug-menu-item-var name (the-as int (car (cdr (cdr template)))) (the-as int (ref template 4)))) (debug-menu-item-var-make-int (the-as debug-menu-item-var result) (the-as (function int debug-menu-msg int int int) (debug-menu-func-decode (car (cdr (cdr (cdr template)))))) (/ (the-as int (ref template 5)) 8) (the-as symbol (ref template 6)) (/ (the-as int (ref template 7)) 8) (/ (the-as int (ref template 8)) 8) (= kind 'hex-var)) ;; PC: GAT1 integer values are stored in hundredths but displayed with one ;; decimal digit; ordinary integer and hexadecimal entries stay integral. (set! (-> (the-as debug-menu-item-var result) ifloat-p) (= kind 'int-var-gat1)) ;;#t) result) ((= kind 'float-var) (set! result (new 'debug 'debug-menu-item-var name (the-as int (car (cdr (cdr template)))) (the-as int (ref template 4)))) (debug-menu-item-var-make-float (the-as debug-menu-item-var result) (the-as (function int debug-menu-msg float float float) (debug-menu-func-decode (car (cdr (cdr (cdr template)))))) (the float (/ (the-as int (ref template 5)) 8)) (the-as symbol (ref template 6)) (the float (/ (the-as int (ref template 7)) 8)) (the float (/ (the-as int (ref template 8)) 8)) (/ (the-as int (ref template 9)) 8)) result) ((= kind 'float-fixed-var) (set! result (new 'debug 'debug-menu-item-var name (the-as int (car (cdr (cdr template)))) (the-as int (ref template 4)))) (debug-menu-item-var-make-float (the-as debug-menu-item-var result) (the-as (function int debug-menu-msg float float float) (debug-menu-func-decode (car (cdr (cdr (cdr template)))))) (* 0.001 (the float (/ (the-as int (ref template 5)) 8))) (the-as symbol (ref template 6)) (* 0.001 (the float (/ (the-as int (ref template 7)) 8))) (* 0.001 (the float (/ (the-as int (ref template 8)) 8))) (/ (the-as int (ref template 9)) 8)) result) (else #f)))))) (label cfg-41) (the-as debug-menu-node result)) (defun debug-menu-find-from-template ((context debug-menu-context) (path pair)) "Find a debug-menu that was added by a template. This could be used to modify it after, for example to add in options that might not be known at compile-time." (let ((node (the-as object (-> context root-menu)))) (while (begin (label cfg-17) (and node (type-type? (-> (the-as debug-menu-node node) type) debug-menu) (not (null? path)))) (let ((items (-> (the-as debug-menu node) items)) (path-name (car path))) (set! path (cdr path)) (let ((item (car items))) (while (not (null? items)) (when (string= (the-as string path-name) (-> (the-as debug-menu-item item) name)) (if (type-type? (rtype-of item) debug-menu-item-submenu) (set! node (-> (the-as debug-menu-item-submenu item) submenu)) (set! node item)) (goto cfg-17)) (set! items (cdr items)) (set! item (car items))))) (set! node #f) (goto cfg-24)) (label cfg-24) (the-as debug-menu node))) ;;;;;;;;;;;;;;;;;;;;;;;; ;; rendering ;;;;;;;;;;;;;;;;;;;;;;;; (defun debug-menu-item-submenu-render ((item debug-menu-item-submenu) (x int) (y int) (submenus int) (selected symbol)) "Draw the text for a submenu. Like Render... The submenus parameter is the number of _open_ menus below the one containing this item" (let ((font (-> item parent context font))) (set-origin! font x y) (set! (-> font color) (cond ((zero? submenus) (font-color menu)) ;; in the active menu, white (selected (font-color menu-selected-parent)) ;; a parent, but selected (else (font-color menu-parent)) ;; a parent, but not selected )) (with-dma-buffer-add-bucket ((dma-buf (-> (current-frame) debug-buf)) (bucket-id debug-no-zbuf)) (draw-string-adv (-> item name) dma-buf font) (draw-string-adv "..." dma-buf font))) item) (defun debug-menu-item-function-render ((item debug-menu-item-function) (x int) (y int) (submenus int) (selected symbol)) "Draw the text for a function entry. Also updates the timer for the highlight." (let ((font (-> item parent context font))) (set-origin! font x y) (set! (-> font color) (cond ((> (-> item hilite-timer) 0) ;; if the hilite is >0, we ran the function successfully, so we hilite in blue for a bit (1-! (-> item hilite-timer)) (font-color menu-selected)) ((< (-> item hilite-timer) 0) ;; if we're negative, it failed, so hilite in red (1+! (-> item hilite-timer)) (font-color menu-func-bad)) ((nonzero? submenus) ;; in a parent menu (font-color menu-parent)) (else ;; option in the active menu. (font-color menu)))) (with-dma-buffer-add-bucket ((dma-buf (-> (current-frame) debug-buf)) (bucket-id debug-no-zbuf)) (draw-string (-> item name) dma-buf font))) item) (defun debug-menu-item-flag-render ((item debug-menu-item-flag) (x int) (y int) (submenus int) (selected symbol)) "Draw the text for a flag." (let ((font (-> item parent context font))) (set-origin! font x y) (set! (-> font color) (cond ((= (-> item is-on) 'invalid) (font-color menu-invalid) ;; can't use this one. ) ((-> item is-on) (if (zero? submenus) (font-color menu-flag-on) ;; on, and in active menu (font-color menu-flag-on-parent) ;; on, and in parent menu )) ((zero? submenus) (font-color menu-flag-off) ;; off, and in active menu ) (else (font-color menu-flag-off-parent) ;; off, and in parent menu ))) (with-dma-buffer-add-bucket ((dma-buf (-> (current-frame) debug-buf)) (bucket-id debug-no-zbuf)) (draw-string (-> item name) dma-buf font))) item) (defun debug-menu-item-var-render ((item debug-menu-item-var) (x int) (y int) (submenus int) (selected symbol)) "Draw the text for a variable" (let ((font (-> item parent context font))) (set-origin! font x y) (set! (-> font color) (cond ((zero? submenus) (if (-> item grabbed-joypad-p) (font-color menu-selected) ;; active menu, using joypad (font-color menu) ;; active menu, but not grabbed )) (selected (font-color menu-selected-parent) ;; selected entry in a parent menu ) (else (font-color menu-parent)))) (with-dma-buffer-add-bucket ((dma-buf (-> (current-frame) debug-buf)) (bucket-id debug-no-zbuf)) (draw-string-adv (-> item name) dma-buf font) (draw-string-adv ":" dma-buf font) (cond ((>= (-> item show-len) (length (-> item display-str))) ;; enough room to just draw the whole thing (draw-string (-> item display-str) dma-buf font)) (else ;; not enough room. normally just draw ... (draw-string "..." dma-buf font) ;; display the whole thing if: we're selected and there are no submenus. (set! selected (and (zero? submenus) selected)) (when selected (set-origin! font 20 204) (draw-string-adv (-> item name) dma-buf font) (draw-string-adv ":" dma-buf font) (draw-string (-> item display-str) dma-buf font)))))) item) (defun debug-menu-item-render ((item debug-menu-item) (x int) (y int) (submenus int) (selected symbol)) "Draw an item. This feels like it should have been a method..." ;; do a refresh, if it's time. (when (> (-> item refresh-delay) 0) (+! (-> item refresh-ctr) -1) (when (<= (-> item refresh-ctr) 0) (set! (-> item refresh-ctr) (-> item refresh-delay)) (debug-menu-item-send-msg item (debug-menu-msg update)))) ;; call the appropriate render function. (cond ((= (-> item type) debug-menu-item-submenu) (debug-menu-item-submenu-render (the-as debug-menu-item-submenu item) x y submenus selected)) ((= (-> item type) debug-menu-item-function) (debug-menu-item-function-render (the-as debug-menu-item-function item) x y submenus selected)) ((= (-> item type) debug-menu-item-flag) (debug-menu-item-flag-render (the-as debug-menu-item-flag item) x y submenus selected)) ((= (-> item type) debug-menu-item-var) (debug-menu-item-var-render (the-as debug-menu-item-var item) x y submenus selected)) (else (format 0 "ERROR: Found unknown item type!~%"))) item) (defun debug-menu-render ((menu debug-menu) (x-pos int) (y-pos int) (selected debug-menu-node) (submenus int)) "Render a menu." ;; draw the background (let ((selection-index 0)) (let* ((items (-> menu items)) (item (car items))) (while (not (null? items)) (if (= item selected) (goto cfg-7)) (+! selection-index 1) (set! items (cdr items)) (set! item (car items)))) (label cfg-7) (if (< 16 selection-index) (set! y-pos (- y-pos (* (+ selection-index -16) 8))))) (with-dma-buffer-add-bucket ((dma-buf (-> (current-frame) debug-buf)) (bucket-id debug-no-zbuf)) ;; og:preserve-this fixed for widescreen (draw-sprite2d-xy dma-buf (correct-x-int x-pos) y-pos (correct-x-int (-> menu pix-width)) (-> menu pix-height) (static-rgba #x00 #x00 #x00 #x40))) ;; draw each item (let* ((item-x (+ x-pos 3)) (item-y (+ y-pos 3)) (items (-> menu items)) (item (car items))) (while (not (null? items)) ;; draw > on the selected object (when (= item selected) ;; dim it if it's in a parent menu. (set! (-> menu context font color) (if (nonzero? submenus) (font-color menu-parent) (font-color menu))) (set-origin! (-> menu context font) item-x item-y) (with-dma-buffer-add-bucket ((dma-buf (-> (current-frame) debug-buf)) (bucket-id debug-no-zbuf)) (draw-string ">" dma-buf (-> menu context font)))) ;; actually draw the item. ;; og:preserve-this do not render if text is out of bounds... (when (and (< -20 item-y) (> 256 item-y)) (debug-menu-item-render (the-as debug-menu-item item) (+ item-x 12) item-y submenus (= item selected))) (+! item-y 8) (set! items (cdr items)) (set! item (car items)))) menu) (defun debug-menu-context-render ((context debug-menu-context)) "Render all menus" (let ((x-pos 6)) ;; loop down the stack of menus (dotimes (stack-idx (-> context sel-length)) ;; the menu being drawn at this depth (let ((menu (-> context sel-menu stack-idx))) ;; the thing that's selected at this depth. (let ((selection (-> menu selected-item))) (debug-menu-render menu x-pos 28 selection (+ (- -1 stack-idx) (-> context sel-length)))) (set! x-pos (+ x-pos 3 (-> menu pix-width)))))) context) ;;;;;;;;;;;;;;;;;;;;;;;; ;; navigate ;;;;;;;;;;;;;;;;;;;;;;;; (defun debug-menu-context-select-next-or-prev-item ((context debug-menu-context) (direction int)) "Go up or down 1 in the currently open thing. The sign of arg1 determines direction" (local-vars (next-item object)) ;; search for the currently selected thing. (let ((menu (-> context sel-menu (+ (-> context sel-length) -1)))) (let ((selected (-> menu selected-item)) (previous-cell '()) ;; thing before selection (selected-cell '()) ;; current selection ) (let ((items (-> menu items))) (while (not (null? items)) (when (= (car items) selected) (set! selected-cell items) (goto cfg-7)) (set! previous-cell items) (set! items (cdr items)))) (label cfg-7) (when (null? selected-cell) (format 0 "ERROR: Couldn't find selected item in menu.~%") (set! context context) (goto cfg-19)) (cond ((>= direction 0) (if (null? (cdr selected-cell)) (set! next-item (car (-> menu items))) (set! next-item (car (cdr selected-cell))))) ((null? previous-cell) (set! next-item (car (last (-> menu items))))) (else (set! next-item (car previous-cell))))) (set! (-> menu selected-item) (the-as debug-menu-item next-item))) (label cfg-19) context) (defun debug-menu-context-select-new-item ((context debug-menu-context) (offset int)) "Move the current selection by offset entries, shortening the move at either end and wrapping only when the selection already starts on the first or last entry." (let* ((menu (-> context sel-menu (+ (-> context sel-length) -1))) (selected (-> menu selected-item)) (item-count 0) (selected-index -1)) (let ((items (-> menu items))) (while (not (null? items)) (if (= (car items) selected) (set! selected-index item-count)) (set! items (cdr items)) (+! item-count 1))) (when (= selected-index -1) (format 0 "ERROR: Couldn't find selected item in menu.~%") (set! context context) (goto cfg-25)) (cond ((>= offset 0) (cond ((= selected-index (+ item-count -1)) (set! offset 1)) ((>= (+ selected-index offset) item-count) (set! offset (+ (- -1 selected-index) item-count)))) (dotimes (i offset) (debug-menu-context-select-next-or-prev-item context 1))) (else (cond ((zero? selected-index) (set! offset -1)) ((< (+ selected-index offset) 0) (set! offset (- selected-index)))) (dotimes (i (- offset)) (debug-menu-context-select-next-or-prev-item context -1))))) (label cfg-25) context) (defun debug-menu-context-open-submenu ((context debug-menu-context) (submenu debug-menu)) "Push a nonempty submenu onto the selection stack, select its first item when needed, and activate its entries. Refuse stacks deeper than eight menus." (let ((depth (-> context sel-length))) (when (>= depth 8) (format 0 "ERROR: Trying to exceed maximum menu depth!") (return submenu)) (when (null? (-> submenu items)) (format 0 "ERROR: Submenu has no items!") (return submenu)) (set! (-> context sel-menu depth) submenu) (if (not (-> submenu selected-item)) (set! (-> submenu selected-item) (the-as debug-menu-item (-> submenu items car)))) (set! (-> context sel-length) (+ depth 1))) (debug-menu-context-send-msg context (debug-menu-msg activate) (debug-menu-dest current-selection))) (defun debug-menu-context-close-submenu ((context debug-menu-context)) "Deactivate the current menu, then pop it from the open-menu stack when it is below the root." (debug-menu-context-send-msg context (debug-menu-msg deactivate) (debug-menu-dest current-selection)) (if (< 1 (-> context sel-length)) (+! (-> context sel-length) -1)) context) ;;;;;;;;;;;;;;;;;;;;;;;; ;; message handling ;;;;;;;;;;;;;;;;;;;;;;;; ;; items each have their own handlers for messages. (defun debug-menu-item-submenu-msg ((item debug-menu-item-submenu) (message debug-menu-msg)) "Open the item's submenu when it receives a press message." ;; on press, open the submenu (when (= message (debug-menu-msg press)) (let ((context (-> item parent context))) (debug-menu-context-open-submenu context (-> item submenu)))) item) (defun debug-menu-item-function-msg ((item debug-menu-item-function) (message debug-menu-msg)) "Invoke the item's callback on press and set a short blue or red highlight according to its result. Clear the highlight when the item is deactivated." (cond ((= message (debug-menu-msg press)) ;; on press, call the function! (cond ((-> item activate-func) (if ((-> item activate-func) (-> item id)) (set! (-> item hilite-timer) 6) (set! (-> item hilite-timer) -6))) (else (set! (-> item hilite-timer) -6)))) ((= message (debug-menu-msg deactivate)) ;; on deactivate, clear hilite. (set! (-> item hilite-timer) 0) 0)) item) (defun debug-menu-item-flag-msg ((item debug-menu-item-flag) (message debug-menu-msg)) "Toggle the flag callback on press, query it on update or activation, and refresh the open menus after a change." (cond ((= message (debug-menu-msg press)) ;; on press, call the function. (if (-> item activate-func) (set! (-> item is-on) ((-> item activate-func) (-> item id) (debug-menu-msg press)))) ;; also update all open menus. (let ((context (-> item parent context))) (debug-menu-context-send-msg context (debug-menu-msg update) (debug-menu-dest open-menus)))) ((or (= message (debug-menu-msg update)) (= message (debug-menu-msg activate))) ;; just query the value. (if (-> item activate-func) (set! (-> item is-on) ((-> item activate-func) (-> item id) (debug-menu-msg update)))) ;; update the refresh counter. (set! (-> item refresh-ctr) (-> item refresh-delay)))) item) ;;;;;;;;;;;;;;;;;;;;;;;; ;; joypad handling ;;;;;;;;;;;;;;;;;;;;;;;; (defun debug-menu-item-var-joypad-handler ((item debug-menu-item-var)) "Edit the grabbed variable from the directional pad. Begin with the configured step, repeat after a short delay, and double the step every 30 held frames up to the numeric cap. Releasing X commits the value; pressing Circle while releasing restores the saved value." (cond ((not (cpad-hold? 0 x)) (let ((context (-> item parent context))) (debug-menu-context-release-joypad context)) (set! (-> item grabbed-joypad-p) #f) (when (cpad-pressed? 0 circle) (cond ((-> item float-p) (if (-> item factivate-func) (set! (-> item fval) ((-> item factivate-func) (-> item id) (debug-menu-msg press) (-> item fundo-val) (-> item fval))))) (else (if (-> item factivate-func) (set! (-> item fval) (the-as float ((the-as (function int int int int int) (-> item factivate-func)) (-> item id) 4 (the-as int (-> item fundo-val)) (the-as int (-> item fval)))))))) (debug-menu-item-var-update-display-str item)) (let ((context (-> item parent context))) (debug-menu-context-send-msg context (debug-menu-msg update) (debug-menu-dest open-menus)))) ((or (cpad-hold? 0 right) (cpad-hold? 0 left) (cpad-hold? 0 down) (cpad-hold? 0 up)) (let ((direction (cond ((cpad-hold? 0 right) 10) ((cpad-hold? 0 up) 1) ((cpad-hold? 0 down) -1) (else -10)))) (when (!= direction (-> item inc-dir)) (set! (-> item inc-dir) direction) (set! (-> item inc-delay) 15) (set! (-> item inc-delay-ctr) 0) (set! (-> item step-delay-ctr) 30) (set! (-> item fstep) (-> item fstart-inc)) (set! (-> item fstep) (-> item fstart-inc)))) (cond ((<= (-> item inc-delay-ctr) 0) (if (> (-> item inc-delay) 0) (+! (-> item inc-delay) -1)) (when (zero? (-> item inc-delay)) (cond ((<= (-> item step-delay-ctr) 0) (set! (-> item step-delay-ctr) 30) (cond ((-> item float-p) (if (< (-> item fstep) 10000000.0) (set! (-> item fstep) (* 2.0 (-> item fstep))))) (else (if (< (the-as int (-> item fstep)) #x989680) (set! (-> item fstep) (the-as float (* (the-as int (-> item fstep)) 2))))))) (else (+! (-> item step-delay-ctr) -1)))) (set! (-> item inc-delay-ctr) (-> item inc-delay)) (cond ((-> item float-p) (when (-> item factivate-func) (let ((new-float-value (+ (-> item fval) (* (the float (-> item inc-dir)) (-> item fstep))))) (if (-> item range-p) (set! new-float-value (fmin (fmax new-float-value (-> item frange-min)) (-> item frange-max)))) (set! (-> item fval) ((-> item factivate-func) (-> item id) (debug-menu-msg press) new-float-value (-> item fval)))))) (else (when (-> item factivate-func) (let ((new-int-value (+ (the-as int (-> item fval)) (* (-> item inc-dir) (the-as int (-> item fstep)))))) (if (-> item range-p) (set! new-int-value (min (max new-int-value (the-as int (-> item frange-min))) (the-as int (-> item frange-max))))) (set! (-> item fval) (the-as float ((the-as (function int int int int int) (-> item factivate-func)) (-> item id) 4 new-int-value (the-as int (-> item fval))))))))) (debug-menu-item-var-update-display-str item) (let ((context (-> item parent context))) (debug-menu-context-send-msg context (debug-menu-msg update) (debug-menu-dest current-selection)))) (else (+! (-> item inc-delay-ctr) -1)))) (else (set! (-> item inc-dir) 0) 0)) item) (defun debug-menu-item-var-msg ((item debug-menu-item-var) (message debug-menu-msg)) "Begin variable editing on press, save the undo value, release the joypad on deactivation, and query the callback on update or activation." (cond ((= message (debug-menu-msg deactivate)) (when (-> item grabbed-joypad-p) (let ((context (-> item parent context))) (debug-menu-context-release-joypad context)) (set! (-> item grabbed-joypad-p) #f))) ((= message (debug-menu-msg press)) (when (not (-> item grabbed-joypad-p)) (let ((context (-> item parent context))) (when (debug-menu-context-grab-joypad context item (the-as (function basic none) debug-menu-item-var-joypad-handler)) (set! (-> item grabbed-joypad-p) #t) (set! (-> item fundo-val) (-> item fval)) (set! (-> item fundo-val) (-> item fval)) (set! (-> item inc-dir) 0) 0)))) ((or (= message (debug-menu-msg update)) (= message (debug-menu-msg activate))) (cond ((-> item float-p) (if (-> item factivate-func) (set! (-> item fval) ((-> item factivate-func) (-> item id) (debug-menu-msg update) (-> item fval) (-> item fval))))) (else (if (-> item factivate-func) (set! (-> item fval) (the-as float ((the-as (function int int int int int) (-> item factivate-func)) (-> item id) 3 (the-as int (-> item fval)) (the-as int (-> item fval)))))))) (debug-menu-item-var-update-display-str item) (set! (-> item refresh-ctr) (-> item refresh-delay)))) item) (defun debug-menu-item-send-msg ((item debug-menu-item) (message debug-menu-msg)) "Call the appropriate message handler for the given item." (cond ((= (-> item type) debug-menu-item-submenu) (debug-menu-item-submenu-msg (the-as debug-menu-item-submenu item) message)) ((= (-> item type) debug-menu-item-function) (debug-menu-item-function-msg (the-as debug-menu-item-function item) message)) ((= (-> item type) debug-menu-item-flag) (debug-menu-item-flag-msg (the-as debug-menu-item-flag item) message)) ((= (-> item type) debug-menu-item-var) (debug-menu-item-var-msg (the-as debug-menu-item-var item) message)) (else (format 0 "ERROR: Found unknown item type!~%"))) item) (defun-recursive debug-menu-send-msg debug-menu ((menu debug-menu) (message debug-menu-msg) (recursive symbol)) "Send to all items in menu. Arg2 picks if we are recursive or not." (let* ((items (-> menu items)) (item (car items))) (while (not (null? items)) (debug-menu-item-send-msg (the-as debug-menu-item item) message) (if (and recursive (= (-> (the-as debug-menu-item item) type) debug-menu-item-submenu)) (debug-menu-send-msg (-> (the-as debug-menu-item-submenu item) submenu) message #t)) (set! items (cdr items)) (set! item (car items)))) menu) (defun debug-menu-context-send-msg ((context debug-menu-context) (message debug-menu-msg) (destination debug-menu-dest)) "Send the arg1 message to the given place." (cond ((= destination (debug-menu-dest root)) ;; sent to root, recursively. This will hit the whole menu. (debug-menu-send-msg (-> context root-menu) message #t)) ((= destination (debug-menu-dest open-menus)) ;; only send to open things (when (-> context is-active) ;; only if context is open (dotimes (i (-> context sel-length)) ;; go through stack (let ((menu (-> context sel-menu i))) ;; send, not recursive (debug-menu-send-msg menu message #f))))) ((= destination (debug-menu-dest current-selection)) (when (-> context is-active) ;; context open (if (nonzero? (-> context sel-length)) ;; something in the stack (debug-menu-send-msg (-> context sel-menu (+ (-> context sel-length) -1)) message #f) ;; send to that. ))) ((= destination (debug-menu-dest activation)) ;; this is a special case for when we want to activate or deactivate something. (cond ((= message (debug-menu-msg activate)) (when (not (-> context is-active)) (set! (-> context is-active) #t) (debug-menu-context-send-msg context (debug-menu-msg activate) (debug-menu-dest open-menus)))) ((= message (debug-menu-msg deactivate)) (when (-> context is-active) (debug-menu-context-send-msg context (debug-menu-msg deactivate) (debug-menu-dest open-menus)) (set! (-> context is-active) #f)))))) context) (defun debug-menu-context-activate-selection ((context debug-menu-context)) "Press on the selected thing. Note that we named this enum press, not activate." (let ((item (-> context sel-menu (+ (-> context sel-length) -1) selected-item))) (debug-menu-item-send-msg item (debug-menu-msg press))) context) (defun debug-menus-default-joypad-func ((context debug-menu-context)) "Control the menu from the joystick" (cond ((cpad-pressed? 0 square) (cond ((< 1 (-> context sel-length)) (debug-menu-context-close-submenu context)) (else))) ((cpad-pressed? 0 x) (debug-menu-context-activate-selection context)) ((cpad-pressed? 0 up) (debug-menu-context-select-new-item context -1)) ((cpad-pressed? 0 down) (debug-menu-context-select-new-item context 1)) ((cpad-pressed? 0 left) (debug-menu-context-select-new-item context -5)) ((cpad-pressed? 0 right) (debug-menu-context-select-new-item context 5))) context) (defun debug-menus-active ((context debug-menu-context)) "Run the menu context" (when (not (-> context is-hidden)) ;; grab inputs (if (-> context joypad-func) ((-> context joypad-func) (-> context joypad-item)) (debug-menus-default-joypad-func context)) ;; render (debug-menu-context-render context)) context) (defun debug-menus-handler ((context debug-menu-context)) "Update and draw the menu context while it is active." (if (-> context is-active) (debug-menus-active context)) context)