diff --git a/README.adoc b/README.adoc index e6e198c..2dcfa22 100644 --- a/README.adoc +++ b/README.adoc @@ -2711,6 +2711,37 @@ proc main = main() ---- +== A VTE example + +The following code shows how we can use the vte library to create a plain shell window: + +[[vte.nim]] +[source,nim] +.vte.nim +---- +# https://vincent.bernat.im/en/blog/2017-write-own-terminal +import gintro/[gtk, glib, gobject, gio, vte] + +proc appActivate(app: Application) = + let window = newApplicationWindow(app) + window.title = "GTK3 & Nim" + window.defaultSize = (600, 200) + let terminal = newTerminal() + let environ = getEnviron() + let command = environ.environGetenv("SHELL") + var pid = 0 + echo terminal.spawnSync({}, nil, [command], [], {SpawnFlag.leaveDescriptorsOpen}, nil, nil, pid, nil) + window.add(terminal) + showAll(window) + +proc main = + let app = newApplication("org.gtk.example") + connect(app, "activate", appActivate) + discard run(app) + +main() +---- + == A GStreamer example Recently someone asked about gstreamer support for gintro, see https://github.com/StefanSalewski/gintro/issues/59 . @@ -2744,6 +2775,131 @@ proc main = main() ---- +== Threading Examples + +A common problem with GTK GUI's is the fact that functions that run for a longer time period in the +GTK main thread can block the GUI for display updates or user input. We can solve this problem +by creating a separate thread which runs these functions in the background. + +The GTK GUI is not really thread safe, so we can not update the GUI from other threads directly. An +easy way to solve this problem is to use the glib functions g-idle-add() or g-timeout-add(). + +https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-idle-add + +https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-timeout-add + +The glib function g-timeout-add() adds a user defined function to the GTK main loop that is called +periodically, and the function g-idle-add() adds a user defined function to the GTK main loop +that is called when GTK is not busy with other stuff. Both functions are executed in the GTK main +thread, so both can update GUI widgets or call other GTK related functions. + +In our first example we create a Nim worker thread that sends data over a Nim channel to the main +thread. We use timeoutAdd() to call periodically a function that reads data from the channel and +updates the GUI. In this example the worker thread only does a plain countdown -- in a real +work example it would do the hard work in the background, i.e. running a chess engine to find the +next best move or sorting a large data base. The function updateGUI() receives the current +counter value and updates the label of a button. + +[[thread1.nim]] +[source,nim] +.thread1.nim +---- +# https://nim-lang.org/docs/channels.html +# nim c --threads:on --gc:arc -r thread1.nim + +import gintro/[gtk, glib, gobject, gio] +from os import sleep + +var channel: Channel[int] +var workThread: system.Thread[void] + +proc workProc = + var countdown {.global.} = 25 + while countdown > 0: + sleep(1000) + dec(countdown) + channel.send(countdown) + +proc updateGUI(b: Button): bool = + let msg = channel.tryRecv() + if msg.dataAvailable: + b.label = $msg.msg + if msg.msg == 0: + workThread.joinThread + channel.close + return SOURCE_REMOVE + return SOURCE_CONTINUE + +proc buttonClicked (b: Button) = + b.label = utf8Strreverse(b.label, -1) + +proc activate (app: Application) = + let window = newApplicationWindow(app) + window.title = "Countdown" + window.defaultSize = (250, 50) + let button = newButton("Click Me") + window.add(button) + button.connect("clicked", buttonClicked) + window.showAll + channel.open + createThread(workThread, workProc) + discard timeoutAdd(1000 div 60, updateGUI, button) + +proc main = + let app = newApplication("org.gtk.example") + connect(app, "activate", activate) + discard app.run + +main() +---- + +The next example uses the function g-idle-add() from inside the worker thread to call a user defined function +that then updates a GUI widget: + +[[thread2.nim]] +[source,nim] +.thread2.nim +---- +# nim c --threads:on --gc:arc -r thread2.nim + +import gintro/[gtk, glib, gobject, gio] +from os import sleep + +var workThread: system.Thread[void] +var button: Button + +proc idleFunc(i: int): bool = + button.label = $i + return SOURCE_REMOVE + +proc workProc = + var countdown {.global.} = 25 + while countdown > 0: + sleep(1000) + dec(countdown) + idleAdd(idleFunc, countdown) + +proc buttonClicked(button: Button) = + button.label = utf8Strreverse(button.label, -1) + +proc activate(app: Application) = + let window = newApplicationWindow(app) + window.title = "Countdown" + window.defaultSize = (250, 50) + button = newButton("Click Me") + window.add(button) + button.connect("clicked", buttonClicked) + window.showAll + createThread(workThread, workProc) + +proc main = + let app = newApplication("org.gtk.example") + connect(app, "activate", activate) + discard app.run + +main() +---- + == A HeaderBar example for GTK4 The following code is the Nim version of a GTK4 example in C. It will work in its unmodified form diff --git a/examples/gtk3/makeAll b/examples/gtk3/makeAll index 6fcd3f1..d6ffc9b 100644 --- a/examples/gtk3/makeAll +++ b/examples/gtk3/makeAll @@ -19,6 +19,7 @@ nim c listview.nim nim c menubar.nim nim c overlay_tree1.nim nim c simpledrawingarea.nim +nim c spinbutton.nim nim c sourceview_test.nim nim c t0.nim nim c vte.nim diff --git a/examples/gtk3/makeAllArc b/examples/gtk3/makeAllArc index 788a621..1cc9d5a 100644 --- a/examples/gtk3/makeAllArc +++ b/examples/gtk3/makeAllArc @@ -19,6 +19,7 @@ nim c --gc:arc listview.nim nim c --gc:arc menubar.nim nim c --gc:arc overlay_tree1.nim nim c --gc:arc simpledrawingarea.nim +nim c --gc:arc spinbutton.nim nim c --gc:arc sourceview_test.nim nim c --gc:arc t0.nim nim c --gc:arc vte.nim diff --git a/examples/gtk3/thread1.nim b/examples/gtk3/thread1.nim new file mode 100644 index 0000000..0f06361 --- /dev/null +++ b/examples/gtk3/thread1.nim @@ -0,0 +1,47 @@ +# https://nim-lang.org/docs/channels.html +# nim c --threads:on --gc:arc -r thread1.nim + +import gintro/[gtk, glib, gobject, gio] +from os import sleep + +var channel: Channel[int] +var workThread: system.Thread[void] + +proc workProc = + var countdown {.global.} = 25 + while countdown > 0: + sleep(1000) + dec(countdown) + channel.send(countdown) + +proc updateGUI(b: Button): bool = + let msg = channel.tryRecv() + if msg.dataAvailable: + b.label = $msg.msg + if msg.msg == 0: + workThread.joinThread + channel.close + return SOURCE_REMOVE + return SOURCE_CONTINUE + +proc buttonClicked (b: Button) = + b.label = utf8Strreverse(b.label, -1) + +proc activate (app: Application) = + let window = newApplicationWindow(app) + window.title = "Countdown" + window.defaultSize = (250, 50) + let button = newButton("Click Me") + window.add(button) + button.connect("clicked", buttonClicked) + window.showAll + channel.open + createThread(workThread, workProc) + discard timeoutAdd(1000 div 60, updateGUI, button) + +proc main = + let app = newApplication("org.gtk.example") + connect(app, "activate", activate) + discard app.run + +main() diff --git a/examples/gtk3/thread2.nim b/examples/gtk3/thread2.nim new file mode 100644 index 0000000..5540dbd --- /dev/null +++ b/examples/gtk3/thread2.nim @@ -0,0 +1,38 @@ +# nim c --threads:on --gc:arc -r thread2.nim + +import gintro/[gtk, glib, gobject, gio] +from os import sleep + +var workThread: system.Thread[void] +var button: Button + +proc idleFunc(i: int): bool = + button.label = $i + return SOURCE_REMOVE + +proc workProc = + var countdown {.global.} = 25 + while countdown > 0: + sleep(1000) + dec(countdown) + idleAdd(idleFunc, countdown) + +proc buttonClicked(button: Button) = + button.label = utf8Strreverse(button.label, -1) + +proc activate(app: Application) = + let window = newApplicationWindow(app) + window.title = "Countdown" + window.defaultSize = (250, 50) + button = newButton("Click Me") + window.add(button) + button.connect("clicked", buttonClicked) + window.showAll + createThread(workThread, workProc) + +proc main = + let app = newApplication("org.gtk.example") + connect(app, "activate", activate) + discard app.run + +main()