Skip to content

Expose some missing ImPlot functions#98

Merged
hazeycode merged 5 commits intozig-gamedev:mainfrom
odorovskoy:feat/expose-implot-functions
Mar 8, 2026
Merged

Expose some missing ImPlot functions#98
hazeycode merged 5 commits intozig-gamedev:mainfrom
odorovskoy:feat/expose-implot-functions

Conversation

@odorovskoy
Copy link
Contributor

Changes for #97.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request exposes additional ImPlot functions in the zgui library, addressing issue #97. The changes add Zig wrappers and C++ bindings for various ImPlot functions related to styling, axis configuration, coordinate transformation, subplots, and custom item rendering. The implementation follows existing patterns in the codebase for wrapping C++ ImPlot APIs with idiomatic Zig interfaces.

Changes:

  • Added C++ wrapper functions in zplot.cpp for new ImPlot functions including style setters, axis configuration, coordinate conversions, and internal functions for custom item rendering
  • Implemented Zig API bindings in plot.zig with struct-based optional parameters following existing conventions
  • Added AxisOrAuto enum to support IMPLOT_AUTO (-1) sentinel value for axis parameters
  • Updated StyleCol enum to include auto = -1 for IMPLOT_AUTO color support
  • Added default parameter to addLine in gui.zig for consistency with other drawing functions
  • Updated .gitignore to exclude IDE folders

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/zplot.cpp Added C++ wrappers for SetNextLineStyle, SetNextFillStyle, SetNextMarkerStyle, SetNextErrorBarStyle, SetupAxisFormat, SetupAxisTicks, SetupAxisLimitsConstraints, SetupAxisZoomConstraints, SetNextAxesToFit, PixelsToPlot, PlotToPixels, GetPlotPos, GetPlotSize, GetPlotMousePos, BeginSubplots, EndSubplots, PushPlotClipRect, PopPlotClipRect, BeginItem, EndItem, and FitPoint functions. Includes implot_internal.h for BeginItem/EndItem.
src/plot.zig Added Zig bindings for all new C++ wrappers with idiomatic struct-based APIs. Modified StyleCol and Axis enums to use i32 and added AxisOrAuto enum. Included formatter wrapper function for setupAxisFormat.
src/gui.zig Added default value (1.0) to thickness parameter in addLine function for consistency with other drawing functions.
.gitignore Added patterns to ignore IDE folders (.vscode and .idea).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

src/plot.zig Outdated
Comment on lines +106 to +108
pub const StyleCol = enum(i32) {
auto = -1,
line = 0,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Adding auto = -1 to the StyleCol enum will break the array size calculation in line 84 of the Style struct. The enum now has 22 fields (including auto), but the C++ ImPlotStyle.Colors array has size ImPlotCol_COUNT which is 21. The auto value is a sentinel (like IMPLOT_AUTO) and should not be counted as an array index. This will cause a size mismatch with the C++ struct and potential memory corruption.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

True. In general, zgui Style itself is an extern struct while ImGuiStyle is not, it's a C++ struct. Strictly speaking #65 should be completed to use extern structs with the same layout on both sides.

src/plot.zig Outdated

//----------------------------------------------------------------------------------------------
/// Get the plot draw list for custom rendering to the current plot area. Call between begin/endPlot.
pub const getPlotDrawList = gui.getWindowDrawList;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The alias to gui.getWindowDrawList is incorrect. ImPlot's GetPlotDrawList() is a separate function that should have its own C++ wrapper in zplot.cpp and Zig binding. While both return ImDrawList*, they serve different purposes - GetPlotDrawList() returns the draw list for the current plot area, while GetWindowDrawList() returns the draw list for the current window. Without a proper wrapper function zguiPlot_GetPlotDrawList in zplot.cpp, this will not work.

Suggested change
pub const getPlotDrawList = gui.getWindowDrawList;
const GetWindowDrawListFn = @TypeOf(gui.getWindowDrawList);
const GetWindowDrawListReturnType = @typeInfo(GetWindowDrawListFn).Fn.return_type.?;
pub fn getPlotDrawList() GetWindowDrawListReturnType {
return zguiPlot_GetPlotDrawList();
}
extern fn zguiPlot_GetPlotDrawList() GetWindowDrawListReturnType;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, I don't think there is a dedicated plot draw list, that's why it works perfectly fine as is, without any wrappers, because ImPlot just returns ImGui window draw list. I'll create the wrapper function though, to hide that implementation detail.

src/plot.zig Outdated
Comment on lines +446 to +459
labels: [][*:0]const u8 = &.{},
keep_default: bool = false,
};
pub fn setupAxisTicks(axis: Axis, args: SetupAxisTicks) void {
const n_ticks = std.math.cast(i32, args.values.len) orelse {
@panic("Too many tick values!");
};
zguiPlot_SetupAxisTicks(axis, args.values.ptr, n_ticks, args.labels.ptr, args.keep_default);
}
extern fn zguiPlot_SetupAxisTicks(
axis: Axis,
values: [*]const f64,
n_ticks: i32,
labels: [*][*:0]const u8,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The default for args.labels in SetupAxisTicks is an empty slice (&.{}), but setupAxisTicks always passes args.labels.ptr along with n_ticks = args.values.len to zguiPlot_SetupAxisTicks. The underlying ImPlot API expects either a null pointer or an array of at least n_ticks label pointers; passing a non-null pointer to a zero-length buffer can cause out-of-bounds reads when ImPlot indexes labels, potentially leaking or corrupting memory when it interprets arbitrary memory as strings. Make labels optional and pass a null pointer when no labels are provided, or ensure the labels buffer has at least n_ticks elements and validate labels.len against values.len before calling into ImPlot.

Suggested change
labels: [][*:0]const u8 = &.{},
keep_default: bool = false,
};
pub fn setupAxisTicks(axis: Axis, args: SetupAxisTicks) void {
const n_ticks = std.math.cast(i32, args.values.len) orelse {
@panic("Too many tick values!");
};
zguiPlot_SetupAxisTicks(axis, args.values.ptr, n_ticks, args.labels.ptr, args.keep_default);
}
extern fn zguiPlot_SetupAxisTicks(
axis: Axis,
values: [*]const f64,
n_ticks: i32,
labels: [*][*:0]const u8,
/// Optional tick labels. If null, no labels are provided and a null pointer is passed to ImPlot.
/// If non-null, the slice must have at least `values.len` elements.
labels: ?[][*:0]const u8 = null,
keep_default: bool = false,
};
pub fn setupAxisTicks(axis: Axis, args: SetupAxisTicks) void {
const n_ticks = std.math.cast(i32, args.values.len) orelse {
@panic("Too many tick values!");
};
const labels_ptr: ?[*][*:0]const u8 = blk: {
if (args.labels) |labels_slice| {
if (labels_slice.len < args.values.len) {
@panic("Number of labels must be at least the number of tick values!");
}
break :blk labels_slice.ptr;
} else {
break :blk null;
}
};
zguiPlot_SetupAxisTicks(axis, args.values.ptr, n_ticks, labels_ptr, args.keep_default);
}
extern fn zguiPlot_SetupAxisTicks(
axis: Axis,
values: [*]const f64,
n_ticks: i32,
labels: ?[*][*:0]const u8,

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I'll push updates shortly.

@zig-gamedev zig-gamedev deleted a comment from Copilot AI Feb 25, 2026
@zig-gamedev zig-gamedev deleted a comment from Copilot AI Feb 25, 2026
@zig-gamedev zig-gamedev deleted a comment from Copilot AI Feb 25, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

src/plot.zig Outdated
Comment on lines +537 to +542
pub fn beginSubplots(title_id: [*:0]const u8, args: BeginSubplots) bool {
// Both must be greater than 0 according to ImPlot docs.
const rs: i32 = @intCast(args.rows);
const cs: i32 = @intCast(args.cols);
return zguiPlot_BeginSubplots(title_id, rs, cs, args.width, args.height, args.flags, args.row_ratios, args.col_ratios);
}
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

beginSubplots accepts title_id: [*:0]const u8 whereas beginPlot uses [:0]const u8. Taking the raw pointer here makes it easier to accidentally pass a non-null-terminated string and is inconsistent with the rest of the public API. Consider changing the parameter to [:0]const u8 for safety and consistency (the extern wrapper can still take [ *:0 ]const u8).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right.

src/plot.zig Outdated
Comment on lines +539 to +540
const rs: i32 = @intCast(args.rows);
const cs: i32 = @intCast(args.cols);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

beginSubplots comments that rows/cols must be > 0, but it doesn't enforce this (0 is still representable in u32). Passing 0 through to ImPlot can trigger assertions or undefined behavior depending on build settings. Add a runtime check (assert/panic) for args.rows > 0 and args.cols > 0, and consider using std.math.cast(i32, ...) to produce a clearer error if the values don't fit in i32.

Suggested change
const rs: i32 = @intCast(args.rows);
const cs: i32 = @intCast(args.cols);
assert(args.rows > 0);
assert(args.cols > 0);
const rs = std.math.cast(i32, args.rows) orelse @panic("BeginSubplots: rows value does not fit into i32");
const cs = std.math.cast(i32, args.cols) orelse @panic("BeginSubplots: cols value does not fit into i32");

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@odorovskoy odorovskoy Mar 7, 2026

Choose a reason for hiding this comment

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

Sure.

Comment on lines +448 to +461
pub fn setupAxisTicks(axis: Axis, args: SetupAxisTicks) void {
const n_ticks = std.math.cast(i32, args.values.len) orelse {
@panic("Too many tick values!");
};

const labels_ptr = blk: {
if (args.labels) |labels| {
break :blk if (labels.len != 0) labels.ptr else null;
} else {
break :blk null;
}
};

zguiPlot_SetupAxisTicks(axis, args.values.ptr, n_ticks, labels_ptr, args.keep_default);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

setupAxisTicks can pass a labels buffer that is shorter than args.values.len to zguiPlot_SetupAxisTicks without any validation. If callers supply args.labels where labels.len is non-zero but less than args.values.len, ImPlot will index past the end of the labels array and treat arbitrary memory as label pointers, which can leak memory contents or corrupt process state. Consider enforcing that labels is null or has at least args.values.len elements before calling zguiPlot_SetupAxisTicks, and panic or return early when this precondition is not met.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fine.

Copy link
Member

@hazeycode hazeycode left a comment

Choose a reason for hiding this comment

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

Thanks!

@hazeycode hazeycode merged commit 1af65a8 into zig-gamedev:main Mar 8, 2026
3 checks passed
@odorovskoy odorovskoy deleted the feat/expose-implot-functions branch March 9, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants