diff --git a/sdl3.asd b/sdl3.asd index 53b4519..0f427e7 100644 --- a/sdl3.asd +++ b/sdl3.asd @@ -278,5 +278,16 @@ ((:file "package") (:file "01open-file-dialog") (:file "02open-file-dialog-multi") - (:file "03open-folder-dialog")))))) + (:file "03open-folder-dialog"))) + (:module "menu" + :components + ((:file "package") + (:file "01system-menu") + (:file "02screen-menu") + (:file "03advanced/package") + (:file "03advanced/menu-model") + (:file "03advanced/menu-controller") + (:file "03advanced/sdl3-renderer") + (:file "03advanced/action-handlers") + (:file "03advanced/screen-menu-classes")))))) :description "test function here") diff --git a/src/keyboard/type.lisp b/src/keyboard/type.lisp index 7627238..2aca971 100644 --- a/src/keyboard/type.lisp +++ b/src/keyboard/type.lisp @@ -350,7 +350,7 @@ (:insert #x40000049) (:home #x4000004a) (:pageup #x4000004b) - (:end #x4000004) + (:end #x4000004d) (:pagedown #x4000004e) (:right #x4000004f) (:left #x40000050) diff --git a/src/main/wrap.lisp b/src/main/wrap.lisp index 67b2e65..9a0c3e4 100644 --- a/src/main/wrap.lisp +++ b/src/main/wrap.lisp @@ -9,8 +9,11 @@ name -> callback name aragc -> argument count argv -> list of argument" - `(cffi:defcallback ,name :int ((,argc :int) (,argv (:pointer :string))) - ,@body)) + `(progn + (defun ,name (,argc ,argv) + ,@body) + (cffi:defcallback ,name :int ((,argc :int) (,argv (:pointer :string))) + (funcall (symbol-function ',name) ,argc ,argv)))) (export 'defmain-fun) (defun run-app (callback &rest args) @@ -43,20 +46,28 @@ appstate -> a place where the app can optionally store a pointer for future use. aragc -> the standard ANSI C main's argc; number of elements in argv. argv -> the standard ANSI C main's argv; array of command line arguments. " - `(cffi:defcallback ,name app-result ((appstate (:pointer :pointer)) - (,argc :int) - (,argv (:pointer :string))) - (declare (ignore appstate)) - ,@body)) + `(progn + (defun ,name (appstate ,argc ,argv) + (declare (ignore appstate)) + ,@body) + (cffi:defcallback ,name app-result ((appstate (:pointer :pointer)) + (,argc :int) + (,argv (:pointer :string))) + (declare (ignore appstate)) + (funcall (symbol-function ',name) appstate ,argc ,argv)))) (export 'def-app-init) (defmacro def-app-iterate (name () &body body) "ret[app-result] appstate an optional pointer, provided by the app in SDL_AppInit. " - `(cffi:defcallback ,name app-result ((appstate :pointer)) - (declare (ignore appstate)) - ,@body)) + `(progn + (defun ,name (appstate) + (declare (ignore appstate)) + ,@body) + (cffi:defcallback ,name app-result ((appstate :pointer)) + (declare (ignore appstate)) + (funcall (symbol-function ',name) appstate)))) (export 'def-app-iterate) (defmacro def-app-event (name (event-type pevent) &body body) @@ -65,15 +76,19 @@ app-state -> an optional pointer, provided by the app in SDL_AppInit. event -> the new event for the app to examine. event-type -> sdl event " - (let ((event-type-val (gensym))) - `(cffi:defcallback ,name app-result ((appstate :pointer) (,pevent (:pointer (:union event)))) - (declare (ignore appstate)) - (cffi:with-foreign-slots (((,event-type-val type)) - ,pevent - (:union event)) - (let ((,event-type (cffi:foreign-enum-keyword 'event-type ,event-type-val))) - ,@body))))) -(export 'def-app-event) + (let ((event-type-val (gensym))) + `(progn + (defun ,name (appstate ,pevent) + (declare (ignore appstate)) + (cffi:with-foreign-slots (((,event-type-val type)) + ,pevent + (:union event)) + (let ((,event-type (cffi:foreign-enum-keyword 'event-type ,event-type-val))) + ,@body))) + (cffi:defcallback ,name app-result ((appstate :pointer) (,pevent (:pointer (:union event)))) + (declare (ignore appstate)) + (funcall (symbol-function ',name) appstate ,pevent))))) + (export 'def-app-event) (defmacro def-app-quit (name (result) &body body) @@ -81,9 +96,13 @@ event-type -> sdl event app-state -> an optional pointer, provided by the app in SDL_AppInit. result -> the result code that terminated the app (success or failure). " - `(cffi:defcallback ,name :void ((appstate :pointer) (,result app-result)) - (declare (ignore appstate)) - ,@body)) + `(progn + (defun ,name (appstate ,result) + (declare (ignore appstate)) + ,@body) + (cffi:defcallback ,name :void ((appstate :pointer) (,result app-result)) + (declare (ignore appstate)) + (funcall (symbol-function ',name) appstate ,result)))) (export 'def-app-quit) (defun enter-app-main-callbacks (appinit appiter appevent appquit &rest args) diff --git a/test/menu/01system-menu.lisp b/test/menu/01system-menu.lisp new file mode 100644 index 0000000..9d7815f --- /dev/null +++ b/test/menu/01system-menu.lisp @@ -0,0 +1,62 @@ +(in-package :sdl3.demo.menu) + +(defparameter *window-system* nil) +(defparameter *renderer-system* nil) + +;;; Right mouse button number in SDL3 +(defconstant +button-right+ 3) + +(sdl3:def-app-init system-menu-init (argc argv) + (declare (ignore argc argv)) + (sdl3:set-app-metadata "Example System Menu" "1.0" "com.example.menu") + (when (not (sdl3:init :video)) + (format t "~a~%" (sdl3:get-error)) + (return-from system-menu-init :failure)) + (multiple-value-bind (rst window renderer) + (sdl3:create-window-and-renderer + "System Menu — right-click to open" 640 200 0) + (if (not rst) + (progn + (format t "~a~%" (sdl3:get-error)) + (return-from system-menu-init :failure)) + (setf *window-system* window + *renderer-system* renderer))) + :continue) + +(sdl3:def-app-iterate system-menu-iterate () + (sdl3:set-render-draw-color *renderer-system* 30 30 30 255) + (sdl3:render-clear *renderer-system*) + (sdl3:set-render-draw-color *renderer-system* 200 200 200 255) + (sdl3:render-debug-text *renderer-system* 160.0 92.0 "Right-click anywhere to open the system menu.") + (sdl3:render-present *renderer-system*) + :continue) + +(sdl3:def-app-event system-menu-event (type event) + (declare (ignore type)) + (let ((ev (sdl3:event-unmarshal event))) + (typecase ev + (sdl3:quit-event :success) + (sdl3:mouse-button-event + (when (and (slot-value ev 'sdl3:%down) + (= (slot-value ev 'sdl3:%button) +button-right+)) + (let ((menu-x (round (slot-value ev 'sdl3:%x))) + (menu-y (round (slot-value ev 'sdl3:%y)))) + (unless (sdl3:show-window-system-menu *window-system* menu-x menu-y) + (format t "show-window-system-menu failed: ~a~%" (sdl3:get-error))))) + :continue) + (t :continue)))) + +(sdl3:def-app-quit system-menu-quit (result) + (declare (ignore result)) + (sdl3:destroy-renderer *renderer-system*) + (sdl3:destroy-window *window-system*) + (sdl3:pump-events) + (sdl3:quit-sub-system :video) + (sdl3:quit)) + +(defun do-system-menu-demo () + (sdl3:enter-app-main-callbacks + 'system-menu-init + 'system-menu-iterate + 'system-menu-event + 'system-menu-quit)) diff --git a/test/menu/02screen-menu.lisp b/test/menu/02screen-menu.lisp new file mode 100644 index 0000000..89654c5 --- /dev/null +++ b/test/menu/02screen-menu.lisp @@ -0,0 +1,272 @@ +(in-package :sdl3.demo.menu) + +(defparameter *window-screen-menu* nil) +(defparameter *renderer-screen-menu* nil) +(defparameter *open-menu-index* nil) +(defparameter *hover-menu-index* nil) +(defparameter *hover-item-index* nil) +(defparameter *status-message* "Click File, Edit or Help.") + +;; Each item: (label hotkey-display hotkey-mod hotkey-key) +;; hotkey-mod is :ctrl or nil; hotkey-key is an SDL keycode keyword or nil. +(defparameter *screen-menu-model* + '((:file "File" + ("Open" "Ctrl+O" :ctrl :o) + ("Save" "Ctrl+S" :ctrl :s) + ("Quit" "Ctrl+Q" :ctrl :q)) + (:edit "Edit" + ("Copy" "Ctrl+C" :ctrl :c) + ("Paste" "Ctrl+V" :ctrl :v) + ("Select All" "Ctrl+A" :ctrl :a)) + (:help "Help" + ("Shortcuts" "F1" nil :f1) + ("About" "" nil nil)))) + +(defconstant +mouse-button-left+ 1) +(defconstant +menu-bar-height+ 28) +(defconstant +menu-title-width+ 78) +(defconstant +submenu-width+ 200) +(defconstant +submenu-item-height+ 24) + +(defun screen-menu-entry (index) + (nth index *screen-menu-model*)) + +(defun screen-menu-title (index) + (second (screen-menu-entry index))) + +(defun screen-menu-items (index) + (cddr (screen-menu-entry index))) + +(defun item-label (item) (first item)) +(defun item-hotkey-str (item) (second item)) +(defun item-hotkey-mod (item) (third item)) +(defun item-hotkey-key (item) (fourth item)) + +(defun ctrl-held-p (ev) + (let ((mods (slot-value ev 'sdl3:%mod))) + (typecase mods + (list (or (member :ctrl mods) (member :lctrl mods) (member :rctrl mods))) + (symbol (member mods '(:ctrl :lctrl :rctrl))) + (integer (not (zerop (logand mods #x00c0)))) + (t nil)))) + +(defun item-hotkey-matches-p (item ev) + (let ((mod (item-hotkey-mod item)) + (key (item-hotkey-key item))) + (when key + (and (eq (slot-value ev 'sdl3:%key) key) + (if (eq mod :ctrl) + (ctrl-held-p ev) + t))))) + +(defun screen-menu-count () + (length *screen-menu-model*)) + +(defun rect-hit-p (x y left top width height) + (and (>= x left) + (< x (+ left width)) + (>= y top) + (< y (+ top height)))) + +(defun title-left (index) + (* index +menu-title-width+)) + +(defun submenu-item-at (x y) + (when *open-menu-index* + (let* ((left (title-left *open-menu-index*)) + (top +menu-bar-height+) + (items (screen-menu-items *open-menu-index*)) + (height (* (length items) +submenu-item-height+))) + (when (rect-hit-p x y left top +submenu-width+ height) + (floor (- y top) +submenu-item-height+))))) + +(defun menu-index-at (x y) + (when (rect-hit-p x y 0 0 (* (screen-menu-count) +menu-title-width+) +menu-bar-height+) + (let ((index (floor x +menu-title-width+))) + (when (< index (screen-menu-count)) + index)))) + +(defun make-menu-rect (x y w h) + (make-instance 'sdl3:rect + :%x (float x 1.0) + :%y (float y 1.0) + :%w (float w 1.0) + :%h (float h 1.0))) + +(defun fill-menu-rect (renderer x y w h red green blue) + (sdl3:set-render-draw-color renderer red green blue 255) + (sdl3:render-fill-rect renderer (make-menu-rect x y w h))) + +(defun stroke-menu-rect (renderer x y w h red green blue) + (sdl3:set-render-draw-color renderer red green blue 255) + (sdl3:render-rect renderer (make-menu-rect x y w h))) + +(defun activate-screen-menu-item (menu-index item-index) + (let* ((menu-title (screen-menu-title menu-index)) + (item (nth item-index (screen-menu-items menu-index))) + (label (item-label item))) + (setf *open-menu-index* nil + *hover-item-index* nil + *status-message* (format nil "Selected ~a / ~a" menu-title label)) + (format t "screen menu action: ~a~%" *status-message*) + (if (and (zerop menu-index) + (string= label "Quit")) + :success + :continue))) + +(sdl3:def-app-init screen-menu-init (argc argv) + (declare (ignore argc argv)) + (sdl3:set-app-metadata "Example Screen Menu" "1.0" "com.example.menu.screen") + (when (not (sdl3:init :video)) + (format t "~a~%" (sdl3:get-error)) + (return-from screen-menu-init :failure)) + (multiple-value-bind (rst window renderer) + (sdl3:create-window-and-renderer + "Screen Menu — File / Edit / Help" 720 360 0) + (if (not rst) + (progn + (format t "~a~%" (sdl3:get-error)) + (return-from screen-menu-init :failure)) + (setf *window-screen-menu* window + *renderer-screen-menu* renderer + *open-menu-index* nil + *hover-menu-index* nil + *hover-item-index* nil + *status-message* "Click File, Edit or Help."))) + :continue) + +(sdl3:def-app-iterate screen-menu-iterate () + (sdl3:set-render-draw-color *renderer-screen-menu* 244 239 228 255) + (sdl3:render-clear *renderer-screen-menu*) + + (fill-menu-rect *renderer-screen-menu* 0 0 720 +menu-bar-height+ 214 205 190) + (stroke-menu-rect *renderer-screen-menu* 0 0 720 +menu-bar-height+ 120 105 88) + + (loop for index from 0 below (screen-menu-count) + for left = (title-left index) + do (when (or (eql index *open-menu-index*) + (eql index *hover-menu-index*)) + (fill-menu-rect *renderer-screen-menu* + left 0 +menu-title-width+ +menu-bar-height+ + 166 201 186)) + (stroke-menu-rect *renderer-screen-menu* + left 0 +menu-title-width+ +menu-bar-height+ + 120 105 88) + (sdl3:set-render-draw-color *renderer-screen-menu* 35 35 35 255) + (sdl3:render-debug-text *renderer-screen-menu* + (+ left 16.0) + 10.0 + (screen-menu-title index))) + + (sdl3:set-render-draw-color *renderer-screen-menu* 52 52 52 255) + (sdl3:render-debug-text *renderer-screen-menu* 24.0 72.0 "On-screen menu demo") + (sdl3:render-debug-text *renderer-screen-menu* 24.0 96.0 "File / Edit / Help are rendered by SDL.") + (sdl3:render-debug-text *renderer-screen-menu* 24.0 120.0 "Click a title to open its menu. Hotkeys: Ctrl+O/S/Q, Ctrl+C/V/A, F1.") + (sdl3:render-debug-text *renderer-screen-menu* 24.0 168.0 *status-message*) + + (when *open-menu-index* + (let* ((left (title-left *open-menu-index*)) + (items (screen-menu-items *open-menu-index*)) + (panel-height (* (length items) +submenu-item-height+))) + (fill-menu-rect *renderer-screen-menu* + left +menu-bar-height+ +submenu-width+ panel-height + 255 252 245) + (stroke-menu-rect *renderer-screen-menu* + left +menu-bar-height+ +submenu-width+ panel-height + 120 105 88) + (loop for item in items + for item-index from 0 + for top = (+ +menu-bar-height+ (* item-index +submenu-item-height+)) + for label = (item-label item) + for hint = (item-hotkey-str item) + do (when (eql item-index *hover-item-index*) + (fill-menu-rect *renderer-screen-menu* + left top +submenu-width+ +submenu-item-height+ + 186 221 198)) + (sdl3:set-render-draw-color *renderer-screen-menu* 35 35 35 255) + (sdl3:render-debug-text *renderer-screen-menu* + (+ left 12.0) + (+ top 8.0) + label) + (when (and hint (plusp (length hint))) + (sdl3:set-render-draw-color *renderer-screen-menu* 100 100 100 255) + (sdl3:render-debug-text *renderer-screen-menu* + (float (- (+ left +submenu-width+) 4 + (* 8 (length hint))) 1.0) + (+ top 8.0) + hint))))) + (sdl3:render-present *renderer-screen-menu*) + :continue) + +(sdl3:def-app-event screen-menu-event (type event) + (declare (ignore type)) + (let ((ev (sdl3:event-unmarshal event))) + (typecase ev + (sdl3:quit-event :success) + (sdl3:mouse-motion-event + (let* ((mouse-x (round (slot-value ev 'sdl3:%x))) + (mouse-y (round (slot-value ev 'sdl3:%y))) + (menu-index (menu-index-at mouse-x mouse-y)) + (item-index (submenu-item-at mouse-x mouse-y))) + (setf *hover-menu-index* menu-index + *hover-item-index* item-index) + (when (and *open-menu-index* menu-index) + (setf *open-menu-index* menu-index + *hover-item-index* nil))) + :continue) + (sdl3:mouse-button-event + (if (and (slot-value ev 'sdl3:%down) + (= (slot-value ev 'sdl3:%button) +mouse-button-left+)) + (let* ((mouse-x (round (slot-value ev 'sdl3:%x))) + (mouse-y (round (slot-value ev 'sdl3:%y))) + (menu-index (menu-index-at mouse-x mouse-y)) + (item-index (submenu-item-at mouse-x mouse-y))) + (cond (menu-index + (setf *open-menu-index* (if (eql *open-menu-index* menu-index) + nil + menu-index) + *hover-menu-index* menu-index + *hover-item-index* nil + *status-message* (format nil "Opened ~a menu" (screen-menu-title menu-index))) + :continue) + (item-index + (activate-screen-menu-item *open-menu-index* item-index)) + (t + (setf *open-menu-index* nil + *hover-item-index* nil) + :continue))) + :continue)) + (sdl3:keyboard-event + (when (and (slot-value ev 'sdl3:%down) + (not (slot-value ev 'sdl3:%repeat))) + (cond + ((eq (slot-value ev 'sdl3:%key) :escape) + (if *open-menu-index* + (setf *open-menu-index* nil + *hover-item-index* nil + *status-message* "Menu closed.") + (return-from screen-menu-event :success))) + (t + (loop for menu-index from 0 below (screen-menu-count) + do (loop for item-index from 0 + for item in (screen-menu-items menu-index) + when (item-hotkey-matches-p item ev) + do (return-from screen-menu-event + (activate-screen-menu-item menu-index item-index))))))) + :continue) + (t :continue)))) + +(sdl3:def-app-quit screen-menu-quit (result) + (declare (ignore result)) + (sdl3:destroy-renderer *renderer-screen-menu*) + (sdl3:destroy-window *window-screen-menu*) + (sdl3:pump-events) + (sdl3:quit-sub-system :video) + (sdl3:quit)) + +(defun do-screen-menu-demo () + (sdl3:enter-app-main-callbacks + 'screen-menu-init + 'screen-menu-iterate + 'screen-menu-event + 'screen-menu-quit)) \ No newline at end of file diff --git a/test/menu/03advanced/action-handlers.lisp b/test/menu/03advanced/action-handlers.lisp new file mode 100644 index 0000000..9eb3588 --- /dev/null +++ b/test/menu/03advanced/action-handlers.lisp @@ -0,0 +1,46 @@ +(in-package :sdl3.demo.menu) + +(declaim (special *status-message-classes*)) + +(defun handle-action-quit (action label) + (declare (ignore action label)) + :success) + +(defun handle-action-open (action label) + (declare (ignore action)) + (setf *status-message-classes* (format nil "Opening '~a'..." label)) + :continue) + +(defun handle-action-preferences (action label) + (declare (ignore action)) + (setf *status-message-classes* (format nil "Preferences requested: ~a" label)) + :continue) + +(defparameter *action-handlers* (make-hash-table :test #'eq)) + +(defun register-action-handler (action handler) + (setf (gethash action *action-handlers*) handler)) + +(defun unregister-action-handler (action) + (remhash action *action-handlers*)) + +(defun initialize-default-action-handlers () + (clrhash *action-handlers*) + (register-action-handler :quit #'handle-action-quit) + (register-action-handler :open #'handle-action-open) + (register-action-handler :preferences #'handle-action-preferences) + *action-handlers*) + +(defun reset-action-handlers () + (initialize-default-action-handlers)) + +(initialize-default-action-handlers) + +(defun execute-command-action (action label) + (let ((handler (and (symbolp action) + (gethash action *action-handlers*)))) + (setf *status-message-classes* (format nil "Action: ~a" label)) + (format t "screen menu class action: ~a (~a)~%" label action) + (if handler + (funcall handler action label) + :continue))) diff --git a/test/menu/03advanced/menu-controller.lisp b/test/menu/03advanced/menu-controller.lisp new file mode 100644 index 0000000..d435fa1 --- /dev/null +++ b/test/menu/03advanced/menu-controller.lisp @@ -0,0 +1,127 @@ +(in-package :sdl3.gui.menu.controller) + +(defun process-dropdown-click (bar x y) + (let* ((menu (nth (sdl3.gui.menu.model:bar-open-menu-index bar) + (sdl3.gui.menu.model:bar-menus bar))) + (panel-left (sdl3.gui.menu.model:menu-left menu)) + (panel-top (+ (sdl3.gui.menu.model:bar-top bar) + (sdl3.gui.menu.model:bar-height bar))) + (item-index (sdl3.gui.menu.model:dropdown-item-index-at + menu panel-left panel-top x y))) + (if (null item-index) + (values :miss nil nil) + (let ((entry (nth item-index (sdl3.gui.menu.model:menu-entries menu)))) + (cond + ((typep entry 'sdl3.gui.menu.model:separator-entry) + (values :continue nil nil)) + ((typep entry 'sdl3.gui.menu.model:submenu-entry) + (setf (sdl3.gui.menu.model:bar-open-submenu-entry-index bar) item-index + (sdl3.gui.menu.model:bar-hover-sub-item-index bar) nil) + (values :continue nil nil)) + ((typep entry 'sdl3.gui.menu.model:command-entry) + (sdl3.gui.menu.model:close-menu bar) + (values :command + (sdl3.gui.menu.model:entry-action entry) + (sdl3.gui.menu.model:entry-label entry))) + (t + (values :continue nil nil))))))) + +(defun process-submenu-click (bar x y) + (let* ((open-index (sdl3.gui.menu.model:bar-open-menu-index bar)) + (menu (and open-index (nth open-index (sdl3.gui.menu.model:bar-menus bar)))) + (sub-entry-index (sdl3.gui.menu.model:bar-open-submenu-entry-index bar))) + (if (not (and menu sub-entry-index)) + (values :miss nil nil) + (let ((entry (nth sub-entry-index (sdl3.gui.menu.model:menu-entries menu)))) + (if (not (typep entry 'sdl3.gui.menu.model:submenu-entry)) + (values :miss nil nil) + (let* ((panel-left (sdl3.gui.menu.model:menu-left menu)) + (panel-top (+ (sdl3.gui.menu.model:bar-top bar) + (sdl3.gui.menu.model:bar-height bar)))) + (multiple-value-bind (sub-left sub-top) + (sdl3.gui.menu.model:submenu-panel-origin + menu panel-left panel-top sub-entry-index) + (let* ((sub-menu (sdl3.gui.menu.model:entry-submenu entry)) + (sub-index (sdl3.gui.menu.model:dropdown-item-index-at + sub-menu sub-left sub-top x y))) + (if (null sub-index) + (values :miss nil nil) + (let ((sub-entry (nth sub-index + (sdl3.gui.menu.model:menu-entries sub-menu)))) + (if (typep sub-entry 'sdl3.gui.menu.model:command-entry) + (progn + (sdl3.gui.menu.model:close-menu bar) + (values :command + (sdl3.gui.menu.model:entry-action sub-entry) + (sdl3.gui.menu.model:entry-label sub-entry))) + (values :continue nil nil)))))))))))) + +(defun handle-left-click (bar x y) + (let ((title-index (sdl3.gui.menu.model:title-menu-index-at bar x y)) + (open-index (sdl3.gui.menu.model:bar-open-menu-index bar))) + (cond + (title-index + (if (eql title-index open-index) + (sdl3.gui.menu.model:close-menu bar) + (sdl3.gui.menu.model:open-menu bar title-index)) + (values :continue nil nil)) + (open-index + (multiple-value-bind (kind action label) + (process-submenu-click bar x y) + (if (eq kind :miss) + (multiple-value-bind (kind2 action2 label2) + (process-dropdown-click bar x y) + (if (eq kind2 :miss) + (progn + (sdl3.gui.menu.model:close-menu bar) + (values :continue nil nil)) + (values kind2 action2 label2))) + (values kind action label)))) + (t + (values :continue nil nil))))) + +(defun handle-mouse-motion (bar x y) + (let ((title-index (sdl3.gui.menu.model:title-menu-index-at bar x y))) + (setf (sdl3.gui.menu.model:bar-hover-menu-index bar) title-index) + (when (and (sdl3.gui.menu.model:bar-open-menu-index bar) title-index) + (unless (eql title-index (sdl3.gui.menu.model:bar-open-menu-index bar)) + (sdl3.gui.menu.model:open-menu bar title-index))) + (let ((open-index (sdl3.gui.menu.model:bar-open-menu-index bar))) + (when open-index + (let* ((menu (nth open-index (sdl3.gui.menu.model:bar-menus bar))) + (panel-left (sdl3.gui.menu.model:menu-left menu)) + (panel-top (+ (sdl3.gui.menu.model:bar-top bar) + (sdl3.gui.menu.model:bar-height bar))) + (item-index (sdl3.gui.menu.model:dropdown-item-index-at + menu panel-left panel-top x y))) + (setf (sdl3.gui.menu.model:bar-hover-item-index bar) item-index) + (cond + ((and item-index + (typep (nth item-index (sdl3.gui.menu.model:menu-entries menu)) + 'sdl3.gui.menu.model:submenu-entry)) + (setf (sdl3.gui.menu.model:bar-open-submenu-entry-index bar) item-index) + (multiple-value-bind (sub-left sub-top) + (sdl3.gui.menu.model:submenu-panel-origin + menu panel-left panel-top item-index) + (let* ((sub-menu (sdl3.gui.menu.model:entry-submenu + (nth item-index (sdl3.gui.menu.model:menu-entries menu)))) + (sub-index (sdl3.gui.menu.model:dropdown-item-index-at + sub-menu sub-left sub-top x y))) + (setf (sdl3.gui.menu.model:bar-hover-sub-item-index bar) sub-index)))) + ((sdl3.gui.menu.model:bar-open-submenu-entry-index bar) + (multiple-value-bind (sub-left sub-top) + (sdl3.gui.menu.model:submenu-panel-origin + menu panel-left panel-top + (sdl3.gui.menu.model:bar-open-submenu-entry-index bar)) + (let* ((sub-menu (sdl3.gui.menu.model:entry-submenu + (nth (sdl3.gui.menu.model:bar-open-submenu-entry-index bar) + (sdl3.gui.menu.model:menu-entries menu)))) + (sub-index (sdl3.gui.menu.model:dropdown-item-index-at + sub-menu sub-left sub-top x y))) + (setf (sdl3.gui.menu.model:bar-hover-sub-item-index bar) sub-index) + (unless sub-index + (setf (sdl3.gui.menu.model:bar-open-submenu-entry-index bar) nil + (sdl3.gui.menu.model:bar-hover-sub-item-index bar) nil))))) + (t + (setf (sdl3.gui.menu.model:bar-open-submenu-entry-index bar) nil + (sdl3.gui.menu.model:bar-hover-sub-item-index bar) nil)))))))) diff --git a/test/menu/03advanced/menu-model.lisp b/test/menu/03advanced/menu-model.lisp new file mode 100644 index 0000000..c98b6d6 --- /dev/null +++ b/test/menu/03advanced/menu-model.lisp @@ -0,0 +1,162 @@ +(in-package :sdl3.gui.menu.model) + +;;;; Layout constants + +(defconstant +font-char-width+ 8) +(defconstant +font-text-height+ 8) +(defconstant +menu-bar-height+ 30) +(defconstant +menu-title-pad-x+ 16) +(defconstant +menu-title-gap+ 4) +(defconstant +menu-item-pad-x+ 12) +(defconstant +menu-item-pad-y+ 8) +(defconstant +menu-item-gap-label-hotkey+ 26) +(defconstant +submenu-arrow-width+ 12) +(defconstant +separator-height+ 12) +(defconstant +submenu-min-width+ 140) + +;;;; Entry types + +(defclass menu-entry () + ((label :initarg :label :initform "" :accessor entry-label))) + +(defclass command-entry (menu-entry) + ((hotkey :initarg :hotkey :initform "" :accessor entry-hotkey) + (action :initarg :action :initform :none :accessor entry-action))) + +(defclass separator-entry (menu-entry) ()) + +(defclass submenu-entry (menu-entry) + ((submenu :initarg :submenu :accessor entry-submenu))) + +;;;; Menu structures + +(defclass dropdown-menu () + ((title :initarg :title :accessor menu-title) + (entries :initarg :entries :accessor menu-entries) + (left :initform 0 :accessor menu-left) + (top :initform 0 :accessor menu-top) + (title-width :initform 0 :accessor menu-title-width) + (panel-width :initform 0 :accessor menu-panel-width) + (panel-height :initform 0 :accessor menu-panel-height))) + +(defclass menu-bar () + ((menus :initarg :menus :accessor bar-menus) + (left :initarg :left :initform 0 :accessor bar-left) + (top :initarg :top :initform 0 :accessor bar-top) + (width :initarg :width :initform 760 :accessor bar-width) + (height :initarg :height :initform +menu-bar-height+ :accessor bar-height) + (open-menu-index :initform nil :accessor bar-open-menu-index) + (hover-menu-index :initform nil :accessor bar-hover-menu-index) + (hover-item-index :initform nil :accessor bar-hover-item-index) + (open-submenu-entry-index :initform nil :accessor bar-open-submenu-entry-index) + (hover-sub-item-index :initform nil :accessor bar-hover-sub-item-index))) + +;;;; Geometry helpers + +(defun text-width (text) + (* (length text) +font-char-width+)) + +(defun entry-row-height (entry) + (if (typep entry 'separator-entry) + +separator-height+ + (+ (* 2 +menu-item-pad-y+) +font-text-height+))) + +(defun command-entry-content-width (entry) + (let* ((label-w (text-width (entry-label entry))) + (hotkey (entry-hotkey entry)) + (hotkey-w (if (plusp (length hotkey)) (text-width hotkey) 0)) + (gap-w (if (> hotkey-w 0) +menu-item-gap-label-hotkey+ 0))) + (+ label-w gap-w hotkey-w))) + +(defun submenu-entry-content-width (entry) + (+ (text-width (entry-label entry)) +submenu-arrow-width+)) + +(defun entry-content-width (entry) + (cond ((typep entry 'command-entry) (command-entry-content-width entry)) + ((typep entry 'submenu-entry) (submenu-entry-content-width entry)) + ((typep entry 'separator-entry) 0) + (t (text-width (entry-label entry))))) + +(defun dropdown-panel-width (menu) + (let ((content-max 0)) + (dolist (entry (menu-entries menu)) + (setf content-max (max content-max (entry-content-width entry)))) + (max +submenu-min-width+ + (+ (* 2 +menu-item-pad-x+) content-max)))) + +(defun dropdown-panel-height (menu) + (let ((h 0)) + (dolist (entry (menu-entries menu)) + (incf h (entry-row-height entry))) + h)) + +;;;; Layout + +(defun layout-dropdown-recursive (menu) + (setf (menu-title-width menu) + (+ (* 2 +menu-title-pad-x+) (text-width (menu-title menu))) + (menu-panel-width menu) (dropdown-panel-width menu) + (menu-panel-height menu) (dropdown-panel-height menu)) + (dolist (entry (menu-entries menu)) + (when (typep entry 'submenu-entry) + (layout-dropdown-recursive (entry-submenu entry))))) + +(defun layout-menu-bar (bar) + (let ((cursor (bar-left bar))) + (loop for menu in (bar-menus bar) + do (layout-dropdown-recursive menu) + (setf (menu-left menu) cursor + (menu-top menu) (bar-top bar)) + (incf cursor (+ (menu-title-width menu) +menu-title-gap+))))) + +;;;; Hit testing + +(defun menu-rect-hit-p (x y left top width height) + (and (>= x left) (< x (+ left width)) + (>= y top) (< y (+ top height)))) + +(defun title-menu-index-at (bar x y) + (loop for menu in (bar-menus bar) + for index from 0 + when (menu-rect-hit-p x y + (menu-left menu) (menu-top menu) + (menu-title-width menu) (bar-height bar)) + do (return index) + finally (return nil))) + +(defun dropdown-item-index-at (menu panel-left panel-top x y) + (when (menu-rect-hit-p x y panel-left panel-top + (menu-panel-width menu) (menu-panel-height menu)) + (let ((cursor-y panel-top)) + (loop for entry in (menu-entries menu) + for index from 0 + for row-h = (entry-row-height entry) + do (when (menu-rect-hit-p x y panel-left cursor-y + (menu-panel-width menu) row-h) + (return-from dropdown-item-index-at index)) + (incf cursor-y row-h)) + nil))) + +(defun submenu-panel-origin (parent-menu parent-panel-left parent-panel-top parent-entry-index) + "Return (values sub-left sub-top) — top-left corner of the nested panel." + (let ((cursor-y parent-panel-top)) + (loop for entry in (menu-entries parent-menu) + for index from 0 + do (when (= index parent-entry-index) + (return (values (+ parent-panel-left (menu-panel-width parent-menu) -1) + cursor-y))) + (incf cursor-y (entry-row-height entry))))) + +;;;; Open / close state + +(defun open-menu (bar menu-index) + (setf (bar-open-menu-index bar) menu-index + (bar-hover-item-index bar) nil + (bar-open-submenu-entry-index bar) nil + (bar-hover-sub-item-index bar) nil)) + +(defun close-menu (bar) + (setf (bar-open-menu-index bar) nil + (bar-hover-item-index bar) nil + (bar-open-submenu-entry-index bar) nil + (bar-hover-sub-item-index bar) nil)) diff --git a/test/menu/03advanced/package.lisp b/test/menu/03advanced/package.lisp new file mode 100644 index 0000000..ed283c8 --- /dev/null +++ b/test/menu/03advanced/package.lisp @@ -0,0 +1,73 @@ +(defpackage :sdl3.gui.menu.model + (:nicknames :gui.menu.model + :sdl3.gui.menu + :gui.menu) + (:use #:cl) + (:export + ;; classes + #:menu-entry + #:command-entry + #:separator-entry + #:submenu-entry + #:dropdown-menu + #:menu-bar + ;; entry accessors + #:entry-label + #:entry-hotkey + #:entry-action + #:entry-submenu + ;; dropdown-menu accessors + #:menu-title + #:menu-entries + #:menu-left + #:menu-top + #:menu-title-width + #:menu-panel-width + #:menu-panel-height + ;; menu-bar accessors + #:bar-menus + #:bar-left + #:bar-top + #:bar-width + #:bar-height + #:bar-open-menu-index + #:bar-hover-menu-index + #:bar-hover-item-index + #:bar-open-submenu-entry-index + #:bar-hover-sub-item-index + ;; constants + #:+font-char-width+ + #:+font-text-height+ + #:+menu-bar-height+ + #:+menu-title-pad-x+ + #:+menu-title-gap+ + #:+menu-item-pad-x+ + #:+menu-item-pad-y+ + #:+menu-item-gap-label-hotkey+ + #:+submenu-arrow-width+ + #:+separator-height+ + #:+submenu-min-width+ + ;; functions + #:text-width + #:entry-row-height + #:entry-content-width + #:layout-menu-bar + #:title-menu-index-at + #:dropdown-item-index-at + #:submenu-panel-origin + #:open-menu + #:close-menu)) + +(defpackage :sdl3.gui.menu.controller + (:nicknames :gui.menu.controller) + (:use #:cl) + (:export + #:handle-left-click + #:handle-mouse-motion)) + +(defpackage :sdl3.gui.menu.sdl3-renderer + (:nicknames :gui.menu.sdl3-renderer) + (:use #:cl) + (:export + #:render-debug-text + #:draw-menu-bar)) diff --git a/test/menu/03advanced/screen-menu-classes.lisp b/test/menu/03advanced/screen-menu-classes.lisp new file mode 100644 index 0000000..23c957d --- /dev/null +++ b/test/menu/03advanced/screen-menu-classes.lisp @@ -0,0 +1,171 @@ + + +(in-package :sdl3.demo.menu) + +(defparameter *window-screen-menu-classes* nil) +(defparameter *renderer-screen-menu-classes* nil) +(defparameter *menu-bar-demo* nil) +(defparameter *status-message-classes* + "Class-based menu demo. Click File/Edit/Help.") + +(defconstant +mouse-button-left+ 1) + +(defun make-demo-menu-bar () + (let* ((recent-submenu + (make-instance 'sdl3.gui.menu.model:dropdown-menu + :title "Recent" + :entries (list + (make-instance 'sdl3.gui.menu.model:command-entry + :label "alpha.txt" :hotkey "" + :action :recent-alpha) + (make-instance 'sdl3.gui.menu.model:command-entry + :label "beta.txt" :hotkey "" + :action :recent-beta) + (make-instance 'sdl3.gui.menu.model:separator-entry) + (make-instance 'sdl3.gui.menu.model:command-entry + :label "Clear Recent" :hotkey "" + :action :clear-recent)))) + (file-menu + (make-instance 'sdl3.gui.menu.model:dropdown-menu + :title "File" + :entries (list + (make-instance 'sdl3.gui.menu.model:command-entry + :label "New" :hotkey "Ctrl+N" + :action :new) + (make-instance 'sdl3.gui.menu.model:command-entry + :label "Open" :hotkey "Ctrl+O" + :action :open) + (make-instance 'sdl3.gui.menu.model:separator-entry) + (make-instance 'sdl3.gui.menu.model:submenu-entry + :label "Recent" + :submenu recent-submenu) + (make-instance 'sdl3.gui.menu.model:separator-entry) + (make-instance 'sdl3.gui.menu.model:command-entry + :label "Quit" :hotkey "Ctrl+Q" + :action :quit)))) + (edit-menu + (make-instance 'sdl3.gui.menu.model:dropdown-menu + :title "Edit" + :entries (list + (make-instance 'sdl3.gui.menu.model:command-entry + :label "Undo" :hotkey "Ctrl+Z" + :action :undo) + (make-instance 'sdl3.gui.menu.model:command-entry + :label "Redo" :hotkey "Ctrl+Y" + :action :redo) + (make-instance 'sdl3.gui.menu.model:separator-entry) + (make-instance 'sdl3.gui.menu.model:command-entry + :label "Preferences" :hotkey "" + :action :preferences)))) + (help-menu + (make-instance 'sdl3.gui.menu.model:dropdown-menu + :title "Help" + :entries (list + (make-instance 'sdl3.gui.menu.model:command-entry + :label "Documentation" :hotkey "F1" + :action :docs) + (make-instance 'sdl3.gui.menu.model:separator-entry) + (make-instance 'sdl3.gui.menu.model:command-entry + :label "About" :hotkey "" + :action :about)))) + (bar (make-instance 'sdl3.gui.menu.model:menu-bar + :menus (list file-menu edit-menu help-menu) + :left 0 :top 0 :width 760 + :height sdl3.gui.menu.model:+menu-bar-height+))) + (sdl3.gui.menu.model:layout-menu-bar bar) + bar)) + +(sdl3:def-app-init screen-menu-classes-init (argc argv) + (declare (ignore argc argv)) + (sdl3:set-app-metadata "Example Screen Menu Classes" "1.0" + "com.example.menu.screen.classes") + (when (not (sdl3:init :video)) + (format t "~a~%" (sdl3:get-error)) + (return-from screen-menu-classes-init :failure)) + (multiple-value-bind (ok window renderer) + (sdl3:create-window-and-renderer "Screen Menu - Class Based" 760 520 0) + (if (not ok) + (progn + (format t "~a~%" (sdl3:get-error)) + (return-from screen-menu-classes-init :failure)) + (setf *window-screen-menu-classes* window + *renderer-screen-menu-classes* renderer + *menu-bar-demo* (make-demo-menu-bar) + *status-message-classes* + "Class-based menu demo. Click File/Edit/Help."))) + :continue) + +(sdl3:def-app-iterate screen-menu-classes-iterate () + (sdl3:set-render-draw-color *renderer-screen-menu-classes* 244 239 228 255) + (sdl3:render-clear *renderer-screen-menu-classes*) + + (sdl3:set-render-draw-color *renderer-screen-menu-classes* 52 52 52 255) + (sdl3.gui.menu.sdl3-renderer:render-debug-text + *renderer-screen-menu-classes* + 24.0 82.0 + "Third menu demo: class model with command/separator/submenu.") + (sdl3.gui.menu.sdl3-renderer:render-debug-text + *renderer-screen-menu-classes* + 24.0 106.0 + "Dropdown widths are computed from visible text + paddings.") + (sdl3.gui.menu.sdl3-renderer:render-debug-text + *renderer-screen-menu-classes* + 24.0 150.0 + *status-message-classes*) + + (sdl3.gui.menu.sdl3-renderer:draw-menu-bar + *renderer-screen-menu-classes* + *menu-bar-demo*) + + (sdl3:render-present *renderer-screen-menu-classes*) + :continue) + +(sdl3:def-app-event screen-menu-classes-event (type event) + (declare (ignore type)) + (let ((ev (sdl3:event-unmarshal event))) + (typecase ev + (sdl3:quit-event + :success) + (sdl3:mouse-motion-event + (sdl3.gui.menu.controller:handle-mouse-motion + *menu-bar-demo* + (round (slot-value ev 'sdl3:%x)) + (round (slot-value ev 'sdl3:%y))) + :continue) + (sdl3:mouse-button-event + (if (and (slot-value ev 'sdl3:%down) + (= (slot-value ev 'sdl3:%button) +mouse-button-left+)) + (multiple-value-bind (kind action label) + (sdl3.gui.menu.controller:handle-left-click + *menu-bar-demo* + (round (slot-value ev 'sdl3:%x)) + (round (slot-value ev 'sdl3:%y))) + (if (eq kind :command) + (execute-command-action action label) + :continue)) + :continue)) + (sdl3:keyboard-event + (when (and (slot-value ev 'sdl3:%down) + (not (slot-value ev 'sdl3:%repeat)) + (eq (slot-value ev 'sdl3:%key) :escape)) + (if (sdl3.gui.menu.model:bar-open-menu-index *menu-bar-demo*) + (sdl3.gui.menu.model:close-menu *menu-bar-demo*) + (return-from screen-menu-classes-event :success))) + :continue) + (t + :continue)))) + +(sdl3:def-app-quit screen-menu-classes-quit (result) + (declare (ignore result)) + (sdl3:destroy-renderer *renderer-screen-menu-classes*) + (sdl3:destroy-window *window-screen-menu-classes*) + (sdl3:pump-events) + (sdl3:quit-sub-system :video) + (sdl3:quit)) + +(defun do-screen-menu-classes-demo () + (sdl3:enter-app-main-callbacks + 'screen-menu-classes-init + 'screen-menu-classes-iterate + 'screen-menu-classes-event + 'screen-menu-classes-quit)) diff --git a/test/menu/03advanced/sdl3-renderer.lisp b/test/menu/03advanced/sdl3-renderer.lisp new file mode 100644 index 0000000..2f90c11 --- /dev/null +++ b/test/menu/03advanced/sdl3-renderer.lisp @@ -0,0 +1,132 @@ +(in-package :sdl3.gui.menu.sdl3-renderer) + +(defun make-menu-rect (x y w h) + (make-instance 'sdl3:rect + :%x (float x 1.0) :%y (float y 1.0) + :%w (float w 1.0) :%h (float h 1.0))) + +(defun fill-menu-rect (renderer x y w h red green blue) + (sdl3:set-render-draw-color renderer red green blue 255) + (sdl3:render-fill-rect renderer (make-menu-rect x y w h))) + +(defun stroke-menu-rect (renderer x y w h red green blue) + (sdl3:set-render-draw-color renderer red green blue 255) + (sdl3:render-rect renderer (make-menu-rect x y w h))) + +(defun line-menu (renderer x1 y1 x2 y2 red green blue) + (sdl3:set-render-draw-color renderer red green blue 255) + (sdl3:render-line renderer + (float x1 1.0) (float y1 1.0) + (float x2 1.0) (float y2 1.0))) + +(defun render-debug-text (renderer x y text) + (sdl3:render-debug-text renderer (float x 1.0) (float y 1.0) text)) + +(defun draw-dropdown-panel (renderer menu panel-left panel-top hover-index) + (fill-menu-rect renderer panel-left panel-top + (sdl3.gui.menu.model:menu-panel-width menu) + (sdl3.gui.menu.model:menu-panel-height menu) + 255 252 245) + (stroke-menu-rect renderer panel-left panel-top + (sdl3.gui.menu.model:menu-panel-width menu) + (sdl3.gui.menu.model:menu-panel-height menu) + 120 105 88) + (let ((cursor-y panel-top)) + (loop for entry in (sdl3.gui.menu.model:menu-entries menu) + for index from 0 + for row-h = (sdl3.gui.menu.model:entry-row-height entry) + do (progn + (cond + ((typep entry 'sdl3.gui.menu.model:separator-entry) + (line-menu renderer + (+ panel-left sdl3.gui.menu.model:+menu-item-pad-x+) + (+ cursor-y (floor row-h 2)) + (- (+ panel-left (sdl3.gui.menu.model:menu-panel-width menu)) + sdl3.gui.menu.model:+menu-item-pad-x+) + (+ cursor-y (floor row-h 2)) + 170 160 145)) + (t + (when (eql index hover-index) + (fill-menu-rect renderer + panel-left cursor-y + (sdl3.gui.menu.model:menu-panel-width menu) row-h + 186 221 198)) + (sdl3:set-render-draw-color renderer 35 35 35 255) + (render-debug-text renderer + (+ panel-left sdl3.gui.menu.model:+menu-item-pad-x+) + (+ cursor-y sdl3.gui.menu.model:+menu-item-pad-y+) + (sdl3.gui.menu.model:entry-label entry)) + (cond + ((typep entry 'sdl3.gui.menu.model:command-entry) + (let* ((hotkey (sdl3.gui.menu.model:entry-hotkey entry)) + (hotkey-w (sdl3.gui.menu.model:text-width hotkey)) + (hotkey-x (- (+ panel-left (sdl3.gui.menu.model:menu-panel-width menu)) + sdl3.gui.menu.model:+menu-item-pad-x+ + hotkey-w))) + (when (plusp (length hotkey)) + (sdl3:set-render-draw-color renderer 102 102 102 255) + (render-debug-text renderer + hotkey-x + (+ cursor-y sdl3.gui.menu.model:+menu-item-pad-y+) + hotkey)))) + ((typep entry 'sdl3.gui.menu.model:submenu-entry) + (sdl3:set-render-draw-color renderer 102 102 102 255) + (render-debug-text renderer + (- (+ panel-left + (sdl3.gui.menu.model:menu-panel-width menu)) + sdl3.gui.menu.model:+menu-item-pad-x+ + sdl3.gui.menu.model:+submenu-arrow-width+) + (+ cursor-y sdl3.gui.menu.model:+menu-item-pad-y+) + ">"))))) + (incf cursor-y row-h))))) + +(defun draw-menu-bar (renderer bar) + (fill-menu-rect renderer + (sdl3.gui.menu.model:bar-left bar) (sdl3.gui.menu.model:bar-top bar) + (sdl3.gui.menu.model:bar-width bar) (sdl3.gui.menu.model:bar-height bar) + 214 205 190) + (stroke-menu-rect renderer + (sdl3.gui.menu.model:bar-left bar) (sdl3.gui.menu.model:bar-top bar) + (sdl3.gui.menu.model:bar-width bar) (sdl3.gui.menu.model:bar-height bar) + 120 105 88) + (loop for menu in (sdl3.gui.menu.model:bar-menus bar) + for index from 0 + do (when (or (eql index (sdl3.gui.menu.model:bar-open-menu-index bar)) + (eql index (sdl3.gui.menu.model:bar-hover-menu-index bar))) + (fill-menu-rect renderer + (sdl3.gui.menu.model:menu-left menu) + (sdl3.gui.menu.model:menu-top menu) + (sdl3.gui.menu.model:menu-title-width menu) + (sdl3.gui.menu.model:bar-height bar) + 166 201 186)) + (stroke-menu-rect renderer + (sdl3.gui.menu.model:menu-left menu) + (sdl3.gui.menu.model:menu-top menu) + (sdl3.gui.menu.model:menu-title-width menu) + (sdl3.gui.menu.model:bar-height bar) + 120 105 88) + (sdl3:set-render-draw-color renderer 35 35 35 255) + (render-debug-text renderer + (+ (sdl3.gui.menu.model:menu-left menu) + sdl3.gui.menu.model:+menu-title-pad-x+) + (+ (sdl3.gui.menu.model:bar-top bar) 10) + (sdl3.gui.menu.model:menu-title menu))) + (let ((open-index (sdl3.gui.menu.model:bar-open-menu-index bar))) + (when open-index + (let* ((menu (nth open-index (sdl3.gui.menu.model:bar-menus bar))) + (panel-left (sdl3.gui.menu.model:menu-left menu)) + (panel-top (+ (sdl3.gui.menu.model:bar-top bar) + (sdl3.gui.menu.model:bar-height bar)))) + (draw-dropdown-panel renderer menu panel-left panel-top + (sdl3.gui.menu.model:bar-hover-item-index bar)) + (let ((sub-entry-index (sdl3.gui.menu.model:bar-open-submenu-entry-index bar))) + (when sub-entry-index + (let ((entry (nth sub-entry-index (sdl3.gui.menu.model:menu-entries menu)))) + (when (typep entry 'sdl3.gui.menu.model:submenu-entry) + (multiple-value-bind (sub-left sub-top) + (sdl3.gui.menu.model:submenu-panel-origin + menu panel-left panel-top sub-entry-index) + (draw-dropdown-panel renderer + (sdl3.gui.menu.model:entry-submenu entry) + sub-left sub-top + (sdl3.gui.menu.model:bar-hover-sub-item-index bar))))))))))) diff --git a/test/menu/package.lisp b/test/menu/package.lisp new file mode 100644 index 0000000..793a496 --- /dev/null +++ b/test/menu/package.lisp @@ -0,0 +1,11 @@ +(defpackage :sdl3.demo.menu + (:use #:cl) + (:export #:do-system-menu-demo + #:do-screen-menu-demo + #:do-screen-menu-classes-demo)) + +;;;; (in-package :sdl3.demo.menu) + +;;;; (sdl3.demo.menu:do-system-menu-demo) +;;;; (sdl3.demo.menu:do-screen-menu-demo) +;;;; (sdl3.demo.menu:do-screen-menu-classes-demo)