# HG changeset patch # User Steve Losh # Date 1484065323 0 # Node ID 0f88cfcdcd443d10fe5986ad2eda4225146a60f7 # Parent 21f5607dfbad3f6113c068722b1021c9b6cc788f Publish the menus post diff -r 21f5607dfbad -r 0f88cfcdcd44 content/blog/2017/01/chip8-menus.markdown --- a/content/blog/2017/01/chip8-menus.markdown Thu Jan 05 19:46:43 2017 +0000 +++ b/content/blog/2017/01/chip8-menus.markdown Tue Jan 10 16:22:03 2017 +0000 @@ -1,8 +1,8 @@ +++ title = "CHIP-8 in Common Lisp: Menus" snip = "Let's add some polish." -date = 2017-01-07T16:40:00Z -draft = true +date = 2017-01-10T16:20:00Z +draft = false +++ @@ -10,11 +10,11 @@ games, and we've got a rudimentary debugging system in place so we can figure out where things go wrong. -Up to now we've been communicating with a running emulator mostly through NREPL -or SLIME. This is fine for development, but in this post we'll add some +Up to now we've been communicating with the running emulator mostly through +NREPL or SLIME. This is fine for development, but in this post we'll add some much-needed polish in the form of menus. This is the kind of boring work that -often gets left until the end during game development, so let's just get it over -and done with and out of the way now. +often gets left until the end during game development, so let's just get it out +of the way. The full series of posts so far: @@ -24,6 +24,7 @@ 4. [CHIP-8 in Common Lisp: Sound](http://stevelosh.com/blog/2016/12/chip8-sound/) 5. [CHIP-8 in Common Lisp: Disassembly](http://stevelosh.com/blog/2017/01/chip8-disassembly/) 6. [CHIP-8 in Common Lisp: Debugging Infrastructure](http://stevelosh.com/blog/2017/01/chip8-debugging-infrastructure/) +7. [CHIP-8 in Common Lisp: Menus](http://stevelosh.com/blog/2017/01/chip8-menus/) The full emulator source is on [BitBucket][] and [GitHub][]. @@ -37,12 +38,13 @@ Qtools has some [rudimentary support][qtools-menus] for menus. Unfortunately it won't quite work with our emulator as-is, so we'll need to shuffle things around -a bit. When we added the screen to the emulator back in the Graphics post, we -just created a subclass of `QGLWidget` and passed that along to +a bit. When we added the screen to the emulator back in the [graphics][] post +we just created a subclass of `QGLWidget` and passed it along to `with-main-window`. This works for displaying the screen, but if you try to `define-menu` on this widget Qtools will signal an error. [qtools-menus]: https://shinmera.github.io/qtools/#QTOOLS:DEFINE-MENU +[graphics]: http://stevelosh.com/blog/2016/12/chip8-graphics/ What we need to do is create a `QMainWindow` widget instead, and put our `QGLWidget` inside of that. Then we can add some menus to the main window and @@ -82,8 +84,8 @@ (setf (screen-chip screen) chip)) ``` -This ensures that when the main window gets closed the screen widget will get -cleaned up properly. +This ensures the screen widget will get cleaned up properly when the main window +is closed. Now we need to move some of the initialization code that used to be in the screen widget up into the main window, and also add a bit more to connect @@ -96,22 +98,22 @@ (q+:focus-proxy main-window) screen)) ``` -The `window-title` needs to go on the top-level widget of course, so we can pull -that out of the screen's initializer. We also set the screen to be the "central -widget". You can read the [Qt docs][central widget] for the full story, but -essentially a `QMainWindow` is just a container for other widgets and we need to -designate one as the primary widget. +The `window-title` needs to go on the top-level widget, so we can pull that out +of the screen's initializer. We also set the screen to be the "central widget" +of the main window. You can read the [Qt docs][central widget] for the full +story, but essentially a `QMainWindow` is just a container for other widgets and +we need to designate one as the primary widget. We'll also want to set the [focus proxy][] of the main window to be the screen, -because we want the screen to handle all keyboard input just like before. -Setting the focus proxy tells Qt that whenever the main window gets focused it -should actually go ahead and focus the screen instead. If we didn't do this, -then if the main window itself got focused (which can happen when tabbing -through applications, clicking the title bar, etc) it wouldn't propagate the -keystrokes down to the screen. +because we want the screen to handle keyboard input just like before. Setting +the focus proxy tells Qt that whenever the main window gets focused it should +actually focus the screen instead. If we didn't do this, then if the main +window itself got focused (which can happen when tabbing through applications, +clicking the title bar, etc) it wouldn't propagate the keystrokes down to the +screen. -This is all kind of fiddly stuff, but it's the fit and finish that separates -a toy project from something that actually feels [finished][]. +This is all kind of fiddly stuff, but it's the polish that separates a toy +project from something that actually feels [finished][]. [focus proxy]: https://doc.qt.io/qt-4.8/qwidget.html#setFocusProxy [central widget]: https://doc.qt.io/qt-4.8/qmainwindow.html @@ -137,10 +139,14 @@ (defun run-gui (chip thunk) (with-main-window - (window (make-main-window chip)) ; NEW + (window (setf *main-window* (make-main-window chip))) ; NEW (funcall thunk))) ``` +I usually try not to use the return value of a `setf` form because I think it's +kind of ugly, but it saved an entire `let` here so decided to I break my own +style rule. + ## Updating the Screen The only thing we need to change for the `screen` is its initializer: @@ -168,7 +174,7 @@ ### The File Menu -Let's start with a simple File menu that will just have two items: +Let's start with a simple `File` menu that will just have two items: * Load a ROM * Quit the emulator @@ -177,10 +183,8 @@ ```lisp (define-menu (main-window File) - (:item ("Load ROM..." (ctrl o)) - (load-rom main-window)) - (:item ("Quit" (ctrl q)) - (die))) + (:item ("Load ROM..." (ctrl o)) (load-rom main-window)) + (:item ("Quit" (ctrl q)) (die))) ``` Note that we use the Windows-centric shortcut key names. Qt will handle @@ -203,12 +207,16 @@ [1f]: https://groups.google.com/forum/message/raw?msg=comp.lang.lisp/9SKZ5YJUmBg/Fj05OZQomzIJ ```lisp +(defparameter *default-directory* + (uiop:native-namestring + (asdf:system-source-directory :cl-chip8))) + (defun get-rom-path (window) (let ((path (q+:qfiledialog-get-open-file-name - window - "Load ROM" - (uiop:native-namestring (asdf:system-source-directory :cl-chip8)) - "ROM Files (*.rom);;All Files (*)"))) + window ; parent widget + "Load ROM" ; dialog title + *default-directory* ; starting directory + "ROM Files (*.rom);;All Files (*)"))) ; filters (if (string= path "") nil path))) @@ -226,7 +234,7 @@ The filter string is actually parsed by Qt, and by default will prevent the user from selecting any file that doesn't end in `.rom`. You might want to add a few more options extensions here if you think people will have named their ROMs -differently. We also add a second filter that will let the user select any +differently. We also add a second filter that will let the user select *any* file, in case they have a ROM with a filename we haven't anticipated. The result looks like this: @@ -236,16 +244,16 @@ empty string. We'll check for that and return a more Lispy `nil` from the function. -That's it for the file menu. It's basic but trust me: it's a lot nicer to load -ROMs through a normal dialog than to have to poke at the `chip` in the REPL. +That's it for the `File` menu. It's basic, but it's a lot nicer to load ROMs +through a normal dialog than to have to poke at the `chip` in the REPL. ### The Display Menu -Back in the Graphics post we saw how some ROMs expect the CHIP-8 to wrap sprites -around the screen when their coordinates get too large, and other ROMs require -that they *not* wrap. There's no good way to automatically detect this (aside -from hashing particular ROMs and hard-coding the setting for those) so we'll -expose this as an option to the user in a Display menu. +Back in the [graphics][] post we saw how some ROMs expect the CHIP-8 to wrap +sprites around the screen when their coordinates get too large, and other ROMs +require that they *not* wrap. There's no good way to automatically detect this +(aside from hashing particular ROMs and hard-coding the setting for those) so +we'll expose this as an option to the user in a `Display` menu. The menu itself is simple — we'll make a submenu that will contain the options and a helper function to actually do the work: @@ -282,8 +290,11 @@ ### The Sound Menu Our final menu will allow the user to select what kind of sound the buzzer -should play, because it would be a shame to let all our work in the Sound post -go to waste. We'll do it just like the Display menu: +should play, because it would be a shame to let all our work in the [sound][] +post go to waste. We'll implement the `Sound` menu just like the `Display` +menu: + +[sound]: http://stevelosh.com/blog/2016/12/chip8-sound/ ```lisp (define-menu (main-window Sound)