well_of_text/src/wells.nim

160 lines
4.6 KiB
Nim
Raw Normal View History

2023-09-08 10:29:04 +02:00
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
2023-09-08 13:25:25 +02:00
import std/[deques, streams]
import bumpy, pixie, sdl2
2023-09-08 10:29:04 +02:00
type
2023-09-08 13:25:25 +02:00
Rect = bumpy.Rect
2023-09-08 10:29:04 +02:00
Pane* = ref object
spans: seq[Span]
arrangement: Arrangement
2023-09-11 19:00:05 +02:00
image: Image
texture: TexturePtr
2023-09-08 10:29:04 +02:00
Well* = ref object
panes: Deque[Pane]
2023-09-11 19:00:05 +02:00
width, height: int
2023-09-11 20:06:09 +02:00
textWidth, textHeight: int
2023-09-08 10:29:04 +02:00
using
pane: Pane
well: Well
proc newWell*(width, height: int): Well =
2023-09-11 19:00:05 +02:00
result = Well(width: width, height: height)
2023-09-08 10:29:04 +02:00
result.panes.addFirst Pane()
2023-09-11 19:00:05 +02:00
proc dimensions(well): Vec2 = vec2(float well.width, float well.height)
2023-09-08 10:29:04 +02:00
2023-09-12 10:03:06 +02:00
proc margins(well): tuple[w: int, h: int] = (well.width div 9, well.height div 9)
proc textArea(well): tuple[w: int, h: int] = (
well.width - ((well.width div 9) shl 1),
well.height - ((well.height div 9) shl 1),
)
proc vec2(t: (int, int)): Vec2 = vec2(float t[0], float t[1])
2023-09-08 10:29:04 +02:00
proc rect*(well): Rect =
result.wh = well.dimensions
2023-09-11 19:00:05 +02:00
proc rectAt(well; i: int): Rect =
if i < well.panes.len:
let
(quo, rem) = divmod(i, 3)
2023-09-12 10:03:06 +02:00
shiftOff = succ quo
2023-09-11 19:00:05 +02:00
fullWh = well.dimensions
2023-09-12 10:03:06 +02:00
textArea = well.textArea
paneArea = vec2(float(well.width shr shiftOff), float(well.height shr shiftOff))
result.w = float(textArea.w shr shiftOff)
result.h = float(textArea.h shr shiftOff)
2023-09-11 19:00:05 +02:00
case rem
of 0:
2023-09-12 10:03:06 +02:00
result.xy = fullWh - (paneArea * 2.0)
2023-09-11 19:00:05 +02:00
of 1:
2023-09-12 10:03:06 +02:00
result.x = fullWh.x - paneArea.x
result.y = fullWh.y - (paneArea.y * 2.0)
2023-09-11 19:00:05 +02:00
of 2:
2023-09-12 10:03:06 +02:00
result.x = fullWh.x - (paneArea.x * 2.0)
result.y = fullWh.y - paneArea.y
2023-09-08 10:29:04 +02:00
else: discard
2023-09-12 10:03:06 +02:00
result.xy = result.xy + (result.wh / 9.0)
proc quadAt(well; i: int): Rect =
if i < well.panes.len:
let
(quo, rem) = divmod(i, 3)
shiftOff = succ quo
fullWh = well.dimensions
assert rem == 0
result.wh = vec2(float(well.width shr shiftOff), float(well.height shr shiftOff))
result.xy = fullWh - result.wh
2023-09-08 10:29:04 +02:00
2023-09-11 20:06:09 +02:00
proc place(well; offset, width, height: int): (Pane, Rect) =
2023-09-08 10:29:04 +02:00
## 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
2023-09-11 19:00:05 +02:00
let span = newSpan(text & "\n", font)
2023-09-08 10:29:04 +02:00
while true:
2023-09-11 19:00:05 +02:00
let pane = well.panes.peekLast()
2023-09-08 10:29:04 +02:00
pane.spans.add(span)
var
2023-09-12 10:03:06 +02:00
textArea = well.textArea.vec2
arrangement = typeset(pane.spans, textArea)
2023-09-08 10:29:04 +02:00
bounds = layoutBounds arrangement
2023-09-12 10:03:06 +02:00
if bounds.y <= textArea.y:
2023-09-11 19:00:05 +02:00
doAssert pane.spans.len > 0, "text does not find on a single pane - " & $bounds & $well.dimensions
2023-09-08 10:29:04 +02:00
pane.arrangement = arrangement
break
else:
2023-09-11 19:00:05 +02:00
discard pane.spans.pop()
well.panes.addLast Pane()
2023-09-08 10:29:04 +02:00
proc append*(well; stream: Stream; font: Font) =
var line: string
while readLine(stream, line):
append(well, line, font)
2023-09-11 19:00:05 +02:00
type Intersection = tuple
src, dst: Rect
index: int
2023-09-08 13:25:25 +02:00
const
amask = uint32 0xff000000
rmask = uint32 0x000000ff
gmask = uint32 0x0000ff00
bmask = uint32 0x00ff0000
2023-09-11 19:00:05 +02:00
proc texture*(well; index: int; renderer: RendererPtr): TexturePtr =
if index < well.panes.len:
let pane = well.panes[index]
if pane.texture.isNil and not pane.arrangement.isNil:
assert pane.image.isNil
let
2023-09-12 10:03:06 +02:00
textArea = well.textArea
pane.image = newImage(textArea.w, textArea.h)
2023-09-11 19:00:05 +02:00
pane.image.fill(rgba(255, 255, 255, 255))
pane.image.fillText(pane.arrangement)
var surface = createRGBSurfaceFrom(
pane.image.data[0].addr,
pane.image.width.cint,
pane.image.height.cint,
cint 32,
pane.image.width.cint shl 2,
rmask, gmask, bmask, amask,
)
assert(not surface.isNil, $getError())
pane.texture = createTextureFromSurface(renderer, surface)
destroy(surface)
assert(not pane.texture.isNil, $getError())
result = pane.texture
iterator intersectingPanes*(well; view: Rect): Intersection =
var
intersect: Intersection
zoom = 1.0
while intersect.index < well.panes.len:
var paneRect = well.rectAt(intersect.index)
2023-09-12 10:03:06 +02:00
let stepDown = (intersect.index mod 3) == 0
if stepDown:
2023-09-11 19:00:05 +02:00
zoom = zoom * 2.0
if view.overlaps paneRect:
intersect.dst = view and paneRect
intersect.src = rect(intersect.dst.xy - paneRect.xy, intersect.dst.wh) * zoom
yield(intersect)
2023-09-12 10:03:06 +02:00
if stepDown:
let quad = well.quadAt(intersect.index)
if not view.overlaps quad:
echo "view ", view, " does not overlap quadrant ", quad
# all further panes are non-intersecting
break
2023-09-11 19:00:05 +02:00
inc(intersect.index)