fixes and new examples
This commit is contained in:
parent
c79299485c
commit
8979188f4c
9 changed files with 888 additions and 185 deletions
367
README.adoc
367
README.adoc
|
|
@ -1,6 +1,6 @@
|
||||||
= High level GTK3 (and GTK4) bindings for the Nim programming language
|
= High level GTK3 (and GTK4) bindings for the Nim programming language
|
||||||
(c) Stefan Salewski
|
(c) Stefan Salewski
|
||||||
//Version 0.5.3
|
//Version 0.5.4
|
||||||
:experimental:
|
:experimental:
|
||||||
:imagesdir: http://ssalewski.de/tmp
|
:imagesdir: http://ssalewski.de/tmp
|
||||||
:source-highlighter: pygments
|
:source-highlighter: pygments
|
||||||
|
|
@ -19,7 +19,7 @@ TIP: A more fancy copy of this document with dark source code background is avai
|
||||||
NOTE: This work is partly based on earlier works of J. Mansour and has been supported by A. Rumpf and other _Nim_ and _GTK/Gnome_ developers.
|
NOTE: This work is partly based on earlier works of J. Mansour and has been supported by A. Rumpf and other _Nim_ and _GTK/Gnome_ developers.
|
||||||
The `combinatorics` module was kindly provided by R. Behrends.
|
The `combinatorics` module was kindly provided by R. Behrends.
|
||||||
|
|
||||||
NOTE: Starting with current release 0.5.3 we do not generate field entries for objects and we do
|
NOTE: Starting with release 0.5.3 we do not generate field entries for objects and we do
|
||||||
not generate class structs and private objects. Also we stopped exporting the low level functions
|
not generate class structs and private objects. Also we stopped exporting the low level functions
|
||||||
like gtk_button_new(). For a real high-level binding we should not need these. If that is a serious
|
like gtk_button_new(). For a real high-level binding we should not need these. If that is a serious
|
||||||
limitation for you, then use release 0.5.2 for now and create an github issue for your use case, we
|
limitation for you, then use release 0.5.2 for now and create an github issue for your use case, we
|
||||||
|
|
@ -1416,16 +1416,216 @@ proc main =
|
||||||
main()
|
main()
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
|
== A ListView example with CSS styling
|
||||||
|
|
||||||
|
Recently C. Eric Cashon provided this example at https://discourse.gnome.org/t/gtk-treeview-cell-color-change/1750/3
|
||||||
|
|
||||||
|
I will show his original code here too, so we can compare it better with the Nim version.
|
||||||
|
We see that Nim code has currently some disadvantages still, for example we have no
|
||||||
|
varargs procs implemented, so setting of properties and attributes is done using GValues,
|
||||||
|
which is typesafe, but not really compact. That is not too bad, but we may consider
|
||||||
|
creating macros to support a more dense, but still typesafe way similar to C's varargs functions.
|
||||||
|
|
||||||
|
[[cell_color1.c]]
|
||||||
|
[source,c]
|
||||||
|
.cell_color1.c
|
||||||
|
----
|
||||||
|
// gcc -Wall cell_color1.c -o cell_color1 `pkg-config --cflags --libs gtk+-3.0`
|
||||||
|
// https://discourse.gnome.org/t/gtk-treeview-cell-color-change/1750/4
|
||||||
|
// C. Eric Cashon
|
||||||
|
|
||||||
|
#include<gtk/gtk.h>
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
ID,
|
||||||
|
PROGRAM,
|
||||||
|
COLOR1,
|
||||||
|
COLOR2,
|
||||||
|
COLUMNS
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
gtk_init(&argc, &argv);
|
||||||
|
|
||||||
|
GtkWidget *window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||||
|
gtk_window_set_title(GTK_WINDOW(window), "Select Cell");
|
||||||
|
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
|
||||||
|
gtk_container_set_border_width(GTK_CONTAINER(window), 20);
|
||||||
|
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
||||||
|
|
||||||
|
GtkTreeIter iter;
|
||||||
|
GtkListStore *store=gtk_list_store_new(COLUMNS, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter, ID, 0, PROGRAM, "Gedit", COLOR1, "DarkCyan", COLOR2, "cyan", -1);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter, ID, 1, PROGRAM, "Gimp", COLOR1, "LightSlateGray", COLOR2, "cyan", -1);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter, ID, 2, PROGRAM, "Inkscape", COLOR1, "DarkCyan", COLOR2, "cyan", -1);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter, ID, 3, PROGRAM, "Firefox", COLOR1, "LightSlateGray", COLOR2, "cyan", -1);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter, ID, 4, PROGRAM, "Calculator", COLOR1, "DarkCyan", COLOR2, "cyan", -1);
|
||||||
|
gtk_list_store_append(store, &iter);
|
||||||
|
gtk_list_store_set(store, &iter, ID, 5, PROGRAM, "Devhelp", COLOR1, "LightSlateGray", COLOR2, "cyan", -1);
|
||||||
|
|
||||||
|
GtkWidget *tree=gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
|
||||||
|
gtk_widget_set_hexpand(tree, TRUE);
|
||||||
|
gtk_widget_set_vexpand(tree, TRUE);
|
||||||
|
g_object_set(tree, "activate-on-single-click", TRUE, NULL);
|
||||||
|
|
||||||
|
GtkTreeSelection *selection=gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
|
||||||
|
gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
|
||||||
|
|
||||||
|
GtkCellRenderer *renderer1=gtk_cell_renderer_text_new();
|
||||||
|
g_object_set(renderer1, "editable", FALSE, NULL);
|
||||||
|
|
||||||
|
GtkCellRenderer *renderer2=gtk_cell_renderer_text_new();
|
||||||
|
g_object_set(renderer2, "editable", TRUE, NULL);
|
||||||
|
|
||||||
|
//Bind the COLOR column to the "cell-background" property.
|
||||||
|
GtkTreeViewColumn *column1=gtk_tree_view_column_new_with_attributes("ID", renderer1, "text", ID, "cell-background", COLOR1, NULL);
|
||||||
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column1);
|
||||||
|
GtkTreeViewColumn *column2 = gtk_tree_view_column_new_with_attributes("Program", renderer2, "text", PROGRAM, "cell-background", COLOR2, NULL);
|
||||||
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column2);
|
||||||
|
|
||||||
|
GtkWidget *grid=gtk_grid_new();
|
||||||
|
gtk_grid_attach(GTK_GRID(grid), tree, 0, 0, 1, 1);
|
||||||
|
|
||||||
|
gtk_container_add(GTK_CONTAINER(window), grid);
|
||||||
|
|
||||||
|
gchar *css_string=g_strdup("treeview{background-color: rgba(0,255,255,1.0); font-size:30pt} treeview:selected{background-color: rgba(255,255,0,1.0); color: rgba(0,0,255,1.0);}");
|
||||||
|
GError *css_error=NULL;
|
||||||
|
GtkCssProvider *provider=gtk_css_provider_new();
|
||||||
|
gtk_css_provider_load_from_data(provider, css_string, -1, &css_error);
|
||||||
|
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
if(css_error!=NULL)
|
||||||
|
{
|
||||||
|
g_print("CSS loader error %s\n", css_error->message);
|
||||||
|
g_error_free(css_error);
|
||||||
|
}
|
||||||
|
g_object_unref(provider);
|
||||||
|
g_free(css_string);
|
||||||
|
|
||||||
|
gtk_widget_show_all(window);
|
||||||
|
|
||||||
|
gtk_main();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
And this is the Nim version, created with c2nim and some manual tuning:
|
||||||
|
|
||||||
|
[[css_colored_listview.nim]]
|
||||||
|
[source,nim]
|
||||||
|
.css_colored_listview.nim
|
||||||
|
----
|
||||||
|
# nim c css_colored_listview.nim
|
||||||
|
import gintro/[gtk, gobject]
|
||||||
|
import gintro/gdk except Window # there is a problem with gdk.Window -- we have to investigate!
|
||||||
|
const # maybe we should use Nim's enum here?
|
||||||
|
Id = 0
|
||||||
|
Program = 1
|
||||||
|
Color1 = 2
|
||||||
|
Color2 = 3
|
||||||
|
Columns = 4
|
||||||
|
|
||||||
|
proc bye(w: Window) =
|
||||||
|
mainQuit()
|
||||||
|
echo "Bye..."
|
||||||
|
|
||||||
|
proc toStringVal(s: string): Value =
|
||||||
|
let gtype = typeFromName("gchararray")
|
||||||
|
discard init(result, gtype)
|
||||||
|
setString(result, s)
|
||||||
|
|
||||||
|
proc toUIntVal(i: int): Value =
|
||||||
|
let gtype = typeFromName("guint")
|
||||||
|
discard init(result, gtype)
|
||||||
|
setUint(result, i)
|
||||||
|
|
||||||
|
proc toBoolVal(b: bool): Value =
|
||||||
|
let gtype = typeFromName("gboolean")
|
||||||
|
discard init(result, gtype)
|
||||||
|
setBoolean(result, b)
|
||||||
|
|
||||||
|
# we use the old gtk style with init() as is used in the C original -- maybe better use modern app sytle
|
||||||
|
proc main() =
|
||||||
|
gtk.init()
|
||||||
|
let window = newWindow()
|
||||||
|
window.title = "Select Cell"
|
||||||
|
window.position = WindowPosition.center
|
||||||
|
window.defaultSize = (500, 500)
|
||||||
|
window.borderWidth = 20
|
||||||
|
connect(window, "destroy", bye)
|
||||||
|
var iter: TreeIter
|
||||||
|
var h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"), typeFromName("gchararray")]
|
||||||
|
var store = newListStore(Columns, cast[pointer]( unsafeaddr h)) # this cast is ugly, we should fix it in the bindings soon.
|
||||||
|
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
|
||||||
|
for i, n in progNames:
|
||||||
|
store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original
|
||||||
|
store.setValue(iter, Id, toUIntVal(i))
|
||||||
|
store.setValue(iter, Program, toStringVal(n))
|
||||||
|
store.setValue(iter, Color1, toStringVal(if (i and 1) != 0: "LightSlateGray" else: "DarkCyan"))
|
||||||
|
store.setValue(iter, Color2, toStringVal("cyan"))
|
||||||
|
var tree = newTreeViewWithModel(store)
|
||||||
|
tree.setHexpand
|
||||||
|
tree.setVexpand
|
||||||
|
setProperty(tree, "activate-on-single-click", toBoolVal(true))
|
||||||
|
var selection = tree.getSelection()
|
||||||
|
selection.setMode(SelectionMode.single)
|
||||||
|
var renderer1 = newCellRendererText()
|
||||||
|
setProperty(renderer1, "editable", toBoolVal(false))
|
||||||
|
var renderer2 = newCellRendererText()
|
||||||
|
setProperty(renderer2, "editable", toBoolVal(true))
|
||||||
|
## Bind the Color column to the "cell-background" property.
|
||||||
|
var column1 = newTreeViewColumn()
|
||||||
|
column1.setTitle("ID")
|
||||||
|
column1.packStart(renderer1, true)
|
||||||
|
column1.addAttribute(renderer1, "text", Id)
|
||||||
|
column1.addAttribute(renderer1, "cell-background", Color1)
|
||||||
|
discard tree.appendColumn(column1)
|
||||||
|
var column2 = newTreeViewColumn()
|
||||||
|
column1.setTitle("Program")
|
||||||
|
column1.packStart(renderer2, true)
|
||||||
|
column1.addAttribute(renderer2, "text", Program)
|
||||||
|
column1.addAttribute(renderer2, "cell-background", Color2)
|
||||||
|
discard tree.appendColumn(column2)
|
||||||
|
var grid = newGrid() # grid with only one occupied cell makes not much sense here -- but so we can add more widgets later
|
||||||
|
grid.attach(tree, 0, 0, 1, 1)
|
||||||
|
window.add(grid)
|
||||||
|
const cssString = # note: big font selected intentionally
|
||||||
|
"""treeview{background-color: rgba(0,255,255,1.0); font-size:30pt} treeview:selected{background-color: rgba(255,255,0,1.0);
|
||||||
|
color: rgba(0,0,255,1.0);}"""
|
||||||
|
var provider = newCssProvider()
|
||||||
|
discard provider.loadFromData(cssString)
|
||||||
|
addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
|
window.showAll
|
||||||
|
gtk.main()
|
||||||
|
|
||||||
|
main()
|
||||||
|
----
|
||||||
|
|
||||||
|
When you compile with `nim c -d:release -d:danger --passC:-flto css_colored_listview.nim`
|
||||||
|
you will get an executable size of 80k, which is big compared with the 20k of the C version, but
|
||||||
|
not too bad.
|
||||||
|
|
||||||
== A more advanced example for cairo drawing with zooming, panning, scrolling
|
== A more advanced example for cairo drawing with zooming, panning, scrolling
|
||||||
|
|
||||||
The following code is a plain Nim version of a drawing demo which I wrote some years ago in Ruby (http://ssalewski.de/PetEd-Demo.html.en).
|
The following code is a plain Nim version of a drawing demo which I wrote some years ago in Ruby (http://ssalewski.de/PetEd-Demo.html.en).
|
||||||
With the latest fix to Nim GTK3 bindings it seems to work fine, but some rewriting for optimized performance
|
Cairo surface is currently manually freed, because GC may have a too large delay.
|
||||||
may be needed, I have to investigate that. Cairo surface is currently manually freed, because GC may have a too large delay.
|
|
||||||
|
|
||||||
You can resize the window and zoom in with the mouse wheel. When zoomed in scroll bars appear. You
|
You can resize the window and zoom in with the mouse wheel. When zoomed in scroll bars appear. You
|
||||||
can hold the middle mouse button pressed while moving the mouse for panning, and you can press left mouse button
|
can hold the middle mouse button pressed while moving the mouse for panning, and you can press left mouse button
|
||||||
and move the mouse to first draw a selection rectangle and zoom into it when releasing the mouse button.
|
and move the mouse to first draw a selection rectangle and zoom into it when releasing the mouse button.
|
||||||
|
|
||||||
|
In the examples directory there is also a simplified version called `simpledrawingarea.nim` which does all
|
||||||
|
the drawings in the draw callback, without using a buffering surface. This is generally preferable for
|
||||||
|
plain applications.
|
||||||
|
|
||||||
[[drawingarea.nim]]
|
[[drawingarea.nim]]
|
||||||
[source,nim]
|
[source,nim]
|
||||||
.drawingarea.nim
|
.drawingarea.nim
|
||||||
|
|
@ -1445,6 +1645,8 @@ and move the mouse to first draw a selection rectangle and zoom into it when rel
|
||||||
# And finally that custom surface and custom cairo context is an
|
# And finally that custom surface and custom cairo context is an
|
||||||
# important test for the language bindings.
|
# 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]
|
import gintro/[gtk, gdk, glib, gobject, gio, cairo]
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -1483,7 +1685,7 @@ 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)
|
# drawing area and scroll bars in 2x2 grid (PDA == Plain Drawing Area)
|
||||||
|
|
||||||
type
|
type
|
||||||
PosAdj = ref object of Adjustment
|
PosAdj = ref object of Adjustment
|
||||||
|
|
@ -1504,6 +1706,8 @@ type
|
||||||
selecting: bool
|
selecting: bool
|
||||||
userZoom: float
|
userZoom: float
|
||||||
surf: Surface
|
surf: Surface
|
||||||
|
pattern: Pattern
|
||||||
|
cr: cairo.Context
|
||||||
darea: DrawingArea
|
darea: DrawingArea
|
||||||
hadjustment: PosAdj
|
hadjustment: PosAdj
|
||||||
vadjustment: PosAdj
|
vadjustment: PosAdj
|
||||||
|
|
@ -1520,23 +1724,24 @@ type
|
||||||
lastMousePosY: float
|
lastMousePosY: float
|
||||||
zoomRectX1: float
|
zoomRectX1: float
|
||||||
zoomRectY1: float
|
zoomRectY1: float
|
||||||
|
oldSizeX: int
|
||||||
|
oldSizeY: int
|
||||||
drawWorld: proc (cr: Context)
|
drawWorld: proc (cr: Context)
|
||||||
extents: proc (): tuple[x, y, w, h: float]
|
extents: proc (): tuple[x, y, w, h: float]
|
||||||
|
|
||||||
proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
|
proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
|
||||||
if this.surf.isNil: return
|
if this.pattern.isNil: return
|
||||||
cr.setSource(patternCreateForSurface(this.surf))
|
cr.setSource(this.pattern)
|
||||||
cr.paint
|
cr.paint
|
||||||
if this.selecting:
|
if this.selecting:
|
||||||
cr.rectangle(this.lastButtonDownPosX, this.lastButtonDownPosY,
|
cr.rectangle(this.lastButtonDownPosX, this.lastButtonDownPosY,
|
||||||
this.zoomRectX1 - this.lastButtonDownPosX,
|
this.zoomRectX1 - this.lastButtonDownPosX, this.zoomRectY1 - this.lastButtonDownPosY)
|
||||||
this.zoomRectY1 - this.lastButtonDownPosY)
|
|
||||||
cr.setSource(0, 0, 1, 0.5) # SELECT_RECT_COL) # 0, 0, 1, 0.5
|
cr.setSource(0, 0, 1, 0.5) # SELECT_RECT_COL) # 0, 0, 1, 0.5
|
||||||
cr.fillPreserve
|
cr.fillPreserve
|
||||||
cr.setSource(0, 0, 0)
|
cr.setSource(0, 0, 0)
|
||||||
cr.setLineWidth(2)
|
cr.setLineWidth(2)
|
||||||
cr.stroke
|
cr.stroke
|
||||||
return SignalEventStopPropagation
|
return gdk.EVENT_STOP # EVENT_PROPAGATE
|
||||||
#return true # TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
|
#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()
|
# clamp to correct values, 0 <= value <= (adj.upper - adj.pageSize), block calling onAdjustmentEvent()
|
||||||
|
|
@ -1553,27 +1758,47 @@ proc updateAdjustments(this: PDA; dx, dy: float) =
|
||||||
updateVal(this.hadjustment, dx)
|
updateVal(this.hadjustment, dx)
|
||||||
updateVal(this.vadjustment, dy)
|
updateVal(this.vadjustment, dy)
|
||||||
|
|
||||||
# maybe for optimization we should only allocate a new surface when its size has changed.
|
|
||||||
proc paint(this: PDA) =
|
proc paint(this: PDA) =
|
||||||
if this.surf != nil:
|
# echo "paint"
|
||||||
destroy(this.surf) # manually destroy surface -- GC would do it for us, but GC is slow...
|
this.cr.save
|
||||||
this.surf = this.darea.window.createSimilarSurface(Content.color,
|
this.cr.translate(this.hadjustment.upper * 0.5 - this.hadjustment.value, # our origin is the center
|
||||||
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)
|
this.vadjustment.upper * 0.5 - this.vadjustment.value)
|
||||||
cr.scale(this.fullScale * this.userZoom, this.fullScale * this.userZoom)
|
this.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.cr.translate(-this.dataX - this.dataWidth * 0.5, -this.dataY - this.dataHeight * 0.5)
|
||||||
this.drawWorld(cr) # call the user provided drawing function
|
this.drawWorld(this.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.restore
|
||||||
|
|
||||||
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.dataX, this.dataY, this.dataWidth,
|
||||||
this.dataHeight) = this.extents() # query user defined size
|
this.dataHeight) = this.extents() # query user defined size
|
||||||
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
|
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
|
||||||
this.darea.allocatedHeight.float / this.dataHeight)
|
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
|
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) =
|
proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
|
||||||
this.updateAdjustments(dx, dy)
|
this.updateAdjustments(dx, dy)
|
||||||
|
|
@ -1590,37 +1815,45 @@ proc getUserCoordinates(this: PDA; eventX, eventY: float): (float, float) =
|
||||||
proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
|
proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
|
||||||
let state = getState(event)
|
let state = getState(event)
|
||||||
let (x, y) = event.getCoords
|
let (x, y) = event.getCoords
|
||||||
if (state.contains(button1)): # selecting
|
if state.contains(button1): # selecting
|
||||||
this.selecting = true
|
this.selecting = true
|
||||||
this.zoomRectX1 = x
|
this.zoomRectX1 = x
|
||||||
this.zoomRectY1 = y
|
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
|
elif button2 in state: # panning
|
||||||
this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
|
this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
|
||||||
|
else:
|
||||||
|
return gdk.EVENT_PROPAGATE
|
||||||
this.lastMousePosX = x
|
this.lastMousePosX = x
|
||||||
this.lastMousePosY = y
|
this.lastMousePosY = y
|
||||||
|
return gdk.EVENT_STOP
|
||||||
#event.request # request more motion events ?
|
#event.request # request more motion events ?
|
||||||
|
|
||||||
# zooming with mouse wheel -- data near mouse pointer should not move if possible!
|
# 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)
|
# hadjustment.value + event.x is the position in our zoomed_in world, (userZoom / z0 - 1)
|
||||||
# is the relative movement caused by zooming
|
# 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 =
|
proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
|
||||||
let z0 = this.userZoom
|
let z0 = this.userZoom
|
||||||
var d = getScrollDirection(event)
|
case getScrollDirection(event)
|
||||||
var (x, y) = event.getCoords
|
of ScrollDirection.up:
|
||||||
if d == ScrollDirection.up:
|
|
||||||
this.userZoom *= ZoomFactorMouseWheel
|
this.userZoom *= ZoomFactorMouseWheel
|
||||||
elif d == ScrollDirection.down:
|
of ScrollDirection.down:
|
||||||
this.userZoom /= ZoomFactorMouseWheel
|
this.userZoom /= ZoomFactorMouseWheel
|
||||||
if (this.userZoom < 1):
|
if this.userZoom < 1:
|
||||||
this.userZoom = 1
|
this.userZoom = 1
|
||||||
|
else:
|
||||||
|
return gdk.EVENT_PROPAGATE
|
||||||
if this.zoomNearMousepointer:
|
if this.zoomNearMousepointer:
|
||||||
|
let (x, y) = event.getCoords
|
||||||
this.updateAdjustmentsAndPaint((this.hadjustment.value + x) * (this.userZoom / z0 - 1),
|
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
|
else: # zoom to center
|
||||||
this.updateAdjustmentsAndPaint((this.hadjustment.value +
|
this.updateAdjustmentsAndPaint((this.hadjustment.value +
|
||||||
this.darea.allocatedWidth.float * 0.5) * (this.userZoom / z0 - 1),
|
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))
|
||||||
|
return gdk.EVENT_STOP
|
||||||
|
|
||||||
proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
||||||
var (x, y) = event.getCoords
|
var (x, y) = event.getCoords
|
||||||
|
|
@ -1631,23 +1864,24 @@ proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
||||||
echo "buttonPressEvent", x, " ", y
|
echo "buttonPressEvent", x, " ", y
|
||||||
(x, y) = this.getUserCoordinates(x, y)
|
(x, y) = this.getUserCoordinates(x, y)
|
||||||
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
|
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
|
||||||
|
return gdk.EVENT_STOP
|
||||||
|
|
||||||
# zoom into selected rectangle and center it
|
# 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 =
|
proc buttonReleaseEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
||||||
var (x, y) = event.getCoords
|
let (x, y) = event.getCoords
|
||||||
var b = getButton(event)
|
let b = getButton(event)
|
||||||
if b == 1:
|
if b == 1:
|
||||||
this.selecting = false
|
this.selecting = false
|
||||||
let z1 = min(this.darea.allocatedWidth.float / (
|
let z1 = min(this.darea.allocatedWidth.float / (this.lastButtonDownPosX - x).abs,
|
||||||
this.lastButtonDownPosX - x).abs, this.darea.allocatedHeight.float / (
|
this.darea.allocatedHeight.float / (this.lastButtonDownPosY - y).abs)
|
||||||
this.lastButtonDownPosY - y).abs)
|
|
||||||
if z1 < ZoomFactorSelectMax: # 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.userZoom *= z1
|
||||||
this.updateAdjustmentsAndPaint(
|
this.updateAdjustmentsAndPaint(
|
||||||
((2 * this.hadjustment.value + x + this.lastButtonDownPosX) * z1 -
|
((x + this.lastButtonDownPosX) * z1 - this.darea.allocatedWidth.float) * 0.5 + this.hadjustment.value * (z1 - 1),
|
||||||
this.darea.allocatedWidth.float) * 0.5 - this.hadjustment.value,
|
((y + this.lastButtonDownPosY) * z1 - this.darea.allocatedHeight.float) * 0.5 + this.vadjustment.value * (z1 - 1))
|
||||||
((2 * this.vadjustment.value + y + this.lastButtonDownPosY) * z1 -
|
return gdk.EVENT_STOP
|
||||||
this.darea.allocatedHeight.float) * 0.5 - this.vadjustment.value)
|
return gdk.EVENT_PROPAGATE
|
||||||
|
|
||||||
proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
|
proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
|
||||||
pda.paint
|
pda.paint
|
||||||
|
|
@ -1655,36 +1889,41 @@ proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
|
||||||
|
|
||||||
proc newPDA: PDA =
|
proc newPDA: PDA =
|
||||||
initGrid(result)
|
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.zoomNearMousepointer = ZoomNearMousepointer # mouse wheel zooming
|
||||||
result.userZoom = 1.0
|
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 = newPosAdj()
|
||||||
result.hadjustment.handlerID = result.hadjustment.connect("value-changed",
|
result.hadjustment.handlerID = result.hadjustment.connect("value-changed", onAdjustmentEvent, result)
|
||||||
onAdjustmentEvent, result)
|
|
||||||
result.vadjustment = newPosAdj()
|
result.vadjustment = newPosAdj()
|
||||||
result.vadjustment.handlerID = result.vadjustment.connect("value-changed",
|
result.vadjustment.handlerID = result.vadjustment.connect("value-changed", onAdjustmentEvent, result)
|
||||||
onAdjustmentEvent, result)
|
|
||||||
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
|
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
|
||||||
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
|
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
|
||||||
result.hscrollbar.setHExpand
|
result.hscrollbar.setHExpand
|
||||||
result.vscrollbar.setVExpand
|
result.vscrollbar.setVExpand
|
||||||
|
result.hscrollbar.connect("size-allocate", hscrollbarSizeAllocateCallback, result)
|
||||||
|
result.vscrollbar.connect("size-allocate", vscrollbarSizeAllocateCallback, result)
|
||||||
result.attach(result.darea, 0, 0, 1, 1)
|
result.attach(result.darea, 0, 0, 1, 1)
|
||||||
result.attach(result.vscrollbar, 1, 0, 1, 1)
|
result.attach(result.vscrollbar, 1, 0, 1, 1)
|
||||||
result.attach(result.hscrollbar, 0, 1, 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)
|
let window = newApplicationWindow(app)
|
||||||
window.title = "Drawing example"
|
window.title = "Drawing example"
|
||||||
|
# window.defaultSize = initData.windowSize
|
||||||
window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
|
window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
|
||||||
let pda = newPDA()
|
let pda = newPDA()
|
||||||
pda.drawWorld = initData.draw
|
pda.drawWorld = initData.draw
|
||||||
|
|
@ -1694,14 +1933,13 @@ proc appActivate(app: Application; initData: ref PDA_Data) =
|
||||||
|
|
||||||
proc newDisplay*(initData: PDA_Data) =
|
proc newDisplay*(initData: PDA_Data) =
|
||||||
let app = newApplication("org.gtk.example")
|
let app = newApplication("org.gtk.example")
|
||||||
var d = new (ref PDA_Data)
|
connect(app, "startup", appStartup)
|
||||||
d[] = initData
|
connect(app, "activate", appActivate, initData)
|
||||||
connect(app, "activate", appActivate, d)
|
|
||||||
discard run(app)
|
discard run(app)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
|
|
||||||
const # arbitrary locations for our data
|
const # arbitrary locations for our data
|
||||||
DataX = 150.0
|
DataX = 150.0
|
||||||
DataY = 250.0
|
DataY = 250.0
|
||||||
DataWidth = 200.0
|
DataWidth = 200.0
|
||||||
|
|
@ -1721,21 +1959,16 @@ when isMainModule:
|
||||||
cr.setSource(0, 0, 0)
|
cr.setSource(0, 0, 0)
|
||||||
cr.setLineWidth(2)
|
cr.setLineWidth(2)
|
||||||
var i = 0.0
|
var i = 0.0
|
||||||
while true:
|
while min(DataWidth - 2 * i, DataHeight - 2 * i) > 0:
|
||||||
if min(DataWidth - 2.0 * i, DataHeight - 2.0 * i) <= 0:
|
|
||||||
break
|
|
||||||
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
|
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
|
||||||
i += 10.0
|
i += 10
|
||||||
cr.stroke
|
cr.stroke
|
||||||
|
|
||||||
proc test =
|
proc test =
|
||||||
var data: PDA_Data
|
let data = PDA_Data(draw: drawWorld, extents: worldExtents, windowSize: (800, 600))
|
||||||
data.draw = drawWorld
|
|
||||||
data.extents = worldExtents
|
|
||||||
data.windowSize = (800, 600)
|
|
||||||
newDisplay(data)
|
newDisplay(data)
|
||||||
|
|
||||||
test()
|
test() # 337 lines
|
||||||
----
|
----
|
||||||
|
|
||||||
We can use this module as a library easily and get this simple drawing tool with full zoom and scroll support:
|
We can use this module as a library easily and get this simple drawing tool with full zoom and scroll support:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# nim c connect_args.nim
|
# nim c connect_args.nim
|
||||||
import gintro/[gtk, glib, gobject, gio]
|
import gintro/[gtk, gobject, gio]
|
||||||
|
|
||||||
type
|
type
|
||||||
O = object
|
O = object
|
||||||
i: int
|
i, j: int
|
||||||
|
|
||||||
proc b1Callback(button: Button; str: string) =
|
proc b1Callback(button: Button; str: string) =
|
||||||
echo str
|
echo str
|
||||||
|
|
@ -12,7 +12,7 @@ proc b2Callback(button: Button; o: O) =
|
||||||
echo "Value of field i in object o = ", o.i
|
echo "Value of field i in object o = ", o.i
|
||||||
|
|
||||||
proc b3Callback(button: Button; r: ref O) =
|
proc b3Callback(button: Button; r: ref O) =
|
||||||
echo "Value of field i in ref to object O = ", r.i
|
echo "Value of field i in ref to object o = ", r.i
|
||||||
|
|
||||||
proc b4Callback(button: Button; w: ApplicationWindow) =
|
proc b4Callback(button: Button; w: ApplicationWindow) =
|
||||||
if w.title == "Nim with GTK3":
|
if w.title == "Nim with GTK3":
|
||||||
|
|
@ -20,27 +20,23 @@ proc b4Callback(button: Button; w: ApplicationWindow) =
|
||||||
else:
|
else:
|
||||||
w.title = "Nim with GTK3"
|
w.title = "Nim with GTK3"
|
||||||
|
|
||||||
proc appActivate (app: Application) =
|
proc appActivate(app: Application) =
|
||||||
var o: O
|
let
|
||||||
var r: ref O
|
o = O(i: 1234567)
|
||||||
new r
|
r = (ref O)(i: 7654321)
|
||||||
o.i = 1234567
|
window = newApplicationWindow(app)
|
||||||
r.i = 7654321
|
box = newBox(Orientation.vertical, 0)
|
||||||
let window = newApplicationWindow(app)
|
b1 = newButton("Nim with GTK3")
|
||||||
let box = newBox(Orientation.vertical, 0)
|
b2 = newButton("Passing an object from stack")
|
||||||
|
b3 = newButton("Passing an object from heap")
|
||||||
|
b4 = newButton("Passing a Widget")
|
||||||
window.title = "Parameters for callbacks"
|
window.title = "Parameters for callbacks"
|
||||||
let b1 = newButton("Nim with GTK3")
|
b1.connect("clicked", b1Callback, "is much fun.")
|
||||||
let b2 = newButton("Passing an object from stack")
|
b2.connect("clicked", b2Callback, o)
|
||||||
let b3 = newButton("Passing an object from heap")
|
b3.connect("clicked", b3Callback, r)
|
||||||
let b4 = newButton("Passing a Widget")
|
b4.connect("clicked", b4Callback, window)
|
||||||
b1.connect("clicked", b1Callback, "is much fun.")
|
for b in [b1, b2, b3, b4]:
|
||||||
b2.connect("clicked", b2Callback, o)
|
box.add(b)
|
||||||
b3.connect("clicked", b3Callback, r)
|
|
||||||
b4.connect("clicked", b4Callback, window)
|
|
||||||
box.add(b1)
|
|
||||||
box.add(b2)
|
|
||||||
box.add(b3)
|
|
||||||
box.add(b4)
|
|
||||||
window.add(box)
|
window.add(box)
|
||||||
window.showAll
|
window.showAll
|
||||||
|
|
||||||
|
|
|
||||||
88
examples/gtk3/css_colored_listview.nim
Normal file
88
examples/gtk3/css_colored_listview.nim
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
## gcc -Wall cell_color1.c -o cell_color1 `pkg-config --cflags --libs gtk+-3.0`
|
||||||
|
## https://discourse.gnome.org/t/gtk-treeview-cell-color-change/1750/4
|
||||||
|
## C. Eric Cashon
|
||||||
|
|
||||||
|
# nim c css_colored_listview.nim
|
||||||
|
import gintro/[gtk, gobject]
|
||||||
|
import gintro/gdk except Window # there is a problem with gdk.Window -- we have to investigate!
|
||||||
|
const # maybe we should use Nim's enum here?
|
||||||
|
Id = 0
|
||||||
|
Program = 1
|
||||||
|
Color1 = 2
|
||||||
|
Color2 = 3
|
||||||
|
Columns = 4
|
||||||
|
|
||||||
|
proc bye(w: Window) =
|
||||||
|
mainQuit()
|
||||||
|
echo "Bye..."
|
||||||
|
|
||||||
|
proc toStringVal(s: string): Value =
|
||||||
|
let gtype = typeFromName("gchararray")
|
||||||
|
discard init(result, gtype)
|
||||||
|
setString(result, s)
|
||||||
|
|
||||||
|
proc toUIntVal(i: int): Value =
|
||||||
|
let gtype = typeFromName("guint")
|
||||||
|
discard init(result, gtype)
|
||||||
|
setUint(result, i)
|
||||||
|
|
||||||
|
proc toBoolVal(b: bool): Value =
|
||||||
|
let gtype = typeFromName("gboolean")
|
||||||
|
discard init(result, gtype)
|
||||||
|
setBoolean(result, b)
|
||||||
|
|
||||||
|
# we use the old gtk style with init() as is used in the C original -- maybe better use modern app sytle
|
||||||
|
proc main() =
|
||||||
|
gtk.init()
|
||||||
|
let window = newWindow()
|
||||||
|
window.title = "Select Cell"
|
||||||
|
window.position = WindowPosition.center
|
||||||
|
window.defaultSize = (500, 500)
|
||||||
|
window.borderWidth = 20
|
||||||
|
connect(window, "destroy", bye)
|
||||||
|
var iter: TreeIter
|
||||||
|
var h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"), typeFromName("gchararray")]
|
||||||
|
var store = newListStore(Columns, cast[pointer]( unsafeaddr h)) # this cast is ugly, we should fix it in the bindings soon.
|
||||||
|
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
|
||||||
|
for i, n in progNames:
|
||||||
|
store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original
|
||||||
|
store.setValue(iter, Id, toUIntVal(i))
|
||||||
|
store.setValue(iter, Program, toStringVal(n))
|
||||||
|
store.setValue(iter, Color1, toStringVal(if (i and 1) != 0: "LightSlateGray" else: "DarkCyan"))
|
||||||
|
store.setValue(iter, Color2, toStringVal("cyan"))
|
||||||
|
var tree = newTreeViewWithModel(store)
|
||||||
|
tree.setHexpand
|
||||||
|
tree.setVexpand
|
||||||
|
setProperty(tree, "activate-on-single-click", toBoolVal(true))
|
||||||
|
var selection = tree.getSelection()
|
||||||
|
selection.setMode(SelectionMode.single)
|
||||||
|
var renderer1 = newCellRendererText()
|
||||||
|
setProperty(renderer1, "editable", toBoolVal(false))
|
||||||
|
var renderer2 = newCellRendererText()
|
||||||
|
setProperty(renderer2, "editable", toBoolVal(true))
|
||||||
|
## Bind the Color column to the "cell-background" property.
|
||||||
|
var column1 = newTreeViewColumn()
|
||||||
|
column1.setTitle("ID")
|
||||||
|
column1.packStart(renderer1, true)
|
||||||
|
column1.addAttribute(renderer1, "text", Id)
|
||||||
|
column1.addAttribute(renderer1, "cell-background", Color1)
|
||||||
|
discard tree.appendColumn(column1)
|
||||||
|
var column2 = newTreeViewColumn()
|
||||||
|
column1.setTitle("Program")
|
||||||
|
column1.packStart(renderer2, true)
|
||||||
|
column1.addAttribute(renderer2, "text", Program)
|
||||||
|
column1.addAttribute(renderer2, "cell-background", Color2)
|
||||||
|
discard tree.appendColumn(column2)
|
||||||
|
var grid = newGrid() # grid with only one occupied cell makes not much sense here -- but so we can add more widgets later
|
||||||
|
grid.attach(tree, 0, 0, 1, 1)
|
||||||
|
window.add(grid)
|
||||||
|
const cssString = # note: big font selected intentionally
|
||||||
|
"""treeview{background-color: rgba(0,255,255,1.0); font-size:30pt} treeview:selected{background-color: rgba(255,255,0,1.0);
|
||||||
|
color: rgba(0,0,255,1.0);}"""
|
||||||
|
var provider = newCssProvider()
|
||||||
|
discard provider.loadFromData(cssString)
|
||||||
|
addProviderForScreen(getDefaultScreen(), provider, STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
|
window.showAll
|
||||||
|
gtk.main()
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
# And finally that custom surface and custom cairo context is an
|
# And finally that custom surface and custom cairo context is an
|
||||||
# important test for the language bindings.
|
# 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]
|
import gintro/[gtk, gdk, glib, gobject, gio, cairo]
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -51,7 +53,7 @@ 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)
|
# drawing area and scroll bars in 2x2 grid (PDA == Plain Drawing Area)
|
||||||
|
|
||||||
type
|
type
|
||||||
PosAdj = ref object of Adjustment
|
PosAdj = ref object of Adjustment
|
||||||
|
|
@ -72,6 +74,8 @@ type
|
||||||
selecting: bool
|
selecting: bool
|
||||||
userZoom: float
|
userZoom: float
|
||||||
surf: Surface
|
surf: Surface
|
||||||
|
pattern: Pattern
|
||||||
|
cr: cairo.Context
|
||||||
darea: DrawingArea
|
darea: DrawingArea
|
||||||
hadjustment: PosAdj
|
hadjustment: PosAdj
|
||||||
vadjustment: PosAdj
|
vadjustment: PosAdj
|
||||||
|
|
@ -88,17 +92,18 @@ type
|
||||||
lastMousePosY: float
|
lastMousePosY: float
|
||||||
zoomRectX1: float
|
zoomRectX1: float
|
||||||
zoomRectY1: float
|
zoomRectY1: float
|
||||||
|
oldSizeX: int
|
||||||
|
oldSizeY: int
|
||||||
drawWorld: proc (cr: Context)
|
drawWorld: proc (cr: Context)
|
||||||
extents: proc (): tuple[x, y, w, h: float]
|
extents: proc (): tuple[x, y, w, h: float]
|
||||||
|
|
||||||
proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
|
proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
|
||||||
if this.surf.isNil: return
|
if this.pattern.isNil: return
|
||||||
cr.setSource(patternCreateForSurface(this.surf))
|
cr.setSource(this.pattern)
|
||||||
cr.paint
|
cr.paint
|
||||||
if this.selecting:
|
if this.selecting:
|
||||||
cr.rectangle(this.lastButtonDownPosX, this.lastButtonDownPosY,
|
cr.rectangle(this.lastButtonDownPosX, this.lastButtonDownPosY,
|
||||||
this.zoomRectX1 - this.lastButtonDownPosX,
|
this.zoomRectX1 - this.lastButtonDownPosX, this.zoomRectY1 - this.lastButtonDownPosY)
|
||||||
this.zoomRectY1 - this.lastButtonDownPosY)
|
|
||||||
cr.setSource(0, 0, 1, 0.5) # SELECT_RECT_COL) # 0, 0, 1, 0.5
|
cr.setSource(0, 0, 1, 0.5) # SELECT_RECT_COL) # 0, 0, 1, 0.5
|
||||||
cr.fillPreserve
|
cr.fillPreserve
|
||||||
cr.setSource(0, 0, 0)
|
cr.setSource(0, 0, 0)
|
||||||
|
|
@ -121,36 +126,47 @@ proc updateAdjustments(this: PDA; dx, dy: float) =
|
||||||
updateVal(this.hadjustment, dx)
|
updateVal(this.hadjustment, dx)
|
||||||
updateVal(this.vadjustment, dy)
|
updateVal(this.vadjustment, dy)
|
||||||
|
|
||||||
# maybe for optimization we should only allocate a new surface when its size has changed.
|
|
||||||
proc paint(this: PDA) =
|
proc paint(this: PDA) =
|
||||||
if this.surf != nil:
|
# echo "paint"
|
||||||
destroy(this.surf) # manually destroy surface -- GC would do it for us, but GC is slow...
|
this.cr.save
|
||||||
this.surf = this.darea.window.createSimilarSurface(Content.color,
|
this.cr.translate(this.hadjustment.upper * 0.5 - this.hadjustment.value, # our origin is the center
|
||||||
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)
|
this.vadjustment.upper * 0.5 - this.vadjustment.value)
|
||||||
cr.scale(this.fullScale * this.userZoom, this.fullScale * this.userZoom)
|
this.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.cr.translate(-this.dataX - this.dataWidth * 0.5, -this.dataY - this.dataHeight * 0.5)
|
||||||
this.drawWorld(cr) # call the user provided drawing function
|
this.drawWorld(this.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.restore
|
||||||
|
|
||||||
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.dataX, this.dataY, this.dataWidth,
|
||||||
this.dataHeight) = this.extents() # query user defined size
|
this.dataHeight) = this.extents() # query user defined size
|
||||||
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
|
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
|
||||||
this.darea.allocatedHeight.float / this.dataHeight)
|
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
|
this.paint
|
||||||
|
return gdk.EVENT_STOP
|
||||||
|
|
||||||
proc hscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
|
proc hscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
|
||||||
pda.hadjustment.setUpper(r.width.float * pda.userZoom)
|
pda.hadjustment.setUpper(r.width.float * pda.userZoom)
|
||||||
pda.hadjustment.setPageSize(r.width.float)
|
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) =
|
proc vscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
|
||||||
pda.vadjustment.setUpper(r.height.float * pda.userZoom)
|
pda.vadjustment.setUpper(r.height.float * pda.userZoom)
|
||||||
pda.vadjustment.setPageSize(r.height.float)
|
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) =
|
proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
|
||||||
this.updateAdjustments(dx, dy)
|
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 =
|
proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
|
||||||
let state = getState(event)
|
let state = getState(event)
|
||||||
let (x, y) = event.getCoords
|
let (x, y) = event.getCoords
|
||||||
if (state.contains(button1)): # selecting
|
if state.contains(button1): # selecting
|
||||||
this.selecting = true
|
this.selecting = true
|
||||||
this.zoomRectX1 = x
|
this.zoomRectX1 = x
|
||||||
this.zoomRectY1 = y
|
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
|
elif button2 in state: # panning
|
||||||
this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
|
this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
|
||||||
|
else:
|
||||||
|
return gdk.EVENT_PROPAGATE
|
||||||
this.lastMousePosX = x
|
this.lastMousePosX = x
|
||||||
this.lastMousePosY = y
|
this.lastMousePosY = y
|
||||||
|
return gdk.EVENT_STOP
|
||||||
#event.request # request more motion events ?
|
#event.request # request more motion events ?
|
||||||
|
|
||||||
# zooming with mouse wheel -- data near mouse pointer should not move if possible!
|
# 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)
|
# hadjustment.value + event.x is the position in our zoomed_in world, (userZoom / z0 - 1)
|
||||||
# is the relative movement caused by zooming
|
# 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 =
|
proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
|
||||||
let z0 = this.userZoom
|
let z0 = this.userZoom
|
||||||
var d = getScrollDirection(event)
|
case getScrollDirection(event)
|
||||||
var (x, y) = event.getCoords
|
of ScrollDirection.up:
|
||||||
if d == ScrollDirection.up:
|
|
||||||
this.userZoom *= ZoomFactorMouseWheel
|
this.userZoom *= ZoomFactorMouseWheel
|
||||||
elif d == ScrollDirection.down:
|
of ScrollDirection.down:
|
||||||
this.userZoom /= ZoomFactorMouseWheel
|
this.userZoom /= ZoomFactorMouseWheel
|
||||||
if (this.userZoom < 1):
|
if this.userZoom < 1:
|
||||||
this.userZoom = 1
|
this.userZoom = 1
|
||||||
|
else:
|
||||||
|
return gdk.EVENT_PROPAGATE
|
||||||
if this.zoomNearMousepointer:
|
if this.zoomNearMousepointer:
|
||||||
|
let (x, y) = event.getCoords
|
||||||
this.updateAdjustmentsAndPaint((this.hadjustment.value + x) * (this.userZoom / z0 - 1),
|
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
|
else: # zoom to center
|
||||||
this.updateAdjustmentsAndPaint((this.hadjustment.value +
|
this.updateAdjustmentsAndPaint((this.hadjustment.value +
|
||||||
this.darea.allocatedWidth.float * 0.5) * (this.userZoom / z0 - 1),
|
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))
|
||||||
|
return gdk.EVENT_STOP
|
||||||
|
|
||||||
proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
||||||
var (x, y) = event.getCoords
|
var (x, y) = event.getCoords
|
||||||
|
|
@ -208,23 +232,24 @@ proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
||||||
echo "buttonPressEvent", x, " ", y
|
echo "buttonPressEvent", x, " ", y
|
||||||
(x, y) = this.getUserCoordinates(x, y)
|
(x, y) = this.getUserCoordinates(x, y)
|
||||||
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
|
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
|
||||||
|
return gdk.EVENT_STOP
|
||||||
|
|
||||||
# zoom into selected rectangle and center it
|
# 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 =
|
proc buttonReleaseEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
|
||||||
var (x, y) = event.getCoords
|
let (x, y) = event.getCoords
|
||||||
var b = getButton(event)
|
let b = getButton(event)
|
||||||
if b == 1:
|
if b == 1:
|
||||||
this.selecting = false
|
this.selecting = false
|
||||||
let z1 = min(this.darea.allocatedWidth.float / (
|
let z1 = min(this.darea.allocatedWidth.float / (this.lastButtonDownPosX - x).abs,
|
||||||
this.lastButtonDownPosX - x).abs, this.darea.allocatedHeight.float / (
|
this.darea.allocatedHeight.float / (this.lastButtonDownPosY - y).abs)
|
||||||
this.lastButtonDownPosY - y).abs)
|
|
||||||
if z1 < ZoomFactorSelectMax: # 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.userZoom *= z1
|
||||||
this.updateAdjustmentsAndPaint(
|
this.updateAdjustmentsAndPaint(
|
||||||
((2 * this.hadjustment.value + x + this.lastButtonDownPosX) * z1 -
|
((x + this.lastButtonDownPosX) * z1 - this.darea.allocatedWidth.float) * 0.5 + this.hadjustment.value * (z1 - 1),
|
||||||
this.darea.allocatedWidth.float) * 0.5 - this.hadjustment.value,
|
((y + this.lastButtonDownPosY) * z1 - this.darea.allocatedHeight.float) * 0.5 + this.vadjustment.value * (z1 - 1))
|
||||||
((2 * this.vadjustment.value + y + this.lastButtonDownPosY) * z1 -
|
return gdk.EVENT_STOP
|
||||||
this.darea.allocatedHeight.float) * 0.5 - this.vadjustment.value)
|
return gdk.EVENT_PROPAGATE
|
||||||
|
|
||||||
proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
|
proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
|
||||||
pda.paint
|
pda.paint
|
||||||
|
|
@ -232,25 +257,24 @@ proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
|
||||||
|
|
||||||
proc newPDA: PDA =
|
proc newPDA: PDA =
|
||||||
initGrid(result)
|
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.zoomNearMousepointer = ZoomNearMousepointer # mouse wheel zooming
|
||||||
result.userZoom = 1.0
|
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 = newPosAdj()
|
||||||
result.hadjustment.handlerID = result.hadjustment.connect("value-changed",
|
result.hadjustment.handlerID = result.hadjustment.connect("value-changed", onAdjustmentEvent, result)
|
||||||
onAdjustmentEvent, result)
|
|
||||||
result.vadjustment = newPosAdj()
|
result.vadjustment = newPosAdj()
|
||||||
result.vadjustment.handlerID = result.vadjustment.connect("value-changed",
|
result.vadjustment.handlerID = result.vadjustment.connect("value-changed", onAdjustmentEvent, result)
|
||||||
onAdjustmentEvent, result)
|
|
||||||
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
|
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
|
||||||
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
|
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
|
||||||
result.hscrollbar.setHExpand
|
result.hscrollbar.setHExpand
|
||||||
|
|
@ -261,9 +285,13 @@ proc newPDA: PDA =
|
||||||
result.attach(result.vscrollbar, 1, 0, 1, 1)
|
result.attach(result.vscrollbar, 1, 0, 1, 1)
|
||||||
result.attach(result.hscrollbar, 0, 1, 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)
|
let window = newApplicationWindow(app)
|
||||||
window.title = "Drawing example"
|
window.title = "Drawing example"
|
||||||
|
# window.defaultSize = initData.windowSize
|
||||||
window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
|
window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
|
||||||
let pda = newPDA()
|
let pda = newPDA()
|
||||||
pda.drawWorld = initData.draw
|
pda.drawWorld = initData.draw
|
||||||
|
|
@ -273,14 +301,13 @@ proc appActivate(app: Application; initData: ref PDA_Data) =
|
||||||
|
|
||||||
proc newDisplay*(initData: PDA_Data) =
|
proc newDisplay*(initData: PDA_Data) =
|
||||||
let app = newApplication("org.gtk.example")
|
let app = newApplication("org.gtk.example")
|
||||||
var d = new (ref PDA_Data)
|
connect(app, "startup", appStartup)
|
||||||
d[] = initData
|
connect(app, "activate", appActivate, initData)
|
||||||
connect(app, "activate", appActivate, d)
|
|
||||||
discard run(app)
|
discard run(app)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
|
|
||||||
const # arbitrary locations for our data
|
const # arbitrary locations for our data
|
||||||
DataX = 150.0
|
DataX = 150.0
|
||||||
DataY = 250.0
|
DataY = 250.0
|
||||||
DataWidth = 200.0
|
DataWidth = 200.0
|
||||||
|
|
@ -300,18 +327,13 @@ when isMainModule:
|
||||||
cr.setSource(0, 0, 0)
|
cr.setSource(0, 0, 0)
|
||||||
cr.setLineWidth(2)
|
cr.setLineWidth(2)
|
||||||
var i = 0.0
|
var i = 0.0
|
||||||
while true:
|
while min(DataWidth - 2 * i, DataHeight - 2 * i) > 0:
|
||||||
if min(DataWidth - 2.0 * i, DataHeight - 2.0 * i) <= 0:
|
|
||||||
break
|
|
||||||
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
|
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
|
||||||
i += 10.0
|
i += 10
|
||||||
cr.stroke
|
cr.stroke
|
||||||
|
|
||||||
proc test =
|
proc test =
|
||||||
var data: PDA_Data
|
let data = PDA_Data(draw: drawWorld, extents: worldExtents, windowSize: (800, 600))
|
||||||
data.draw = drawWorld
|
|
||||||
data.extents = worldExtents
|
|
||||||
data.windowSize = (800, 600)
|
|
||||||
newDisplay(data)
|
newDisplay(data)
|
||||||
|
|
||||||
test()
|
test() # 337 lines
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,7 @@ nim c label.nim
|
||||||
nim c listview.nim
|
nim c listview.nim
|
||||||
nim c menubar.nim
|
nim c menubar.nim
|
||||||
nim c t0.nim
|
nim c t0.nim
|
||||||
|
nim c simpledrawingarea.nim
|
||||||
|
nim c css_colored_listview.nim
|
||||||
|
nim c gearsmenu.nim
|
||||||
|
nim c cairoImage.nim
|
||||||
|
|
|
||||||
310
examples/gtk3/simpledrawingarea.nim
Normal file
310
examples/gtk3/simpledrawingarea.nim
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
# Plain demo for zooming, panning, scrolling with GTK DrawingArea
|
||||||
|
# (c) S. Salewski, 21-DEC-2010 (initial Ruby version)
|
||||||
|
# Nim version April 2019
|
||||||
|
# License MIT
|
||||||
|
|
||||||
|
# This is a simplified example, where all the drawing is done in
|
||||||
|
# the draw callback function. No separate paint proc with own buffer surface.
|
||||||
|
|
||||||
|
# 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
|
||||||
|
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...
|
||||||
|
|
||||||
|
|-------------------------|
|
||||||
|
|<-------- A ------------>|
|
||||||
|
| |
|
||||||
|
| |---------------| |
|
||||||
|
| | <---- a ----->| |
|
||||||
|
| | visible | |
|
||||||
|
| |---------------| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
|-------------------------|
|
||||||
|
|
||||||
|
a is the visible, zoomed in area == darea.allocatedWidth
|
||||||
|
A is the total data range
|
||||||
|
A/a == userZoom >= 1
|
||||||
|
For horizontal adjustment we use
|
||||||
|
hadjustment.setUpper(darea.allocatedWidth * userZoom) == A
|
||||||
|
hadjustment.setPageSize(darea.allocatedWidth) == a
|
||||||
|
So hadjustment.value == left side of visible area
|
||||||
|
|
||||||
|
Initially, we set userZoom = 1, scale our data to fit into darea.allocatedWidth
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
# drawing area and scroll bars in 2x2 grid (PDA == Plain Drawing Area)
|
||||||
|
|
||||||
|
type
|
||||||
|
PosAdj = ref object of Adjustment
|
||||||
|
handlerID: uint64
|
||||||
|
|
||||||
|
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
|
||||||
|
selecting: bool
|
||||||
|
userZoom: float
|
||||||
|
darea: DrawingArea
|
||||||
|
hadjustment: PosAdj
|
||||||
|
vadjustment: PosAdj
|
||||||
|
hscrollbar: Scrollbar
|
||||||
|
vscrollbar: Scrollbar
|
||||||
|
fullScale: float
|
||||||
|
dataX: float
|
||||||
|
dataY: float
|
||||||
|
dataWidth: float
|
||||||
|
dataHeight: float
|
||||||
|
lastButtonDownPosX: float
|
||||||
|
lastButtonDownPosY: float
|
||||||
|
lastMousePosX: float
|
||||||
|
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 =
|
||||||
|
cr.save
|
||||||
|
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
|
||||||
|
cr.restore
|
||||||
|
if this.selecting:
|
||||||
|
cr.rectangle(this.lastButtonDownPosX, 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)
|
||||||
|
cr.setLineWidth(2)
|
||||||
|
cr.stroke
|
||||||
|
return gdk.EVENT_STOP # EVENT_PROPAGATE
|
||||||
|
#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) =
|
||||||
|
adj.signalHandlerBlock(adj.handlerID)
|
||||||
|
adj.setValue(max(0.0, min(adj.value + d, adj.upper - adj.pageSize)))
|
||||||
|
adj.signalHandlerUnblock(adj.handlerID)
|
||||||
|
|
||||||
|
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.hadjustment.setPageSize(this.darea.allocatedWidth.float)
|
||||||
|
this.vadjustment.setPageSize(this.darea.allocatedHeight.float)
|
||||||
|
updateVal(this.hadjustment, dx)
|
||||||
|
updateVal(this.vadjustment, dy)
|
||||||
|
|
||||||
|
proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure; this: PDA): bool =
|
||||||
|
(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)
|
||||||
|
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)
|
||||||
|
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) =
|
||||||
|
((eventX - this.hadjustment.upper * 0.5 + this.hadjustment.value) / (
|
||||||
|
this.fullScale * this.userZoom) + this.dataX + this.dataWidth * 0.5,
|
||||||
|
(eventY - this.vadjustment.upper * 0.5 + this.vadjustment.value) / (
|
||||||
|
this.fullScale * this.userZoom) + this.dataY + this.dataHeight * 0.5)
|
||||||
|
|
||||||
|
proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
|
||||||
|
let state = getState(event)
|
||||||
|
let (x, y) = event.getCoords
|
||||||
|
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
|
||||||
|
case getScrollDirection(event)
|
||||||
|
of ScrollDirection.up:
|
||||||
|
this.userZoom *= ZoomFactorMouseWheel
|
||||||
|
of ScrollDirection.down:
|
||||||
|
this.userZoom /= ZoomFactorMouseWheel
|
||||||
|
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))
|
||||||
|
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
|
||||||
|
this.lastMousePosX = x
|
||||||
|
this.lastMousePosY = y
|
||||||
|
this.lastButtonDownPosX = x
|
||||||
|
this.lastButtonDownPosY = y
|
||||||
|
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 =
|
||||||
|
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)
|
||||||
|
if z1 < ZoomFactorSelectMax: # else selection rectangle will persist, we may output a message...
|
||||||
|
this.userZoom *= z1
|
||||||
|
this.updateAdjustmentsAndPaint(
|
||||||
|
((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.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.hadjustment = newPosAdj()
|
||||||
|
result.hadjustment.handlerID = result.hadjustment.connect("value-changed", onAdjustmentEvent, result)
|
||||||
|
result.vadjustment = newPosAdj()
|
||||||
|
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
|
||||||
|
result.vscrollbar.setVExpand
|
||||||
|
result.hscrollbar.connect("size-allocate", hscrollbarSizeAllocateCallback, result)
|
||||||
|
result.vscrollbar.connect("size-allocate", vscrollbarSizeAllocateCallback, result)
|
||||||
|
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 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
|
||||||
|
pda.extents = initData.extents
|
||||||
|
window.add(pda)
|
||||||
|
showAll(window)
|
||||||
|
|
||||||
|
proc newDisplay*(initData: PDA_Data) =
|
||||||
|
let app = newApplication("org.gtk.example")
|
||||||
|
connect(app, "startup", appStartup)
|
||||||
|
connect(app, "activate", appActivate, initData)
|
||||||
|
discard run(app)
|
||||||
|
|
||||||
|
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 min(DataWidth - 2 * i, DataHeight - 2 * i) > 0:
|
||||||
|
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
|
||||||
|
i += 10
|
||||||
|
cr.stroke
|
||||||
|
|
||||||
|
proc test =
|
||||||
|
let data = PDA_Data(draw: drawWorld, extents: worldExtents, windowSize: (800, 600))
|
||||||
|
newDisplay(data)
|
||||||
|
|
||||||
|
test() # 310 lines
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
author = "Stefan Salewski"
|
author = "Stefan Salewski"
|
||||||
description = "High level GObject-Introspection based GTK3/GTK4 bindings"
|
description = "High level GObject-Introspection based GTK3/GTK4 bindings"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# This is the high level cairo module for Nim -- based on the low level ngtk3 module, manually tuned.
|
# This is the high level cairo module for Nim -- based on the low level ngtk3 module, manually tuned.
|
||||||
# (c) S. Salewski 2017, cairo 1.15.6
|
# (c) S. Salewski 2017, cairo 1.15.6
|
||||||
# v0.4.18
|
# v0.5.04
|
||||||
# 24-APR-2019
|
# 14-SEP-2019
|
||||||
|
|
||||||
# Cairo does not really support gobject introspection and gobject's toggle references.
|
# Cairo does not really support gobject introspection and gobject's toggle references.
|
||||||
# So code for memory management is a bit different compared to other gtk modules.
|
# So code for memory management is a bit different compared to other gtk modules.
|
||||||
|
|
@ -25,7 +25,8 @@
|
||||||
# result.impl = cairo_create(target.impl)
|
# result.impl = cairo_create(target.impl)
|
||||||
# discard cairo_set_user_data(result.impl, NUDK, cast[pointer](result), gcuref)
|
# discard cairo_set_user_data(result.impl, NUDK, cast[pointer](result), gcuref)
|
||||||
|
|
||||||
# We have to call GC_ref() because we store no direct reference to the proxy object.
|
# We have to call GC_ref() because we store no direct reference to the proxy object, while
|
||||||
|
# cairo_get_target() allows us to get the surface back from cairo context.
|
||||||
# For this example cairo_create() references the surface.
|
# For this example cairo_create() references the surface.
|
||||||
# When ref count of surface is decreased, maybe because context is destroyed, then gcuref is called
|
# When ref count of surface is decreased, maybe because context is destroyed, then gcuref is called
|
||||||
# which calls GC_unref() on proxy. When there is no other ref to proxy GC can free proxy and surface
|
# which calls GC_unref() on proxy. When there is no other ref to proxy GC can free proxy and surface
|
||||||
|
|
@ -34,7 +35,8 @@
|
||||||
# But there is at least one design flaw:
|
# But there is at least one design flaw:
|
||||||
# When we create for example a surface and GC releases it, then gcunref() is called on it while
|
# When we create for example a surface and GC releases it, then gcunref() is called on it while
|
||||||
# gc_ref() was newer called. When we consider that the cairo context is the only element which
|
# gc_ref() was newer called. When we consider that the cairo context is the only element which
|
||||||
# stores references to other objects, then we can simplify the code to
|
# stores references to other objects, then we can simplify the code to (Footnote: This is not really
|
||||||
|
# true, for example Pattern stores references to surface too -- but it should work similar.)
|
||||||
|
|
||||||
# proc newImageSurface*(format: Format; width, height: int): Surface =
|
# proc newImageSurface*(format: Format; width, height: int): Surface =
|
||||||
# new(result, surfaceDestroy)
|
# new(result, surfaceDestroy)
|
||||||
|
|
@ -55,7 +57,17 @@
|
||||||
# with
|
# with
|
||||||
# proc gcuref(o: pointer) {.cdecl.} = GC_unref(cast[RootRef](o))
|
# proc gcuref(o: pointer) {.cdecl.} = GC_unref(cast[RootRef](o))
|
||||||
|
|
||||||
|
# cairo_image_surface_create() set refcount of the plain cairo surface to 1.
|
||||||
|
# cairo_create() increases refcount of surface (by 2), cairo destroy decreases
|
||||||
|
# surface refcount again. cairo_surface_set_user_data() ensures that
|
||||||
|
# Nim surface proxy element is unreffed when refcount of cairo surface
|
||||||
|
# drops to zero, so surfaceDestroy() frees the cairo memory.
|
||||||
|
|
||||||
|
# Note: For some other procs like cairo_set_source_surface() we also need code like
|
||||||
|
# discard cairo_surface_set_user_data(target.impl, NUDK, cast[pointer](target), gcuref)
|
||||||
|
|
||||||
# Currently this is all not really tested.
|
# Currently this is all not really tested.
|
||||||
|
|
||||||
# One problem may be procs like cairo_surface_create_similar() when called frequently, maybe
|
# One problem may be procs like cairo_surface_create_similar() when called frequently, maybe
|
||||||
# for each frame in animations. GC may be too slow in freeing the memory.
|
# for each frame in animations. GC may be too slow in freeing the memory.
|
||||||
# We should try to avoid such usage, or we may add a way to manually free memory.
|
# We should try to avoid such usage, or we may add a way to manually free memory.
|
||||||
|
|
@ -290,9 +302,6 @@ proc newContext*(target: Surface): Context =
|
||||||
result.impl = cairo_create(target.impl)
|
result.impl = cairo_create(target.impl)
|
||||||
discard cairo_surface_set_user_data(target.impl, NUDK, cast[pointer](target), gcuref)
|
discard cairo_surface_set_user_data(target.impl, NUDK, cast[pointer](target), gcuref)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc cairo_reference*(cr: ptr Context00): ptr Context00 {.importc, libcairo.}
|
proc cairo_reference*(cr: ptr Context00): ptr Context00 {.importc, libcairo.}
|
||||||
#
|
#
|
||||||
proc reference*(cr: Context): Context =
|
proc reference*(cr: Context): Context =
|
||||||
|
|
@ -330,7 +339,11 @@ proc pushGroup*(cr: Context; content: Content) =
|
||||||
proc cairo_pattern_destroy*(pattern: ptr Pattern00) {.importc, libcairo.}
|
proc cairo_pattern_destroy*(pattern: ptr Pattern00) {.importc, libcairo.}
|
||||||
#
|
#
|
||||||
proc patternDestroy*(pattern: Pattern) =
|
proc patternDestroy*(pattern: Pattern) =
|
||||||
cairo_pattern_destroy(pattern.impl)
|
#cairo_pattern_destroy(pattern.impl)
|
||||||
|
|
||||||
|
if pattern != nil and pattern.impl != nil:
|
||||||
|
cairo_pattern_destroy(pattern.impl)
|
||||||
|
pattern.impl = nil
|
||||||
|
|
||||||
proc cairo_pattern_set_user_data*(pattern: ptr Pattern00; key: ptr UserDataKey; userData: pointer;
|
proc cairo_pattern_set_user_data*(pattern: ptr Pattern00; key: ptr UserDataKey; userData: pointer;
|
||||||
destroy: DestroyFunc00): Status {.importc, libcairo.}
|
destroy: DestroyFunc00): Status {.importc, libcairo.}
|
||||||
|
|
@ -405,11 +418,9 @@ proc getReferenceCount*(surface: Surface): int =
|
||||||
proc cairo_set_source_surface*(cr: ptr Context00; surface: ptr Surface00; x, y: cdouble) {.importc, libcairo.}
|
proc cairo_set_source_surface*(cr: ptr Context00; surface: ptr Surface00; x, y: cdouble) {.importc, libcairo.}
|
||||||
#
|
#
|
||||||
proc setSourceSurface*(cr: Context; surface: Surface; x, y: float) =
|
proc setSourceSurface*(cr: Context; surface: Surface; x, y: float) =
|
||||||
#GC_ref(source) # we guess that surface is nor referenced, but a new intern pattern is created
|
GC_ref(surface)
|
||||||
let h = cairo_surface_get_reference_count(surface.impl)
|
|
||||||
cairo_set_source_surface(cr.impl, surface.impl, x.cdouble, y.cdouble)
|
cairo_set_source_surface(cr.impl, surface.impl, x.cdouble, y.cdouble)
|
||||||
assert(cairo_surface_get_reference_count(surface.impl) == h) # is our guess OK?
|
discard cairo_surface_set_user_data(surface.impl, NUDK, cast[pointer](surface), gcuref)
|
||||||
#
|
|
||||||
#const `sourceSurface=`* = setSourceSurface
|
#const `sourceSurface=`* = setSourceSurface
|
||||||
|
|
||||||
proc cairo_set_tolerance*(cr: ptr Context00; tolerance: cdouble) {.importc, libcairo.}
|
proc cairo_set_tolerance*(cr: ptr Context00; tolerance: cdouble) {.importc, libcairo.}
|
||||||
|
|
@ -1378,15 +1389,22 @@ proc cairo_pattern_get_user_data*(pattern: ptr Pattern00; key: ptr UserDataKey):
|
||||||
proc getUserData*(pattern: Pattern; key: ptr UserDataKey): pointer =
|
proc getUserData*(pattern: Pattern; key: ptr UserDataKey): pointer =
|
||||||
cairo_pattern_get_user_data(pattern.impl, key)
|
cairo_pattern_get_user_data(pattern.impl, key)
|
||||||
|
|
||||||
|
proc cairo_pattern_reference*(pattern: ptr Pattern00): ptr Pattern00 {.importc, libcairo.}
|
||||||
|
#
|
||||||
|
proc patternReference*(pattern: Pattern): Pattern =
|
||||||
|
discard cairo_pattern_reference(pattern.impl)
|
||||||
|
return pattern
|
||||||
|
|
||||||
|
# Returns the current source pattern. This object is owned by cairo.
|
||||||
|
# To keep a reference to it, you must call cairo_pattern_reference().
|
||||||
proc cairo_get_source*(cr: ptr Context00): ptr Pattern00 {.importc, libcairo.}
|
proc cairo_get_source*(cr: ptr Context00): ptr Pattern00 {.importc, libcairo.}
|
||||||
#
|
#
|
||||||
proc getSource*(cr: Context): Pattern =
|
proc getSource*(cr: Context): Pattern =
|
||||||
let h = cairo_get_source(cr.impl)
|
let h = cairo_get_source(cr.impl)
|
||||||
let d = cairo_pattern_get_user_data(h, NUDK)
|
let d = cairo_pattern_get_user_data(h, NUDK)
|
||||||
if d.isNil:
|
if d.isNil:
|
||||||
#assert false # may this happen?
|
|
||||||
new(result, patternDestroy)
|
new(result, patternDestroy)
|
||||||
result.impl = h
|
result.impl = cairo_pattern_reference(h)
|
||||||
else:
|
else:
|
||||||
result = cast[Pattern](d)
|
result = cast[Pattern](d)
|
||||||
assert(result.impl == h)
|
assert(result.impl == h)
|
||||||
|
|
@ -2077,11 +2095,7 @@ proc patternCreateMesh*(): Pattern =
|
||||||
result.impl = cairo_pattern_create_mesh()
|
result.impl = cairo_pattern_create_mesh()
|
||||||
### discard cairo_pattern_set_user_data(result.impl, NUDK, cast[pointer](result), gcuref)
|
### discard cairo_pattern_set_user_data(result.impl, NUDK, cast[pointer](result), gcuref)
|
||||||
|
|
||||||
proc cairo_pattern_reference*(pattern: ptr Pattern00): ptr Pattern00 {.importc, libcairo.}
|
|
||||||
#
|
|
||||||
proc patternReference*(pattern: Pattern): Pattern =
|
|
||||||
discard cairo_pattern_reference(pattern.impl)
|
|
||||||
return pattern
|
|
||||||
|
|
||||||
proc cairo_pattern_status*(pattern: ptr Pattern00): Status {.importc, libcairo.}
|
proc cairo_pattern_status*(pattern: ptr Pattern00): Status {.importc, libcairo.}
|
||||||
#
|
#
|
||||||
|
|
@ -2821,4 +2835,5 @@ when CAIRO_HAS_TEE_SURFACE:
|
||||||
discard cairo_tee_surface_index(surface.impl, index.cuint)
|
discard cairo_tee_surface_index(surface.impl, index.cuint)
|
||||||
return surface
|
return surface
|
||||||
|
|
||||||
# 2786 lines
|
# 2851 lines
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ macro mconnect(widget: gobject.Object; signal: string; p: typed; arg: typed; ign
|
||||||
inc(ProcID)
|
inc(ProcID)
|
||||||
let wt = getType(widget)
|
let wt = getType(widget)
|
||||||
let at = getTypeInst(arg)
|
let at = getTypeInst(arg)
|
||||||
|
#echo at.repr
|
||||||
|
#echo at.typeKind == ntyRef
|
||||||
let wts = ($(wt[1].toStrLit)).replace(":ObjectType")
|
let wts = ($(wt[1].toStrLit)).replace(":ObjectType")
|
||||||
let ats = $at.toStrLit
|
let ats = $at.toStrLit
|
||||||
let signalName = ($signal).replace("-", "_") # maybe we should just use plain proc names
|
let signalName = ($signal).replace("-", "_") # maybe we should just use plain proc names
|
||||||
|
|
@ -74,7 +76,10 @@ proc $1$2 {.cdecl.} =
|
||||||
ahl = "(self: gobject.Object)"
|
ahl = "(self: gobject.Object)"
|
||||||
r1s.add(" $3(cast[$4](h)")
|
r1s.add(" $3(cast[$4](h)")
|
||||||
if not ignoreArg.boolVal:
|
if not ignoreArg.boolVal:
|
||||||
r1s.add(", cast[$5](xdata)")
|
if getTypeInst(arg).typeKind == ntyRef:
|
||||||
|
r1s.add(", cast[$5](xdata)")
|
||||||
|
else:
|
||||||
|
r1s.add(", cast[ptr $5](xdata)[]")
|
||||||
r1s.add(")\n")
|
r1s.add(")\n")
|
||||||
else: # signals with multiple arguments and maybe using interface providers
|
else: # signals with multiple arguments and maybe using interface providers
|
||||||
(sn, wid, num, ahl, all) = sci.split(RecSep)
|
(sn, wid, num, ahl, all) = sci.split(RecSep)
|
||||||
|
|
@ -136,16 +141,19 @@ proc $1$2 {.cdecl.} =
|
||||||
r1s.add(" if " & names[i] & "1" & ".isNil:\n")
|
r1s.add(" if " & names[i] & "1" & ".isNil:\n")
|
||||||
r1s.add(" new " & names[i] & "1" & "\n")
|
r1s.add(" new " & names[i] & "1" & "\n")
|
||||||
r1s.add(" " & names[i] & "1.impl = " & names[i] & "\n")
|
r1s.add(" " & names[i] & "1.impl = " & names[i] & "\n")
|
||||||
|
|
||||||
if not ignoreArg.boolVal:
|
if not ignoreArg.boolVal:
|
||||||
resu.add(", cast[$5](data)")
|
# resu.add(", cast[$5](data)")
|
||||||
|
if getTypeInst(arg).typeKind == ntyRef:
|
||||||
|
resu.add(", cast[$5](data)")
|
||||||
|
else:
|
||||||
|
resu.add(", cast[ptr $5](data)[]")
|
||||||
resu.add(")")
|
resu.add(")")
|
||||||
if resl == "gboolean":
|
if resl == "gboolean":
|
||||||
resu.add(".ord.gboolean")
|
resu.add(".ord.gboolean")
|
||||||
r1s.add(resu & "\n")
|
r1s.add(resu & "\n")
|
||||||
all = all.replace(")", "; data: pointer)")
|
all = all.replace(")", "; data: pointer)")
|
||||||
r1s = r1s % [$procNameCdecl, all, $p, wts, ats]
|
r1s = r1s % [$procNameCdecl, all, $p, wts, ats]
|
||||||
#echo r1s
|
# echo r1s
|
||||||
result = parseStmt(r1s)
|
result = parseStmt(r1s)
|
||||||
if not ignoreArg.boolVal:
|
if not ignoreArg.boolVal:
|
||||||
ahl = ahl.replace(")", "; arg: " & ats & ")")
|
ahl = ahl.replace(")", "; arg: " & ats & ")")
|
||||||
|
|
@ -154,7 +162,7 @@ proc $1$2 {.cdecl.} =
|
||||||
else:
|
else:
|
||||||
ahl = "(self: " & wts & ")" & ahl.split(")", 1)[1]
|
ahl = "(self: " & wts & ")" & ahl.split(")", 1)[1]
|
||||||
|
|
||||||
let r2s =
|
let r2sunused =
|
||||||
if ignoreArg.boolVal:
|
if ignoreArg.boolVal:
|
||||||
"""
|
"""
|
||||||
proc $1(self: $2; p: proc $3): culong {.discardable.} =
|
proc $1(self: $2; p: proc $3): culong {.discardable.} =
|
||||||
|
|
@ -164,7 +172,7 @@ $1($6, $7)
|
||||||
else:
|
else:
|
||||||
"""
|
"""
|
||||||
proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} =
|
proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} =
|
||||||
when a is RootRef:
|
when a is ref:
|
||||||
GC_ref(a)
|
GC_ref(a)
|
||||||
sc$5(self, $6, cast[pointer](a))
|
sc$5(self, $6, cast[pointer](a))
|
||||||
else:
|
else:
|
||||||
|
|
@ -172,10 +180,38 @@ proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} =
|
||||||
new(ar)
|
new(ar)
|
||||||
deepCopy(ar[], a)
|
deepCopy(ar[], a)
|
||||||
GC_ref(ar)
|
GC_ref(ar)
|
||||||
sc$5(self, $6, cast[pointer](ar[]))
|
# sc$5(self, $6, cast[pointer](ar[]))
|
||||||
|
sc$5(self, $6, cast[pointer](ar))
|
||||||
$1($7, $8, $9)
|
$1($7, $8, $9)
|
||||||
""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit)]
|
""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit)]
|
||||||
#echo r2s
|
|
||||||
|
# maybe we write this better this way:
|
||||||
|
let r2s =
|
||||||
|
if ignoreArg.boolVal:
|
||||||
|
"""
|
||||||
|
proc $1(self: $2; p: proc $3): culong {.discardable.} =
|
||||||
|
sc$4(self, $5, nil)
|
||||||
|
$1($6, $7)
|
||||||
|
""" % [$procName, wts, ahl, signalName, $procNameCdecl, $(widget.toStrLit), $p]
|
||||||
|
elif getTypeInst(arg).typeKind == ntyRef:
|
||||||
|
"""
|
||||||
|
proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} =
|
||||||
|
GC_ref(a)
|
||||||
|
sc$5(self, $6, cast[pointer](a))
|
||||||
|
$1($7, $8, $9)
|
||||||
|
""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit)]
|
||||||
|
else:
|
||||||
|
"""
|
||||||
|
proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} =
|
||||||
|
var ar: ref $4
|
||||||
|
new(ar)
|
||||||
|
deepCopy(ar[], a)
|
||||||
|
GC_ref(ar)
|
||||||
|
# sc$5(self, $6, cast[pointer](ar[]))
|
||||||
|
sc$5(self, $6, cast[pointer](ar))
|
||||||
|
$1($7, $8, $9)
|
||||||
|
""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit)]
|
||||||
|
# echo r2s
|
||||||
result.add(parseStmt(r2s))
|
result.add(parseStmt(r2s))
|
||||||
|
|
||||||
template connect*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped =
|
template connect*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue