~xigoi/vier

b8805db62f119adb98ae340921bc0ef191d7d973 — Adam Blažek 8 months ago 8d25cd8
Filled and outline shapes
35 files changed, 139 insertions(+), 114 deletions(-)

A .reuse/dep5
M README.md
D assets/icons/modes/color.png.license
D assets/icons/modes/command.png.license
D assets/icons/modes/inject.png.license
D assets/icons/modes/select.png.license
D assets/icons/template.png.license
D assets/icons/tools/brush.png.license
A assets/icons/tools/ellipse-filled.png
R assets/icons/tools/{ellipse.png => ellipse-outline.png}
D assets/icons/tools/flood.png.license
R assets/icons/tools/{rectangle.png => rectangle-filled.png}
A assets/icons/tools/rectangle-outline.png
D assets/icons/tools/rectangle.png.license
D assets/icons/tools/segment.png.license
M scripts/upload.fish
M src/vector.nim
M src/vier.nim
D website/assets/icons/modes/add.png.license
D website/assets/icons/modes/color.png.license
D website/assets/icons/modes/command.png.license
D website/assets/icons/modes/inject.png.license
D website/assets/icons/modes/select.png.license
D website/assets/icons/template.png.license
D website/assets/icons/tools/brush.png.license
A website/assets/icons/tools/ellipse-filled.png
R assets/icons/template.png => website/assets/icons/tools/ellipse-outline.png
D website/assets/icons/tools/flood.png.license
A website/assets/icons/tools/rectangle-filled.png
A website/assets/icons/tools/rectangle-outline.png
D website/assets/icons/tools/rectangle.png.license
D website/assets/icons/tools/segment.png.license
M website/index.html
M website/index.xd
R assets/icons/modes/add.png.license => website/screenshot.png.license
A .reuse/dep5 => .reuse/dep5 +12 -0
@@ 0,0 1,12 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Vier
Upstream-Contact: Adam Blažek <blazead5@cvut.cz>
Source: https://git.sr.ht/~xigoi/vier

Files: assets/icons/*
Copyright: Adam Blažek <blazead5@cvut.cz>
License: GPL-3.0-or-later

Files: website/assets/icons/*
Copyright: Adam Blažek <blazead5@cvut.cz>
License: GPL-3.0-or-later

M README.md => README.md +5 -0
@@ 1,3 1,8 @@
<!--
  © 2023 Adam Blažek <blazead5@cvut.cz>
  SPDX-License-Identifier: GPL-3.0-or-later
-->

# vier

_Vier_ is a keyboard-focused pixel art editor heavily inspired by Vim.

D assets/icons/modes/color.png.license => assets/icons/modes/color.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D assets/icons/modes/command.png.license => assets/icons/modes/command.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D assets/icons/modes/inject.png.license => assets/icons/modes/inject.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D assets/icons/modes/select.png.license => assets/icons/modes/select.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D assets/icons/template.png.license => assets/icons/template.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D assets/icons/tools/brush.png.license => assets/icons/tools/brush.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

A assets/icons/tools/ellipse-filled.png => assets/icons/tools/ellipse-filled.png +0 -0
R assets/icons/tools/ellipse.png => assets/icons/tools/ellipse-outline.png +0 -0
D assets/icons/tools/flood.png.license => assets/icons/tools/flood.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

R assets/icons/tools/rectangle.png => assets/icons/tools/rectangle-filled.png +0 -0
A assets/icons/tools/rectangle-outline.png => assets/icons/tools/rectangle-outline.png +0 -0
D assets/icons/tools/rectangle.png.license => assets/icons/tools/rectangle.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D assets/icons/tools/segment.png.license => assets/icons/tools/segment.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

M scripts/upload.fish => scripts/upload.fish +2 -0
@@ 1,4 1,6 @@
#!/usr/bin/env fish
# © 2023 Adam Blažek <blazead5@cvut.cz>
# SPDX-License-Identifier: GPL-3.0-or-later
tar -cvz -C website . -f site.tar.gz
hut pages publish -d xigoi.srht.site -s vier site.tar.gz
rm site.tar.gz

M src/vector.nim => src/vector.nim +6 -0
@@ 44,6 44,12 @@ func `>=`*(u, v: Vec): bool =
func abs*(u: Vec): Vec =
  (abs(u.x), abs(u.y))

func min*(u, v: Vec): Vec =
  (min(u.x, v.x), min(u.y, v.y))

func max*(u, v: Vec): Vec =
  (max(u.x, v.x), max(u.y, v.y))

func square*(n: int32): Vec =
  (n, n)


M src/vier.nim => src/vier.nim +105 -69
@@ 18,32 18,28 @@ import std/unicode

type
  Mode = enum
    mInject = "Inject"
    mAdd = "Add"
    mSelect = "Select"
    mColor = "Color"
    mCommand = "Command"
    Inject
    Add
    Select
    Color
    Command

  ToolKind = enum
    tBrush = "Brush"
    tFlood = "Flood"
    tRect = "Rect"
    tSegment = "Segment"
    tEllipse = "Ellipse"

  ShapeMode = enum
    rFilled
    rOutline
    Brush
    Flood
    Segment
    RectFilled
    RectOutline
    EllipseFilled
    EllipseOutline

  Tool = object
    case kind: ToolKind
    of tBrush:
    of Brush:
      brushSize: int32 # ignored for now
    of tFlood:
    of Flood:
      tolerance: int32
    of tRect, tEllipse:
      shapeMode: ShapeMode # ignored for now
    of tSegment:
    else:
      discard

  PixelChange = object


@@ 105,18 101,20 @@ var modeIcons: array[Mode, Texture]
var toolIcons: array[ToolKind, Texture]
proc loadIcons() =
  modeIcons = [
    mInject: loadTextureStatic("icons/modes/inject.png"),
    mAdd: loadTextureStatic("icons/modes/add.png"),
    mSelect: loadTextureStatic("icons/modes/select.png"),
    mColor: loadTextureStatic("icons/modes/color.png"),
    mCommand: loadTextureStatic("icons/modes/command.png")
    Inject: loadTextureStatic("icons/modes/inject.png"),
    Add: loadTextureStatic("icons/modes/add.png"),
    Select: loadTextureStatic("icons/modes/select.png"),
    Color: loadTextureStatic("icons/modes/color.png"),
    Command: loadTextureStatic("icons/modes/command.png")
  ]
  toolIcons = [
    tBrush: loadTextureStatic("icons/tools/brush.png"),
    tFlood: loadTextureStatic("icons/tools/flood.png"),
    tRect: loadTextureStatic("icons/tools/rectangle.png"),
    tSegment: loadTextureStatic("icons/tools/segment.png"),
    tEllipse: loadTextureStatic("icons/tools/ellipse.png")
    Brush: loadTextureStatic("icons/tools/brush.png"),
    Flood: loadTextureStatic("icons/tools/flood.png"),
    Segment: loadTextureStatic("icons/tools/segment.png"),
    RectFilled: loadTextureStatic("icons/tools/rectangle-filled.png"),
    RectOutline: loadTextureStatic("icons/tools/rectangle-outline.png"),
    EllipseFilled: loadTextureStatic("icons/tools/ellipse-filled.png"),
    EllipseOutline: loadTextureStatic("icons/tools/ellipse-outline.png")
  ]

proc loadTexture(picture: Picture) =


@@ 247,7 245,35 @@ proc lineSegment(start, `end`: Vec, includeStart: bool): seq[Vec] =
      point.y += signY
    result.add(point)

proc ellipse(a, b: Vec): seq[Vec] =
proc rectangleFilled(a, b: Vec): seq[Vec] =
  collect:
    for y in min(a.y, b.y)..max(a.y, b.y):
      for x in min(a.x, b.x)..max(a.x, b.x):
        (x, y)

proc rectangleOutline(a, b: Vec): seq[Vec] =
  let u = min(a, b)
  let v = max(a, b)
  concat(
    collect(
      for x in u.x..v.x:
        (x, u.y)
    ),
    collect(
      for x in u.x..v.x:
        (x, v.y)
    ),
    collect(
      for y in u.y + 1..v.y - 1:
        (u.x, y)
    ),
    collect(
      for y in u.y + 1..v.y - 1:
        (v.x, y)
    ),
  )

proc ellipse(a, b: Vec, filled: bool): seq[Vec] =
  # Algorithm taken from http://members.chello.at/easyfilter/bresenham.pdf
  var
    a = a


@@ 267,7 293,10 @@ proc ellipse(a, b: Vec): seq[Vec] =
  a.y += (diameter.y + 1) div 2
  b.y = a.y - parity
  while a.x <= b.x:
    result.add([(a.x, a.y), (b.x, a.y), (a.x, b.y), (b.x, b.y)])
    if filled:
      result.add(rectangleOutline(a, b))
    else:
      result.add([(a.x, a.y), (b.x, a.y), (a.x, b.y), (b.x, b.y)])
    let doubleError = 2 * error
    if doubleError <= errorIncrement.y:
      a.y.inc()


@@ 283,10 312,12 @@ proc ellipse(a, b: Vec): seq[Vec] =
    result.add([(a.x - 1, a.y), (b.x + 1, a.y), (a.x - 1, b.y), (b.x + 1, b.y)])
    a.y.inc()
    b.y.dec()
  if filled:
    result = toSeq(toHashSet(result))

proc select(picture: Picture, target: Vec, tool: Tool) =
  case tool.kind
  of tBrush:
  of Brush:
    # TODO: brush size
    if picture.selection.len == 0:
      picture.selection.add(target)


@@ 294,7 325,7 @@ proc select(picture: Picture, target: Vec, tool: Tool) =
      picture.selection.add(
        lineSegment(picture.selection[^1], target, includeStart = false)
      )
  of tFlood:
  of Flood:
    var stack = @[target]
    var toSelect = initHashSet[Vec]()
    while stack.len != 0:


@@ 316,16 347,16 @@ proc select(picture: Picture, target: Vec, tool: Tool) =
      if pos.y < picture.image.height - 1:
        check(pos.x, pos.y + 1)
    picture.selection = toSelect.toSeq()
  of tRect:
    picture.selection =
      collect:
        for y in min(picture.anchor.y, target.y)..max(picture.anchor.y, target.y):
          for x in min(picture.anchor.x, target.x)..max(picture.anchor.x, target.x):
            (x, y)
  of tSegment:
  of RectFilled:
    picture.selection = rectangleFilled(picture.anchor, target)
  of RectOutline:
    picture.selection = rectangleOutline(picture.anchor, target)
  of Segment:
    picture.selection = lineSegment(picture.anchor, target, includeStart = true)
  of tEllipse:
    picture.selection = ellipse(picture.anchor, target)
  of EllipseFilled:
    picture.selection = ellipse(picture.anchor, target, filled = true)
  of EllipseOutline:
    picture.selection = ellipse(picture.anchor, target, filled = false)

proc injectColor(picture: Picture, color: Color) =
  ## Changes all selected pixels to the given color.


@@ 365,32 396,32 @@ proc clickPixel(picture: Picture, target: Vec, mode: Mode, tool: Tool, color: Co
  picture.selection.setLen(0)
  picture.anchor = target
  case mode
  of mInject:
  of Inject:
    picture.select(target, tool)
    picture.injectColor(color)
  of mAdd:
  of Add:
    picture.select(target, tool)
    picture.addColor(color)
  of mSelect:
  of Select:
    picture.select(target, tool)
  of mColor, mCommand:
  of Color, Command:
    discard

proc dragPixel(picture: Picture, target: Vec, mode: Mode, tool: Tool, color: Color) =
  ## Reacts to dragging the mouse over the given point with the mouse button held down,
  ## or to moving the cursor over the point while holding spacebar.
  case mode
  of mInject:
  of Inject:
    picture.select(target, tool)
    picture.undo()
    picture.injectColor(color)
  of mAdd:
  of Add:
    picture.select(target, tool)
    picture.undo()
    picture.addColor(color)
  of mSelect:
  of Select:
    picture.select(target, tool)
  of mColor, mCommand:
  of Color, Command:
    discard

proc isSaved(picture: Picture): bool =


@@ 461,7 492,7 @@ proc draw(picture: Picture, app: App, origin: Vec, selected: bool) =
    canvasSize.x,
    if picture.isSaved: White else: unsavedNameColor,
  )
  if app.mode == mSelect:
  if app.mode == Select:
    for pixel in picture.selection:
      drawRectangle(
        origin + picture.scale * pixel, square(picture.scale), selectionInnerColor


@@ 559,7 590,7 @@ proc clearSelection(app: App) =
  app.selectedPicture.clearSelection()

proc activeSelection(app: App): bool =
  app.mode == mSelect and app.selectedPicture.selection.len != 0
  app.mode == Select and app.selectedPicture.selection.len != 0

proc copy(app: App) =
  let selection = app.selectedPicture.selection


@@ 651,12 682,12 @@ proc draw(app: App) =
proc processKeyboard(app: App) =
  let shift = isKeyDown(LeftShift) or isKeyDown(RightShift)
  let control = isKeyDown(LeftControl) or isKeyDown(RightControl)
  if app.mode == mCommand:
  if app.mode == Command:
    if isKeyPressed(Escape):
      app.mode = mInject
      app.mode = Inject
      app.command = ""
    elif isKeyPressed(Enter):
      app.mode = mInject
      app.mode = Inject
      try:
        let res = app.spry.eval("[" & app.command & "]")
        if res.isNil or res of NilVal:


@@ 667,7 698,7 @@ proc processKeyboard(app: App) =
        app.command = getCurrentExceptionMsg()
    elif isKeyPressed(Backspace):
      if app.command.len == 0:
        app.mode = mInject
        app.mode = Inject
      else:
        app.command.setLen(app.command.len - 1)
    else:


@@ 682,11 713,11 @@ proc processKeyboard(app: App) =
        app.selectedPicture.addColor(app.color)
        app.clearSelection()
      else:
        app.mode = mAdd
        app.mode = Add
    if isKeyPressed(B):
      app.tool = Tool(kind: tBrush)
      app.tool = Tool(kind: Brush)
    if isKeyPressed(C):
      app.mode = mColor
      app.mode = Color
    if isKeyPressed(D):
      let picture = app.selectedPicture
      let


@@ 704,15 735,18 @@ proc processKeyboard(app: App) =
      picture.add(change)
      picture.apply(change)
    if isKeyPressed(E):
      app.tool = Tool(kind: tEllipse)
      if shift:
        app.tool = Tool(kind: EllipseOutline)
      else:
        app.tool = Tool(kind: EllipseFilled)
    if isKeyPressed(F):
      app.tool = Tool(kind: tFlood)
      app.tool = Tool(kind: Flood)
    if isKeyPressed(I):
      if app.activeSelection:
        app.selectedPicture.injectColor(app.color)
        app.clearSelection()
      else:
        app.mode = mInject
        app.mode = Inject
    if isKeyPressed(P):
      app.paste()
    if isKeyPressed(Q):


@@ 724,14 758,16 @@ proc processKeyboard(app: App) =
    if isKeyPressed(R):
      if control:
        app.selectedPicture.redo
      elif shift:
        app.tool = Tool(kind: RectOutline)
      else:
        app.tool = Tool(kind: tRect)
        app.tool = Tool(kind: RectFilled)
    if isKeyPressed(S):
      app.tool = Tool(kind: tSegment)
      app.tool = Tool(kind: Segment)
    if isKeyPressed(U):
      app.selectedPicture.undo
    if isKeyPressed(V):
      app.mode = mSelect
      app.mode = Select
    if isKeyPressed(W):
      if shift:
        app.pictures.apply(write)


@@ 746,7 782,7 @@ proc processKeyboard(app: App) =
      else:
        app.color = app.selectedPicture.colorAtCursor
    if isKeyPressed(Semicolon):
      app.mode = mCommand
      app.mode = Command
      app.command = ""
      while (var ch = getCharPressed(); ch != 0):
        discard


@@ 771,7 807,7 @@ proc processKeyboard(app: App) =
        movement = square(0)
      app.movementTime.inc
    case app.mode
    of mInject, mAdd, mSelect:
    of Inject, Add, Select:
      if movement != square(0):
        if control:
          app.selectedPictureIndex =


@@ 800,7 836,7 @@ proc processKeyboard(app: App) =
            app.color
          ,
        )
    of mColor:
    of Color:
      if movement != square(0):
        if control:
          app.selectedPaletteIndex =


@@ 809,7 845,7 @@ proc processKeyboard(app: App) =
        else:
          app.selectedPalette.moveCursor(movement)
          app.updateColor()
    of mCommand:
    of Command:
      discard

proc processMouse(app: App) =

D website/assets/icons/modes/add.png.license => website/assets/icons/modes/add.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D website/assets/icons/modes/color.png.license => website/assets/icons/modes/color.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D website/assets/icons/modes/command.png.license => website/assets/icons/modes/command.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D website/assets/icons/modes/inject.png.license => website/assets/icons/modes/inject.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D website/assets/icons/modes/select.png.license => website/assets/icons/modes/select.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D website/assets/icons/template.png.license => website/assets/icons/template.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D website/assets/icons/tools/brush.png.license => website/assets/icons/tools/brush.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

A website/assets/icons/tools/ellipse-filled.png => website/assets/icons/tools/ellipse-filled.png +0 -0
R assets/icons/template.png => website/assets/icons/tools/ellipse-outline.png +0 -0
D website/assets/icons/tools/flood.png.license => website/assets/icons/tools/flood.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

A website/assets/icons/tools/rectangle-filled.png => website/assets/icons/tools/rectangle-filled.png +0 -0
A website/assets/icons/tools/rectangle-outline.png => website/assets/icons/tools/rectangle-outline.png +0 -0
D website/assets/icons/tools/rectangle.png.license => website/assets/icons/tools/rectangle.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

D website/assets/icons/tools/segment.png.license => website/assets/icons/tools/segment.png.license +0 -2
@@ 1,2 0,0 @@
© 2023 Adam Blažek <blazead5@cvut.cz>
SPDX-License-Identifier: GPL-3.0-or-later

M website/index.html => website/index.html +3 -3
@@ 29,7 29,7 @@ nimble <span class="token function">install</span>
<p>Vier has these modes:</p>
<dl><dt><img src="assets/icons/modes/inject.png" alt="Inject mode icon" /> Inject mode</dt><dd>In this mode, the user can move around the selected picture using <kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> keys and draw with the currently selected tool. The color of the pixels being drawn is replaced by the selected color, so this can also work as an “eraser” if the selected color is transparent. If multiple pictures are opened, they can be switched between using <kbd>Ctrl</kbd>-<kbd>H</kbd> and <kbd>Ctrl</kbd>-<kbd>L</kbd>.</dd><dt><img src="assets/icons/modes/add.png" alt="Add mode icon" /> Add mode</dt><dd>In this mode, the user can move around the selected picture using <kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> keys and draw with the currently selected tool. The selected color is added on the color of the pixels being drawn, which allows shading an area with a transparent color. If multiple pictures are opened, they can be switched between using <kbd>Ctrl</kbd>-<kbd>H</kbd> and <kbd>Ctrl</kbd>-<kbd>L</kbd>.</dd><dt><img src="assets/icons/modes/select.png" alt="Select mode icon" /> Select mode</dt><dd>In this mode, the user can move around the selected picture using <kbd>H</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> keys and select a part of the picture using the currently selected tool. The selection is currently useless; copy-paste functionality will be implemented later. If multiple pictures are opened, they can be switched between using <kbd>Ctrl</kbd>-<kbd>H</kbd> and <kbd>Ctrl</kbd>-<kbd>L</kbd>.</dd><dt><img src="assets/icons/modes/color.png" alt="Color mode icon" /> Color mode</dt><dd>In this mode, the user can move around a color palette (displayed at the top of the screen) to change the selected color (displayed in the upper left corner). If multiple palettes are available, they can be switched between using <kbd>Ctrl</kbd>-<kbd>H</kbd> and <kbd>Ctrl</kbd>-<kbd>L</kbd>.</dd><dt><img src="assets/icons/modes/command.png" alt="Command mode icon" /> Command mode</dt><dd>In this mode, the user can enter a command, displayed at the bottom of the screen, to perform complex actions such as opening a file. The commands are in the <a href="https://sprylang.se/">Spry</a> programming language, though this is not important for basic usage. The command is executed by pressing <kbd>⏎</kbd> or cancelled by pressing <kbd>Esc</kbd>.</dd></dl>
<p>The following tools are available:</p>
<dl><dt><img src="assets/icons/tools/brush.png" alt="Brush icon" /> Brush</dt><dd>The brush tool selects pixels that it moves over.</dd><dt><img src="assets/icons/tools/segment.png" alt="Segment icon" /> Segment</dt><dd>The segment tool selects a line segment.</dd><dt><img src="assets/icons/tools/rectangle.png" alt="Rectangle icon" /> Rectangle</dt><dd>The rectangle tool selects an axis-aligned rectangle.</dd><dt><img src="assets/icons/tools/ellipse.png" alt="Ellipse icon" /> Rectangle</dt><dd>The rectangle tool selects the outline of an axis-aligned ellipse.</dd><dt><img src="assets/icons/tools/flood.png" alt="Flood icon" /> Flood</dt><dd>The flood tool selects a contiguous area of pixels with the same color.</dd></dl></section>
<dl><dt><img src="assets/icons/tools/brush.png" alt="Brush icon" /> Brush</dt><dd>The brush tool selects pixels that it moves over.</dd><dt><img src="assets/icons/tools/segment.png" alt="Segment icon" /> Segment</dt><dd>The segment tool selects a line segment.</dd><dt><img src="assets/icons/tools/rectangle-filled.png" alt="Rectangle icon" /> Rectangle</dt><dd>The rectangle tool selects an axis-aligned rectangle.</dd><dt><img src="assets/icons/tools/rectangle-outline.png" alt="Rectangle outline icon" /> Rectangle outline</dt><dd>The rectangle tool selects the outline of an axis-aligned rectangle.</dd><dt><img src="assets/icons/tools/ellipse-filled.png" alt="Ellipse icon" /> Ellipse</dt><dd>The ellipse tool selects an axis-aligned ellipse.</dd><dt><img src="assets/icons/tools/ellipse-outline.png" alt="Ellipse outline icon" /> Ellipse outline</dt><dd>The ellipse outline tool selects the outline of an axis-aligned ellipse.</dd><dt><img src="assets/icons/tools/flood.png" alt="Flood icon" /> Flood</dt><dd>The flood tool selects a contiguous area of pixels with the same color.</dd></dl></section>
<section><h2 class="xd-section-heading">Keyboard mappings</h2><p>The key mappings are inspired by Vim. They are currently not customizable.</p>
<table><tr><th>Key</th><th>Alone</th><th>Shift</th><th>Control</th></tr>
<tr><td><kbd>⎵</kbd></td><td>hold to draw</td><td>hold to draw with secondary color</td></tr>


@@ 37,7 37,7 @@ nimble <span class="token function">install</span>
<tr><td><kbd>B</kbd></td><td>Tool ← Brush</td></tr>
<tr><td><kbd>C</kbd></td><td>Mode ← Color</td></tr>
<tr><td><kbd>D</kbd></td><td>delete pixel</td></tr>
<tr><td><kbd>E</kbd></td><td>Tool ← Ellipse</td></tr>
<tr><td><kbd>E</kbd></td><td>Tool ← Ellipse</td><td>Tool ← Ellipse outline</td></tr>
<tr><td><kbd>F</kbd></td><td>Tool ← Flood</td></tr>
<tr><td><kbd>H</kbd></td><td>move left</td><td></td><td>previous picture/palette</td></tr>
<tr><td><kbd>I</kbd></td><td>Mode ← Inject</td></tr>


@@ 46,7 46,7 @@ nimble <span class="token function">install</span>
<tr><td><kbd>L</kbd></td><td>move right</td><td></td><td>next picture/palette</td></tr>
<tr><td><kbd>P</kbd></td><td>paste</td></tr>
<tr><td><kbd>Q</kbd></td><td>quit</td><td>force quit</td></tr>
<tr><td><kbd>R</kbd></td><td>Tool ← Rectangle</td><td></td><td>redo</td></tr>
<tr><td><kbd>R</kbd></td><td>Tool ← Rectangle</td><td>Tool ← Rectangle outline</td><td>redo</td></tr>
<tr><td><kbd>S</kbd></td><td>Tool ← Segment</td></tr>
<tr><td><kbd>U</kbd></td><td>undo</td></tr>
<tr><td><kbd>V</kbd></td><td>Mode ← Select</td></tr>

M website/index.xd => website/index.xd +6 -4
@@ 62,8 62,10 @@
  [description-list
    ; [link-image Brush icon; assets/icons/tools/brush.png] Brush; The brush tool selects pixels that it moves over.
    ; [link-image Segment icon; assets/icons/tools/segment.png] Segment; The segment tool selects a line segment.
    ; [link-image Rectangle icon; assets/icons/tools/rectangle.png] Rectangle; The rectangle tool selects an axis-aligned rectangle.
    ; [link-image Ellipse icon; assets/icons/tools/ellipse.png] Ellipse; The ellipse tool selects the outline of an axis-aligned ellipse.
    ; [link-image Rectangle icon; assets/icons/tools/rectangle-filled.png] Rectangle; The rectangle tool selects an axis-aligned rectangle.
    ; [link-image Rectangle outline icon; assets/icons/tools/rectangle-outline.png] Rectangle outline; The rectangle tool selects the outline of an axis-aligned rectangle.
    ; [link-image Ellipse icon; assets/icons/tools/ellipse-filled.png] Ellipse; The ellipse tool selects an axis-aligned ellipse.
    ; [link-image Ellipse outline icon; assets/icons/tools/ellipse-outline.png] Ellipse outline; The ellipse outline tool selects the outline of an axis-aligned ellipse.
    ; [link-image Flood icon; assets/icons/tools/flood.png] Flood; The flood tool selects a contiguous area of pixels with the same color.
  ]
]


@@ 76,7 78,7 @@
    [row [<kbd> B]; Tool ← Brush]
    [row [<kbd> C]; Mode ← Color]
    [row [<kbd> D]; delete pixel]
    [row [<kbd> E]; Tool ← Ellipse]
    [row [<kbd> E]; Tool ← Ellipse; Tool ← Ellipse outline]
    [row [<kbd> F]; Tool ← Flood]
    [row [<kbd> H]; move left; [# move to left edge]; previous picture/palette]
    [row [<kbd> I]; Mode ← Inject]


@@ 86,7 88,7 @@
    [# row [<kbd> M]; Tool ← Mirror]
    [row [<kbd> P]; paste]
    [row [<kbd> Q]; quit; force quit]
    [row [<kbd> R]; Tool ← Rectangle; ; redo]
    [row [<kbd> R]; Tool ← Rectangle; Tool ← Rectangle outline; redo]
    [row [<kbd> S]; Tool ← Segment]
    [row [<kbd> U]; undo]
    [row [<kbd> V]; Mode ← Select]

R assets/icons/modes/add.png.license => website/screenshot.png.license +0 -0