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
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ target_compile_definitions(dasModuleImplot PRIVATE
IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1
IMPLOT_DISABLE_OBSOLETE_FUNCTIONS=1
IMGUI_API=${IMGUI_IMPORT_ATTR}
# Must match dasImgui's ImWchar width, or ImWchar-taking imgui symbols mangle as
# unsigned short here and unsigned int there, and the link fails on FindGlyph.
IMGUI_USE_WCHAR32=1
$<$<CONFIG:Release>:DAS_NO_ASSERTIONS>
$<$<CONFIG:MinSizeRel>:DAS_NO_ASSERTIONS>
$<$<CONFIG:RelWithDebInfo>:DAS_NO_ASSERTIONS>
Expand Down
1 change: 1 addition & 0 deletions bind/bind_implot.das
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
options gen2
options _comment_hygiene = true
require cbind/cbind_boost
require daslib/fio
require daslib/defer
Expand Down
22 changes: 9 additions & 13 deletions daslib/imgui_implot_app.das
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
options gen2
options _comment_hygiene = true
options indenting = 4

module imgui_implot_app public
Expand All @@ -20,12 +21,9 @@ def private implot_module_root() : string {

def public with_implot_app(feature_path : string;
body : block<(app : ImguiApp) : void>) {
//! Spawn `daslang-live` against an implot feature with BOTH the dasImgui and
//! dasImguiImplot modules loaded, run `body` while alive, then shut down. Thin
//! wrapper over `with_imgui_app`: passes the implot module as an extra
//! `-load_module` (dasImgui is always loaded by with_imgui_app, and the implot
//! module requires it, so it must follow) and resolves the
//! `modules/dasImguiImplot/` feature prefix under the implot root.
//! Spawn `daslang-live` against an implot feature with BOTH dasImgui and
//! dasImguiImplot loaded, run `body` while alive, then shut down. The implot module
//! is an extra `-load_module` and MUST follow dasImgui, which it requires.
let root = implot_module_root()
with_imgui_app(feature_path, [root], root, "modules/dasImguiImplot/", body)
}
Expand All @@ -34,10 +32,9 @@ def public with_implot_recording_app(feature_path : string;
output_apng_basename : string;
max_seconds : int;
body : block<(app : ImguiApp) : void>) {
//! Native-fps recording session against an implot tutorial feature (the common
//! case). Delegates to the fps overload — gen2's trailing-block sugar can't see
//! through a default parameter between the last positional and the block, so the
//! overload pair keeps call sites clean.
//! Native-fps recording session against an implot tutorial feature. A separate
//! overload rather than a default `fps`, because gen2's trailing-block sugar can't
//! see through a default parameter between the last positional and the block.
with_implot_recording_app(feature_path, output_apng_basename, max_seconds, RECORD_FPS, body)
}

Expand All @@ -47,9 +44,8 @@ def public with_implot_recording_app(feature_path : string;
fps : int;
body : block<(app : ImguiApp) : void>) {
//! Record an `.apng` of an implot feature with BOTH dasImgui and dasImguiImplot
//! loaded — the recording counterpart of `with_implot_app`. Threads the implot
//! module as an extra `-load_module`, resolves a relative `feature_path` under the
//! implot root, and roots the `.apng` asset dir there too.
//! loaded — the recording counterpart of `with_implot_app`. Relative `feature_path`
//! and the `.apng` asset dir both resolve under the implot root.
let root = implot_module_root()
with_recording_app(feature_path, output_apng_basename, max_seconds, fps,
[root], root, root, body)
Expand Down
35 changes: 18 additions & 17 deletions daslib/imgui_implot_boost.das
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
options gen2
options _comment_hygiene = true
options indenting = 4
options no_unused_block_arguments = false
options no_unused_function_arguments = false
Expand Down Expand Up @@ -77,26 +78,26 @@ def public with_colormap(cmap : ImPlotColormap; blk : block) {

// Push one ImPlotCol color override (e.g. ImPlotCol.Line) for the body.
def public with_style_color(idx : ImPlotCol; col : float4; blk : block) {
PushStyleColor(idx, col)
implot::PushStyleColor(idx, col)
invoke(blk)
PopStyleColor(1)
implot::PopStyleColor(1)
}

// Push one ImPlotStyleVar override for the body — float / int / float2 forms.
def public with_style_var(idx : ImPlotStyleVar; value : float; blk : block) {
PushStyleVar(idx, value)
implot::PushStyleVar(idx, value)
invoke(blk)
PopStyleVar(1)
implot::PopStyleVar(1)
}
def public with_style_var(idx : ImPlotStyleVar; value : int; blk : block) {
PushStyleVar(idx, value)
implot::PushStyleVar(idx, value)
invoke(blk)
PopStyleVar(1)
implot::PopStyleVar(1)
}
def public with_style_var(idx : ImPlotStyleVar; value : float2; blk : block) {
PushStyleVar(idx, value)
implot::PushStyleVar(idx, value)
invoke(blk)
PopStyleVar(1)
implot::PopStyleVar(1)
}

// Clip drawing to the plot area (optionally expanded by `expand` px) for the body.
Expand Down Expand Up @@ -131,57 +132,57 @@ def public with_legend_popup(label_id : string; mouse_button : int; blk : block)
def public with_drag_drop_source_item(label_id : string; blk : block) {
if (BeginDragDropSourceItem(label_id)) { // native default flags = 0
invoke(blk)
EndDragDropSource()
implot::EndDragDropSource()
}
}
def public with_drag_drop_source_item(label_id : string; flags : int; blk : block) {
if (BeginDragDropSourceItem(label_id, flags)) {
invoke(blk)
EndDragDropSource()
implot::EndDragDropSource()
}
}
def public with_drag_drop_source_plot(blk : block) {
if (BeginDragDropSourcePlot()) { // native default flags = 0
invoke(blk)
EndDragDropSource()
implot::EndDragDropSource()
}
}
def public with_drag_drop_source_plot(flags : int; blk : block) {
if (BeginDragDropSourcePlot(flags)) {
invoke(blk)
EndDragDropSource()
implot::EndDragDropSource()
}
}
def public with_drag_drop_source_axis(axis : ImAxis; blk : block) {
if (BeginDragDropSourceAxis(axis)) { // native default flags = 0
invoke(blk)
EndDragDropSource()
implot::EndDragDropSource()
}
}
def public with_drag_drop_source_axis(axis : ImAxis; flags : int; blk : block) {
if (BeginDragDropSourceAxis(axis, flags)) {
invoke(blk)
EndDragDropSource()
implot::EndDragDropSource()
}
}

// Drag-drop TARGET on the whole plot / an axis / the legend.
def public with_drag_drop_target_plot(blk : block) {
if (BeginDragDropTargetPlot()) {
invoke(blk)
EndDragDropTarget()
implot::EndDragDropTarget()
}
}
def public with_drag_drop_target_axis(axis : ImAxis; blk : block) {
if (BeginDragDropTargetAxis(axis)) {
invoke(blk)
EndDragDropTarget()
implot::EndDragDropTarget()
}
}
def public with_drag_drop_target_legend(blk : block) {
if (BeginDragDropTargetLegend()) {
invoke(blk)
EndDragDropTarget()
implot::EndDragDropTarget()
}
}

Expand Down
71 changes: 26 additions & 45 deletions daslib/imgui_implot_boost_v2.das
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
options gen2
options _comment_hygiene = true
options indenting = 4
options no_unused_block_arguments = false
options no_unused_function_arguments = false
Expand Down Expand Up @@ -36,9 +37,8 @@ def private finalize_state(widget_ident : string; kind : string; var state : aut

struct LegendEntry {
//! One legend entry of a `plot` scope, captured per frame into `PlotScopeState.legend`.
//! Lets a test / recording target a synthetic legend-toggle click (`legend_toggle`) at the
//! exact rect ImPlot hit-tests, and verify the series hid / showed (`shown`). The rect is
//! screen-space pixels `[x0,y0]`–`[x1,y1]`; entries are in legend (submission) order.
//! Lets a test target a synthetic `legend_toggle` click at the exact rect ImPlot hit-tests
//! and verify the series hid / showed. Screen-space px, in legend (submission) order.
series : string //! series label (the `label_id` passed to plot_line / etc.); `label` is a reserved word
shown : bool //! series currently visible (false after a legend-toggle hide)
hovered : bool //! legend entry is hovered this frame (sync gate for a synthetic toggle click)
Expand All @@ -49,11 +49,9 @@ struct LegendEntry {
}

struct PlotScopeState {
//! Per-frame snapshot of a `plot` scope, pasted into the widget's snapshot
//! payload by `stateless_finalize`. Geometry is screen-space pixels;
//! limits / mouse are in plot DATA coordinates. Everything but `visible` is
//! captured between BeginPlot/EndPlot — the only window where ImPlot's query
//! functions are valid — so the fields are absent on a clipped/collapsed plot.
//! Per-frame snapshot of a `plot` scope, pasted into the widget's snapshot payload by
//! `stateless_finalize`. Geometry is screen-space px; limits / mouse are plot DATA coords.
//! All but `visible` is captured between BeginPlot/EndPlot, ImPlot's only valid query window.
@optional pos : float2 //! plot area top-left, screen px
@optional plot_size : float2 //! plot area size, screen px
@optional x_min : double //! X axis lower limit (data coords)
Expand All @@ -72,20 +70,16 @@ struct PlotScopeState {
@optional mouse_x : double //! GetPlotMousePos().x (data coords)
@optional mouse_y : double //! GetPlotMousePos().y
@optional legend_retries : int //! internal: frames left to re-read not-yet-resolved legend labels
visible : bool //! BeginPlot returned true (plot drew this frame)
visible : bool //! BeginPlot returned true; when false the captured fields above are absent
legend : array<LegendEntry> //! per-series legend entries (label / shown / clickable rect), legend order; empty when not visible
}

let private LEGEND_LABEL_RETRY_FRAMES = 240 // bounded window to resolve a just-registered item's label

// Read the legend AFTER EndPlot, by title: rect / shown are written during EndPlot's legend render.
// LegendEntryLabel returns a das-heap-owned copy (issue #9), so cached labels survive ImPlot resetting
// its Legend.Labels next frame; the cache is rebuilt only when the entry set size changes.
def private capture_legend(var state : PlotScopeState; title : string) {
// Read the plot's legend into the snapshot AFTER EndPlot, by title — the rect / shown are
// written during EndPlot's legend render, so GetPlot(title) gives current-frame values.
// LegendEntryLabel returns a das-heap-owned copy (it allocateString's into our context — see
// the C++ forwarder, issue #9), so the cached label stays valid even after ImPlot resets its
// internal Legend.Labels buffer next frame. We cache labels (rebuild only when the entry set
// changes) rather than re-allocating every frame. Rects / shown are cheap scalars, refreshed
// every frame. (A relabel with no count change reads stale — exotic for a steady plot.)
let n = LegendEntryCount(title)
if (length(state.legend) != n) {
state.legend |> clear()
Expand All @@ -95,10 +89,7 @@ def private capture_legend(var state : PlotScopeState; title : string) {
}
state.legend_retries = LEGEND_LABEL_RETRY_FRAMES
}
// A newly-registered item reports into the legend (count / rect / show) a frame or two before
// ImPlot resolves its label, so the initial cache can hold "". Re-read empty labels for a bounded
// window after the entry set last changed, then stop — resolves the lag without per-frame string
// churn, and terminates even if a label were ever legitimately empty (a hidden "##id").
// ImPlot resolves a new item's label a frame or two after its legend slot, so re-read empty ones.
let resolving = state.legend_retries > 0
for (i in range(n)) {
let r = LegendEntryRect(title, i)
Expand All @@ -119,11 +110,9 @@ def private capture_legend(var state : PlotScopeState; title : string) {

[container]
def plot(var state : PlotScopeState; title : string; size : float2; flags : ImPlotFlags; blk : block) {
//! `BeginPlot(title, size, flags)` / `EndPlot()` — only-on-true pairing, so the
//! body and the state capture run iff the plot is visible. Registers as kind
//! "implot_plot"; the snapshot payload mirrors PlotState (geometry + axis limits
//! + hovered + mouse-plot-pos), captured in-scope where ImPlot's queries are valid.
//! Items submitted in the body nest under this plot's snapshot path.
//! `BeginPlot(title, size, flags)` / `EndPlot()` — only-on-true pairing, so the body and
//! the state capture run iff the plot is visible. Registers as kind "implot_plot" with a
//! PlotScopeState payload; items in the body nest under this plot's snapshot path.
let im_open = BeginPlot(title, size, flags)
state.visible = im_open
var box = float4(0.0f, 0.0f, 0.0f, 0.0f)
Expand Down Expand Up @@ -252,7 +241,7 @@ def public set_axes(x_axis : ImAxis; y_axis : ImAxis) {
}

// ===== Next-item styling (SetNext*) =====
//

// Auto-sentinels mirror ImPlot's defaults: weight/size/alpha = -1 ("auto"),
// color = float4(0,0,0,-1) (IMPLOT_AUTO_COL). Pass an explicit value to override.

Expand Down Expand Up @@ -290,7 +279,7 @@ def public next_axes_to_fit() {
}

// ===== Plot items (float | double | int) =====
//

// Single OR-typed wrappers over the type-overloaded native forwarders. The paired
// x/y forms tie both arrays to one element type T (mixed-type x/y has no native
// overload). `flags` is the per-item ImPlot*Flags as a bare int (all are typedef int).
Expand Down Expand Up @@ -440,9 +429,8 @@ def public plot_bar_groups(label_ids : array<string>;
item_count : int; group_count : int;
group_size : double = 0.67lf; shift : double = 0.0lf; flags : int = 0) {
//! `PlotBarGroups(...)` — `item_count` series clustered into `group_count` groups.
//! `values` is a flat `item_count*group_count` row-major matrix (`values[item*group_count+group]`)
//! and `label_ids` names one series per row. The Stacked flag stacks within a group, Horizontal
//! flips the bars onto the y-axis.
//! `values` is a flat row-major `item_count*group_count` matrix (`values[item*group_count+group]`);
//! `label_ids` names one series per row. Stacked stacks within a group, Horizontal flips to y.
PlotBarGroups(label_ids, values, item_count, group_count, group_size, shift, flags)
}

Expand All @@ -457,15 +445,10 @@ def public plot_pie_chart(label_ids : array<string>;
}

// ===== Drag tools =====
//
// Interactive handles INSIDE a plot scope: the user drags them and their bound
// value updates in place. Unlike the items above, a drag tool owns mutable state
// (the value being dragged), so the caller passes a PERSISTENT state struct (a
// global) — the wrapper mutates it through `addr` each frame and serializes it
// into the snapshot under a path nested below the plot (`<plot>/<id>`), with a
// bbox computed from the handle's DATA position via PlotToPixels (a drag tool
// submits no normal ImGui item, so GetItemRect — what widget_finalize captures —
// would be stale). That bbox is what a test/playwright synth-drags onto.

// Interactive handles INSIDE a plot scope owning mutable state: the caller passes a PERSISTENT
// (global) state struct, mutated each frame and serialized under `<plot>/<id>`. The bbox comes
// from PlotToPixels(DATA pos) — a drag tool submits no ImGui item, so GetItemRect would be stale.

struct DragPointState {
//! A draggable (x,y) point handle. x/y are plot DATA coordinates and update in
Expand Down Expand Up @@ -572,12 +555,10 @@ def public pixels_to_plot(px : float2;
}

// ===== Colormaps =====
//
// A colormap maps a normalized t in [0,1] to a color. Push one (with_colormap /
// push_colormap) and the auto-colored items + heatmaps inside it sample their colors
// from it; colormap_scale draws the matching colorbar legend. `cmap` is the
// ImPlotColormap enum (Deep / Viridis / Plasma / Jet / Greys / …) — passed straight
// through (the binding exposes ImPlot's typedef-int colormap handle AS the enum).

// A colormap maps normalized t in [0,1] to a color: push one (with_colormap / push_colormap) and
// the auto-colored items + heatmaps inside sample from it; colormap_scale draws the colorbar.
// `cmap` is ImPlotColormap (Deep / Viridis / …) — ImPlot's typedef-int handle exposed AS the enum.

def public push_colormap(cmap : ImPlotColormap) {
//! `PushColormap(cmap)` — make `cmap` the active colormap for subsequent items.
Expand Down
Loading
Loading