On the train to Praha
This commit is contained in:
commit
f344b29e1e
|
@ -0,0 +1,10 @@
|
||||||
|
let
|
||||||
|
syndicate = builtins.getFlake "syndicate";
|
||||||
|
pkgs =
|
||||||
|
import <nixpkgs> { overlays = (builtins.attrValues syndicate.overlays); };
|
||||||
|
in pkgs.nim2Packages.buildNimPackage (finalAttrs: prevAttrs: {
|
||||||
|
pname = "immutulator";
|
||||||
|
version = "unstable";
|
||||||
|
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||||
|
propagatedBuildInputs = [ pkgs.nim2Packages.getdns pkgs.nim2Packages.sdl2 ];
|
||||||
|
})
|
|
@ -0,0 +1,19 @@
|
||||||
|
include ../syndicate-nim/depends.tup
|
||||||
|
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../bumpy/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../chroma/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../crunchy/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../nimsimd/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../nimsvg/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../pixie/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../svui/nim
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../syndicate-nim/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../vmath/src
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../zippy/src
|
||||||
|
|
||||||
|
NIM_FLAGS += --path:%<flatty>/src
|
||||||
|
NIM_GROUPS += $(TUP_CWD)/../nimble/<flatty>
|
||||||
|
|
||||||
|
# NIM_FLAGS += --path:%<sdl2>/src
|
||||||
|
# NIM_GROUPS += $(TUP_CWD)/../nimble/<sdl2>
|
||||||
|
NIM_FLAGS += --path:$(TUP_CWD)/../sdl2-nim/src
|
|
@ -0,0 +1,10 @@
|
||||||
|
let
|
||||||
|
syndicate = builtins.getFlake "syndicate";
|
||||||
|
pkgs =
|
||||||
|
import <nixpkgs> { overlays = (builtins.attrValues syndicate.overlays); };
|
||||||
|
in pkgs.nim2Packages.buildNimPackage (finalAttrs: prevAttrs: {
|
||||||
|
pname = "well_of_text";
|
||||||
|
version = "unstable";
|
||||||
|
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||||
|
propagatedBuildInputs = [ pkgs.nim2Packages.getdns pkgs.nim2Packages.sdl2 ];
|
||||||
|
})
|
|
@ -0,0 +1,4 @@
|
||||||
|
include_rules
|
||||||
|
: well_of_text.nim |> !nim_bin |> {bin}
|
||||||
|
: text_spans.nim |> !nim_bin |> {bin}
|
||||||
|
: wells.nim |> !nim_bin |> {bin}
|
|
@ -0,0 +1,77 @@
|
||||||
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||||
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
|
import std/[deques]
|
||||||
|
import pixie
|
||||||
|
|
||||||
|
type
|
||||||
|
Pane* = ref object
|
||||||
|
image: pixie.Image
|
||||||
|
spans: seq[Span]
|
||||||
|
arrangement: Arrangement
|
||||||
|
|
||||||
|
Well* = ref object
|
||||||
|
panes: Deque[Pane]
|
||||||
|
|
||||||
|
proc newFrame(width, height: int): Frame =
|
||||||
|
let (w, h) = (width div 2, height div 2)
|
||||||
|
new result
|
||||||
|
result.panes[0] = Pane(image: newImage(w, h))
|
||||||
|
result.panes[1] = Pane(image: newImage(w, h))
|
||||||
|
result.panes[2] = Pane(image: newImage(w, h))
|
||||||
|
|
||||||
|
proc paneVec2(frame: Frame): Vec2 =
|
||||||
|
vec2(frame.panes[0].image.width.float32, frame.panes[0].image.height.float32)
|
||||||
|
|
||||||
|
proc allocChild(frame: Frame) =
|
||||||
|
assert frame.child.isNil
|
||||||
|
let (w, h) = (frame.panes[0].image.width div 2, frame.panes[0].image.height div 2)
|
||||||
|
doAssert w > 2 and h > 2, $w & "x" & $h
|
||||||
|
frame.child = newFrame(w, h)
|
||||||
|
|
||||||
|
proc setDepth(frame: Frame; n: Natural) =
|
||||||
|
var
|
||||||
|
frame = frame
|
||||||
|
i = n
|
||||||
|
while i > 0:
|
||||||
|
dec(i)
|
||||||
|
if frame.child.isNil:
|
||||||
|
allocChild(frame)
|
||||||
|
frame = frame.child
|
||||||
|
|
||||||
|
proc `[]`*(frame: Frame; i: Natural): Pane =
|
||||||
|
var
|
||||||
|
frame = frame
|
||||||
|
i = i
|
||||||
|
while true:
|
||||||
|
if i < 4:
|
||||||
|
return frame.panes[i]
|
||||||
|
if frame.child.isNil:
|
||||||
|
allocChild(frame)
|
||||||
|
frame = frame.child
|
||||||
|
dec(i, 4)
|
||||||
|
|
||||||
|
iterator walkPanes(frame: Frame): tuple[coord: Vec2, pane: Pane] =
|
||||||
|
var frame = frame
|
||||||
|
while not frame.isNil and
|
||||||
|
frame.panes[0].image.width > 2 and
|
||||||
|
frame.panes[1].image.height > 2:
|
||||||
|
let coord = frame.paneVec2
|
||||||
|
yield(coord, frame.panes[0])
|
||||||
|
yield(coord - vec2(coord.x, 0), frame.panes[1])
|
||||||
|
yield(coord - vec2(0, coord.y), frame.panes[2])
|
||||||
|
frame = frame.child
|
||||||
|
|
||||||
|
proc append*(frame: Fram; text: string; font: Font) =
|
||||||
|
let span = newSpan(text, font)
|
||||||
|
|
||||||
|
proc append*(frame: Fram; stream: Stream; font: Font) =
|
||||||
|
var line: string
|
||||||
|
while readLine(stream, line):
|
||||||
|
append(frame, font, line)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
let frame = newFrame(800, 600)
|
||||||
|
frame.setDepth(3)
|
||||||
|
for (coord, pane) in walkPanes(frame):
|
||||||
|
echo coord, " - ", pane.image.width, "x", pane.image.height
|
|
@ -0,0 +1,26 @@
|
||||||
|
import pixie
|
||||||
|
|
||||||
|
let image = newImage(200, 200)
|
||||||
|
image.fill(rgba(255, 255, 255, 255))
|
||||||
|
|
||||||
|
const typefacePath =
|
||||||
|
"/nix/store/ay5vhxszmibk0nrhx1vid4nhgvgdniq8-corefonts-1/share/fonts/truetype/Trebuchet_MS.ttf"
|
||||||
|
|
||||||
|
let typeface = readTypeface(typefacePath)
|
||||||
|
|
||||||
|
proc newFont(typeface: Typeface, size: float32, color: Color): Font =
|
||||||
|
result = newFont(typeface)
|
||||||
|
result.size = size
|
||||||
|
result.paint.color = color
|
||||||
|
|
||||||
|
let spans = @[
|
||||||
|
newSpan("verb [with object] ",
|
||||||
|
newFont(typeface, 12, color(0.78125, 0.78125, 0.78125, 1))),
|
||||||
|
newSpan("strallow\n", newFont(typeface, 36, color(0, 0, 0, 1))),
|
||||||
|
newSpan("\nstral·low\n", newFont(typeface, 13, color(0, 0.5, 0.953125, 1))),
|
||||||
|
newSpan("\n1. free (something) from restrictive restrictions \"the regulations are intended to strallow changes in public policy\" ",
|
||||||
|
newFont(typeface, 14, color(0.3125, 0.3125, 0.3125, 1)))
|
||||||
|
]
|
||||||
|
|
||||||
|
image.fillText(typeset(spans, vec2(180, 180)), translate(vec2(10, 10)))
|
||||||
|
image.writeFile("text_spans.png")
|
|
@ -0,0 +1,62 @@
|
||||||
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||||
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
|
import std/[sequtils]
|
||||||
|
import pixie
|
||||||
|
|
||||||
|
proc newFont*(typeface: Typeface, size: float32): Font =
|
||||||
|
result = newFont(typeface)
|
||||||
|
result.size = size
|
||||||
|
|
||||||
|
func height(arr: Arrangement): float =
|
||||||
|
if arr.positions.len > 0: result = arr.positions[arr.positions.high].y
|
||||||
|
|
||||||
|
proc add(result: var Arrangement; other: sink Arrangement) =
|
||||||
|
if result.lines.len == 0:
|
||||||
|
result = other
|
||||||
|
else:
|
||||||
|
let runeOff = result.runes.len
|
||||||
|
add(result.lines, map(other.lines,
|
||||||
|
proc (x: (int, int)): (int, int) = (x[0]+runeOff, x[1]+runeOff)))
|
||||||
|
add(result.spans, map(other.spans,
|
||||||
|
proc (x: (int, int)): (int, int) = (x[0]+runeOff, x[1]+runeOff)))
|
||||||
|
add(result.fonts, other.fonts)
|
||||||
|
add(result.runes, other.runes)
|
||||||
|
let yOff = result.positions[result.positions.high].y
|
||||||
|
add(result.positions,
|
||||||
|
map(other.positions,
|
||||||
|
proc(pos: Vec2): Vec2 = vec2(pos.x, pos.y + yOff)))
|
||||||
|
add(result.selectionRects,
|
||||||
|
map(other.selectionRects,
|
||||||
|
proc(rect: Rect): Rect = rect(rect.x, rect.y + yOff, rect.w, rect.h)))
|
||||||
|
|
||||||
|
proc render*(font: Font; text: string): Image =
|
||||||
|
# TODO: render by font size, not by wh
|
||||||
|
var wh = layoutBounds(font, "X")
|
||||||
|
echo "bounds of X are ", wh
|
||||||
|
wh.x = wh.x * 80
|
||||||
|
wh.y = wh.y * 50
|
||||||
|
let margin = wh / 9.0
|
||||||
|
var
|
||||||
|
printSpace = wh * (7.0 / 9.0)
|
||||||
|
pages = @[Arrangement()]
|
||||||
|
|
||||||
|
proc pageEnd(): float =
|
||||||
|
let
|
||||||
|
pi = pages.high
|
||||||
|
li = pages[pi].lines.high
|
||||||
|
ci = pages[pi].lines[li][1]
|
||||||
|
pages[pi].positions[ci].y
|
||||||
|
|
||||||
|
proc append(span: Span) =
|
||||||
|
var arr = typeset(@[span], printSpace)
|
||||||
|
if pages[pages.high].height + arr.height < printSpace.y:
|
||||||
|
pages[pages.high].add arr
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
append(newSpan(text, font))
|
||||||
|
|
||||||
|
result = newImage(int wh.x, int wh.y)
|
||||||
|
fill(result, rgba(255, 255, 255, 255))
|
||||||
|
fillText(result, pages[0], translate(margin))
|
|
@ -0,0 +1,213 @@
|
||||||
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||||
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
|
import std/[asyncdispatch, options, os, 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(
|
||||||
|
topFrame: newFrame(length shl 1, length shl 1),
|
||||||
|
zoomFactor: 1.0,
|
||||||
|
)
|
||||||
|
app.well = newWell(length, length)
|
||||||
|
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 = well.Rect
|
||||||
|
if viewPort.overlaps wellRect:
|
||||||
|
for (pane, rect) in well.intersectingPanes(viewPort)
|
||||||
|
let
|
||||||
|
texture = pane.texture(rect.wh *app.zoomFactor)
|
||||||
|
overlap = viewPort and rect
|
||||||
|
src = rect(overlap.xy - app.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)
|
||||||
|
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 setImage(app: App; image: Image) =
|
||||||
|
app.rect = image.rect
|
||||||
|
echo "create surface of ", image.width, "x", cint image.height, " pixels"
|
||||||
|
var
|
||||||
|
dataPtr = image.data[0].addr
|
||||||
|
surface = createRGBSurfaceFrom(
|
||||||
|
dataPtr,
|
||||||
|
cint image.width, cint image.height,
|
||||||
|
cint 32, cint 4*image.width,
|
||||||
|
rmask, gmask, bmask, amask)
|
||||||
|
app.texture = createTextureFromSurface(app.renderer, surface)
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
|
||||||
|
discard sdl2.init(INIT_TIMER or INIT_VIDEO or INIT_EVENTS)
|
||||||
|
let app = newApp(512)
|
||||||
|
|
||||||
|
let
|
||||||
|
typeface = readTypeface(typefacePath)
|
||||||
|
fontTODO = newFont(typeface, 48)
|
||||||
|
|
||||||
|
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 streamTODO = newFileStream(stdin)
|
||||||
|
|
||||||
|
var
|
||||||
|
evt = sdl2.defaultEvent
|
||||||
|
mousePanning: bool
|
||||||
|
lineTODO: string
|
||||||
|
while true:
|
||||||
|
if readLine(streamTODO, lineTODO):
|
||||||
|
app.well.append(line, fontTODO)
|
||||||
|
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()
|
|
@ -0,0 +1 @@
|
||||||
|
debugger:native
|
|
@ -0,0 +1,93 @@
|
||||||
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||||
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
|
import std/[deques]
|
||||||
|
import pixie
|
||||||
|
|
||||||
|
type
|
||||||
|
Pane* = ref object
|
||||||
|
image: pixie.Image
|
||||||
|
spans: seq[Span]
|
||||||
|
arrangement: Arrangement
|
||||||
|
|
||||||
|
Well* = ref object
|
||||||
|
panes: Deque[Pane]
|
||||||
|
dimensions: Vec2
|
||||||
|
|
||||||
|
using
|
||||||
|
pane: Pane
|
||||||
|
well: Well
|
||||||
|
|
||||||
|
proc newWell*(width, height: int): Well =
|
||||||
|
result = Well(dimensions: vec2(float width, float height))
|
||||||
|
result.panes.addFirst Pane()
|
||||||
|
|
||||||
|
proc margins*(well): Vec2 = well.dimensions / 9.0
|
||||||
|
|
||||||
|
proc bounds*(well): Vec2 = well.margin * 7.0
|
||||||
|
|
||||||
|
proc rect*(well): Rect =
|
||||||
|
result.wh = well.dimensions
|
||||||
|
|
||||||
|
proc rectAt(well, offset:int):Rect =
|
||||||
|
if offset < well.panes.len:
|
||||||
|
let n = succ offset
|
||||||
|
result.w = well.dimensions.x / float(2 * n)
|
||||||
|
result.h = well.dimensions.y / float(2 * n)
|
||||||
|
case offset and 3
|
||||||
|
of 0: (result.x, result.y) = (result.w, result.h)
|
||||||
|
of 1: result.y = result.h
|
||||||
|
of 2: result.x = result.w
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
proc place*(well; offset, width, height: int): (Pane, Rect) =
|
||||||
|
## Return the `Pane` at `offset` from the top of `well` or `nil` if
|
||||||
|
## the offset is too deep. The position and size of the `Pane` is
|
||||||
|
## returned as well.
|
||||||
|
if offset < well.panes.len:
|
||||||
|
result[0] = well.panes[offset]
|
||||||
|
result[1] = well.rectAt(offset)
|
||||||
|
|
||||||
|
proc append*(well; text: string; font: Font) =
|
||||||
|
assert well.panes.len > 0
|
||||||
|
let span = newSpan(text, font)
|
||||||
|
while true:
|
||||||
|
pane = well.panes[0]
|
||||||
|
pane.spans.add(span)
|
||||||
|
var
|
||||||
|
arrangement = typeset(spans, frame.bounds)
|
||||||
|
bounds = layoutBounds arrangement
|
||||||
|
assert bounds.x <= well.boundx.x
|
||||||
|
if bounds.bounds.y <= well.bounds.y:
|
||||||
|
doAssert pane.spans.len > 1, "text does not find on a single pane"
|
||||||
|
pane.arrangement = arrangement
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
well.panes.addFirst Pane()
|
||||||
|
|
||||||
|
proc append*(well; stream: Stream; font: Font) =
|
||||||
|
var line: string
|
||||||
|
while readLine(stream, line):
|
||||||
|
append(well, line, font)
|
||||||
|
|
||||||
|
proc render*(well; index: Natural; scale: float): Image =
|
||||||
|
if index < well.panes.len:
|
||||||
|
let dimensions = well.dimensions * scale
|
||||||
|
if dimensions.x > 8 and dimensions.y > 8:
|
||||||
|
result = newImage(int dimensions.x, int dimensions.y)
|
||||||
|
fill(result, rgba(255, 255, 255, 255))
|
||||||
|
fillText(well.panes[index].arrangment, translate(well.margin))
|
||||||
|
|
||||||
|
|
||||||
|
iterator intersectingPanes(well; rect:Rect): (Pane, Rect) =
|
||||||
|
var i = 0
|
||||||
|
while i <well.panes.len:
|
||||||
|
let bounds = well.boundsAt(i)
|
||||||
|
if i and 3:
|
||||||
|
let quad = Rect(0, 0, bounds.x *2.0, bounds.y * 2.0)
|
||||||
|
if not rect.overlaps quad:
|
||||||
|
# all further panes are non-intersecting
|
||||||
|
break
|
||||||
|
if rect.overlaps bounds:
|
||||||
|
yield (well.panes[i], bounds)
|
||||||
|
inc(i)
|
|
@ -0,0 +1,5 @@
|
||||||
|
bin = @["well_of_text"]
|
||||||
|
license = "Unlicense"
|
||||||
|
requires: "nim", "syndicate", "sdl2"
|
||||||
|
srcDir = "src"
|
||||||
|
version = "20230906"
|
Loading…
Reference in New Issue