0f88cfcdcd44

Publish the menus post
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Tue, 10 Jan 2017 16:22:03 +0000
parents 21f5607dfbad
children 71f98b4ce464
branches/tags (none)
files content/blog/2017/01/chip8-menus.markdown

Changes

--- 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)