fixes and new examples

This commit is contained in:
Stefan Salewski 2019-09-14 12:31:27 +02:00
commit 8979188f4c
9 changed files with 888 additions and 185 deletions

View file

@ -13,6 +13,8 @@
# And finally that custom surface and custom cairo context is an
# important test for the language bindings.
# https://discourse.gnome.org/t/problem-with-gtkscrollbar-gtk-window-resize-and-gtk-adjustment-set-value/1081
import gintro/[gtk, gdk, glib, gobject, gio, cairo]
const
@ -51,7 +53,7 @@ Scrolling: Scrollbars
Panning: Moving mouse while middle mouse button pressed
"""
# drawing area and scroll bars in 2x2 table (PDA == Plain Drawing Area)
# drawing area and scroll bars in 2x2 grid (PDA == Plain Drawing Area)
type
PosAdj = ref object of Adjustment
@ -72,6 +74,8 @@ type
selecting: bool
userZoom: float
surf: Surface
pattern: Pattern
cr: cairo.Context
darea: DrawingArea
hadjustment: PosAdj
vadjustment: PosAdj
@ -88,17 +92,18 @@ type
lastMousePosY: float
zoomRectX1: float
zoomRectY1: float
oldSizeX: int
oldSizeY: int
drawWorld: proc (cr: Context)
extents: proc (): tuple[x, y, w, h: float]
proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
if this.surf.isNil: return
cr.setSource(patternCreateForSurface(this.surf))
if this.pattern.isNil: return
cr.setSource(this.pattern)
cr.paint
if this.selecting:
cr.rectangle(this.lastButtonDownPosX, this.lastButtonDownPosY,
this.zoomRectX1 - this.lastButtonDownPosX,
this.zoomRectY1 - this.lastButtonDownPosY)
this.zoomRectX1 - this.lastButtonDownPosX, this.zoomRectY1 - this.lastButtonDownPosY)
cr.setSource(0, 0, 1, 0.5) # SELECT_RECT_COL) # 0, 0, 1, 0.5
cr.fillPreserve
cr.setSource(0, 0, 0)
@ -121,36 +126,47 @@ proc updateAdjustments(this: PDA; dx, dy: float) =
updateVal(this.hadjustment, dx)
updateVal(this.vadjustment, dy)
# maybe for optimization we should only allocate a new surface when its size has changed.
proc paint(this: PDA) =
if this.surf != nil:
destroy(this.surf) # manually destroy surface -- GC would do it for us, but GC is slow...
this.surf = this.darea.window.createSimilarSurface(Content.color,
this.darea.allocatedWidth, this.darea.allocatedHeight)
let cr = newContext(this.surf)
cr.translate(this.hadjustment.upper * 0.5 - this.hadjustment.value, # our origin is the center
# echo "paint"
this.cr.save
this.cr.translate(this.hadjustment.upper * 0.5 - this.hadjustment.value, # our origin is the center
this.vadjustment.upper * 0.5 - this.vadjustment.value)
cr.scale(this.fullScale * this.userZoom, this.fullScale * this.userZoom)
cr.translate(-this.dataX - this.dataWidth * 0.5, -this.dataY - this.dataHeight * 0.5)
this.drawWorld(cr) # call the user provided drawing function
destroy(cr) # we can also manually destroy the context here, but GC would do it for us
this.cr.scale(this.fullScale * this.userZoom, this.fullScale * this.userZoom)
this.cr.translate(-this.dataX - this.dataWidth * 0.5, -this.dataY - this.dataHeight * 0.5)
this.drawWorld(this.cr) # call the user provided drawing function
this.cr.restore
proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure; this: PDA): bool =
#this.updateAdjustments(0, 0)
(this.dataX, this.dataY, this.dataWidth,
this.dataHeight) = this.extents() # query user defined size
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
this.darea.allocatedHeight.float / this.dataHeight)
if this.surf != nil:
destroy(this.surf) # manually destroy surface -- GC would do it for us, but GC is slow...
this.surf = this.darea.window.createSimilarSurface(Content.color,
this.darea.allocatedWidth, this.darea.allocatedHeight)
if this.pattern != nil:
patternDestroy(this.pattern)
if this.cr != nil:
destroy(this.cr)
this.pattern = patternCreateForSurface(this.surf) # pattern now owns the surface!
this.cr = newContext(this.surf) # this function references target!
this.paint
return gdk.EVENT_STOP
proc hscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
pda.hadjustment.setUpper(r.width.float * pda.userZoom)
pda.hadjustment.setPageSize(r.width.float)
if pda.oldSizeX != 0: # this fix is not exact, as fullScale can ...
updateVal(pda.hadjustment, (r.width - pda.oldSizeX).float * 0.5)
pda.oldSizeX = r.width
proc vscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
pda.vadjustment.setUpper(r.height.float * pda.userZoom)
pda.vadjustment.setPageSize(r.height.float)
if pda.oldSizeY != 0: # ... change when window is rezized. But it's good enough!
updateVal(pda.vadjustment, (r.height - pda.oldSizeY).float * 0.5)
pda.oldSizeY = r.height
proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
this.updateAdjustments(dx, dy)
@ -167,37 +183,45 @@ proc getUserCoordinates(this: PDA; eventX, eventY: float): (float, float) =
proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
let state = getState(event)
let (x, y) = event.getCoords
if (state.contains(button1)): # selecting
if state.contains(button1): # selecting
this.selecting = true
this.zoomRectX1 = x
this.zoomRectY1 = y
this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.allocatedHeight)
elif button2 in state: # panning
this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
else:
return gdk.EVENT_PROPAGATE
this.lastMousePosX = x
this.lastMousePosY = y
return gdk.EVENT_STOP
#event.request # request more motion events ?
# zooming with mouse wheel -- data near mouse pointer should not move if possible!
# hadjustment.value + event.x is the position in our zoomed_in world, (userZoom / z0 - 1)
# is the relative movement caused by zooming
# In other words, this is the delta-move d of a point at position P from zooming:
# d = newPos - P = P * scale - P = P * (z/z0) - P = P * (z/z0 - 1). We have to compensate for this d.
proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
let z0 = this.userZoom
var d = getScrollDirection(event)
var (x, y) = event.getCoords
if d == ScrollDirection.up:
case getScrollDirection(event)
of ScrollDirection.up:
this.userZoom *= ZoomFactorMouseWheel
elif d == ScrollDirection.down:
of ScrollDirection.down:
this.userZoom /= ZoomFactorMouseWheel
if (this.userZoom < 1):
if this.userZoom < 1:
this.userZoom = 1
else:
return gdk.EVENT_PROPAGATE
if this.zoomNearMousepointer:
let (x, y) = event.getCoords
this.updateAdjustmentsAndPaint((this.hadjustment.value + x) * (this.userZoom / z0 - 1),
(this.vadjustment.value + y) * (this.userZoom / z0 - 1))
(this.vadjustment.value + y) * (this.userZoom / z0 - 1))
else: # zoom to center
this.updateAdjustmentsAndPaint((this.hadjustment.value +
this.darea.allocatedWidth.float * 0.5) * (this.userZoom / z0 - 1),
(this.vadjustment.value + this.darea.allocatedHeight.float * 0.5) * (this.userZoom / z0 - 1))
return gdk.EVENT_STOP
proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
var (x, y) = event.getCoords
@ -208,49 +232,49 @@ proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
echo "buttonPressEvent", x, " ", y
(x, y) = this.getUserCoordinates(x, y)
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
return gdk.EVENT_STOP
# zoom into selected rectangle and center it
# math: we first center the selection rectangle, and then compensate for translation due to scale
proc buttonReleaseEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
var (x, y) = event.getCoords
var b = getButton(event)
let (x, y) = event.getCoords
let b = getButton(event)
if b == 1:
this.selecting = false
let z1 = min(this.darea.allocatedWidth.float / (
this.lastButtonDownPosX - x).abs, this.darea.allocatedHeight.float / (
this.lastButtonDownPosY - y).abs)
let z1 = min(this.darea.allocatedWidth.float / (this.lastButtonDownPosX - x).abs,
this.darea.allocatedHeight.float / (this.lastButtonDownPosY - y).abs)
if z1 < ZoomFactorSelectMax: # else selection rectangle will persist, we may output a message...
this.userZoom *= z1
this.updateAdjustmentsAndPaint(
((2 * this.hadjustment.value + x + this.lastButtonDownPosX) * z1 -
this.darea.allocatedWidth.float) * 0.5 - this.hadjustment.value,
((2 * this.vadjustment.value + y + this.lastButtonDownPosY) * z1 -
this.darea.allocatedHeight.float) * 0.5 - this.vadjustment.value)
((x + this.lastButtonDownPosX) * z1 - this.darea.allocatedWidth.float) * 0.5 + this.hadjustment.value * (z1 - 1),
((y + this.lastButtonDownPosY) * z1 - this.darea.allocatedHeight.float) * 0.5 + this.vadjustment.value * (z1 - 1))
return gdk.EVENT_STOP
return gdk.EVENT_PROPAGATE
proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
pda.paint
pda.darea.queueDrawArea(0, 0, pda.darea.allocatedWidth, pda.darea.allocatedHeight)
proc newPDA: PDA =
initGrid(result)
let da = newDrawingArea()
result.darea = da
da.setHExpand
da.setVExpand
da.connect("draw", drawingAreaDrawCb, result)
da.connect("configure-event", dareaConfigureCallback, result)
da.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease,
EventFlag.scroll, button1Motion, button2Motion, pointerMotionHint})
da.connect("motion-notify-event", onMotion, result)
da.connect("scroll_event", scrollEvent, result)
da.connect("button_press_event", buttonPressEvent, result)
da.connect("button_release_event", buttonReleaseEvent, result)
result.zoomNearMousepointer = ZoomNearMousepointer # mouse wheel zooming
result.userZoom = 1.0
result.darea = newDrawingArea()
result.darea.setHExpand
result.darea.setVExpand
result.darea.connect("draw", drawingAreaDrawCb, result)
result.darea.connect("configure-event", dareaConfigureCallback, result)
result.darea.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease,
EventFlag.scroll, button1Motion, button2Motion, pointerMotionHint})
result.darea.connect("motion-notify-event", onMotion, result)
result.darea.connect("scroll_event", scrollEvent, result)
result.darea.connect("button_press_event", buttonPressEvent, result)
result.darea.connect("button_release_event", buttonReleaseEvent, result)
result.hadjustment = newPosAdj()
result.hadjustment.handlerID = result.hadjustment.connect("value-changed",
onAdjustmentEvent, result)
result.hadjustment.handlerID = result.hadjustment.connect("value-changed", onAdjustmentEvent, result)
result.vadjustment = newPosAdj()
result.vadjustment.handlerID = result.vadjustment.connect("value-changed",
onAdjustmentEvent, result)
result.vadjustment.handlerID = result.vadjustment.connect("value-changed", onAdjustmentEvent, result)
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
result.hscrollbar.setHExpand
@ -261,9 +285,13 @@ proc newPDA: PDA =
result.attach(result.vscrollbar, 1, 0, 1, 1)
result.attach(result.hscrollbar, 0, 1, 1, 1)
proc appActivate(app: Application; initData: ref PDA_Data) =
proc appStartup(app: Application) =
echo "appStartup"
proc appActivate(app: Application; initData: PDA_Data) =
let window = newApplicationWindow(app)
window.title = "Drawing example"
# window.defaultSize = initData.windowSize
window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
let pda = newPDA()
pda.drawWorld = initData.draw
@ -273,14 +301,13 @@ proc appActivate(app: Application; initData: ref PDA_Data) =
proc newDisplay*(initData: PDA_Data) =
let app = newApplication("org.gtk.example")
var d = new (ref PDA_Data)
d[] = initData
connect(app, "activate", appActivate, d)
connect(app, "startup", appStartup)
connect(app, "activate", appActivate, initData)
discard run(app)
when isMainModule:
const # arbitrary locations for our data
const # arbitrary locations for our data
DataX = 150.0
DataY = 250.0
DataWidth = 200.0
@ -300,18 +327,13 @@ when isMainModule:
cr.setSource(0, 0, 0)
cr.setLineWidth(2)
var i = 0.0
while true:
if min(DataWidth - 2.0 * i, DataHeight - 2.0 * i) <= 0:
break
while min(DataWidth - 2 * i, DataHeight - 2 * i) > 0:
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
i += 10.0
i += 10
cr.stroke
proc test =
var data: PDA_Data
data.draw = drawWorld
data.extents = worldExtents
data.windowSize = (800, 600)
let data = PDA_Data(draw: drawWorld, extents: worldExtents, windowSize: (800, 600))
newDisplay(data)
test()
test() # 337 lines