Skip to content

Commit

Permalink
Merge pull request #133 from Gnimuc/makie
Browse files Browse the repository at this point in the history
Makie integration
  • Loading branch information
JamesWrigley authored Jul 29, 2024
2 parents b8b2ff6 + 69ebd5f commit 38eadb5
Show file tree
Hide file tree
Showing 11 changed files with 407 additions and 24 deletions.
5 changes: 4 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "CImGui"
uuid = "5d785b6c-b76f-510e-a07c-3070796c7e87"
authors = ["Yupei Qi <[email protected]>"]
version = "2.0.0"
version = "2.1.0"

[deps]
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"
Expand All @@ -10,15 +10,18 @@ CSyntax = "ea656a56-6ca6-5dda-bba5-7b6963a5f74c"

[weakdeps]
GLFW = "f7f18e0c-5ee9-5ccd-a5bf-e8befd85ed98"
GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a"
ModernGL = "66fc600b-dfda-50eb-8b99-91cfa97b1301"

[extensions]
GlfwOpenGLBackend = ["GLFW", "ModernGL"]
MakieIntegration = ["GLFW", "ModernGL", "GLMakie"]

[compat]
CEnum = "0.4, 0.5"
CImGuiPack_jll = "0.3.0"
CSyntax = "0.4"
GLFW = "3"
GLMakie = "0.10.5"
ModernGL = "1"
julia = "1.9"
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ makedocs(;
size_threshold=500000,
size_threshold_warn=400000
),
pages=["index.md", "api.md", "backends.md", "changelog.md"]
pages=["index.md", "api.md", "backends.md", "makie.md", "changelog.md"]
)

deploydocs(;
Expand Down
3 changes: 2 additions & 1 deletion docs/src/_changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ CurrentModule = CImGui
This documents notable changes in CImGui.jl. The format is based on [Keep a
Changelog](https://keepachangelog.com).

## Unreleased
## [v2.1.0] - 2024-07-29

### Added
- The OpenGL version can now be set with [`render()`](@ref).
- Experimental [Makie integration](@ref) ([#133]).

## [v2.0.0] - 2024-06-27

Expand Down
2 changes: 1 addition & 1 deletion docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ imgui_version
```@autodocs
Modules = [CImGui]
Order = [:constant, :function, :type]
Filter = t -> nameof(t) ∉ (:imgui_version, :render, :set_backend)
Filter = t -> nameof(t) ∉ (:imgui_version, :render, :set_backend, :MakieFigure)
```
18 changes: 18 additions & 0 deletions docs/src/makie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
```@meta
CurrentModule = CImGui
```

# Makie integration

We currently have *very experimental* [Makie](https://docs.makie.org/stable)
support through
[GLMakie](https://docs.makie.org/stable/explanations/backends/glmakie). GLMakie
mostly works around a `Screen{T}` object to display a scene, where `T` is some
OpenGL-supporting window. GLMakie sets this to a `GLFW.Window`, but we've made a
custom window type to represent a single `Figure` to be drawn in ImGui. What we
get from GLMakie is a framebuffer with a color image texture attachment, and
that's displayed by us as an image.

```@docs
MakieFigure
```
86 changes: 86 additions & 0 deletions examples/makie_demo.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import GLFW
using GLMakie
import GLMakie.Makie as Makie
import CImGui as ig
import CImGui.CSyntax: @c
import ModernGL as gl


ig.set_backend(:GlfwOpenGL3)

function generate_data(type::Symbol=:random, N=1000)
if type === :random
[Point2f(i, rand()) for i in 1:N]
end
end

function HelpMarker(msg)
ig.TextDisabled("(?)");

if ig.IsItemHovered() && ig.BeginTooltip()
ig.PushTextWrapPos(ig.GetFontSize() * 35.0);
ig.TextUnformatted(msg);
ig.PopTextWrapPos();
ig.EndTooltip();
end
end

function makie_demo(; engine=nothing)
# Create a plot
f = Figure()
scene = Makie.get_scene(f)
ax1 = Axis(f[1, 1]; title="Random data")
data = Observable(generate_data())
lines!(ax1, data)
data2 = Observable(generate_data())
ax2 = Axis(f[2, 1])
lines!(ax2, data2)

ctx = ig.CreateContext()
io = ig.GetIO()
io.ConfigFlags = unsafe_load(io.ConfigFlags) | ig.lib.ImGuiConfigFlags_DockingEnable
io.ConfigFlags = unsafe_load(io.ConfigFlags) | ig.lib.ImGuiConfigFlags_ViewportsEnable

ax1_tight_spacing = true
auto_resize_x = true
auto_resize_y = false

# Start the GUI
ig.render(ctx; engine, window_size=(1280, 760), window_title="ImGui Window") do
ig.Begin("Makie demo")

if ig.Button("Random data")
data[] = generate_data()
end

@c ig.Checkbox("Ax1 tight tick spacing", &ax1_tight_spacing)
ig.SameLine()
HelpMarker("""
Try zooming into the top plot, if this option is disabled
the axis will not resize itself to stop clipping the tick labels on the Y axis.
""")

@c ig.Checkbox("Auto resize X", &auto_resize_x)
ig.SameLine()
@c ig.Checkbox("Auto resize Y", &auto_resize_y)

if ig.MakieFigure("plot", f; auto_resize_x, auto_resize_y)
if ax1_tight_spacing
Makie.tight_ticklabel_spacing!(ax1)
end

Makie.tight_ticklabel_spacing!(ax2)
end

ig.Text("Mouse position in scene: $(scene.events.mouseposition[])")
ig.Text("Scene size: $(size(scene))")
ig.Text("Mouse position in ax1: $(mouseposition(ax1))")

ig.End()
end
end

# Run automatically if the script is launched from the command-line
if !isempty(Base.PROGRAM_FILE)
makie_demo()
end
42 changes: 24 additions & 18 deletions ext/GlfwOpenGLBackend.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module GlfwOpenGLBackend

import CSyntax: @c
import CImGui
import CImGui as ig
import CImGui.lib as lib
import GLFW
import ModernGL as GL
Expand All @@ -24,7 +24,7 @@ end

const g_ImageTexture = Dict{Int, GL.GLuint}()

function CImGui._create_image_texture(::Val{:GlfwOpenGL3}, image_width, image_height; format=GL.GL_RGBA, type=GL.GL_UNSIGNED_BYTE)
function ig._create_image_texture(::Val{:GlfwOpenGL3}, image_width, image_height; format=GL.GL_RGBA, type=GL.GL_UNSIGNED_BYTE)
id = GL.GLuint(0)
@c GL.glGenTextures(1, &id)
GL.glBindTexture(GL.GL_TEXTURE_2D, id)
Expand All @@ -36,19 +36,22 @@ function CImGui._create_image_texture(::Val{:GlfwOpenGL3}, image_width, image_he
return Int(id)
end

function CImGui._update_image_texture(::Val{:GlfwOpenGL3}, id, image_data, image_width, image_height; format=GL.GL_RGBA, type=GL.GL_UNSIGNED_BYTE)
function ig._update_image_texture(::Val{:GlfwOpenGL3}, id, image_data, image_width, image_height; format=GL.GL_RGBA, type=GL.GL_UNSIGNED_BYTE)
GL.glBindTexture(GL.GL_TEXTURE_2D, g_ImageTexture[id])
GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0, GL.GLsizei(image_width), GL.GLsizei(image_height), format, type, image_data)
end

function CImGui._destroy_image_texture(::Val{:GlfwOpenGL3}, id)
function ig._destroy_image_texture(::Val{:GlfwOpenGL3}, id)
id = g_ImageTexture[id]
@c GL.glDeleteTextures(1, &id)
delete!(g_ImageTexture, id)
return true
end

function CImGui._render(ui, ctx::Ptr{lib.ImGuiContext}, ::Val{:GlfwOpenGL3};
_window::Union{Nothing, GLFW.Window} = nothing
ig._current_window(::Val{:GlfwOpenGL3}) = _window

function ig._render(ui, ctx::Ptr{lib.ImGuiContext}, ::Val{:GlfwOpenGL3};
hotloading=true,
on_exit=nothing,
clear_color=Cfloat[0.45, 0.55, 0.60, 1.00],
Expand All @@ -65,6 +68,8 @@ function CImGui._render(ui, ctx::Ptr{lib.ImGuiContext}, ::Val{:GlfwOpenGL3};

# Configure GLFW
glsl_version = get_glsl_version(opengl_version)
GLFW.WindowHint(GLFW.VISIBLE, true)
GLFW.WindowHint(GLFW.DECORATED, true)
GLFW.WindowHint(GLFW.CONTEXT_VERSION_MAJOR, opengl_version.major)
GLFW.WindowHint(GLFW.CONTEXT_VERSION_MINOR, opengl_version.minor)

Expand All @@ -75,11 +80,12 @@ function CImGui._render(ui, ctx::Ptr{lib.ImGuiContext}, ::Val{:GlfwOpenGL3};

# Start the test engine, if we have one
if !isnothing(engine)
CImGui._start_test_engine(engine, ctx)
ig._start_test_engine(engine, ctx)
end

# Create window
window = GLFW.CreateWindow(window_size[1], window_size[2], window_title)
global _window = GLFW.CreateWindow(window_size[1], window_size[2], window_title)
window = _window
@assert window != C_NULL
GLFW.MakeContextCurrent(window)
GLFW.SwapInterval(1) # enable vsync
Expand All @@ -95,7 +101,7 @@ function CImGui._render(ui, ctx::Ptr{lib.ImGuiContext}, ::Val{:GlfwOpenGL3};
# Start the Dear ImGui frame
lib.ImGui_ImplOpenGL3_NewFrame()
lib.ImGui_ImplGlfw_NewFrame()
CImGui.NewFrame()
ig.NewFrame()

result = if hotloading
@invokelatest ui()
Expand All @@ -104,26 +110,26 @@ function CImGui._render(ui, ctx::Ptr{lib.ImGuiContext}, ::Val{:GlfwOpenGL3};
end

if !isnothing(engine) && engine.show_test_window
CImGui._show_test_window(engine)
ig._show_test_window(engine)
end

tests_completed = (!isnothing(engine)
&& engine.exit_on_completion
&& !CImGui._test_engine_is_running(engine))
&& !ig._test_engine_is_running(engine))
if result === :imgui_exit_loop || tests_completed
GLFW.SetWindowShouldClose(window, true)
end

# Rendering
CImGui.Render()
ig.Render()
GLFW.MakeContextCurrent(window)

display_w, display_h = GLFW.GetFramebufferSize(window)

GL.glViewport(0, 0, display_w, display_h)
GL.glClearColor((clear_color isa Ref ? clear_color[] : clear_color)...)
GL.glClear(GL.GL_COLOR_BUFFER_BIT)
lib.ImGui_ImplOpenGL3_RenderDrawData(Ptr{Cint}(CImGui.GetDrawData()))
lib.ImGui_ImplOpenGL3_RenderDrawData(Ptr{Cint}(ig.GetDrawData()))

GLFW.MakeContextCurrent(window)
GLFW.SwapBuffers(window)
Expand All @@ -136,19 +142,19 @@ function CImGui._render(ui, ctx::Ptr{lib.ImGuiContext}, ::Val{:GlfwOpenGL3};
end
end
catch e
@error "Error in CImGui $(CImGui._backend[]) renderloop!" exception=(e, catch_backtrace())
@error "Error in CImGui $(ig._backend[]) renderloop!" exception=(e, catch_backtrace())
finally
if !isnothing(on_exit)
for func in vcat(ig._exit_handlers, isnothing(on_exit) ? [] : [on_exit])
try
on_exit()
catch exit_ex
@error "Error in on_exit()!" exception=exit_ex
func()
catch ex
@error "Error in exit handler!" exception=(ex, catch_backtrace())
end
end

lib.ImGui_ImplOpenGL3_Shutdown()
lib.ImGui_ImplGlfw_Shutdown()
CImGui.DestroyContext(ctx)
ig.DestroyContext(ctx)
GLFW.DestroyWindow(window)
end
end
Expand Down
Loading

2 comments on commit 38eadb5

@JamesWrigley
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/111969

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v2.1.0 -m "<description of version>" 38eadb54028c1246f4caa858072f12c1e0ceac4d
git push origin v2.1.0

Please sign in to comment.