Commit Graph

247 Commits

Author SHA1 Message Date
b4a366baa9 Introduce Drawing Tools Concept, Pencil and Actor
The uix.Canvas widget now maintains a selected Tool which configures how
the mouse interacts with the (editable) Canvas widget.

The default Tool is the PencilTool and implements the old behavior: it
draws pixels when clicked and dragged based on your currently selected
Color Swatch. This tool automatically becomes active when you toggle the
Palette tab in the editor mode.

A new Tool is the ActorTool which becomes active when you select the
Doodads tab. In the ActorTool you can't draw pixels on the level, but
when you mouse over a Doodad instance (Actor) in your level, you may
pick it up and drag it someplace else.

Left-click an Actor to pick it up and drag it somewhere else.
Right-click to delete it completely.

You can also delete an Actor by dragging it OFF of the Canvas, like back
onto the palette drawer or onto the menu bar.
2018-10-20 17:08:20 -07:00
0044b72943 Drag Doodads Onto Levels in Edit Mode
Add the ability to drag and drop Doodads onto the level. The Doodad
buttons on the palette now trigger a Drag/Drop behavior when clicked,
and a "blueprint colored" version of the Doodad follows your cursor,
centered on it.

Actors are assigned a random UUID ID when they are placed into a level.

The Canvas gained a MaskColor property that forces all pixels in the
drawing to render as the same color. This is a visual-only effect, and
is used when dragging Doodads in so they render as "blueprints" instead
of their actual colors until they are dropped.

Fix the chunk bitmap cache system so it saves in the $XDG_CACHE_FOLDER
instead of /tmp and has better names. They go into
`~/.config/doodle/chunks/` and have UUID file names -- but they
disappear quickly! As soon as they are cached into SDL2 they are removed
from disk.

Other changes:

- UI: Add Hovering() method that returns the widgets that are beneath
      a point (your cursor) and those that are not, for easy querying
      for event propagation.
- UI: Add ability to return an ErrStopPropagation to tell the master
      Scene (outside the UI) not to continue sending events to other
      parts of the code, so that you don't draw pixels during a drag
      event.
2018-10-20 16:03:59 -07:00
20771fbe13 Draw Actors Embedded in Levels in Edit Mode
Add the JSON format for embedding Actors (Doodad instances) inside of a
Level. I made a test map that manually inserted a couple of actors.

Actors are given to the Canvas responsible for the Level via the
function `InstallActors()`. So it means you'll call LoadLevel and then
InstallActors to hook everything up.

The Canvas creates sub-Canvas widgets from each Actor.

After drawing the main level geometry from the Canvas.Chunker, it calls
the drawActors() function which does the same but for Actors.

Levels keep a global map of all Actors that exist. For any Actors that
are visible within the Viewport, their sub-Canvas widgets are presented
appropriately on top of the parent Canvas. In case their sub-Canvas
overlaps the parent's boundaries, their sub-Canvas is resized and moved
appropriately.

- Allow the MainWindow to be resized at run time, and the UI
  recalculates its sizing and position.
- Made the in-game Shell properties editable via environment variables.
  The kirsle.env file sets a blue and pink color scheme.
- Begin the ground work for Levels and Doodads to embed files inside
  their data via the level.FileSystem type.
- UI: Labels can now contain line break characters. It will
  appropriately render multiple lines of render.Text and take into
  account the proper BoxSize to contain them all.
- Add environment variable DOODLE_DEBUG_ALL=true that will turn on ALL
  debug overlay and visualization options.
- Add debug overlay to "tag" each Canvas widget with some of its
  details, like its Name and World Position. Can be enabled with the
  environment variable DEBUG_CANVAS_LABEL=true
- Improved the FPS debug overlay to show in labeled columns and multiple
  colors, with easy ability to add new data points to it.
2018-10-19 13:32:25 -07:00
1c5a0842e4 Tune Some Constants via Environment Variables
Some of the constants in the `balance` package can be set at startup
time via environment variables. With this, you can customize the color
and style of the developer shell, turn on debugging visuals to outline
Canvas widgets, and more.

The parser is at `balance/debug.go` and human readable descriptions
are in the `balance/README.md`
2018-10-19 09:55:41 -07:00
97394f6cdb WIP Finishing Up Texture Caching System
Apart from putting the cached bitmaps in a better place, this about
finishes up the texture caching optimization and IT IS FAST!

When I spam drag a lot of pixels around the FPS may drop to the 40's but
once the caches are warmed up the FPS returns to 60 and stays there,
even if the screen is very busy with pixels.

An undocumented debug feature: set the environment variable
DEBUG_CHUNK_COLOR='#00FFFF' to set a bitmap background color besides
white to be used when caching the chunks. It helps to visualize where on
the screen the bitmaps are being used. May go away in the future.

Changes:

- Found that the old default chunk size of 1000 was slow to generate
  bitmap images to cache. The 100px test size was fast and 128 sounds
  like a good middle ground number to pick for now.
- Fixed all the problems with scroll behavior and offset by inverting
  the sign of the scroll behavior. Scrolling to the Right and Down
  actually subtracts X,Y values instead of adds them.
2018-10-17 23:01:21 -07:00
279a980106 WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!

BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
  out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
  is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
  functions probably.

Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.

The texture caching workflow is briefly:

- The uix.Canvas widget's Present() function iterates over the list of
  Chunk Coordinates that are visible inside of the current viewport
  (i.e. viewable on screen)
- For each Chunk:
  - Make it render and/or return its cached Texture object.
  - Work out how much of the Chunk will be visible and how to crop the
    boxes for the Copy()
  - Copy the cached Texture instead of drawing all the pixels every
    time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
  - It calls Chunk.ToBitmap() to save a bitmap on disk.
  - It calls Engine.NewBitmap() to get a Texture it can hang onto.
  - It hangs onto the Texture and returns it on future calls.
  - Any call to Set() or Delete() a pixel will invalidate the cache
    (mark the Chunk "dirty") and Texture() will rebuild next call.

The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.

The interface `render.Engine` adds two new methods:

* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)

NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.

The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.

The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.

Other changes:

- Chunker now assigns each Chunk what their coordinate and size are, so
  that the chunk can self reference that information. This info is
  considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
  Coordinates that are visible in your viewport, rather than iterating
  over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
  to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
  caching. Currently these go to files in /tmp but will soon go into your
  $XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
  cached bitmap texture of the pixels it's responsible for. The texture
  is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
  was the first test for caching bitmap images for efficiency. Can show
  any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
  of string pointers to make it SUPER EASY to add/remove labels.
2018-10-17 20:52:44 -07:00
5bf7d554f7 Add doodad.exe binary and PNG to Drawing Converter
Adds the `doodad` binary which will be a command line tool to work with
Doodads and Levels and assist with development.

The `doodad` binary has subcommands like git and the first command is
`convert` which converts between image files (PNG or BMP) and Doodle
drawing files (Level or Doodad). You can "screenshot" a level into a PNG
or you can initialize a new drawing from a PNG.
2018-10-16 12:26:41 -07:00
b67c4b67b2 Add Initial "Doodad Palette" UX
* Add a tab bar to the top of the Palette window that has two
  radiobuttons for "Palette" and "Doodads"
* UI: add the concept of a Hidden() widget and the corresponding Hide()
  and Show() methods. Hidden widgets are skipped over when evaluating
  Frame packing, rendering, and event supervision.
* The Palette Window in editor mode now displays one of two tabs:
  * Palette: the old color swatch palette now lives here.
  * Doodads: the new Doodad palette.
* The Doodad Palette shows a grid of buttons (2 per row) showing the
  available Doodad drawings in the user's config folder.
* The Doodad buttons act as radiobuttons for now and have no other
  effect. TODO will be making them react to drag-drop events.
* UI: added a `Children()` method as the inverse of `Parent()` for
  container widgets (like Frame, Window and Button) to expose their
  children. The BaseWidget just returns an empty []Widget.
* Console: added a `repl` command that keeps the dev console open and
  prefixes every command with `$` filled out -- for rapid JavaScript
  console evaluation.
2018-10-08 13:06:42 -07:00
f18dcf9c2c Move Editor Canvas Into UI + UI Improvements
* Increase the default window size from 800x600 to 1024x768.
* Move the drawing canvas in EditorMode to inside the EditorUI where it can
  be better managed with the other widgets it shares the screen with.
* Slightly fix Frame packing bug (with East orientation) that was causing
  right-aligned statusbar items to be partially cropped off-screen. Moved a
  couple statusbar labels in EditorMode to the right.
* Add `Parent()` and `Adopt()` methods to widgets for when they're managed
  by containers like the Frame.
* Add utility functions to UI toolkit for computing a widget's Absolute
  Position and Absolute Rect, by crawling all parent widgets and summing
  them up.
* Add `lib/debugging` package with useful stack tracing utilities.
* Add `make guitest` to launch the program into the GUI Test.
  The command line flag is: `doodle -guitest`
* Console: add a `close` command which returns to the MainScene.
* Initialize the font cache directory (~/.cache/doodle/fonts) but don't
  extract the fonts there yet.
2018-10-08 10:38:49 -07:00
cfe26cb964 Add configdir and unify file loading/saving
* Create a configuration directory to store the user's local levels
  and doodads. On Linux this is at ~/.config/doodle
* Unify the file loading and saving functions: you can type into the
  console "edit example" and it will open `example.level` from your
  levels folder or else `example.doodad` from the doodads folder, in the
  appropriate mode.
* You can further specify the file extension: `edit example.doodad` and
  it will load it from the doodads folder only.
* Any slash characters in a file name are taken literally as a relative
  or absolute path.
* The UI Save/Load buttons now share the same code path as the console
  commands, so the `save` command always saves as a Doodad when the
  EditorScene is in Doodad Mode.
2018-10-02 10:11:38 -07:00
a7fd3aa1ca Doodad Edit Mode: Saving and Loading From Disk
Adds the first features to Edit Mode to support creation of Doodad
files! The "New Doodad" button pops up a prompt for a Doodad size
(default 100px) and configures the Canvas widget and makes a Doodad
struct instead of a Level to manage.

* Move the custom Canvas widget from `level.Canvas` to `uix.Canvas`
  (the uix package is for our custom UI widgets now)
* Rename the `doodads.Doodad` interface (for runtime instances of
  Doodads) to `doodads.Actor` and make `doodads.Doodad` describe the
  file format and JSON schema instead.
* Rename the `EditLevel()` method to `EditDrawing()` and it inspects the
  file extension to know whether to launch the Edit Mode for a Level or
  for a Doodad drawing.
* Doodads can be edited by using the `-edit` CLI flag or using the
  in-game file open features (including `edit` command of dev console).
* Add a `Scrollable` boolean to uix.Canvas to restrict the keyboard
  being able to scroll the level, for editing Doodads which have a fixed
  size.
2018-09-26 10:07:22 -07:00
e25869644c Fix Play Mode, Level Handover & Collision Detection
* Edit Mode now uses the Level object itself to keep the drawing data
  rather than pull its Palette and Chunks out, so it can hang on to more
  information. The Canvas widget is given references to the
  Level.Palette and Level.Chunker via Canvas.LoadLevel()
* Fix the handoff between Edit Mode and Play Mode. They pass the Level
  object back and forth and the Filename, because it's not part of the
  Level. You can save the map with its original settings after returning
  from Play Mode.
* Fix the collision detection in Play Mode. It broke previously when
  palettes were added because of the difference between a render.Point
  and a level.Pixel and it couldn't easily look up coordinates. The new
  Chunker system provides a render.Point lookup API.
* All pixels are solid for collision right now, TODO is to return Swatch
  information from the pixels touching the player character and react
  accordingly (non-solid, fire flag, etc.)
* Remove the level.Grid type as it has been replaced by the Chunker.
* Clean up some unused variables and functions.
2018-09-25 09:40:34 -07:00
3c185528f9 Implement Chunk System for Pixel Data
Starts the implementation of the chunk-based pixel storage system for
levels and drawings.

Previously the levels had a Pixels structure which was just an array of
X,Y and palette index triplets. The new chunk system divides the map up
into square chunks, and lets each chunk manage its own memory layout.

The "MapAccessor" layout is implemented first which is a map of X,Y
coordinates to their Swatches (pointer to an index of the palette). When
serialized the MapAccessor maps the "X,Y": "index" similarly to the old
Pixels array.

The object hierarchy for the chunk system is like:

* Chunker: the manager of the chunks who keeps track of the ChunkSize
  and a map of "chunk coordinates" to the chunk in charge of it.
  * Chunk: a part of the drawing ChunkSize length square. A chunk has a
    Type (of how it stores its data, 0 being a map[Point]Swatch and 1
    being a [][]Swatch 2D array), and the chunk has an Accessor which
    implements the underlying type.
    * Accessor: an interface for a Chunk to provide access to its
      pixels.
      * MapAccessor: a "sparse map" of coordinates to their Swatches.
      * GridAccessor: TBD, will be a "dense" 2D grid of Swatches.

The JSON files are loaded in two passes:

1. The chunks only load their swatch indexes from disk.
2. With the palette also loaded, the chunks are "inflated" and linked
   to their swatch pointers.

Misc changes:

* The `level.Canvas` UI widget switches from the old Grid data type to
  being able to directly use a `level.Chunker`
* The Chunker is a shared data type between the on-disk level format and
  the actual renderer (level.Canvas), so saving the level is easy
  because you can just pull the Chunker out from the canvas.
* ChunkSize is stored inside the level file and the default value is at
  balance/numbers.go: 1000
2018-09-23 15:42:05 -07:00
90a4859326 Check in updated docs before break 2018-08-22 10:01:02 -07:00
5434484b6e Abstract Drawing Canvas into Reusable Widget
The `level.Canvas` is a widget that holds onto its Palette and Grid and
has interactions to allow scrolling and editing the grid using the
swatches available on the palette.

Thus all of the logic in the Editor Mode for drawing directly onto the
root SDL surface are now handled inside a level.Canvas instance.

The `level.Canvas` widget has the following properties:
* Like any widget it has an X,Y position and a width/height.
* It has a Scroll position to control which slice of its drawing will be
  visible inside its bounding box.
* It supports levels having negative coordinates for their pixels. It
  doesn't care. The default Scroll position is (0,0) at the top left
  corner of the widget but you can scroll into the negatives and see the
  negative pixels.
* Keyboard keys will scroll the viewport inside the canvas.
* The canvas draws only the pixels that are visible inside its bounding
  box.

This feature will eventually pave the way toward:
* Doodads being dropped on top of your map, each Doodad being its own
  Canvas widget.
* Using drawings as button icons for the user interface, as the Canvas
  is a normal widget.
2018-08-16 20:37:19 -07:00
5956863996 Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons
  that work: New Level, New Doodad (same thing), Save, Save as, Open.
* Added ability for the dev console to prompt the user for a question,
  which opens the console automatically. "Save", "Save as" and "Load"
  ask for their filenames this way.
* Started groundwork for theming the app. The palette window is a light
  brown with an orange title bar, the Menu Toolbar has a black
  background, etc.
* Added support for multiple fonts instead of just monospace. DejaVu
  Sans (normal and bold) are used now for most labels and window titles,
  respectively. The dev console uses DejaVu Sans Mono as before.
* Update ui.Label to accept PadX and PadY separately instead of only
  having the Padding option which did both.
* Improvements to Frame packing algorithm.
* Set the SDL draw mode to BLEND so we can use alpha colors properly,
  so now the dev console is semi-translucent.
2018-08-11 17:30:00 -07:00
42caa20f6e Add ideas about level wallpapers 2018-08-11 14:08:00 -07:00
248fa10a5f Update notes and feature ideas 2018-08-10 19:37:01 -07:00
e1cbff8c3f Add Palette Window and Palette Support to Edit Mode
* Add ui.Window to easily create reusable windows with titles.
* Add a palette window (panel) to the right edge of the Edit Mode.
  * Has Radio Buttons listing the colors available in the palette.
* Add palette support to Edit Mode so when you draw pixels, they take
  on the color and attributes of the currently selected Swatch in your
  palette.
* Revise the on-disk format to better serialize the Palette object to
  JSON.
* Break Play Mode: collision detection fails because the Grid key
  elements are now full Pixel objects (which retain their Palette and
  Swatch properties).
  * The Grid will need to be re-worked to separate X,Y coordinates from
    the Pixel metadata to just test "is something there, and what is
    it?"
2018-08-10 17:19:47 -07:00
8624a28ea9 Add StatusBar to Editor Mode, Iterate on UI Toolkit
* Added `BoxSize()` to Widget that reports the full box size including
  borders and margin.
* The Frame uses the `BoxSize()` of widgets to position them.
  Reintroduces some padding issues (boxes on the GUI Test stick out of
  bounds a bit) but is on the right track.
* Renamed `Padding` to `Margin` on the Widget object, since the Margin
  is taken into consideration along with Outline and Border in computing
  the widget's BoxSize.
* Restructured the Label widget to take a Text or TextVariable property
  and the font settings (render.Text) are in a new `Font` property.
2018-08-05 12:54:57 -07:00
316456ef03 Add CheckButton and CheckBox with Bound Booleans
CheckButton is a generic component based on Button that additionally
takes a *bool variable to manage. When the CheckButton is clicked or
unclicked, it will toggle the bool var and its border style will "stick"
in or out depending on the state.

Checkbox is a Frame widget that wraps a CheckButton and another child
widget, such as a Label. Interacting with the child widget will forward
all of its mouse events to the CheckButton, so that the Label could be
clicked instead of just the box itself.
2018-08-01 19:52:09 -07:00
cbef5a46cb UI: Finish Frame Packing
* Frame.Pack() now supports Fill and Expand and works like Tk.
* The GUITest Scene now draws a large window with two fixed side panels,
  an expanding body panel, and a fixed footer with buttons. The panels
  are filled with other buttons and widgets showing off the Frame
  packing.
2018-08-01 18:52:52 -07:00
2e36d9ca85 WIP Labels 2018-07-31 17:18:13 -07:00
11df6cbda9 Move most properties from Button to parent Widget
These properties will be globally useful to all sorts of Widgets and
have been moved from the Button up into the Common widget, and its
interface extended to configure these:
* Padding int32
* Background color
* Foreground color
* Border size, color and style (default solid; raised; sunken)
* Outline size and color

The button adjusts its border style from "raised" to "sunken" for
MouseDown events and its Background color for MouseOver events. Other
widgets such as Labels and Frames will be able to have borders, paddings
and outlines too, but they will be off by default.
2018-07-25 21:24:37 -07:00
602273aa16 Add ui.Supervisor for Widget Event Handling
The Buttons can now be managed by a ui.Supervisor and be notified when
the mouse enters or leaves their bounding box and handle click events.

Current event handlers supported:
* MouseOver
* MouseOut
* MouseDown
* MouseUp
* Click

Each of those events are only fired when the state of the event has
changed, i.e. the first time the mouse enters the widget MouseOver is
called and then when the mouse leaves later, MouseOut is called.

A completed click event (mouse was released while pressed and hovering
the button) triggers both MouseOut and Click, so the button can pop
itself out and also run the click handler.
2018-07-25 20:25:02 -07:00
41e1838549 Add JS + History to Shell, Add Main Scene
* The shell now supports an "eval" command, or "$" for short.
  * Runs it in an Otto JavaScript VM.
  * Some global variables are available, like `d` is the Doodle object
    itself, `log`, `RGBA()` and `Point()`
* The shell supports paging through input history using the arrow keys.
* Added an initial Main Scene
2018-07-25 19:38:54 -07:00
94c1df050b Add initial User Interface Toolkit
With Labels and Buttons so far.

* Labels are pretty much complete, they wrap a render.Text and have a
  Compute() method that returns their Width and Height when rendered
  onto an SDL Surface.
* Buttons wrap a Label widget and Compute() its size and takes that into
  consideration when rendering itself. Buttons render themselves from
  scratch in a "Windows 95" themed way, with configurable colors, border
  widths and outline.
2018-07-25 09:03:49 -07:00
0efb2ab24f Make Collision Detection Flawless!
* Pixel perfect collision detection with level geometry.
* New shell commands (echo, clear) and help commands
2018-07-24 22:26:27 -07:00
d560670b7b Better Collision Detection (Bouncy Jumps Up Hills)
* Add a debug view that draws the player bounding boxes.
* Improve the collision detection to add support for:
  * Doodads being "Grounded" so gravity need not apply.
  * Walking up hills, albeit a bit "bouncily"
  * Harder to clip out of bounds
2018-07-24 20:57:22 -07:00
c3fd2e63cb Refactor grid to use level.Pixel and clean up collision between Edit and Play 2018-07-24 17:44:32 -07:00
e141203c4b Basic Collision Detection, Toggle Between Play/Edit
Known bugs:
* The Pixel format in the Grid has DX and DY attributes and
  it wreaks havoc on collision detection in Play Mode when you
  come straight from the editor. Reloading the map from disk to
  play is OK cuz it lacks these attrs.
2018-07-23 20:10:53 -07:00
e13dd62309 Clean up old SDL refs in render package 2018-07-21 20:57:41 -07:00
9356502a50 Implement Developer Console with Initial Commands
Implements the dev console in-game with various commands to start out
with.

Press the Enter key to show or hide the console. Commands supported:

new
    Start a new map in Edit Mode.

save [filename.json]
    Save the current map to disk. Filename is required unless you
    have saved recently.

edit filename.json
    Open a map from disk in Edit Mode.

play filename.json
    Play a map from disk in Play Mode.
2018-07-21 20:43:01 -07:00
30be42c343 Abstract away all SDL logic into isolated package 2018-07-21 17:12:22 -07:00
cf6d5d999c Refactor variable name for Scene implementors 2018-07-21 15:11:00 -07:00
90f1704886 Add initial Play scene 2018-06-20 19:00:46 -07:00
ede3d58e1d Restructure the app to be scene-based 2018-06-20 18:43:14 -07:00
a4fc6ec231 Update README 2018-06-17 14:15:59 -07:00
35619c2ccc Jot down some ideas 2018-06-17 13:54:33 -07:00
403d24f480 Marshall map pixels more compactly 2018-06-17 10:40:41 -07:00
27fafdc96d Save and restore maps as JSON files
First pass at a level storage format to save and restore maps.

To save a map: press F12. It takes a screenshot PNG into the
screenshots/ folder and outputs a map JSON in the working directory.

To restore a map: "go run cmd/doodle/main.go map.json"
2018-06-17 10:31:44 -07:00
407ef7f455 Milestone: Screenshot to PNG Test Feature 2018-06-17 07:56:51 -07:00
f8fe40c5ef Add global tick counter for debugging, fix unclick state errors 2018-06-16 20:21:42 -07:00
b7751507e4 Stabilize frame rate, add debug overlay 2018-06-16 19:59:23 -07:00
a8e82f4dd2 Initial milestone ideas 2017-10-26 19:32:07 -07:00
33e8cff79f Some initial pixel drawing with the mouse 2017-10-26 19:26:54 -07:00
8c884d4cab Initial commit 2017-10-26 18:03:11 -07:00