How to create a GUI app using libui-ng on Windows?

Hi

I heard that Windows support has been improved in Crystal 1.11.

Can someone tell me how to create a GUI app using libui-ng and libui.cr?
Are static libraries sufficient instead of shared libraries on Windows?

https://nightly.link/libui-ng/libui-ng/workflows/build/master

(nightly.link keeps GitHub Actions artifact links up to date, a Crystal service created by oprypin)

I started up my Windows PC and tried it out a bit, but I couldn’t figure out how to do it with Windows. I thought it would be faster to ask the question on the forum, listen to what people have to say, and do what they say.

I’m interested in this because I’m writing a libui bindings for Ruby.

1 Like

I’ve used libui (not its fork libui-ng, but API is roughly similar) with macOS environment. I didn’t use the libraries you mentioned, it looks like they only offer low-level API, instead I used iu, a wrapper for the libui library API, which is nice to get started with, even though it’s probably no longer maintained. Basically you just need to layout the required components and register the callbacks.

A GUI application usually can’t run in a single thread, but AFAIK Crystal’s multithreading support on Windows isn’t ready yet, so you’ll need to create your own UI threads and worker threads, and take care that interface operations are performed in the UI threads.

I think so but not tested(on Windows), just make sure the linker can find the static library, then add the -static flag. there should be no need for header files, as these libraries map the API using the C binding.

Other thoughts: libui and libui-ng are great libraries, they have the advantage of being lightweight, but they are also missing many important features. If memory serves, when using libui a few years ago, it even lacked events in response to double clicks on table rows. Now I prefer to develop with imgui, also has pretty great Crystal bindings(thanks @oprypin) and can develop native GUI programs with complex features.

1 Like

Hi @existXFx

I would like to use ui too. But I think libui.cr is running on the backend of iu. So I wanted to run libui.cr first.

libui has been forked into libui-ng and some functions have been added, such as uiTableOnRowDoubleClicked.
ImGui is complicated and doesn’t look very native, which is not really my taste.
But hey, I have to try it at least once.

A GUI application usually can’t run in a single thread, but AFAIK Crystal’s multithreading support on Windows isn’t ready yet, so you’ll need to create your own UI threads and worker threads, and take care that interface operations are performed in the UI threads.

Hmm, this seems quite difficult.

Actually, you don’t need another thread - you can just call Fiber.yield in an idle event or a main loop (not sure how to do it in case of libui-ng). Then other fibers will execute when gui thread gives them time for it.

1 Like

To be honest, I am not sure about something much more basic.

First, download the file from the link.
https://nightly.link/libui-ng/libui-ng/workflows/build/master/Mingw-x64-static-release.zip

Then you will find the file builddir/meson-out/libui.a

How to use libui.a on Windows?

libui.cr is written like this ↓

  1. Do I need to modify @[Link("ui")] ?
  2. How do I specify build options such as --link-flags ?
  3. Is libui.a enough? Do we need additional files such as libui.lib?

*.a is for Linux, *.lib is for Windows. You can use link-flags, but it’ll either be -L/directory/path/of/a (Linux) or /LIBPATH:C:\directory\path\to\lib (Windows).

1 Like

You should have these files somewhere in CRYSTAL_LIBRARY_PATH:

  • libui-static.lib: Static library (built with MSVC)
  • libui-dynamic.lib: DLL import library
  • <name of the DLL>.dll: the actual dynamic library

then put @[Link("ui", dll: "<name of the DLL>.dll")].

1 Like

After much trial and error, I finally succeeded in opening a basic window, but I am not sure what worked and what did not.

require "../src/libui/libui.cr"

o = UI::InitOptions.new
err = UI.init pointerof(o)
if !ui_nil?(err)
  exit 1
end

mainwin = (UI.new_window "Writing Windows apps in Crystal? Don't be silly!", 450, 20, 1).not_nil!

# on_closing = ->(w : UI::Window*, data : Void*) {
#  UI.control_destroy ui_control(mainwin)
#  UI.quit
#  0
# }

UI.window_set_margined mainwin, 1
# UI.window_on_closing mainwin, on_closing, nil

UI.control_show ui_control(mainwin)

UI.main
UI.uninit

image

Besides, I get the following error.

Unhandled exception: passing a closure to C is not allowed (Exception)

So I had to comment out the callback.
This is probably for the reason that @existXFx pointed out.

For a callback, you can check Callbacks - Crystal

as a simple fix - replace mainwin to MAINWIN (so a callback will access a constant, not local variable).

For those who came to this page searching for “Windows GUI Crystal language”.

I found it very easy to call powershell.exe and display the dialog. It may be useful in some cases.
You can display the file selection dialog by doing the following

cmd = <<-CMD
powershell.exe -command
    Add-Type -AssemblyName System.Windows.Forms
    $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openFileDialog.initialDirectory = $InitialDirectory
    $openFileDialog.filter = "All files (*.*)| *.*"
    $openFileDialog.ShowDialog() > $null
    Write-Host $openFileDialog.Filename
CMD

ps = Process.new(cmd, shell: true, output: Process::Redirect::Pipe, error: Process::Redirect::Pipe)
stdout = ps.output
content = ps.output.gets_to_end
path = Path.windows(content.strip)
p File.exists?(path)

See : powershell-gui-dialogs.ps1 · GitHub

3 Likes

I understand why you think the above example is silly, but being able to create Windows executables in Crystal is a dream come true.

I love Ruby, but creating an executable file that runs on Windows and creating and distributing a package is difficult with Ruby. That probably will not change in the future.

Crystal’s Windows support is not a “Ruby replacement”. It is a pure extension of what can be done for both Ruby and Crystal communities…

1 Like

I have created a wrapper for Zenity.
This simply calls Zenity from Process.new in a more Crystal-like syntax, which may be useful in some situations. However, this is not dependency free because it requires the Zenity executable for Windows.

I want to mention NAppGUI (https://nappgui.com/) library - it has C API (easily wrappable in Crystal), works on Linux/Mac/Windows and is much more customizable (IMHO) than LibUI.

1 Like

Thanks @konovod
I’m involved with libui, so I can’t give an unbiased :upside_down_face: opinion , but NAppGUI looks very promising. It’s just that many cross-platform lightweight GUI libraries start out active, but after a while development stops. May development continue :mountain_biking_man:

I didn’t know this project and it sounds interesting!

Doesn’t use .NET, MFC, ATL and any other kind of redundant technology. The static linking will produce self-contained small binaries. NAppGUI neither use STL, only a small subset of The Standard C Library that also will be linked to the final product, so any VC++ Redistributable package will be required.

Do you happen to know of any crystal examples that use it?

I don’t know about any crystal examples, but i’ve tried to create wrappers and low level wrappers work like charm. There is even a callback that is called 60 times a second (to do Fiber.yield).
I’m still stuck on high-level DSL though.
The only thing that was problematic for me when i tried - gui layouts are created at startup and not intended to be modified later. I think this was solved by Add dynamic creation of window elements/widgets during runtime · Issue #74 · frang75/nappgui_src · GitHub

2 Likes

very interesting!
I have to give it a try.

1 Like

I want to know the latest way to use the Crystal language with C libraries on Windows. I have looked at the instructions on the official site, but I am not sure about Windows.

https://crystal-lang.org/reference/1.12/syntax_and_semantics/c_bindings/lib.html

I recently found that Crystal 1.12 generates a single executable only if --static is appended, otherwise the dll is placed in the bin directory with the binary excutable.

Perhaps calling the C library on Windows is still under active development and we need to wait a bit until official announcements and the community finds best practices…

Good to hear.
I would appreciate it if you could upload the code to GitHub or another hosting service. In fact, I went to your GitHub to see if the project was uploaded, but but could not find the corresponding project.

1 Like

C bindings work the same on Windows as on Unix systems. There’s really no significant difference. Since 1.12 also the linker behaviour is thr same in linking dynamically by default and statically with --static flag.
Distribution of DLLs is a bit different because theres no system wide packaging system. So they’re usually shipped together with the application.

1 Like