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