fixes for builder get() procs and cairo

This commit is contained in:
Stefan Salewski 2019-04-27 09:02:37 +02:00
commit 4c93a26d04
7 changed files with 3212 additions and 267 deletions

View file

@ -1,45 +1,25 @@
# Plain demo for zooming, panning, scrolling with GTK DrawingArea
# (c) S. Salewski, 21-DEC-2010 (initial Ruby version)
# Nim version April 2019 -- well first draft :-)
# Nim version April 2019
# License MIT
# This version of the demo program uses a separate proc paint()
# which allocates a custom surface for buffered drawing.
# That may be not really necessary, for simple drawings doing all
# the drawing in the "draw" call back is easier and faster. But for
# more complicated drawing operations, for example when using a
# background grid, which is a bit larger than the window size and
# is reused when scrolling, a custom surface may be useful.
# And finally that custom surface and custom cairo context is an
# important test for the language bindings.
import gintro/[gtk, gdk, glib, gobject, gio, cairo]
const
DataX = 150.0
DataY = 250.0
DataWidth = 200.0
DataHeight = 120.0
# The following two functions are intended to be user defined -- we have still
# to investigate how to do that best.
# bounding box of user data -- x, y, w, h -- top left corner, width, height
proc getWorldExtends(): (float, float, float, float) =
(DataX, DataY, DataWidth, DataHeight) # current extents of our user world
# draw to cairo context
proc drawWorld(cr: cairo.Context) =
cr.setSource(1, 1, 1)
cr.paint
cr.setSource(0, 0, 0)
cr.setLineWidth(2)
cr.rectangle(DataX, DataY, DataWidth, DataHeight)
var i = 10.0
while true:
if min(DataWidth - 2.0 * i, DataHeight - 2.0 * i) <= 0:
break
cr.rectangle(DataX + i, DataY + i, DataWidth - 2.0 * i,
DataHeight - 2.0 * i)
i += 10.0
cr.stroke
const
ZOOM_FACTOR_MOUSE_WHEEL = 1.1
ZOOM_FACTOR_SELECT_MAX = 10 # ignore zooming in tiny selection
ZOOM_NEAR_MOUSEPOINTER = true # mouse wheel zooming -- to mousepointer or center
SELECT_RECT_COL = [0.0, 0, 1, 0.5] # blue with transparency
ZoomFactorMouseWheel = 1.1
ZoomFactorSelectMax = 10 # ignore zooming in tiny selection
ZoomNearMousepointer = true # mouse wheel zooming -- to mouse-pointer or center
SelectRectCol = [0.0, 0, 1, 0.5] # blue with transparency
discard """
Zooming, scrolling, panning...
@ -68,7 +48,7 @@ and translate the origin of our data to (0, 0)
Zooming: Mouse wheel or selecting a rectangle with left mouse button pressed
Scrolling: Scrollbars
Panning: Moving mouse while middle mouse button pressed
Panning: Moving mouse while middle mouse button pressed
"""
# drawing area and scroll bars in 2x2 table (PDA == Plain Drawing Area)
@ -80,6 +60,12 @@ type
proc newPosAdj: PosAdj =
initAdjustment(result, 0, 0, 1, 1, 10, 1)
type
PDA_Data* = object
draw*: proc (cr: Context)
extents*: proc (): tuple[x, y, w, h: float]
windowSize*: tuple[w, h: int]
type
PDA = ref object of Grid
zoomNearMousepointer: bool
@ -102,6 +88,8 @@ type
lastMousePosY: float
zoomRectX1: float
zoomRectY1: float
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
@ -116,7 +104,8 @@ proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
cr.setSource(0, 0, 0)
cr.setLineWidth(2)
cr.stroke
return true # TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
return SignalEventStopPropagation
#return true # TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
# clamp to correct values, 0 <= value <= (adj.upper - adj.pageSize), block calling onAdjustmentEvent()
proc updateVal(adj: PosAdj; d: float) =
@ -126,35 +115,30 @@ proc updateVal(adj: PosAdj; d: float) =
proc updateAdjustments(this: PDA; dx, dy: float) =
this.hadjustment.setUpper(this.darea.allocatedWidth.float * this.userZoom)
this.vadjustment.setUpper(this.darea.allocatedHeight.float *
this.userZoom)
this.vadjustment.setUpper(this.darea.allocatedHeight.float * this.userZoom)
this.hadjustment.setPageSize(this.darea.allocatedWidth.float)
this.vadjustment.setPageSize(this.darea.allocatedHeight.float)
updateVal(this.hadjustment, dx)
updateVal(this.vadjustment, dy)
# Maybe we should fix this for latest GTK3, see
# https://developer.gnome.org/gdk3/stable/gdk3-Cairo-Interaction.html
# so use gdk_drawing_context_get_cairo_context ()
# or maybe even better do all drwing from within "draw" callback?
# maybe for optimization we should only allocate a new surface when its size has changed.
proc paint(this: PDA) =
if this.surf != nil:
cairosurfaceDestroy(this.surf.impl) # we may have to fix that later for automatically destroy
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
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)
drawWorld(cr) # call the user provided drawing fonction
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
proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure;
this: PDA): bool =
proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure; this: PDA): bool =
this.updateAdjustments(0, 0)
(this.dataX, this.dataY, this.dataWidth,
this.dataHeight) = getWorldExtends() # query user defined size
this.dataHeight) = this.extents() # query user defined size
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
this.darea.allocatedHeight.float / this.dataHeight)
this.paint
@ -162,8 +146,7 @@ proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure;
proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
this.updateAdjustments(dx, dy)
this.paint
this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth,
this.darea.allocatedHeight)
this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.allocatedHeight)
# event coordinates to user space
proc getUserCoordinates(this: PDA; eventX, eventY: float): (float, float) =
@ -175,16 +158,13 @@ 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
#(x, y) = this.getUserCoordinates(x, y)
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)
this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.allocatedHeight)
elif button2 in state: # panning
this.updateAdjustmentsAndPaint(this.lastMousePosX - x,
this.lastMousePosY - y)
this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
this.lastMousePosX = x
this.lastMousePosY = y
#event.request # request more motion events ?
@ -197,25 +177,20 @@ proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
var d = getScrollDirection(event)
var (x, y) = event.getCoords
if d == ScrollDirection.up:
this.userZoom *= ZOOM_FACTOR_MOUSE_WHEEL
this.userZoom *= ZoomFactorMouseWheel
elif d == ScrollDirection.down:
this.userZoom /= ZOOM_FACTOR_MOUSE_WHEEL
this.userZoom /= ZoomFactorMouseWheel
if (this.userZoom < 1):
this.userZoom = 1
if this.zoomNearMousepointer:
this.updateAdjustmentsAndPaint((this.hadjustment.value + x) * (
this.userZoom / z0 - 1),
(this.vadjustment.value + y) * (
this.userZoom / z0 - 1))
this.updateAdjustmentsAndPaint((this.hadjustment.value + x) * (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))
(this.vadjustment.value + this.darea.allocatedHeight.float * 0.5) * (this.userZoom / z0 - 1))
proc buttonPressEvent(darea: DrawingArea; event: EventButton;
this: PDA): bool =
proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
var (x, y) = event.getCoords
this.lastMousePosX = x
this.lastMousePosY = y
@ -226,8 +201,7 @@ proc buttonPressEvent(darea: DrawingArea; event: EventButton;
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
# zoom into selected rectangle and center it
proc buttonReleaseEvent(darea: DrawingArea; event: EventButton;
this: PDA): bool =
proc buttonReleaseEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
var (x, y) = event.getCoords
var b = getButton(event)
if b == 1:
@ -235,7 +209,7 @@ proc buttonReleaseEvent(darea: DrawingArea; event: EventButton;
let z1 = min(this.darea.allocatedWidth.float / (
this.lastButtonDownPosX - x).abs, this.darea.allocatedHeight.float / (
this.lastButtonDownPosY - y).abs)
if z1 < ZOOM_FACTOR_SELECT_MAX: # else selection rectangle will persist, we may output a message...
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 -
@ -245,24 +219,19 @@ proc buttonReleaseEvent(darea: DrawingArea; event: EventButton;
proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
pda.paint
pda.darea.queueDrawArea(0, 0, pda.darea.allocatedWidth,
pda.darea.allocatedHeight)
pda.darea.queueDrawArea(0, 0, pda.darea.allocatedWidth, pda.darea.allocatedHeight)
proc newPDA: PDA =
initGrid(result)
result.zoomNearMousepointer = ZOOM_NEAR_MOUSEPOINTER # mouse wheel zooming
result.selecting = false
result.zoomNearMousepointer = ZoomNearMousepointer # mouse wheel zooming
result.userZoom = 1.0
result.surf = nil
result.darea = newDrawingArea()
result.darea.setHExpand#(true)
result.darea.setVExpand#(true)
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,
EventFlag.button1Motion, EventFlag.button2Motion,
EventFlag.pointerMotionHint})
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)
@ -275,23 +244,63 @@ proc newPDA: PDA =
onAdjustmentEvent, result)
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
result.hscrollbar.setHExpand#(true)
result.vscrollbar.setVExpand#(true)
result.hscrollbar.setHExpand
result.vscrollbar.setVExpand
result.attach(result.darea, 0, 0, 1, 1)
result.attach(result.vscrollbar, 1, 0, 1, 1)
result.attach(result.hscrollbar, 0, 1, 1, 1)
proc appActivate(app: Application) =
proc appActivate(app: Application; initData: ref PDA_Data) =
let window = newApplicationWindow(app)
window.title = "Drawing example"
window.defaultSize = (400, 400)
window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
let pda = newPDA()
pda.drawWorld = initData.draw
pda.extents = initData.extents
window.add(pda)
showAll(window)
proc main =
proc newDisplay*(initData: PDA_Data) =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
var d = new (ref PDA_Data)
d[] = initData
connect(app, "activate", appActivate, d)
discard run(app)
main()
when isMainModule:
const # arbitrary locations for our data
DataX = 150.0
DataY = 250.0
DataWidth = 200.0
DataHeight = 120.0
# we need two user defined functions -- one gives the extent of the graphics,
# and the other does the cairo drawing using a cairo context.
# bounding box of user data -- x, y, w, h -- top left corner, width, height
proc worldExtents(): (float, float, float, float) =
(DataX, DataY, DataWidth, DataHeight) # current extents of our user world
# draw to cairo context
proc drawWorld(cr: cairo.Context) =
cr.setSource(1, 1, 1)
cr.paint
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
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
i += 10.0
cr.stroke
proc test =
var data: PDA_Data
data.draw = drawWorld
data.extents = worldExtents
data.windowSize = (800, 600)
newDisplay(data)
test()