Skip to content

Instantly share code, notes, and snippets.

@rezich
Last active February 28, 2026 22:50
Show Gist options
  • Select an option

  • Save rezich/4127f1511aa9a59d9d61edfa7832760a to your computer and use it in GitHub Desktop.

Select an option

Save rezich/4127f1511aa9a59d9d61edfa7832760a to your computer and use it in GitHub Desktop.
Some ideas for how to structure a widget-based GUI editor system for a game, which demonstrate some advanced things you can do with the language—not all of which are necessarily advisable, but most if not all of which are hopefully educational
//
// Say you're making some kind of game that involves "Nodes" being placed around
// the game world, and you want to make an editor mode (disabled in release
// builds) that lets you create and manipulate these Nodes.
//
// Setting aside data serialization and an undo/redo system, here is a way that
// you could make such an editor system, that gives you an immediate-mode-ish
// API for easily creating editor-mode "widgets" each frame, with specific
// interaction and rendering behaviors for each different kind of "widget".
//
// Doing this will involve using a few neat tricks which the language currently
// permits, but which may not be immediately obvious to people coming from other
// languages.
//
// This information is presented for purely informational reasons and does not
// necessarily reflect best practices or whatever blah blah blah. But maybe,
// it's not really so bad.
//
// To see what things look like when you *really* go off the rails into
// crazytown and disregard how_to/999_temperance.jai in its entirety, stick
// around for the follow-ups after this.
//
// Game code -------------------------------------------------------------------
Node :: struct {
ID :: #type,isa u64;
id: ID;
position: Vector3;
color: Vector4;
}
nodes: [..] Node;
node_get :: (node_id: Node.ID) -> *Node {
for * nodes if it.id == node_id then return it;
return null;
}
// Entry point / main loop -----------------------------------------------------
EDITOR_ENABLED :: true; // Set to false in release builds
main :: () {
//TODO: Add some Nodes to nodes, probably
while true {
defer reset_temporary_storage();
// Handle input
#if EDITOR_ENABLED {
//TODO: Toggle is_editing when you press some keyboard key
if is_editing {
widgets_reset();
for nodes widget_node(it.id);
//TODO: More widgets for more things in the game in the future
widgets_handle_input();
}
}
// Simulate game state
//TODO: A game (or at least something that does something with nodes)
// Render
//TODO: Render nodes as they would appear in the game
#if EDITOR_ENABLED then if is_editing then widgets_draw();
}
}
#import "Basic";
#import "Math";
// Editor widgets --------------------------------------------------------------
// (Probably in a separate file, only #load'd #if EDITOR_ENABLED)
#if EDITOR_ENABLED {
// This is the data structure for the widget that represents Nodes in the
// editor
Widget_Node :: struct {
SIZE :: 5;
node_id: Node.ID;
}
// This is the "make a widget" procedure that you invoke to make a widget
// that represents a Node in the editor after you invoke widgets_reset()
widget_node :: (node_id: Node.ID, loc := #caller_location) {
// As per the instructions in widget() (below), we define WIDGET_DATA
// here as being Widget_Node, the data type associated with this kind of
// widget
WIDGET_DATA :: Widget_Node;
node := node_get(node_id);
if !node then return;
widget(node.position, {
on_click = (using w: *Widget) { use_data();
// Because we wrote `using w` above, we have unqualified access
// to the contents of w in this scope,
// * * * AND, ALSO, * * *
// because we invoked use_data() above, we ALSO have unqualified
// access to the contents of w.data -- AUTOMATICALLY CAST as a
// *WIDGET_DATA -- when WIDGET_DATA is defined in the
// widget_node() procedure-level scope, and not here in the
// procedure literal that we're assigning the value of to the
// on_click procedure pointer in the Widget.Procs struct
// literal!
//
// Turns out, You Can Just Do That -- neat!
log("Hello, sailor! I'm %, my node_id is %", hash, node_id);
select_this();
},
draw = (using w: Widget) { use_data();
node := node_get(node_id); // from w.data.(*WIDGET_DATA)
if !node then return;
draw_something(
position = position, // from w
size = SIZE, // from w.data.(*WIDGET_DATA)
color = node.color
);
}
}, Widget_Node.{
node_id = node_id
}, loc, xx node_id);
}
}
// Graphics engine -------------------------------------------------------------
// (In your game engine module, which is #import'd in the main file)
// Draw something idk
draw_something :: (
position: Vector3,
size: float,
color: Vector4
) { /* TODO: Draw something...? */ }
// Editor system ---------------------------------------------------------------
// (In your game engine module, which is #import'd in the main file)
is_editing := false; // If true, we're in Editor Mode
// Call these in your main loop
widgets_reset :: () { array_reset_keeping_memory(*widgets); }
widgets_draw :: () { for w: widgets if w.draw then w.draw(w); }
widgets_handle_input :: () {
w: *Widget;
//TODO: Determine which widget in widgets is clicked, and point w to it
if w && w.on_click then w.on_click(w);
}
// Use this in your "make a widget" procedures to make a widget, which should
// always begin with:
//
// WIDGET_DATA :: Your_Widget_Data_Struct_Type;
//
widget :: (
position: Vector3,
procs: Widget.Procs,
data: $T,
loc: Source_Code_Location,
identifiers: ..int
) {
make_hash_from_loc_and_identifiers :: (
loc: Source_Code_Location,
identifiers: [] int
) -> Widget.Hash {
return xx 0; //TODO
}
w := Widget.{
hash = make_hash_from_loc_and_identifiers(loc, identifiers),
data = talloc(size_of(T)),
type = T,
procs = procs,
position = position,
};
w.data.(*T).* = data;
array_add(*widgets, w);
}
#scope_file // We are now done with the "public API" of the editor system
Widget :: struct {
Hash :: #type,isa u64;
hash: Hash; // Unique hash from source code location and other identifiers
data: *void; // Void pointer to data, obviously
type: Type; // The type of data (not used here but useful to have)
// These are the procedure pointers that your widgets can define
Procs :: struct {
on_click: (widget: *Widget);
draw : (widget: Widget);
}
using procs: Procs;
position: Vector3; // Widget position in world space (or whatever)
//
// The following macros are namespaced within Widget, such that they can
// only be accessed either by qualifying them with `Widget.`, *or* from some
// place that has e.g. `using w: *Widget` or `using w: Widget` -- such as a
// procedure that has that as one of its parameters, like the procs in
// Widget.Procs, for instance!
//
// Because the latter is precisely the convention we want, "hiding" these
// macros within the Widget namespace is maybe a kind of nice thing to do,
// because in doing so, we've expressed that this is the intended usage!
//
// Also, this works, even though we're in #scope_file, because that's just
// how it works:
//
// // main.jai
// main :: () {
// using x := make_x(); // ok!
// y = 108; // ok!
// z(); // ok!
// }
// #load "other.jai";
//
// // other.jai
// make_x :: () -> X { return {}; }
// #scope_file
// X :: struct {
// y: int;
// z :: () {}
// }
//
// TL;DR use the following macros in your Widget.Procs procs
//
// Use this in your Widget.Procs procedures to automatically "using" the
// contents of the widget's data -- automatically cast to the correct data
// type, which you defined in your outer "make a widget" procedure as
// WIDGET_DATA, as per the instructions above
use_data :: (calling_scope := #caller_code) #expand {
// I always put these dumb UTF-8 comment braces around Jai strings that
// get #insert'd, both as a visual warning to make it stand out here in
// the source code, and because it makes it easy to find code generation
// issues by inspecting .build/.added_strings_w2.jai
#insert,scope(calling_scope) #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
using _ := data.(*WIDGET_DATA);
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI;
}
// (Maybe instead of _, you could name it correctly_cast_data_pointer, or
// something, if you want to be able to access the pointer directly for some
// reason?)
// Use this in your Widget.Procs procedures to select this widget
select_this :: () #expand { selected_widget_hash = `hash; }
// Use this in your Widget.Procs procedures to check whether or not this
// widget is the currently-selected widget
this_is_selected :: () -> bool #expand { return selected_widget_hash == `hash; }
}
widgets: [..] Widget;
selected_widget_hash: Widget.Hash;
//
// * * * BONUS ADVENTURES IN 999 VIOLATIONS * * *
//
// Maybe it's just too annoying to have to type
//
// on_whatever = (using w: *Widget) { use_data();
// },
//
// for each proc in Widget.Procs, in your "make a widget" procedures, because
// you have to type out the entire procedure signature each time, remember to
// `using` the first parameter each time, *and* remember to invoke use_data()
// each time -- **AND** you have to remember to declare WIDGET_DATA at
// the top of the "make a widget" procedure -- all before you type any code
// that does anything at all for your widget.
//
// What if, instead of having to make a "make a widget" procedure for each kind
// of widget at all, we could just pass some Code literals to a polymorphic
// widget(), and have it make the actual procedure headers and bodies for us,
// then fill those into the procedure pointer members into the corresponding
// Widget.Procs member of the procs member of the widget for us?
//
// As it turns out, this is entirely possible to achieve -- and you don't even
// need a metaprogram to do it.
//
// Game code -------------------------------------------------------------------
// (Unchanged from the previous version)
Node :: struct {
ID :: #type,isa u64;
id: ID;
position: Vector3;
color: Vector4;
}
nodes: [..] Node;
node_get :: (node_id: Node.ID) -> *Node {
for * nodes if it.id == node_id then return it;
return null;
}
// Entry point / main loop -----------------------------------------------------
EDITOR_ENABLED :: true; // Set to false in release builds
main :: () {
//TODO: Add some Nodes to nodes, probably
while true {
defer reset_temporary_storage();
// Handle input
#if EDITOR_ENABLED {
//TODO: Toggle is_editing when you press some keyboard key
if is_editing {
widgets_reset();
// * * *
// Now, instead of having a "make a widget" procedure for each
// kind of widget, we just do it inline here. We no longer have
// to write out the procedure signature for each one, nor
// remember to write use_data() at the top of each one, nor do
// we need to define WIDGET_DATA anywhere -- widget() now
// handles all of that for us!
//
// The only concession we have to make is, instead of passing
// a Widget.Procs struct with procedural literals assigned to
// its members, we instead pass a polymorphic Widget_Proc_Defs
// *type*, with Code literals assigned to its polymoprhic
// parameters.
//
// If this seems confusing, don't worry, we'll explain things
// more as we go.
// * * *
for nodes widget(it.position, Widget_Proc_Defs(
on_click = #code {
log("Hello, sailor! I'm %, my node_id is %", hash, node_id);
select_this();
},
draw = #code {
node := node_get(node_id);
if !node then return;
draw_something(
position = position,
size = SIZE,
color = node.color
);
}
), Widget_Node.{ node_id=it.id }, xx it.id);
// * * *
// Note that the actual code within each of these Code literals
// is identical to its counterpart in the procedure literals
// in widget_node() in the previous version -- the interface
// with which you write your actual widget interaction/rendering
// logic remains completely unchanged!
// * * *
//TODO: More widgets for more things in the game in the future
widgets_handle_input();
}
}
// Simulate game state
//TODO: A game (or at least something that does something with nodes)
// Render
//TODO: Render nodes as they would appear in the game
#if EDITOR_ENABLED then if is_editing then widgets_draw();
}
}
#import "Basic";
#import "Math";
// Editor widgets --------------------------------------------------------------
// (Probably in a separate file, only #load'd #if EDITOR_ENABLED)
#if EDITOR_ENABLED {
// This is the data structure for the widget that represents Nodes in the
// editor
Widget_Node :: struct {
SIZE :: 5;
node_id: Node.ID;
}
// * * *
// Hey look -- no more "make a widget" procedures, as promised!
// * * *
}
// Graphics engine -------------------------------------------------------------
// (In your game engine module, which is #import'd in the main file)
// (Unchanged from the previous version)
// Draw something idk
draw_something :: (
position: Vector3,
size: float,
color: Vector4
) { /* TODO: Draw something...? */ }
// Editor system ---------------------------------------------------------------
// (In your game engine module, which is #import'd in the main file)
is_editing := false; // If true, we're in Editor Mode
// Call these in your main loop
widgets_reset :: () { array_reset_keeping_memory(*widgets); }
widgets_draw :: () { for w: widgets if w.draw then w.draw(w); }
widgets_handle_input :: () {
w: *Widget;
//TODO: Determine which widget in widgets is clicked, and point w to it
if w && w.on_click then w.on_click(w);
}
// * * *
// The new, updated widget() procedure
// * * *
widget :: (
position: Vector3,
$PROC_DEFS: Type, // Must be a Widget_Proc_Defs type!
data: $WIDGET_DATA,
identifiers: ..int,
loc := #caller_location
) {
// * * *
// Check to make sure the second argument is actually a Widget_Proc_Defs and
// not some random garbage the user passed in for some dumb reason
// * * *
#assert type_info(PROC_DEFS).name == "Widget_Proc_Defs";
// * * *
// Build the procs variable that we will assign to the widget's procs member
// * * *
procs: Widget.Procs;
#insert -> string { builder: String_Builder;
for member, member_index: type_info(Widget.Procs).members {
assert(member.type.type == .PROCEDURE);
member_proc_type := member.type.(*Type_Info_Procedure);
// * * *
// One problem with this 999 complexity we have decided to undertake
// here, is that now we are kind of screwed when it comes to
// altering our editor widget system to support additional
// Widget.Procs procedure pointers that have any other parameters.
//
// It *is* possible to work around this, but doing so is very messy,
// very ugly, and then you find yourself ending up reading
// how_to/999_temperance.jai again, and rethinking all of your life
// choices that led you up to this point.
//
// But we've made it this far, so we might as well keep going,
// right?
//
// For the purposes of this demonstration, and in order to maintain
// some tiny shred of sanity, let's just assume that all
// Widget.Procs members must be procedure pointers to procedures
// that have either a Widget or a *Widget as its first and only
// parameter.
// * * *
member_proc_first_argument_type_string: string;
if member_proc_type.argument_types[0].type == {
case .POINTER; member_proc_first_argument_type_string = "*Widget";
case .STRUCT; member_proc_first_argument_type_string = "Widget";
case; assert(false);
}
print(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
#if PROC_DEFS.%1 != #code,null then procs.%1 = (using w: %2) {
using _ := data.(*WIDGET_DATA);
#insert,scope() PROC_DEFS.%1;
};
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI, member.name, member_proc_first_argument_type_string);
// * * *
// On the second #insert'd line of code there, above, we're directly
// #insert-ing the code that was previously hackily #insert'd into
// the calling scope with the use_data() macro in the previous
// version -- now, we no longer need that to be a macro, since we're
// already #insert-ing a string of code here anyway.
//
// But, it still works! WIDGET_DATA is still constant within the
// scope of widget() -- now as a polymorphic parameter rather than
// something you need to remember to define at the top of your
// "make a widget" procedure -- but the result is still the same:
// it gets "baked into" the procedure literal whose value we are
// assigning to the procedure pointer member in procs!
// * * *
}
return builder_to_string(*builder);
}
// * * *
// For more explanation of what we just did and why we just did it, see the
// long comment below, at the end of this file.
// * * *
make_hash_from_loc_and_identifiers :: (
loc: Source_Code_Location,
identifiers: [] int
) -> Widget.Hash {
return xx 0; //TODO
}
w := Widget.{
hash = make_hash_from_loc_and_identifiers(loc, identifiers),
data = talloc(size_of(WIDGET_DATA)),
type = WIDGET_DATA,
procs = procs,
position = position,
};
w.data.(*WIDGET_DATA).* = data;
array_add(*widgets, w);
}
#scope_file // We are now done with the "public API" of the editor system
Widget :: struct {
Hash :: #type,isa u64;
hash: Hash;
data: *void;
type: Type;
Procs :: struct {
on_click: (widget: *Widget);
draw : (widget: Widget);
}
using procs: Procs;
position: Vector3;
// * * *
// These macros are still available for use within the Code literals you use
// to define widget interaction/rendering behavior, in Widget_Proc_Defs,
// just like before.
// * * *
select_this :: () #expand { selected_widget_hash = `hash; }
this_is_selected :: () -> bool #expand { return selected_widget_hash == `hash; }
}
widgets: [..] Widget;
selected_widget_hash: Widget.Hash;
// * * *
// Automatically generate a polymorphic Widget_Proc_Defs struct, with one
// polymorphic parameter for each member of Widget.Procs, but of type Code
// instead (each with a default value of #code,null -- so you can omit any that
// you don't care to use for a given kind of widget, just as you could in the
// previous version with a Widget.Procs literal).
// * * *
#insert -> string { builder: String_Builder;
append(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
Widget_Proc_Defs :: struct(
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI);
members := type_info(Widget.Procs).members;
for members print(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
%1 := #code,null%2
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI, it.name, ifx it_index != members.count-1 then ",");
append(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
) {}
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI);
// * * *
// Yes, you read that last append() correctly -- Widget_Proc_Defs is a zero-
// sized struct that doesn't have any members. We only care about the
// polymorphic parameters, because they're constant, and seen as being
// constant by the compiler when we want to inspect them in the #insert in
// widget() that lets us do our dark magic.
// * * *
return builder_to_string(*builder);
}
// * * *
// You may be wondering: wait, what? Why? Huh?!
//
// Well, we need the Codes to be constant, and if you have something like this:
//
// Some_Codes :: struct { a, b: Code; }
// use_some_codes :: ($CODES: Some_Codes) {
// #if CODES.a != #code,null then #insert CODES.a;
// #if CODES.b != #code,null then #insert CODES.b;
// }
// main :: () {
// use_some_codes({
// b = #code { log("B!"); }
// });
// }
//
// This will fail to compile, because even though CODES is constant within the
// body of use_some_codes(), its members are not! But if we do something like
// this instead:
//
// Some_Codes :: struct(
// A := #code,null,
// B := #code,null
// ) {};
// use_some_codes :: ($CODES: Type) {
// #assert type_info(CODES).name == "Some_Codes";
// #if CODES.A != #code,null then #insert CODES.A;
// #if CODES.B != #code,null then #insert CODES.B;
// }
// main :: () {
// use_some_codes(Some_Codes(
// B = #code { log("B!"); }
// ));
// }
//
// Then it works, because polymorphic parameters must be constant!
// * * *
//
// Please reread and meditate upon how_to/999_temperance.jai now.
// I will do so as well.
//
//
// * * * FURTHER BONUS ADVENTURES IN 999 VIOLATIONS * * *
//
// What if instead of doing that nonsense with Widget_Proc_Defs that we were
// doing last time -- instead, we just stuck those Code parameters directly into
// widget(), at compile-time?
//
// Game code -------------------------------------------------------------------
// (Unchanged from the previous version)
Node :: struct {
ID :: #type,isa u64;
id: ID;
position: Vector3;
color: Vector4;
}
nodes: [..] Node;
node_get :: (node_id: Node.ID) -> *Node {
for * nodes if it.id == node_id then return it;
return null;
}
// Entry point / main loop -----------------------------------------------------
EDITOR_ENABLED :: true; // Set to false in release builds
main :: () {
//TODO: Add some Nodes to nodes, probably
while true {
defer reset_temporary_storage();
// Handle input
#if EDITOR_ENABLED {
//TODO: Toggle is_editing when you press some keyboard key
if is_editing {
widgets_reset();
// * * *
// Now, instead of using Widget_Proc_Defs as we did before,
// we're going to be generating polymorphic Code parameters
// (with a default value of #code,null) for widget(), for each
// Widget.Procs member.
//
// The only concession we have to make for doing so -- at least,
// I'm *pretty* sure we have to do this -- is, we need to use
// the identifiers parameter by name.(Though, if you need to use
// multiple identifiers, you can totally list them as e.g.
//
// identifiers = xx it.id, xx something_else, xx whatever
//
// That's not too bad!
// * * *
for nodes widget(it.position, Widget_Node.{ node_id=it.id },
on_click = #code {
log("Hello, sailor! I'm %, my node_id is %", hash, node_id);
select_this();
},
draw = #code {
node := node_get(node_id);
if !node then return;
draw_something(
position = position,
size = SIZE,
color = node.color
);
},
identifiers = xx it.id
);
//TODO: More widgets for more things in the game in the future
widgets_handle_input();
}
}
// Simulate game state
//TODO: A game (or at least something that does something with nodes)
// Render
//TODO: Render nodes as they would appear in the game
#if EDITOR_ENABLED then if is_editing then widgets_draw();
}
}
#import "Basic";
#import "Math";
// Editor widgets --------------------------------------------------------------
// (Probably in a separate file, only #load'd #if EDITOR_ENABLED)
// (Unchanged from the previous version)
#if EDITOR_ENABLED {
// This is the data structure for the widget
// that represents Nodes in the editor
Widget_Node :: struct {
SIZE :: 5;
node_id: Node.ID;
}
}
// Graphics engine -------------------------------------------------------------
// (In your game engine module, which is #import'd in the main file)
// (Unchanged from the previous version)
// Draw something idk
draw_something :: (
position: Vector3,
size: float,
color: Vector4
) { /* TODO: Draw something...? */ }
// Editor system ---------------------------------------------------------------
// (In your game engine module, which is #import'd in the main file)
is_editing := false; // If true, we're in Editor Mode
// Call these in your main loop
widgets_reset :: () { array_reset_keeping_memory(*widgets); }
widgets_draw :: () { for w: widgets if w.procs.draw then w.procs.draw(w); }
widgets_handle_input :: () {
w: *Widget;
//TODO: Determine which widget in widgets is clicked, and point w to it
if w && w.procs.on_click then w.procs.on_click(w);
}
// * * *
// Note that we un-using'd Widget.procs, so we need to qualify its members
// in the above code.
// * * *
// * * *
// I'm not quite sure why, but we can't have widget() *not* in #scope_file, but
// have Widget *in* #scope_file. But we can work around it by just renaming
// widget() to __widget__(), and putting the following line of code here:
// * * *
widget :: __widget__;
#scope_file
// * * *
// This time, we're going to just say screw it, let's generate and #insert the
// entire widget() procedure at compile time, so we can add a polymorphic Code
// parameter (with a default value of #code,null) for each Widget.Procs member.
//
// Also, we moved the data parameter to be second in the parameters list.
//
// It's going to get a bit messy.
// * * *
#insert -> string { builder: String_Builder;
widget_procs_members := type_info(Widget.Procs).members;
append(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
__widget__ :: (
position: Vector3,
data: $WIDGET_DATA,
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI);
for member: widget_procs_members print(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
$%1 := #code,null,
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI, member.name);
append(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
identifiers: ..int,
loc := #caller_location
) {
procs: Widget.Procs;
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI);
for member, member_index: widget_procs_members {
assert(member.type.type == .PROCEDURE);
member_proc_type := member.type.(*Type_Info_Procedure);
member_proc_first_argument_type_string: string;
if member_proc_type.argument_types[0].type == {
case .POINTER; member_proc_first_argument_type_string = "*Widget";
case .STRUCT; member_proc_first_argument_type_string = "Widget";
case; assert(false);
}
print(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
#if %1 != #code,null then procs.%1 = (using w: %2) {
using _ := data.(*WIDGET_DATA);
#insert,scope() %1;
};
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI, member.name, member_proc_first_argument_type_string);
// * * *
// We had to change the above bit to qualify the procs member with
// `procs.`, because we un-using'd it in Widget.
// * * *
}
append(*builder, #string JAI
//╔════════════════════════════════════════════════════════════════════════════════════════════════╗
make_hash_from_loc_and_identifiers :: (
loc: Source_Code_Location,
identifiers: [] int
) -> Widget.Hash {
return xx 0; //TODO
}
w := Widget.{
hash = make_hash_from_loc_and_identifiers(loc, identifiers),
data = talloc(size_of(WIDGET_DATA)),
type = WIDGET_DATA,
procs = procs,
position = position,
};
w.data.(*WIDGET_DATA).* = data;
array_add(*widgets, w);
}
//╚════════════════════════════════════════════════════════════════════════════════════════════════╝
JAI);
return builder_to_string(*builder);
}
// * * *
// Now do you see why I use those dumb UTF-8 comment-braces on #insert'd
// strings? Not only does it make things easier to visually parse and help with
// code generation debugging, but the secret third reason that I didn't
// explicitly mention before is, you can easily see at a glance just how fucked
// up your code generation is getting, so you can once again go back and reread
// how_to/999_temperance.jai yet again.
// * * *
Widget :: struct {
Hash :: #type,isa u64;
hash: Hash;
data: *void;
type: Type;
Procs :: struct {
on_click: (widget: *Widget);
draw : (widget: Widget);
}
// * * *
// We've un-using'd procs here, because widget() now has a Code parameter
// with the same name as each member of Procs, and each procedure literal
// within widget() needs to be able to access those, but each also has
// `using w`, so, if we didn't un-using procs here, it would shadow the
// constant variable with the non-constant variable.
//
// But that's fine -- it just means that in widgets_draw(),
// widget_handle_input(), and one place in widget(), we need to qualify the
// procs members with `procs.`.
// * * *
procs: Procs;
position: Vector3;
select_this :: () #expand { selected_widget_hash = `hash; }
this_is_selected :: () -> bool #expand { return selected_widget_hash == `hash; }
}
widgets: [..] Widget;
selected_widget_hash: Widget.Hash;
//
// Once again:
//
// Please reread and meditate upon how_to/999_temperance.jai now.
// I will do so as well.
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment