well_of_text/src/well_of_text.nim

207 lines
5.4 KiB
Nim

# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, options, os, streams, tables]
import preserves, syndicate, syndicate/[capabilities]
import bumpy, pixie
import sdl2
import ./wells
const typefacePath =
"/nix/store/ay5vhxszmibk0nrhx1vid4nhgvgdniq8-corefonts-1/share/fonts/truetype/Trebuchet_MS.ttf"
proc unixSocketPath: Unix =
result.path = getEnv("SYNDICATE_SOCK")
if result.path == "":
result.path = getEnv("XDG_RUNTIME_DIR", "/run/user/1000") / "dataspace"
proc envStep: Assertion =
var s = getEnv("SYNDICATE_STEP")
if s != "": parsePreserves(s, Cap)
else: capabilities.mint().toPreserve(Cap)
type
SdlError = object of CatchableError
template check(res: cint) =
if res != 0:
let msg = $sdl2.getError()
raise newException(SdlError, msg)
template check(res: SDL_Return) =
if res == SdlError:
let msg = $sdl2.getError()
raise newException(SdlError, msg)
func toVec2(p: Point): Vec2 = vec2(float p.x, float p.y)
const
amask = uint32 0xff000000
rmask = uint32 0x000000ff
gmask = uint32 0x0000ff00
bmask = uint32 0x00ff0000
type
App = ref object
screen: Image
window: WindowPtr
renderer: RendererPtr
texture: TexturePtr
rect: bumpy.Rect
viewPoint: Vec2
well: Well
zoomFactor: float
func rect(img: Image): bumpy.Rect =
result.w = float img.width
result.h = float img.height
proc newApp(length: cint): App =
## Create a new square plane of `length` pixels.
result = App(
well: newWell(length, length),
zoomFactor: 1.0,
)
discard createWindowAndRenderer(
length, length,
SDL_WINDOW_RESIZABLE,
result.window, result.renderer)
var info: RendererInfo
check getRendererInfo(result.renderer, addr info)
echo "SDL Renderer: ", info.name
func toSdl(rect: bumpy.Rect): sdl2.Rect =
(result.x, result.y, result.w, result.h) =
(cint rect.x, cint rect.y, cint rect.w, cint rect.h)
proc viewPort(app: App; wh: Vec2): bumpy.Rect =
result.wh = wh / app.zoomFactor
result.xy = app.viewPoint - (result.wh * 0.5)
proc redraw(app: App) =
assert app.zoomFactor != 0.0
var
(w, h) = app.window.getSize
sdlViewPort = rect(-float(w shr 1), -float(h shr 1), float w, float h)
viewPort = app.viewPort(sdlViewPort.wh)
app.renderer.setDrawColor(0x80, 0x80, 0x80)
app.renderer.clear()
let wellRect = app.well.rect
if viewPort.overlaps wellRect:
for (index, rect) in app.well.intersectingPanes(viewPort):
let texture = app.well.texture(index, app.renderer, rect.wh * app.zoomFactor)
if texture.isNil: break
let
overlap = viewPort and rect
src = rect(overlap.xy - rect.xy, overlap.wh)
var dst: bumpy.Rect
dst.x = (overlap.x - viewPort.x) * (sdlViewPort.w / viewPort.w)
dst.y = (overlap.y - viewPort.y) * (sdlViewPort.h / viewPort.h)
dst.wh =
if app.zoomFactor == 1.0:
overlap.wh # correct
elif app.zoomFactor > 1.0:
sdlViewPort.wh - dst.xy # correct?
else:
overlap.wh * app.zoomFactor
var (sdlSrc, sdlDst) = (src.toSdl, dst.toSdl)
app.renderer.copy(texture, addr sdlSrc, addr sdlDst)
else:
echo "no overlap of ", viewPort, " and ", wellRect
app.renderer.present()
proc resize(app: App) =
## Resize to new dimensions of the SDL window.
redraw(app)
proc zoom(app: App; change: float) =
app.zoomFactor = app.zoomFactor * (1.0 + ((1 / 8) * change))
app.redraw()
proc pan(app: App; xy: Vec2) =
app.viewPoint.xy = app.viewPoint.xy + (xy / app.zoomFactor)
app.redraw()
proc recenter(app: App) =
reset app.viewPoint
app.zoomFactor = 1.0
app.redraw()
proc main() =
discard sdl2.init(INIT_TIMER or INIT_VIDEO or INIT_EVENTS)
let app = newApp(512)
let
typeface = readTypeface(typefacePath)
font = newFont(typeface)
# TODO
app.redraw()
#[
let
unix = unixSocketPath()
step = envStep()
let actor = bootDataspace("chat") do (root: Cap; turn: var Turn):
connect(turn, unix, step) do (turn: var Turn; ds: Cap):
echo "connected to syndicate over UNIX-socket"
asyncCheck actor.future
]#
const
sdlTimeout = 500
asyncPollTimeout = 500
let stream = newFileStream(stdin)
# TODO
var
evt = sdl2.defaultEvent
mousePanning: bool
line: string
while true:
if readLine(stream, line):
# TODO
app.well.append(line, font)
app.redraw()
# asyncdispatch.poll(0)
if waitEventTimeout(evt, sdlTimeout):
case evt.kind
of MouseWheel:
app.zoom(evt.wheel.y.float)
of WindowEvent:
if evt.window.event == WindowEvent_Resized:
app.resize()
of MouseMotion:
if mousePanning:
var xy = vec2(evt.motion.xrel.float, evt.motion.yrel.float)
app.pan(xy * 2.0)
of MouseButtonDown:
case evt.button.button
of BUTTON_MIDDLE:
mousePanning = true
else: discard
of MouseButtonUp:
case evt.button.button
of BUTTON_MIDDLE:
mousePanning = false
else: discard
of KeyUp:
try:
let code = evt.key.keysym.scancode.Scancode
echo "code: ", code
if code in {SDL_SCANCODE_SPACE, SDL_SCANCODE_ESCAPE}:
app.recenter()
except:
# invalid event.key.keysym.sym sometimes arrive
discard
of KeyDown: discard
of QuitEvent: quit(0)
else:
echo evt.kind
main()