Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/demo-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Prebuilt demo

on:
pull_request:
branches: [master]
branches: [master, standard]
paths:
- .github/workflows/demo-release.yml
- packaging/demo/**
Expand All @@ -12,13 +12,19 @@ on:
- .github/scripts/setup-vulkan-ci.sh
- build_vimgui.sh
- build_vimgui.vsh
- generate_v.sh
- cleanup_imgui_implot.perl
- scripts/update_upstream.sh
- scripts/check_upstream_variant.sh
- scripts/configure_variant.sh
- UPSTREAM_VARIANT
- CMakeLists.txt
- imgui.v
- implot/**
- impl_glfw/**
- impl_vulkan/**
push:
branches: [master]
branches: [master, standard]
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -67,6 +73,9 @@ jobs:
echo "$GITHUB_WORKSPACE/toolchain/v" >> "$GITHUB_PATH"
- name: Install native dependencies
run: sudo apt-get update && sudo apt-get install -y build-essential cmake libglfw3-dev libvulkan-dev libvulkan-volk-dev patchelf pkg-config xvfb zip
- name: Verify generated upstream variant
working-directory: source/modules/antono2/imgui
run: ./scripts/check_upstream_variant.sh
- name: Build native ImGui library
working-directory: source/modules/antono2/imgui
run: |
Expand Down
8 changes: 6 additions & 2 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,9 @@ v run build_vimgui.vsh --linkage shared --glfw bundled --glfw-version 3.4

## Regenerating bindings

Binding regeneration is maintainer-oriented and additionally requires LuaJIT.
Run `./generate_v.sh` only when updating the generated ImGui or ImPlot API.
Binding regeneration is maintainer-oriented. Run `./generate_v.sh` only when
updating the generated ImGui or ImPlot API. It translates the generated API
committed by the pinned upstream submodules; LuaJIT is required only with the
advanced `--regenerate-c` option. Use `./scripts/update_upstream.sh docking` on
the default branch or `./scripts/update_upstream.sh standard` on the standard
variant branch to advance the corresponding upstream line.
47 changes: 35 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ This is an automated process to generate `imgui.v` and `implot.v`
- `v translate` C to V
- `cleanup_imgui.perl` and `cleanup_implot.perl` to fix some errors

## Upstream variants

This repository supports both official Dear ImGui lines:

- `master` is the default **docking** build, generated from cimgui's
`docking_inter` branch. It includes docking and multi-viewport support while
retaining the normal Dear ImGui API.
- The `standard` branch is generated from cimgui's `master` branch for users
who want to track Dear ImGui's smaller standard line exactly.

Both currently track Dear ImGui `1.92.9b`. The `b` is an upstream patch-level
suffix shared by the release; it does not identify the docking variant. Check
`UPSTREAM_VARIANT` in a checkout to see which line its generated bindings and
native sources use. Do not mix a generated binding from one line with a native
library built from the other.

## Dependencies
`v install antono2.vulkan`<br>
`v install antono2.glfw`
Expand Down Expand Up @@ -85,24 +101,31 @@ GLFW provider remain build-time choices for developers; they do not need to
multiply the end-user demo downloads.

## Generate
On `Ubuntu`

```bash
sudo apt install -y luajit
~/.vmodules/antono2/imgui/generate_v.sh
# Go to the installed antono2/imgui module
./generate_v.sh
```

Or
`generate_v.sh` regenerates both V bindings from the generated C API committed
by the pinned cimgui/cimplot revisions, then builds `libvimgui`. It therefore
does not require LuaJIT for a normal upstream refresh. Pass `--regenerate-c`
only when intentionally rerunning the upstream Lua generators; that advanced
mode requires LuaJIT. `v generate.vsh` is retained as a compatibility entry
point and delegates to the same canonical script.

Maintainers can update either line reproducibly with:

```bash
# Install luajit for your OS
# Go to the installed antono2/imgui module
v generate.vsh
./scripts/update_upstream.sh docking
# On the standard branch:
./scripts/update_upstream.sh standard
```

`generate_v.sh` and `generate.vsh` regenerate both bindings and then build
`libvimgui`. To only rebuild the native library after a system upgrade or on
an older Linux distribution, run `v run build_vimgui.vsh`. The Bash
`build_vimgui.sh` helper remains available as a bootstrap fallback on Unix-like
machines where V is not yet in `PATH`.
To only rebuild the native library after a system upgrade or on an older Linux
distribution, run `v run build_vimgui.vsh`. The Bash `build_vimgui.sh` helper
remains available as a bootstrap fallback on Unix-like machines where V is not
yet in `PATH`.

## Thanks
Thank you [@ryoskzypu](https://github.com/ryoskzypu) - from #regex on [libera.chat](https://libera.chat/) - for loving perl and helping people out.
1 change: 1 addition & 0 deletions UPSTREAM_VARIANT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docking
110 changes: 96 additions & 14 deletions cleanup_imgui_implot.perl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ sub clean_one {
while ($i < $#lines && $lines[$i+1] =~ /^\s*\@\[[^\]]+\]\s*$/) { $i++; push @attrs, $lines[$i]; }
if ($i < $#lines && $lines[$i+1] =~ /^\s*(?:pub\s+)?fn\s+/) {
my $decl = $lines[++$i];
# cimplot.h includes the complete cimgui API. Its ig*/ImGui* symbols
# are already declared by the imported imgui module and must not be
# emitted a second time with independently translated signatures.
next if $kind eq 'implot' && $csym =~ /^(?:ig|ImGui)/;
if (my @fn = parse_fn($decl)) { push @out, emit_function($kind, $csym, @fn, \%const_params); }
}
next;
Expand All @@ -94,6 +98,7 @@ sub clean_one {
if ($line =~ /^\s*(?:pub\s+)?fn\s+/) {
if (my @fn = parse_fn($line)) {
my ($vname) = @fn;
next if $kind eq 'implot' && $vname =~ /^im_gui_/;
my $csym = infer_c_symbol($kind, $vname);
push @out, emit_function($kind, $csym, @fn, \%const_params);
}
Expand All @@ -112,13 +117,21 @@ sub clean_one {
}

# Struct block.
if ($line =~ /^\s*(?:pub\s+)?struct\s+((?:C\.)?[A-Za-z_]\w*)\s*\{\s*$/) {
my $orig = $1;
if ($line =~ /^\s*(?:pub\s+)?struct\s+((?:(?:C|imgui)\.)?[A-Za-z_]\w*)\s*\{\s*(\})?\s*$/) {
my ($orig, $inline_closed) = ($1, $2);
my @body;
while (++$i <= $#lines) { last if $lines[$i] =~ /^\s*}\s*$/; push @body, $lines[$i]; }
if (!defined $inline_closed) {
while (++$i <= $#lines) { last if $lines[$i] =~ /^\s*}\s*$/; push @body, $lines[$i]; }
}
# cimplot includes cimgui.h, and current c2v emits empty declarations
# such as `struct ImGuiTextFilter {}` for types owned by the imported
# ImGui module. They are references, not structs that ImPlot may declare.
next if $kind eq 'implot' && $orig =~ /^(?:imgui\.|ImGui)/;
my $clean = clean_type_name($kind, $orig);
my $c_name = $orig;
$c_name =~ s/^C\.//;
$c_name = 'stbrp_node' if $c_name eq 'Stbrp_node';
$c_name = 'stbrp_context_opaque' if $c_name eq 'Stbrp_context_opaque';
if ($kind eq 'implot' && $imported_c_struct{$c_name}) {
my $alias = $imported_c_alias{$c_name};
if (defined $alias && !$seen_type{$clean}++) {
Expand Down Expand Up @@ -164,6 +177,11 @@ sub clean_one {
$final =~ s/\bmain\.//g;
$final =~ s/\bMain\.//g;
$final = final_sanitize($kind, $final);
my $header_aliases = header_alias_decls($kind, $final, $hdr);
$final =~ s/(pub const version_num[^\n]*\n\n)/$1$header_aliases/s if $header_aliases ne '';
my $c_alias_targets = missing_c_alias_target_decls($final);
$final =~ s/(pub const version_num[^\n]*\n\n)/$1$c_alias_targets/s if $c_alias_targets ne '';
$final = final_sanitize($kind, $final);
$final =~ s/\n{4,}/\n\n\n/g;
write_file($outfile, $final);
print "cleaned $kind: $infile -> $outfile\n";
Expand Down Expand Up @@ -213,6 +231,15 @@ sub skip_line {
sub parse_version {
my ($src,$hdr,$kind)=@_;
my ($v,$n)=('', '');
if ($kind eq 'implot') {
for my $file (qw(cimplot/implot/implot.h include/implot/implot.h)) {
next unless -e $file;
my $version_header = slurp($file);
if ($version_header =~ /#define\s+IMPLOT_VERSION\s+"([^"]+)"/) { $v=$1; }
if ($version_header =~ /#define\s+IMPLOT_VERSION_NUM\s+(\d+)/) { $n=$1; }
return ($v,$n) if $v ne '';
}
}
if ($src =~ /file version\s+"([^"]+)"\s+(\d+)/) { ($v,$n)=($1,$2); }
my $m = $CFG{$kind}{version_macro};
if (!$v && $hdr =~ /#define\s+${m}_VERSION\s+"([^"]+)"/) { $v=$1; }
Expand Down Expand Up @@ -261,11 +288,11 @@ sub cleanup_notes {
Vector fields remain lowercase x/y/z/w for V literals.
5. Remove self-module prefixes: imgui.v must not refer to imgui.Type; implot.v
must not refer to implot.Type, because each file is already inside that module.
6. STB rectpack names are intentionally preserved from C when c2v produces
aliases like:
6. STB rectpack names are intentionally preserved from C when c2v title-cases
lower-case C identifiers in aliases like:
example: `pub type Stbrp_node_im = Stbrp_node`
Normalize the RHS to C.stbrp_node and emit an opaque C.stbrp_node typedef
generically through C-backed alias handling.
Normalize those references to C.stbrp_node/C.stbrp_context_opaque and emit
their C-backed declarations with the identifiers used by the headers.
7. Enum aliases are handled dynamically, never by member-name lists. V enums
reject duplicate integer values, while C/C++ enums often define aliases:
any_popup = 1 << 10 | 1 << 11
Expand Down Expand Up @@ -541,6 +568,8 @@ sub special_vec_csym {
sub emit_struct {
my ($kind, $orig_name, $clean_name, $body_lines)=@_;
$orig_name =~ s/^C\.//;
$orig_name = 'stbrp_node' if $orig_name eq 'Stbrp_node';
$orig_name = 'stbrp_context_opaque' if $orig_name eq 'Stbrp_context_opaque';
return '' if $clean_name eq '' || $clean_name =~ /\./;
my @fields;
for my $l (@$body_lines) {
Expand All @@ -549,7 +578,7 @@ sub emit_struct {
next if $l =~ /^\s*$/;
if ($l =~ /^\s*([A-Za-z_]\w*)\s+(.+?)\s*$/) {
my ($field,$typ)=($1,clean_type_expr($kind,$2));
$field = ucfirst($field) unless $orig_name =~ /^(ImVec2|ImVec4|ImVec2_c|ImVec4_c|ImDrawVert)$/;
$field = ucfirst($field) unless $orig_name =~ /^(ImVec2|ImVec4|ImVec2_c|ImVec2i_c|ImVec2ih|ImVec4_c|ImDrawVert)$/;
push @fields, "\t$field $typ";
}
}
Expand Down Expand Up @@ -707,6 +736,8 @@ sub clean_type_expr {
$s =~ s/\bmain\.//g;
$s =~ s/\bMain\.//g;
$s =~ s/\bC\.int\(([^)]+)\)/$1/g;
$s =~ s/\bC\.Stbrp_node\b/C.stbrp_node/g;
$s =~ s/\bC\.Stbrp_context_opaque\b/C.stbrp_context_opaque/g;
$s =~ s/\bi8\b/char/g if $s =~ /\&\s*i8\b/;
if ($kind eq 'imgui') {
$s =~ s/\bimgui\.//g;
Expand Down Expand Up @@ -753,6 +784,7 @@ sub normalize_alias_rhs {
# lower-case stb_rect_pack.h: typedef struct stbrp_node stbrp_node;
# Example broken V: pub type Stbrp_node_im = Stbrp_node
$rhs = 'C.stbrp_node' if $rhs eq 'Stbrp_node';
$rhs = 'C.stbrp_context_opaque' if $rhs eq 'Stbrp_context_opaque';
}
return $rhs;
}
Expand Down Expand Up @@ -826,13 +858,19 @@ sub header_alias_decls {
while ($body =~ /^\s*(?:pub\s+)?type\s+([A-Za-z_]\w*)\s*=/gm) { $defined{$1}=1; }
my %body_c_struct;
while ($body =~ /^\s*pub\s+struct\s+C\.([A-Za-z_]\w*)\b/gm) { $body_c_struct{$1}=1; }
my %referenced;
while ($body =~ /(?<![\w.])([A-Z][A-Za-z0-9_]*)(?![\w.])/g) { $referenced{$1}=1; }
my @decls;
while ($hdr =~ /^\s*typedef\s+struct\s+([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*;/gm) {
my ($backing, $public) = ($1, $2);
next if $backing eq $public || !$body_c_struct{$backing};
my $alias = clean_type_name($kind, $public);
next if $alias eq '' || $defined{$alias};
next if $alias eq '' || $alias !~ /^[A-Z]/ || $defined{$alias};
next unless $referenced{$alias} || $body_c_struct{$backing};
push @decls, "pub type $alias = C.$backing";
if (!$body_c_struct{$backing}) {
push @decls, "@[typedef]\npub struct C.$backing {}";
$body_c_struct{$backing}=1;
}
$defined{$alias}=1;
}
return @decls ? join("\n", @decls) . "\n\n" : '';
Expand All @@ -851,6 +889,17 @@ sub bridge_c_suffix_aliases {
return $s;
}

sub missing_c_alias_target_decls {
my ($s)=@_;
my %declared;
while ($s =~ /^\s*pub\s+struct\s+C\.([A-Za-z_]\w*)\b/gm) { $declared{$1}=1; }
my %missing;
while ($s =~ /^\s*pub\s+type\s+[A-Za-z_]\w*\s*=\s*C\.([A-Za-z_]\w*)\s*$/gm) {
$missing{$1}=1 unless $declared{$1};
}
return join('', map { "@[typedef]\npub struct C.$_ {}\n\n" } sort keys %missing);
}

sub final_sanitize {
my ($kind,$s)=@_;
$s =~ s/^\s*#.*\n//mg;
Expand All @@ -861,21 +910,54 @@ sub final_sanitize {
$s =~ s/\bunsigned\s+long\s+long\s+ImU64\b/u64/g;
$s =~ s/\bunsigned\s+long\s+ImU64\b/u64/g;

# Prefer the generated public typedef declaration when c2v also leaves an
# empty non-public declaration of the same C struct in the translated file.
my %public_c_struct;
while ($s =~ /^\s*pub\s+struct\s+C\.([A-Za-z_]\w*)\b/gm) {
$public_c_struct{$1}=1;
}
for my $name (keys %public_c_struct) {
$s =~ s/^\s*struct\s+C\.\Q$name\E\s*\{\s*\}\s*\n//mg;
}

for my $base (qw(ImVec2 ImVec2i ImVec4 ImColor ImRect)) {
if ($s =~ /\b(?:pub\s+type\s+${base}_c\s*=\s*C\.${base}_c|pub\s+struct\s+C\.${base}_c\b)/) {
$s =~ s/pub\s+type\s+$base\s*=\s*C\.[A-Za-z_]\w*/pub type $base = C.${base}_c/;
$s =~ s/pub\s+type\s+$base\s*=\s*(?:C\.)?[A-Za-z_]\w*/pub type $base = C.${base}_c/;
}
}

# c2v currently truncates compact C declarations such as `float x, y;`.
# These ABI-critical value types are deliberately written in that form by
# cimgui, so restore their complete layouts after translating either branch.
$s =~ s{pub struct C\.ImVec2_c \{(?:[ \t]*\}|[^\n]*\n.*?^\})}{pub struct C.ImVec2_c {
pub mut:
\tx f32
\ty f32
}}ms;
$s =~ s{pub struct C\.ImVec2i_c \{(?:[ \t]*\}|[^\n]*\n.*?^\})}{pub struct C.ImVec2i_c {
pub mut:
\tx int
\ty int
}}ms;
$s =~ s{pub struct C\.ImVec2ih \{(?:[ \t]*\}|[^\n]*\n.*?^\})}{pub struct C.ImVec2ih {
pub mut:
\tx i16
\ty i16
}}ms;
$s =~ s{pub struct C\.ImVec4_c \{(?:[ \t]*\}|[^\n]*\n.*?^\})}{pub struct C.ImVec4_c {
pub mut:
\tx f32
\ty f32
\tz f32
\tw f32
}}ms;

my %seen; my @out;
for my $line (split /\n/, $s) {
if ($line =~ /^\s*pub\s+type\s+([A-Za-z_]\w*)\s*=/) { next if $seen{$1}++; }
push @out,$line;
}
$s=join("\n",@out)."\n";
$s =~ s/(pub struct C\.ImVec2_c \{\n\s*pub mut:\n)\s*[Xx] f32\n\s*[Yy] f32/$1\tx f32\n\ty f32/s;
$s =~ s/(pub struct C\.ImVec2i_c \{\n\s*pub mut:\n)\s*[Xx] i32\n\s*[Yy] i32/$1\tx i32\n\ty i32/s;
$s =~ s/(pub struct C\.ImVec4_c \{\n\s*pub mut:\n)\s*[Xx] f32\n\s*[Yy] f32\n\s*[Zz] f32\n\s*[Ww] f32/$1\tx f32\n\ty f32\n\tz f32\n\tw f32/s;
$s =~ s/\bimgui\.([A-Z][A-Za-z0-9_]*)\b/$1/g if $kind eq 'imgui';
$s =~ s/\bimplot\.([A-Z][A-Za-z0-9_]*)\b/$1/g if $kind eq 'implot';
$s =~ s/\n{4,}/\n\n\n/g;
Expand Down
Loading