201 lines
5.5 KiB
Nim
201 lines
5.5 KiB
Nim
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
import std/[asyncdispatch, hashes, os, strutils, tables, xmlparser, xmltree]
|
|
import preserves, preserves/parse
|
|
import syndicate,
|
|
syndicate/[actors, capabilities, dataspaces, patterns, relay],
|
|
syndicate/protocols/[simpleChatProtocol]
|
|
|
|
import nimsvg
|
|
import pixie, pixie/fileformats/svg
|
|
|
|
import math, sdl2, sdl2/gfx
|
|
|
|
import svui
|
|
|
|
type
|
|
Svui = svui.Svui[Ref]
|
|
SdlError = object of CatchableError
|
|
Attrs = Table[string, Assertion]
|
|
|
|
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)
|
|
|
|
const
|
|
amask = uint32 0xff000000
|
|
rmask = uint32 0x000000ff
|
|
gmask = uint32 0x0000ff00
|
|
bmask = uint32 0x00ff0000
|
|
|
|
type
|
|
Pane = ref object
|
|
svg: string
|
|
texture: TexturePtr
|
|
|
|
type
|
|
App = ref object
|
|
screen: Image
|
|
# ctx: Context
|
|
window: WindowPtr
|
|
renderer: RendererPtr
|
|
# surface: SurfacePtr
|
|
# texture: TexturePtr
|
|
panes: Table[Hash, Pane]
|
|
viewPort: sdl2.Rect
|
|
zoomFactor: float
|
|
|
|
proc newPane(app: App; svg: string): Pane =
|
|
result = Pane(svg: svg)
|
|
try:
|
|
let (w, h) = app.window.getSize
|
|
var
|
|
image = decodeSvg(svg, w, h)
|
|
dataPtr = image.data[0].addr
|
|
surface = createRGBSurfaceFrom(
|
|
dataPtr, cint w, h, cint 32, cint 4*w,
|
|
rmask, gmask, bmask, amask)
|
|
result.texture = createTextureFromSurface(app.renderer, surface)
|
|
destroy surface
|
|
#[
|
|
result.texture = createTexture(
|
|
app.renderer,
|
|
SDL_PIXELFORMAT_ARGB8888,
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
w div 2, h div 2)
|
|
updateTexture(result.texture, nil, image.data[0].addr, cint 32)
|
|
]#
|
|
except Exception as e:
|
|
destroyTexture(result.texture)
|
|
stderr.writeLine "Failed to render SVG, ", e.msg
|
|
|
|
proc newApp(width, height: int): App =
|
|
result = App(zoomFactor: 1.0)
|
|
discard createWindowAndRenderer(
|
|
cint width, cint height,
|
|
SDL_WINDOW_RESIZABLE,
|
|
result.window, result.renderer)
|
|
var info: RendererInfo
|
|
check getRendererInfo(result.renderer, addr info)
|
|
echo "SDL Renderer: ", info.name
|
|
|
|
proc redraw(app: App) =
|
|
var srcRect, dstRect: sdl2.Rect
|
|
(srcRect.w, srcRect.h) = app.window.getSize
|
|
dstRect.w = cint(srcRect.w.float * app.zoomFactor)
|
|
dstRect.h = cint(srcRect.h.float * app.zoomFactor)
|
|
app.renderer.setDrawColor(128, 128, 128)
|
|
app.renderer.clear()
|
|
app.renderer.setViewport(addr app.viewPort)
|
|
for pane in app.panes.values:
|
|
app.renderer.copy(pane.texture, nil, addr dstRect)
|
|
app.renderer.present()
|
|
|
|
proc update(app: App) =
|
|
let (w, h) = app.window.getSize
|
|
if app.screen.isNil or app.screen.width != w or app.screen.height != h:
|
|
# app.screen = newImage(w, h)
|
|
check app.renderer.setLogicalSize(w, h)
|
|
app.viewPort = app.renderer.getViewport
|
|
|
|
#[
|
|
app.screen.fill(rgba(255, 255, 255, 255))
|
|
|
|
var dataPtr = app.screen.data[0].addr
|
|
var mainSurface = createRGBSurfaceFrom(
|
|
dataPtr, cint app.screen.width, cint app.screen.height, cint 32, cint 4*w,
|
|
rmask, gmask, bmask, amask)
|
|
destroy app.texture
|
|
app.texture = app.renderer.createTextureFromSurface(mainSurface)
|
|
]#
|
|
app.redraw()
|
|
|
|
proc zoom(app: App; change: float) =
|
|
# TODO: how does renderer scaling work?
|
|
app.zoomFactor = app.zoomFactor * (1.0 - (0.1 * change))
|
|
app.redraw()
|
|
|
|
proc pan(app: App; x, y: int) =
|
|
app.viewPort.x.inc x
|
|
app.viewPort.y.inc y
|
|
app.redraw()
|
|
|
|
proc main() =
|
|
discard sdl2.init(INIT_TIMER or INIT_VIDEO or INIT_EVENTS)
|
|
let app = newApp(1024, 768)
|
|
app.update()
|
|
|
|
let cap = mint()
|
|
|
|
asyncCheck runActor("chat") do (turn: var Turn):
|
|
|
|
connectUnix(turn, "/run/syndicate/ds", cap) do (turn: var Turn; a: Assertion) -> TurnAction:
|
|
let ds = unembed a
|
|
|
|
onPublish(turn, ds, Svui ? {0: drop(), 1: grab()}) do (svg: string):
|
|
onRetract:
|
|
app.panes.del(hash svg)
|
|
app.update()
|
|
# TODO: keep dead panes around until they are cleaned up.
|
|
# If something crashes then the last state should be visable.
|
|
var pane = app.panes.getOrDefault(hash svg)
|
|
if pane.isNil:
|
|
pane = newPane(app, svg)
|
|
app.panes[hash svg] = pane
|
|
else:
|
|
pane.svg = svg
|
|
app.update() # TODO: wait til end of turn
|
|
|
|
var
|
|
svgHandle: Handle
|
|
groups: Table[Hash, Nodes]
|
|
|
|
const
|
|
sdlTimeout = 500
|
|
asyncPollTimeout = 500
|
|
|
|
var
|
|
evt = sdl2.defaultEvent
|
|
mousePanning: bool
|
|
while true:
|
|
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.update()
|
|
of MouseMotion:
|
|
if mousePanning:
|
|
if (getModState() and (KMOD_LCTRL or KMOD_RCTRL)) == KMOD_NONE:
|
|
app.pan(-2 * evt.motion.xrel, -2 * evt.motion.yrel)
|
|
# TODO: panning accelation curve
|
|
# ../../../genode/repos/os/src/server/event_filter/accelerate_source.h
|
|
else:
|
|
app.pan(evt.motion.xrel, evt.motion.yrel)
|
|
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: discard
|
|
of KeyDown: discard
|
|
of QuitEvent: quit(0)
|
|
else:
|
|
echo evt.kind
|
|
|
|
main()
|