Add better click through semantics

This commit is contained in:
2026-05-21 08:55:27 +01:00
parent 2bff3eb93f
commit 72304ee925
6 changed files with 180 additions and 57 deletions
+9 -4
View File
@@ -10,6 +10,10 @@ enum Type { IMAGE,
TEXT,
RECT };
enum ClickMode { PASS,
BLOCK,
IGNORE };
namespace app {
struct Element {
Type type;
@@ -19,7 +23,7 @@ struct Element {
Vec2<float> scale = {1, 1};
float alpha = 1.0f;
float z = 0;
bool click_through = false;
ClickMode click_mode = ClickMode::BLOCK;
Ruby::Block on_click;
Element(Type type) : type(type) {}
@@ -191,13 +195,14 @@ struct Scene {
Vec2<float> size = e->size();
Rect rect = Rect::from(size);
if (rect.contains(world_to_local(position, e))) {
if (e->click_mode == ClickMode::PASS) {
result.push_back(*it);
} else if (e->click_mode == ClickMode::BLOCK) {
result.push_back(*it);
if (e->click_through)
continue;
else
break;
}
}
}
return result;
}
+132 -33
View File
@@ -320,7 +320,7 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) {
float x = 0, y = 0, z = 0, rotation = 0;
float scale_x = 1, scale_y = 1, alpha = 1;
float pivot_x = 0.5, pivot_y = 0.5;
bool click_through = false;
mrb_sym click_mode_sym = sym_block;
if (!mrb_nil_p(kwargs)) {
mrb_value pos_hash = HASH_GET(kwargs, position);
if (!mrb_nil_p(pos_hash)) {
@@ -343,7 +343,10 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) {
HASH_FLOAT(kwargs, z, z);
HASH_FLOAT(kwargs, rotation, rotation);
HASH_FLOAT(kwargs, alpha, alpha);
HASH_BOOL(kwargs, click_through, click_through);
mrb_value v = HASH_GET(kwargs, click_mode);
if (!mrb_nil_p(v)) {
click_mode_sym = mrb_symbol(v);
}
}
uint64_t id = get_id_Font(mrb, font_val);
@@ -362,7 +365,15 @@ static mrb_value ElementText_init(mrb_state *mrb, mrb_value self) {
element->pivot = {pivot_x, pivot_y};
element->alpha = alpha;
element->z = z;
element->click_through = click_through;
std::string click_mode = mrb_sym2name(mrb, click_mode_sym);
if (click_mode == "pass")
element->click_mode = ClickMode::PASS;
else if (click_mode == "block")
element->click_mode = ClickMode::BLOCK;
else if (click_mode == "ignore")
element->click_mode = ClickMode::IGNORE;
s->id = app::element_pool.acquire(element);
app::element_pool.use(s->id);
@@ -620,19 +631,41 @@ static mrb_value element_text_z_set(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
static mrb_value element_text_click_through_get(mrb_state *mrb, mrb_value self) {
static mrb_value element_text_click_mode_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id];
return mrb_bool_value(element->click_through);
std::string click_mode_str;
switch (element->click_mode) {
case ClickMode::PASS:
click_mode_str = "pass";
break;
case ClickMode::BLOCK:
click_mode_str = "block";
break;
case ClickMode::IGNORE:
click_mode_str = "ignore";
break;
}
return mrb_symbol_value(mrb_intern_cstr(mrb, click_mode_str.c_str()));
}
static mrb_value element_text_click_through_set(mrb_state *mrb, mrb_value self) {
mrb_bool click_through;
mrb_get_args(mrb, "b", &click_through);
static mrb_value element_text_click_mode_set(mrb_state *mrb, mrb_value self) {
mrb_sym click_mode_sym;
mrb_get_args(mrb, "n", &click_mode_sym);
uint64_t id = get_id_ElementText(mrb, self);
app::EText *element = (app::EText *)app::element_pool[id];
element->click_through = click_through;
std::string click_mode_str = mrb_sym2name(mrb, click_mode_sym);
if (click_mode_str == "pass")
element->click_mode = ClickMode::PASS;
else if (click_mode_str == "block")
element->click_mode = ClickMode::BLOCK;
else if (click_mode_str == "ignore")
element->click_mode = ClickMode::IGNORE;
return mrb_nil_value();
}
@@ -676,7 +709,7 @@ static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) {
float x = 0, y = 0, z = 0, rotation = 0;
float scale_x = 1, scale_y = 1, alpha = 1, pivot_x = 0.5, pivot_y = 0.5;
bool click_through = false;
mrb_sym click_mode_sym = sym_block;
if (!mrb_nil_p(kwargs)) {
mrb_value pos_hash = HASH_GET(kwargs, position);
if (!mrb_nil_p(pos_hash)) {
@@ -699,7 +732,10 @@ static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) {
HASH_FLOAT(kwargs, z, z);
HASH_FLOAT(kwargs, rotation, rotation);
HASH_FLOAT(kwargs, alpha, alpha);
HASH_BOOL(kwargs, click_through, click_through);
mrb_value v = HASH_GET(kwargs, click_mode);
if (!mrb_nil_p(v)) {
click_mode_sym = mrb_symbol(v);
}
}
Vec2<float> shape = {(float)w, (float)h};
@@ -720,7 +756,15 @@ static mrb_value ElementRect_init(mrb_state *mrb, mrb_value self) {
element->pivot = {pivot_x, pivot_y};
element->alpha = alpha;
element->z = z;
element->click_through = click_through;
std::string click_mode = mrb_sym2name(mrb, click_mode_sym);
if (click_mode == "pass")
element->click_mode = ClickMode::PASS;
else if (click_mode == "block")
element->click_mode = ClickMode::BLOCK;
else if (click_mode == "ignore")
element->click_mode = ClickMode::IGNORE;
s->id = app::element_pool.acquire(element);
app::element_pool.use(s->id);
@@ -946,19 +990,41 @@ static mrb_value element_rect_z_set(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
static mrb_value element_rect_click_through_get(mrb_state *mrb, mrb_value self) {
static mrb_value element_rect_click_mode_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementRect(mrb, self);
app::ERect *element = (app::ERect *)app::element_pool[id];
return mrb_bool_value(element->click_through);
std::string click_mode_str;
switch (element->click_mode) {
case ClickMode::PASS:
click_mode_str = "pass";
break;
case ClickMode::BLOCK:
click_mode_str = "block";
break;
case ClickMode::IGNORE:
click_mode_str = "ignore";
break;
}
return mrb_symbol_value(mrb_intern_cstr(mrb, click_mode_str.c_str()));
}
static mrb_value element_rect_click_through_set(mrb_state *mrb, mrb_value self) {
mrb_bool click_through;
mrb_get_args(mrb, "b", &click_through);
static mrb_value element_rect_click_mode_set(mrb_state *mrb, mrb_value self) {
mrb_sym click_mode_sym;
mrb_get_args(mrb, "n", &click_mode_sym);
uint64_t id = get_id_ElementRect(mrb, self);
app::ERect *element = (app::ERect *)app::element_pool[id];
element->click_through = click_through;
std::string click_mode = mrb_sym2name(mrb, click_mode_sym);
if (click_mode == "pass")
element->click_mode = ClickMode::PASS;
else if (click_mode == "block")
element->click_mode = ClickMode::BLOCK;
else if (click_mode == "ignore")
element->click_mode = ClickMode::IGNORE;
return mrb_nil_value();
}
@@ -1001,7 +1067,7 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) {
float x = 0, y = 0, z = 0, rotation = 0;
float scale_x = 1, scale_y = 1, alpha = 1, pivot_x = 0.5, pivot_y = 0.5;
bool click_through = false;
mrb_sym click_mode_sym = sym_block;
if (!mrb_nil_p(kwargs)) {
mrb_value pos_hash = HASH_GET(kwargs, position);
if (!mrb_nil_p(pos_hash)) {
@@ -1024,7 +1090,10 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) {
HASH_FLOAT(kwargs, z, z);
HASH_FLOAT(kwargs, rotation, rotation);
HASH_FLOAT(kwargs, alpha, alpha);
HASH_BOOL(kwargs, click_through, click_through);
mrb_value v = HASH_GET(kwargs, click_mode);
if (!mrb_nil_p(v)) {
click_mode_sym = mrb_symbol(v);
}
}
uint64_t id = get_id_Animation(mrb, animation_val);
@@ -1036,7 +1105,15 @@ static mrb_value ElementImage_init(mrb_state *mrb, mrb_value self) {
element->pivot = {pivot_x, pivot_y};
element->alpha = alpha;
element->z = z;
element->click_through = click_through;
std::string click_mode = mrb_sym2name(mrb, click_mode_sym);
if (click_mode == "pass")
element->click_mode = ClickMode::PASS;
else if (click_mode == "block")
element->click_mode = ClickMode::BLOCK;
else if (click_mode == "ignore")
element->click_mode = ClickMode::IGNORE;
s->id = app::element_pool.acquire(element);
app::element_pool.use(s->id);
@@ -1245,19 +1322,41 @@ static mrb_value element_image_z_set(mrb_state *mrb, mrb_value self) {
return mrb_nil_value();
}
static mrb_value element_image_click_through_get(mrb_state *mrb, mrb_value self) {
static mrb_value element_image_click_mode_get(mrb_state *mrb, mrb_value self) {
uint64_t id = get_id_ElementImage(mrb, self);
app::EImage *element = (app::EImage *)app::element_pool[id];
return mrb_bool_value(element->click_through);
std::string click_mode_str;
switch (element->click_mode) {
case ClickMode::PASS:
click_mode_str = "pass";
break;
case ClickMode::BLOCK:
click_mode_str = "block";
break;
case ClickMode::IGNORE:
click_mode_str = "ignore";
break;
}
return mrb_symbol_value(mrb_intern_cstr(mrb, click_mode_str.c_str()));
}
static mrb_value element_image_click_through_set(mrb_state *mrb, mrb_value self) {
mrb_bool click_through;
mrb_get_args(mrb, "b", &click_through);
static mrb_value element_image_click_mode_set(mrb_state *mrb, mrb_value self) {
mrb_sym click_mode_sym;
mrb_get_args(mrb, "n", &click_mode_sym);
uint64_t id = get_id_ElementImage(mrb, self);
app::EImage *element = (app::EImage *)app::element_pool[id];
element->click_through = click_through;
std::string click_mode = mrb_sym2name(mrb, click_mode_sym);
if (click_mode == "pass")
element->click_mode = ClickMode::PASS;
else if (click_mode == "block")
element->click_mode = ClickMode::BLOCK;
else if (click_mode == "ignore")
element->click_mode = ClickMode::IGNORE;
return mrb_nil_value();
}
@@ -1697,8 +1796,8 @@ inline void setup() {
ADD_METHOD(ElementText, "alpha", element_text_alpha_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "z=", element_text_z_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "z", element_text_z_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "click_through=", element_text_click_through_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "click_through", element_text_click_through_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "click_mode=", element_text_click_mode_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementText, "click_mode", element_text_click_mode_get, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "width", element_text_width, MRB_ARGS_NONE());
ADD_METHOD(ElementText, "height", element_text_height, MRB_ARGS_NONE());
@@ -1727,8 +1826,8 @@ inline void setup() {
ADD_METHOD(ElementRect, "alpha", element_rect_alpha_get, MRB_ARGS_NONE());
ADD_METHOD(ElementRect, "z=", element_rect_z_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementRect, "z", element_rect_z_get, MRB_ARGS_NONE());
ADD_METHOD(ElementRect, "click_through=", element_rect_click_through_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementRect, "click_through", element_rect_click_through_get, MRB_ARGS_NONE());
ADD_METHOD(ElementRect, "click_mode=", element_rect_click_mode_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementRect, "click_mode", element_rect_click_mode_get, MRB_ARGS_NONE());
DEF_CLASS(ElementImage, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1));
ADD_METHOD(ElementImage, "on_click", element_image_on_click, MRB_ARGS_BLOCK());
@@ -1751,8 +1850,8 @@ inline void setup() {
ADD_METHOD(ElementImage, "alpha", element_image_alpha_get, MRB_ARGS_NONE());
ADD_METHOD(ElementImage, "z=", element_image_z_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementImage, "z", element_image_z_get, MRB_ARGS_NONE());
ADD_METHOD(ElementImage, "click_through=", element_image_click_through_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementImage, "click_through", element_image_click_through_get, MRB_ARGS_NONE());
ADD_METHOD(ElementImage, "click_mode=", element_image_click_mode_set, MRB_ARGS_REQ(1));
ADD_METHOD(ElementImage, "click_mode", element_image_click_mode_get, MRB_ARGS_NONE());
DEF_CLASS(Scene, MRB_ARGS_NONE());
ADD_METHOD(Scene, "==", scene_equal, MRB_ARGS_REQ(1));
+4 -1
View File
@@ -107,7 +107,10 @@ DEF_SYM(pivot)
DEF_SYM(scale)
DEF_SYM(alpha)
DEF_SYM(fps)
DEF_SYM(click_through)
DEF_SYM(click_mode)
DEF_SYM(pass)
DEF_SYM(block)
DEF_SYM(ignore)
DEF_SYM(call)
DEF_SYM(x)
DEF_SYM(y)
+6 -6
View File
@@ -1,4 +1,4 @@
App.run 720, 480, "Button Test", fps: 24
App.run 720, 480, "Button Test", fps: 60
main_scene = Scene.new
@@ -12,11 +12,11 @@ bg_animation = Animation.new [bg_image1, bg_image2], fps: 5
App.language = :en
App.localize(:en, :btn_text, "Click %{name}!")
text = ElementText.new :btn_text, default_font, 0xFFFF00, {name: "Me"}, position: {x: 50, y: 50}, z: 1, click_through: true, alpha: 1
text = ElementText.new :btn_text, default_font, 0xFFFF00, {name: "Me"}, position: {x: 50, y: 50}, z: 1, click_mode: :pass, alpha: 1
rect = ElementRect.new 100, 25, 0xFF0000, position: {x: 50, y: 50}, z: 0, rotation: 60, scale: { x: 1, y: 1 }, alpha: 0.4
image_element = ElementImage.new bg_animation, position: {x: 0, y: 0}, z: -1, click_through: true, alpha: 1
image_element = ElementImage.new bg_animation, position: {x: 0, y: 0}, z: -1, click_mode: :ignore, alpha: 1
pp text, rect, image_element
@@ -31,7 +31,7 @@ rect.on_click do
text.params!
end
App.map_key :mouse_left, :press
App.map_key :mouse_left, :action_name
start = Time.now
c_ = 0.0
@@ -49,9 +49,9 @@ main_scene.on_update do |delta_time|
pp App.text_input if App.text_input != ""
next unless App.pressed?(:press)
next unless App.pressed?(:action_name)
pp "#{App.pressed?(:press)}, #{App.down?(:press)}, #{App.released?(:press)}"
pp "#{App.pressed?(:action_name)}, #{App.down?(:action_name)}, #{App.released?(:action_name)}"
for elem in el
if elem == image_element
-9
View File
@@ -69,15 +69,6 @@ world.spawn do |e| # Name is optional
# c.tile[x: 0, y: 0] = tiles[0] # set tile at position x,y in tilemap to a specific tile from the sheet (if no auto_tile is used)
c.tile[x: 0, y: 0] = true # true/false otherwise
end
e.add_component :render do |c|
c.z_index = 0
c.renderer = proc do
draw_rect 0, 0, 50, 50, {r: 255, g: 0, b: 0, a: 255} # example of custom rendering code for this entity (0,0 is the position of the entity)
draw_text "Hello World", default_font, 0, 0, {r: 255, g: 255, b: 255, a: 255}
end
# offset from the position of the entity, useful for things like speech bubbles or health bars that should be above the entity, etc
c.offset = {x: 0, y: 0}
end
e.add_component :health do |c|
c.health = 100
end
+28 -3
View File
@@ -222,15 +222,40 @@ Summarization of survey results and its limitations
## What more is needed?
Other features that a game engine should have/has (need to go in depth)
Other features that a game engine should have
core:
- app, defined with fixed heght/width and aspect ratio is maintained with letterboxing, and fps
- scenes
- elements in scenes (in screen coordinates)
- ElementText
- ElementRect
- ElementImage
- ElementScript - uses function to return a pizel buffer each frame, can use frame caching (for stuff liek minimaps)
- should not reallocate every frame, can use dirty flag based system, uses a single string \
from ruby and maybe keep string alive (add to gc exception) on c++ side too and use the same buffer to send to sdl. \
so 2 copies 1 from ruby which can eb directly edited and then 1 a sdl texture object
- ElementWorld - (It is a view through a camera)
- The world has to have an ecs system buitl in it
- components are type definitions (and thier instances are stored in cache efficient arrays)
- systems use queries to select components (with filtering) and can perform bulk operations on them
- entities - an id and position which can relate to compoenent instances
- The world can render everything in view based on camera, and everything inside here is in world coordinates
- shaders, layers on top of teh world, (per world, not possible per entity)
extra:
- physics (forces, mass, movement and collisions)
- Localization
- keymappings (and settings)
- shaders
- tilesets
- sprite system (tiled, animated, static etc.)
- 9 segment ui
- 9 segment ui system
- audio playing
- network interface, with a lib to work with rack backends (and generic socket api)
- persistence store